diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7265ab4 --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +# Common makefile -- loads make rules for each platform + +BUILD=build +INT=integrations +ZTO=zto + +OSTYPE=$(shell uname -s) + +ifeq ($(OSTYPE),Darwin) + include make-mac.mk +endif + +ifeq ($(OSTYPE),Linux) + include make-linux.mk +endif + +ifeq ($(OSTYPE),FreeBSD) + include make-freebsd.mk +endif +ifeq ($(OSTYPE),OpenBSD) + include make-freebsd.mk +endif diff --git a/README.md b/README.md new file mode 100644 index 0000000..b0f0556 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +ZeroTier SDK +====== + + +*** + +## Build Targets +### Static Library +`static_lib` + +## Using Language Bindings +`SDK_LANG_JAVA=1` +`SDK_LANG_CSHARP=1` +`SDK_LANG_PYTHON=1` +`SDK_LANG_=1` diff --git a/api/README.md b/api/README.md new file mode 100644 index 0000000..9b78b98 --- /dev/null +++ b/api/README.md @@ -0,0 +1 @@ +Language bindings \ No newline at end of file diff --git a/api/cpp/README.md b/api/cpp/README.md new file mode 100644 index 0000000..f63c360 --- /dev/null +++ b/api/cpp/README.md @@ -0,0 +1,3 @@ +C\C++ Language Binding API for the ZeroTier SDK +====== + diff --git a/api/csharp/README.md b/api/csharp/README.md new file mode 100644 index 0000000..1b93754 --- /dev/null +++ b/api/csharp/README.md @@ -0,0 +1,3 @@ +C# Language Binding API for the ZeroTier SDK +====== + diff --git a/api/java/README.md b/api/java/README.md new file mode 100644 index 0000000..fa4b006 --- /dev/null +++ b/api/java/README.md @@ -0,0 +1,3 @@ +Java Language Binding API for the ZeroTier SDK +====== + diff --git a/api/python/README.md b/api/python/README.md new file mode 100644 index 0000000..9dd2cbc --- /dev/null +++ b/api/python/README.md @@ -0,0 +1,3 @@ +Python Language Binding API for the ZeroTier SDK +====== + diff --git a/api/swift/README.md b/api/swift/README.md new file mode 100644 index 0000000..6806e5c --- /dev/null +++ b/api/swift/README.md @@ -0,0 +1,3 @@ +Swift Language Binding API for the ZeroTier SDK +====== + diff --git a/check.sh b/check.sh new file mode 100755 index 0000000..2fa530b --- /dev/null +++ b/check.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +red=`tput setaf 1` +green=`tput setaf 2` +reset=`tput sgr0` + +FILE="$1" + +if ([ -f "$FILE" ] && [ -s "$FILE" ]) || ([ -d "$FILE" ] && [ "$(ls -A "$FILE")" ]); +then + echo "${green}[OK ]${reset} $FILE" + exit 0 +else + echo "${red}[FAIL]${reset} $FILE" >&2 + exit 1 +fi diff --git a/ext/README.md b/ext/README.md new file mode 100644 index 0000000..f296fa5 --- /dev/null +++ b/ext/README.md @@ -0,0 +1 @@ +The ext/ folder contains third party code, drivers, installation support files, etc. \ No newline at end of file diff --git a/ext/picotcp/.gitignore b/ext/picotcp/.gitignore new file mode 100644 index 0000000..88593da --- /dev/null +++ b/ext/picotcp/.gitignore @@ -0,0 +1,24 @@ +*.d +*.o +*.a +*.out +*.swp +tags +build +UNIT_* +core +core.* +.DS_Store +cscope.files +cscope.out +*.so +*.aux +*.pdf +*.toc +*.gz +*.log +*.pyc +*.elf +*.gcov +*.gcda +*.gcno diff --git a/ext/picotcp/.travis.yml b/ext/picotcp/.travis.yml new file mode 100644 index 0000000..0a87dd4 --- /dev/null +++ b/ext/picotcp/.travis.yml @@ -0,0 +1,13 @@ +before_install: + - sudo apt-get update -qq + - sudo apt-get install -y vde2 check libvdeplug2-dev libpcap-dev psmisc + - sudo pip install cpp-coveralls + - make clean + - rm -f *.gc* + +install: make GCOV=1 && make units ARCH=faulty GCOV=1 && make test GCOV=1 +language: c +script: + - ./test/coverage.sh +after_success: + - coveralls --exclude test/ --exclude modules/ptsocket --exclude build --exclude modules/pico_dev_mock.c --exclude modules/pico_dev_null.c --exclude modules/pico_dev_pcap.c --exclude modules/pico_dev_tap_windows.c --exclude modules/pico_dev_tun.c --gcov-options='\-lp' diff --git a/ext/picotcp/COPYING b/ext/picotcp/COPYING new file mode 100644 index 0000000..8a5fe4b --- /dev/null +++ b/ext/picotcp/COPYING @@ -0,0 +1,8 @@ +PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. + +Released under the GNU General Public License, version 2. +See LICENSE for details. + +Different licensing models may exist, at the sole discretion of +the Copyright holders. + diff --git a/ext/picotcp/LICENSE b/ext/picotcp/LICENSE new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/ext/picotcp/LICENSE @@ -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/ext/picotcp/MODTREE b/ext/picotcp/MODTREE new file mode 100644 index 0000000..6e343a9 --- /dev/null +++ b/ext/picotcp/MODTREE @@ -0,0 +1,23 @@ +RTOS: +IPV4: +IPV6: +DEVLOOP: +CRC: +ETH: IPV4 +TCP: IPV4 +UDP: IPV4 +IPV4FRAG: IPV4 +NAT: IPV4 UDP +ICMP4: IPV4 +MCAST: UDP +PING: ICMP4 +DHCP_CLIENT: UDP +DHCP_SERVER: UDP +DNS_CLIENT: UDP +IPFILTER: IPV4 +OLSR: MCAST +SLAACV4: IPV4 +SNTP_CLIENT: DNS_CLIENT +TFTP: UDP +MDNS: MCAST +DNS_SD: MDNS diff --git a/ext/picotcp/Makefile b/ext/picotcp/Makefile new file mode 100644 index 0000000..e4cfb94 --- /dev/null +++ b/ext/picotcp/Makefile @@ -0,0 +1,416 @@ +-include ../../config.mk +-include ../../tools/kconfig/.config + +CC:=$(CROSS_COMPILE)gcc +LD:=$(CROSS_COMPILE)ld +AR:=$(CROSS_COMPILE)ar +RANLIB:=$(CROSS_COMPILE)ranlib +SIZE:=$(CROSS_COMPILE)size +STRIP_BIN:=$(CROSS_COMPILE)strip +TEST_LDFLAGS=-pthread $(PREFIX)/modules/*.o $(PREFIX)/lib/*.o -lvdeplug +LIBNAME:="libpicotcp.a" + +PREFIX?=$(PWD)/build +DEBUG?=1 +PROFILE?=0 +PERF?=0 +ENDIAN?=little +STRIP?=0 +RTOS?=0 +GENERIC?=0 +PTHREAD?=0 +ADDRESS_SANITIZER?=1 + +# Default compiled-in protocols +# +TCP?=1 +UDP?=1 +ETH?=1 +IPV4?=1 +IPV4FRAG?=1 +IPV6FRAG?=0 +NAT?=1 +ICMP4?=1 +MCAST?=1 +DEVLOOP?=1 +PING?=1 +DHCP_CLIENT?=1 +DHCP_SERVER?=1 +DNS_CLIENT?=1 +MDNS?=1 +DNS_SD?=1 +SNTP_CLIENT?=1 +IPFILTER?=1 +CRC?=1 +OLSR?=0 +SLAACV4?=1 +TFTP?=1 +AODV?=1 +MEMORY_MANAGER?=0 +MEMORY_MANAGER_PROFILING?=0 +TUN?=0 +TAP?=0 +PCAP?=0 +PPP?=1 +CYASSL?=0 +WOLFSSL?=0 +POLARSSL?=0 + +#IPv6 related +IPV6?=1 + +EXTRA_CFLAGS+=-DPICO_COMPILE_TIME=`date +%s` +EXTRA_CFLAGS+=$(PLATFORM_CFLAGS) + +CFLAGS=-I$(PREFIX)/include -Iinclude -Imodules -Wall -Wdeclaration-after-statement -W -Wextra -Wshadow -Wcast-qual -Wwrite-strings -Wunused-variable -Wundef -Wunused-function $(EXTRA_CFLAGS) +# extra flags recommanded by TIOBE TICS framework to score an A on compiler warnings +CFLAGS+= -Wconversion +# request from Toon +CFLAGS+= -Wcast-align +CFLAGS+= -Wmissing-prototypes +CFLAGS+= -Wno-missing-field-initializers + + +ifeq ($(DEBUG),1) + CFLAGS+=-ggdb +else + ifeq ($(PERF), 1) + CFLAGS+=-O3 + else + CFLAGS+=-Os + endif +endif + +ifeq ($(PROFILE),1) + CFLAGS+=-pg +endif + +ifeq ($(TFTP),1) + MOD_OBJ+=$(LIBBASE)modules/pico_strings.o $(LIBBASE)modules/pico_tftp.o + OPTIONS+=-DPICO_SUPPORT_TFTP +endif + +ifeq ($(AODV),1) + MOD_OBJ+=$(LIBBASE)modules/pico_aodv.o + OPTIONS+=-DPICO_SUPPORT_AODV +endif + +ifeq ($(GENERIC),1) + CFLAGS+=-DGENERIC +endif + +ifeq ($(PTHREAD),1) + CFLAGS+=-DPICO_SUPPORT_PTHREAD +endif + + +ifneq ($(ENDIAN),little) + CFLAGS+=-DPICO_BIGENDIAN +endif + +ifneq ($(RTOS),0) + OPTIONS+=-DPICO_SUPPORT_RTOS +endif + +ifeq ($(ARCH),cortexm4-hardfloat) + CFLAGS+=-DCORTEX_M4_HARDFLOAT -mcpu=cortex-m4 -mthumb -mlittle-endian -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb-interwork -fsingle-precision-constant +endif + +ifeq ($(ARCH),cortexm4-softfloat) + CFLAGS+=-DCORTEX_M4_SOFTFLOAT -mcpu=cortex-m4 -mthumb -mlittle-endian -mfloat-abi=soft -mthumb-interwork +endif + +ifeq ($(ARCH),cortexm3) + CFLAGS+=-DCORTEX_M3 -mcpu=cortex-m3 -mthumb -mlittle-endian -mthumb-interwork +endif + +ifeq ($(ARCH),arm9) + CFLAGS+=-DARM9 -mcpu=arm9e -march=armv5te -gdwarf-2 -Wall -marm -mthumb-interwork -fpack-struct +endif + +ifeq ($(ADDRESS_SANITIZER),1) + TEST_LDFLAGS+=-fsanitize=address -fno-omit-frame-pointer +endif + +ifeq ($(ARCH),faulty) + CFLAGS+=-DFAULTY -DUNIT_TEST + ifeq ($(ADDRESS_SANITIZER),1) + CFLAGS+=-fsanitize=address + endif + CFLAGS+=-fno-omit-frame-pointer + UNITS_OBJ+=test/pico_faulty.o + TEST_OBJ+=test/pico_faulty.o + DUMMY_EXTRA+=test/pico_faulty.o +endif + +ifeq ($(ARCH),msp430) + CFLAGS+=-DMSP430 +endif + +ifeq ($(ARCH),esp8266) + CFLAGS+=-DESP8266 -Wl,-EL -fno-inline-functions -nostdlib -mlongcalls -mtext-section-literals +endif + +ifeq ($(ARCH),mt7681) + CFLAGS+=-DMT7681 -fno-builtin -ffunction-sections -fno-strict-aliasing -m16bit -mabi=2 -mbaseline=V2 -mcpu=n9 -mno-div -mel -mmw-count=8 -mno-ext-mac -mno-dx-regs +endif + +ifeq ($(ARCH),pic24) + CFLAGS+=-DPIC24 -c -mcpu=24FJ256GA106 -MMD -MF -g -omf=elf \ + -mlarge-code -mlarge-data -msmart-io=1 -msfr-warn=off +endif + +ifeq ($(ARCH),atmega128) + CFLAGS+=-Wall -mmcu=atmega128 -DAVR +endif + +ifeq ($(ARCH),none) + CFLAGS+=-DARCHNONE +endif + +ifeq ($(ARCH),shared) + CFLAGS+=-fPIC +endif + +%.o:%.c deps + $(CC) -c $(CFLAGS) -o $@ $< + +CORE_OBJ= stack/pico_stack.o \ + stack/pico_frame.o \ + stack/pico_device.o \ + stack/pico_protocol.o \ + stack/pico_socket.o \ + stack/pico_socket_multicast.o \ + stack/pico_tree.o \ + stack/pico_md5.o + +POSIX_OBJ+= modules/pico_dev_vde.o \ + modules/pico_dev_tun.o \ + modules/pico_dev_tap.o \ + modules/pico_dev_mock.o + +ifneq ($(ETH),0) + include rules/eth.mk +endif +ifneq ($(IPV4),0) + include rules/ipv4.mk +endif +ifneq ($(IPV4FRAG),0) + include rules/ipv4frag.mk +endif +ifneq ($(ICMP4),0) + include rules/icmp4.mk +endif +ifneq ($(TCP),0) + include rules/tcp.mk +endif +ifneq ($(UDP),0) + include rules/udp.mk +endif +ifneq ($(MCAST),0) + include rules/mcast.mk + include rules/igmp.mk + include rules/mld.mk +endif +ifneq ($(NAT),0) + include rules/nat.mk +endif +ifneq ($(DEVLOOP),0) + include rules/devloop.mk +endif +ifneq ($(DHCP_CLIENT),0) + include rules/dhcp_client.mk +endif +ifneq ($(DHCP_SERVER),0) + include rules/dhcp_server.mk +endif +ifneq ($(DNS_CLIENT),0) + include rules/dns_client.mk +endif +ifneq ($(MDNS),0) + include rules/mdns.mk +endif +ifneq ($(DNS_SD),0) + include rules/dns_sd.mk +endif +ifneq ($(IPFILTER),0) + include rules/ipfilter.mk +endif +ifneq ($(CRC),0) + include rules/crc.mk +endif +ifneq ($(OLSR),0) + include rules/olsr.mk +endif +ifneq ($(SLAACV4),0) + include rules/slaacv4.mk +endif +ifneq ($(IPV6),0) + include rules/ipv6.mk +endif +ifneq ($(MEMORY_MANAGER),0) + include rules/memory_manager.mk +endif +ifneq ($(MEMORY_MANAGER_PROFILING),0) + OPTIONS+=-DPICO_SUPPORT_MM_PROFILING +endif +ifneq ($(SNTP_CLIENT),0) + include rules/sntp_client.mk +endif +ifneq ($(TUN),0) + include rules/tun.mk +endif +ifneq ($(TAP),0) + include rules/tap.mk +endif +ifneq ($(PCAP),0) + include rules/pcap.mk +endif +ifneq ($(PPP),0) + include rules/ppp.mk +endif +ifneq ($(CYASSL),0) + include rules/cyassl.mk +endif +ifneq ($(WOLFSSL),0) + include rules/wolfssl.mk +endif +ifneq ($(POLARSSL),0) + include rules/polarssl.mk +endif + +all: mod core lib + +core: $(CORE_OBJ) + @mkdir -p $(PREFIX)/lib + @mv stack/*.o $(PREFIX)/lib + +mod: $(MOD_OBJ) + @mkdir -p $(PREFIX)/modules + @mv modules/*.o $(PREFIX)/modules || echo + +posix: all $(POSIX_OBJ) + @mv modules/*.o $(PREFIX)/modules || echo + + +TEST_ELF= test/picoapp.elf +TEST6_ELF= test/picoapp6.elf + + +test: posix + @mkdir -p $(PREFIX)/test/ + @make -C test/examples PREFIX=$(PREFIX) + @echo -e "\t[CC] picoapp.o" + @$(CC) -c -o $(PREFIX)/examples/picoapp.o test/picoapp.c $(CFLAGS) -Itest/examples + @echo -e "\t[LD] $@" + @$(CC) -g -o $(TEST_ELF) -I include -I modules -I $(PREFIX)/include -Wl,--start-group $(TEST_LDFLAGS) $(TEST_OBJ) $(PREFIX)/examples/*.o -Wl,--end-group + @mv test/*.elf $(PREFIX)/test + @install $(PREFIX)/$(TEST_ELF) $(PREFIX)/$(TEST6_ELF) + +tst: test + +$(PREFIX)/include/pico_defines.h: + @mkdir -p $(PREFIX)/lib + @mkdir -p $(PREFIX)/include + @bash ./mkdeps.sh $(PREFIX) $(OPTIONS) + + +deps: $(PREFIX)/include/pico_defines.h + + + +lib: mod core + @cp -f include/*.h $(PREFIX)/include + @cp -fa include/arch $(PREFIX)/include + @cp -f modules/*.h $(PREFIX)/include + @$(AR) cru $(PREFIX)/lib/$(LIBNAME) $(PREFIX)/modules/*.o $(PREFIX)/lib/*.o \ + || $(AR) cru $(PREFIX)/lib/$(LIBNAME) $(PREFIX)/lib/*.o + @$(RANLIB) $(PREFIX)/lib/$(LIBNAME) + +loop: mod core + mkdir -p $(PREFIX)/test + @$(CC) -c -o $(PREFIX)/modules/pico_dev_loop.o modules/pico_dev_loop.c $(CFLAGS) + @$(CC) -c -o $(PREFIX)/loop_ping.o test/loop_ping.c $(CFLAGS) -ggdb + +units: mod core lib $(UNITS_OBJ) $(MOD_OBJ) + @echo -e "\n\t[UNIT TESTS SUITE]" + @mkdir -p $(PREFIX)/test + @echo -e "\t[CC] units.o" + @$(CC) -g -c -o $(PREFIX)/test/units.o test/units.c $(CFLAGS) -I stack -I modules -I includes -I test/unit -DUNIT_TEST + @echo -e "\t[LD] $(PREFIX)/test/units" + @$(CC) -o $(PREFIX)/test/units $(CFLAGS) $(PREFIX)/test/units.o -lcheck -lm -pthread -lrt \ + $(UNITS_OBJ) $(PREFIX)/modules/pico_aodv.o \ + $(PREFIX)/modules/pico_fragments.o + @$(CC) -o $(PREFIX)/test/modunit_pico_protocol.elf $(CFLAGS) -I. test/unit/modunit_pico_protocol.c stack/pico_tree.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) + @$(CC) -o $(PREFIX)/test/modunit_pico_frame.elf $(CFLAGS) -I. test/unit/modunit_pico_frame.c stack/pico_tree.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) + @$(CC) -o $(PREFIX)/test/modunit_seq.elf $(CFLAGS) -I. test/unit/modunit_seq.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a + @$(CC) -o $(PREFIX)/test/modunit_tcp.elf $(CFLAGS) -I. test/unit/modunit_pico_tcp.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a + @$(CC) -o $(PREFIX)/test/modunit_dns_client.elf $(CFLAGS) -I. test/unit/modunit_pico_dns_client.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a + @$(CC) -o $(PREFIX)/test/modunit_dns_common.elf $(CFLAGS) -I. test/unit/modunit_pico_dns_common.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a + @$(CC) -o $(PREFIX)/test/modunit_mdns.elf $(CFLAGS) -I. test/unit/modunit_pico_mdns.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a + @$(CC) -o $(PREFIX)/test/modunit_dns_sd.elf $(CFLAGS) -I. test/unit/modunit_pico_dns_sd.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a + @$(CC) -o $(PREFIX)/test/modunit_dev_loop.elf $(CFLAGS) -I. test/unit/modunit_pico_dev_loop.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) + @$(CC) -o $(PREFIX)/test/modunit_ipv6_nd.elf $(CFLAGS) -I. test/unit/modunit_pico_ipv6_nd.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a + @$(CC) -o $(PREFIX)/test/modunit_pico_stack.elf $(CFLAGS) -I. test/unit/modunit_pico_stack.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a + @$(CC) -o $(PREFIX)/test/modunit_tftp.elf $(CFLAGS) -I. test/unit/modunit_pico_tftp.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a + @$(CC) -o $(PREFIX)/test/modunit_sntp_client.elf $(CFLAGS) -I. test/unit/modunit_pico_sntp_client.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) + @$(CC) -o $(PREFIX)/test/modunit_ipfilter.elf $(CFLAGS) -I. test/unit/modunit_pico_ipfilter.c stack/pico_tree.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) + @$(CC) -o $(PREFIX)/test/modunit_aodv.elf $(CFLAGS) -I. test/unit/modunit_pico_aodv.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a + @$(CC) -o $(PREFIX)/test/modunit_fragments.elf $(CFLAGS) -I. test/unit/modunit_pico_fragments.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a + @$(CC) -o $(PREFIX)/test/modunit_queue.elf $(CFLAGS) -I. test/unit/modunit_queue.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) + @$(CC) -o $(PREFIX)/test/modunit_dev_ppp.elf $(CFLAGS) -I. test/unit/modunit_pico_dev_ppp.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a + @$(CC) -o $(PREFIX)/test/modunit_mld.elf $(CFLAGS) -I. test/unit/modunit_pico_mld.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a + @$(CC) -o $(PREFIX)/test/modunit_igmp.elf $(CFLAGS) -I. test/unit/modunit_pico_igmp.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a + @$(CC) -o $(PREFIX)/test/modunit_hotplug_detection.elf $(CFLAGS) -I. test/unit/modunit_pico_hotplug_detection.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a + +devunits: mod core lib + @echo -e "\n\t[UNIT TESTS SUITE: device drivers]" + @mkdir -p $(PREFIX)/test/unit/device/ + @echo -e "\t[CC] picotcp_mock.o" + @$(CC) -c -o $(PREFIX)/test/unit/device/picotcp_mock.o $(CFLAGS) -I stack -I modules -I includes -I test/unit test/unit/device/picotcp_mock.c + @$(CC) -c -o $(PREFIX)/test/unit/device/unit_dev_vde.o $(CFLAGS) -I stack -I modules -I includes -I test/unit test/unit/device/unit_dev_vde.c + @echo -e "\t[LD] $(PREFIX)/test/devunits" + @$(CC) -o $(PREFIX)/test/devunits $(CFLAGS) -I stack $(PREFIX)/test/unit/device/*.o -lcheck -lm -pthread -lrt + +units_mm: mod core lib + @echo -e "\n\t[UNIT TESTS SUITE]" + @mkdir -p $(PREFIX)/test + @echo -e "\t[CC] units_mm.o" + @$(CC) -c -o $(PREFIX)/test/units_mm.o test/unit/unit_mem_manager.c $(CFLAGS) -I stack -I modules -I includes -I test/unit + @echo -e "\t[LD] $(PREFIX)/test/units" + @$(CC) -o $(PREFIX)/test/units_mm $(CFLAGS) $(PREFIX)/test/units_mm.o -lcheck -lm -pthread -lrt + + +clean: + @echo -e "\t[CLEAN] $(PREFIX)/" + @rm -rf $(PREFIX) tags + +mbed: + @echo -e "\t[Creating PicoTCP.zip]" + @rm -f PicoTCP.zip + @cp include/pico_socket.h include/socket.tmp + @echo "#define MBED\n" > include/mbed.tmp + @cat include/mbed.tmp include/socket.tmp > include/pico_socket.h + @zip -0 PicoTCP.zip -r include modules stack -x include/arch/ include/arch/* include/pico_config.h include/*.tmp modules/pico_dev_* + @rm include/pico_socket.h include/mbed.tmp + @mv include/socket.tmp include/pico_socket.h + + +style: + @find . -iname "*.[c|h]" | xargs -x uncrustify --replace -l C -c uncrustify.cfg || true + @find . -iname "*unc-backup*" |xargs -x rm || true + +dummy: mod core lib $(DUMMY_EXTRA) + @echo testing configuration... + @$(CC) -c -o test/dummy.o test/dummy.c $(CFLAGS) + @$(CC) -o dummy test/dummy.o $(DUMMY_EXTRA) $(PREFIX)/lib/libpicotcp.a $(LDFLAGS) $(CFLAGS) + @echo done. + @rm -f test/dummy.o dummy + +ppptest: test/ppp.c lib + gcc -ggdb -c -o ppp.o test/ppp.c -I build/include/ -I build/modules/ $(CFLAGS) + gcc -o ppp ppp.o build/lib/libpicotcp.a $(LDFLAGS) $(CFLAGS) + rm -f ppp.o + + +FORCE: diff --git a/ext/picotcp/Makefile.watcom b/ext/picotcp/Makefile.watcom new file mode 100644 index 0000000..cbe04d7 --- /dev/null +++ b/ext/picotcp/Makefile.watcom @@ -0,0 +1,403 @@ +-include ../../config.mk +-include ../../tools/kconfig/.config + +WATCOM_PATH:=/opt/watcom +CC:=$(WATCOM_PATH)/binl/$(CROSS_COMPILE)wcc386 +LD:=$(WATCOM_PATH)/binl/$(CROSS_COMPILE)wcl386 +AR:=$(WATCOM_PATH)/binl/$(CROSS_COMPILE)wlib +RANLIB:=$(WATCOM_PATH)/binl/$(CROSS_COMPILE)ranlib +SIZE:=$(CROSS_COMPILE)size +STRIP_BIN:=$(CROSS_COMPILE)strip +TEST_LDFLAGS=-pthread $(PREFIX)/modules/*.o $(PREFIX)/lib/*.o -lvdeplug +LIBNAME:=libpicotcp.a + +PREFIX?=$(PWD)/build +DEBUG?=1 +PROFILE?=0 +PERF?=0 +ENDIAN?=little +STRIP?=0 +RTOS?=0 +GENERIC?=0 +PTHREAD?=0 +ADDRESS_SANITIZER?=1 + +# Default compiled-in protocols +# +TCP?=1 +UDP?=1 +ETH?=1 +IPV4?=1 +IPV4FRAG?=1 +IPV6FRAG?=0 +NAT?=1 +ICMP4?=1 +MCAST?=1 +DEVLOOP?=1 +PING?=1 +DHCP_CLIENT?=1 +DHCP_SERVER?=1 +DNS_CLIENT?=1 +MDNS?=1 +DNS_SD?=1 +SNTP_CLIENT?=1 +IPFILTER?=1 +CRC?=1 +OLSR?=0 +SLAACV4?=1 +TFTP?=1 +AODV?=1 +MEMORY_MANAGER?=0 +MEMORY_MANAGER_PROFILING?=0 +TUN?=0 +TAP?=0 +PCAP?=0 +PPP?=0 +CYASSL?=0 +WOLFSSL?=0 +POLARSSL?=0 + +#IPv6 related +IPV6?=1 + +EXTRA_CFLAGS+=-dPICO_COMPILE_TIME=`date +%s` +EXTRA_CFLAGS+=$(PLATFORM_CFLAGS) + +CFLAGS=-i=$(WATCOM_PATH)/h -i=$(PREFIX)/include -i=include -i=modules $(EXTRA_CFLAGS) -q + + +ifeq ($(DEBUG),1) + CFLAGS+=-od -of -d9 +else + ifeq ($(PERF), 1) + CFLAGS+= + else + CFLAGS+= + endif +endif + +ifeq ($(TFTP),1) + MOD_OBJ+=$(LIBBASE)modules/pico_strings.o $(LIBBASE)modules/pico_tftp.o + OPTIONS+=-dPICO_SUPPORT_TFTP +endif + +ifeq ($(AODV),1) + MOD_OBJ+=$(LIBBASE)modules/pico_aodv.o + OPTIONS+=-dPICO_SUPPORT_AODV +endif + +ifeq ($(GENERIC),1) + CFLAGS+=-dGENERIC +endif + +ifeq ($(PTHREAD),1) + CFLAGS+=-dPICO_SUPPORT_PTHREAD +endif + + +ifneq ($(ENDIAN),little) + CFLAGS+=-dPICO_BIGENDIAN +endif + +ifneq ($(RTOS),0) + OPTIONS+=-dPICO_SUPPORT_RTOS +endif + +ifeq ($(ARCH),cortexm4-hardfloat) + CFLAGS+=-dCORTEX_M4_HARDFLOAT -mcpu=cortex-m4 -mthumb -mlittle-endian -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb-interwork -fsingle-precision-constant +endif + +ifeq ($(ARCH),cortexm4-softfloat) + CFLAGS+=-dCORTEX_M4_SOFTFLOAT -mcpu=cortex-m4 -mthumb -mlittle-endian -mfloat-abi=soft -mthumb-interwork +endif + +ifeq ($(ARCH),cortexm3) + CFLAGS+=-dCORTEX_M3 -mcpu=cortex-m3 -mthumb -mlittle-endian -mthumb-interwork +endif + +ifeq ($(ARCH),arm9) + CFLAGS+=-dARM9 -mcpu=arm9e -march=armv5te -gdwarf-2 -Wall -marm -mthumb-interwork -fpack-struct +endif + +ifeq ($(ADDRESS_SANITIZER),1) + TEST_LDFLAGS+=-fsanitize=address -fno-omit-frame-pointer +endif + +ifeq ($(ARCH),faulty) + CFLAGS+=-dFAULTY -dUNIT_TEST + CFLAGS+=-fsanitize=address -fno-omit-frame-pointer + UNITS_OBJ+=test/pico_faulty.o + TEST_OBJ+=test/pico_faulty.o + DUMMY_EXTRA+=test/pico_faulty.o +endif + +ifeq ($(ARCH),msp430) + CFLAGS+=-dMSP430 +endif + +ifeq ($(ARCH),esp8266) + CFLAGS+=-dESP8266 -Wl,-EL -fno-inline-functions -nostdlib -mlongcalls -mtext-section-literals +endif + +ifeq ($(ARCH),mt7681) + CFLAGS+=-dMT7681 -fno-builtin -ffunction-sections -fno-strict-aliasing -m16bit -mabi=2 -mbaseline=V2 -mcpu=n9 -mno-div -mel -mmw-count=8 -mno-ext-mac -mno-dx-regs +endif + +ifeq ($(ARCH),pic24) + CFLAGS+=-dPIC24 -mcpu=24FJ256GA106 -MMD -MF -g -omf=elf \ + -mlarge-code -mlarge-data -msmart-io=1 -msfr-warn=off +endif + +ifeq ($(ARCH),atmega128) + CFLAGS+=-Wall -mmcu=atmega128 -dAVR +endif + +ifeq ($(ARCH),none) + CFLAGS+=-dARCHNONE +endif + +ifeq ($(ARCH),shared) + CFLAGS+=-fPIC +endif + +%.o:%.c deps + $(CC) $(CFLAGS) -fo=$@ $< + +CORE_OBJ= stack/pico_stack.o \ + stack/pico_frame.o \ + stack/pico_device.o \ + stack/pico_protocol.o \ + stack/pico_socket.o \ + stack/pico_socket_multicast.o \ + stack/pico_tree.o \ + stack/pico_md5.o + +POSIX_OBJ+= modules/pico_dev_vde.o \ + modules/pico_dev_tun.o \ + modules/pico_dev_tap.o \ + modules/pico_dev_mock.o + +ifneq ($(ETH),0) + include rules/eth.mk +endif +ifneq ($(IPV4),0) + include rules/ipv4.mk +endif +ifneq ($(IPV4FRAG),0) + include rules/ipv4frag.mk +endif +ifneq ($(ICMP4),0) + include rules/icmp4.mk +endif +ifneq ($(TCP),0) + include rules/tcp.mk +endif +ifneq ($(UDP),0) + include rules/udp.mk +endif +ifneq ($(MCAST),0) + include rules/mcast.mk + include rules/igmp.mk +endif +ifneq ($(NAT),0) + include rules/nat.mk +endif +ifneq ($(DEVLOOP),0) + include rules/devloop.mk +endif +ifneq ($(DHCP_CLIENT),0) + include rules/dhcp_client.mk +endif +ifneq ($(DHCP_SERVER),0) + include rules/dhcp_server.mk +endif +ifneq ($(DNS_CLIENT),0) + include rules/dns_client.mk +endif +ifneq ($(MDNS),0) + include rules/mdns.mk +endif +ifneq ($(DNS_SD),0) + include rules/dns_sd.mk +endif +ifneq ($(IPFILTER),0) + include rules/ipfilter.mk +endif +ifneq ($(CRC),0) + include rules/crc.mk +endif +ifneq ($(OLSR),0) + include rules/olsr.mk +endif +ifneq ($(SLAACV4),0) + include rules/slaacv4.mk +endif +ifneq ($(IPV6),0) + include rules/ipv6.mk +endif +ifneq ($(MEMORY_MANAGER),0) + include rules/memory_manager.mk +endif +ifneq ($(MEMORY_MANAGER_PROFILING),0) + OPTIONS+=-dPICO_SUPPORT_MM_PROFILING +endif +ifneq ($(SNTP_CLIENT),0) + include rules/sntp_client.mk +endif +ifneq ($(TUN),0) + include rules/tun.mk +endif +ifneq ($(TAP),0) + include rules/tap.mk +endif +ifneq ($(PCAP),0) + include rules/pcap.mk +endif +ifneq ($(PPP),0) + include rules/ppp.mk +endif +ifneq ($(CYASSL),0) + include rules/cyassl.mk +endif +ifneq ($(WOLFSSL),0) + include rules/wolfssl.mk +endif +ifneq ($(POLARSSL),0) + include rules/polarssl.mk +endif + +all: mod core lib + +core: $(CORE_OBJ) + @mkdir -p $(PREFIX)/lib + @mv stack/*.o $(PREFIX)/lib + +mod: $(MOD_OBJ) + @mkdir -p $(PREFIX)/modules + @mv modules/*.o $(PREFIX)/modules || echo + +posix: all $(POSIX_OBJ) + @mv modules/*.o $(PREFIX)/modules || echo + + +TEST_ELF= test/picoapp.elf +TEST6_ELF= test/picoapp6.elf + + +test: posix + @mkdir -p $(PREFIX)/test/ + @make -C test/examples PREFIX=$(PREFIX) + @echo -e "\t[CC] picoapp.o" + @$(CC) -c -o $(PREFIX)/examples/picoapp.o test/picoapp.c $(CFLAGS) -Itest/examples + @echo -e "\t[LD] $@" + @$(CC) -g -o $(TEST_ELF) -I include -I modules -I $(PREFIX)/include -Wl,--start-group $(TEST_LDFLAGS) $(TEST_OBJ) $(PREFIX)/examples/*.o -Wl,--end-group + @mv test/*.elf $(PREFIX)/test + @install $(PREFIX)/$(TEST_ELF) $(PREFIX)/$(TEST6_ELF) + +tst: test + +$(PREFIX)/include/pico_defines.h: + @mkdir -p $(PREFIX)/lib + @mkdir -p $(PREFIX)/include + @bash ./mkdeps.sh $(PREFIX) $(OPTIONS) + + +deps: $(PREFIX)/include/pico_defines.h + + + +lib: mod core + @cp -f include/*.h $(PREFIX)/include + @cp -fa include/arch $(PREFIX)/include + @cp -f modules/*.h $(PREFIX)/include + @echo -e "\t[AR] $(PREFIX)/lib/$(LIBNAME)" + $(AR) -q -b -n -fag -o=$(PREFIX)/lib/$(LIBNAME) $(PREFIX)/modules/*.o $(PREFIX)/lib/*.o + @echo || $(AR) cru $(PREFIX)/lib/$(LIBNAME) $(PREFIX)/lib/*.o + @echo -e "\t[RANLIB] $(PREFIX)/lib/$(LIBNAME)" + @$(RANLIB) $(PREFIX)/lib/$(LIBNAME) + @echo -e "\t[LIBSIZE] `du -b $(PREFIX)/lib/$(LIBNAME)`" + +loop: mod core + mkdir -p $(PREFIX)/test + @$(CC) -c -o $(PREFIX)/modules/pico_dev_loop.o modules/pico_dev_loop.c $(CFLAGS) + @$(CC) -c -o $(PREFIX)/loop_ping.o test/loop_ping.c $(CFLAGS) -ggdb + +units: mod core lib $(UNITS_OBJ) $(MOD_OBJ) + @echo -e "\n\t[UNIT TESTS SUITE]" + @mkdir -p $(PREFIX)/test + @echo -e "\t[CC] units.o" + @$(CC) -c -o $(PREFIX)/test/units.o test/units.c $(CFLAGS) -I stack -I modules -I includes -I test/unit -dUNIT_TEST + @echo -e "\t[LD] $(PREFIX)/test/units" + @$(CC) -o $(PREFIX)/test/units $(CFLAGS) $(PREFIX)/test/units.o -lcheck -lm -pthread -lrt \ + $(UNITS_OBJ) $(PREFIX)/modules/pico_aodv.o \ + $(PREFIX)/modules/pico_fragments.o + @$(CC) -o $(PREFIX)/test/modunit_pico_protocol.elf $(CFLAGS) -I. test/unit/modunit_pico_protocol.c stack/pico_tree.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) + @$(CC) -o $(PREFIX)/test/modunit_pico_frame.elf $(CFLAGS) -I. test/unit/modunit_pico_frame.c stack/pico_tree.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) + @$(CC) -o $(PREFIX)/test/modunit_seq.elf $(CFLAGS) -I. test/unit/modunit_seq.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a + @$(CC) -o $(PREFIX)/test/modunit_tcp.elf $(CFLAGS) -I. test/unit/modunit_pico_tcp.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a + @$(CC) -o $(PREFIX)/test/modunit_dns_client.elf $(CFLAGS) -I. test/unit/modunit_pico_dns_client.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a + @$(CC) -o $(PREFIX)/test/modunit_dns_common.elf $(CFLAGS) -I. test/unit/modunit_pico_dns_common.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a + @$(CC) -o $(PREFIX)/test/modunit_mdns.elf $(CFLAGS) -I. test/unit/modunit_pico_mdns.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a + @$(CC) -o $(PREFIX)/test/modunit_dns_sd.elf $(CFLAGS) -I. test/unit/modunit_pico_dns_sd.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a + @$(CC) -o $(PREFIX)/test/modunit_dev_loop.elf $(CFLAGS) -I. test/unit/modunit_pico_dev_loop.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) + @$(CC) -o $(PREFIX)/test/modunit_ipv6_nd.elf $(CFLAGS) -I. test/unit/modunit_pico_ipv6_nd.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a + @$(CC) -o $(PREFIX)/test/modunit_pico_stack.elf $(CFLAGS) -I. test/unit/modunit_pico_stack.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a + @$(CC) -o $(PREFIX)/test/modunit_tftp.elf $(CFLAGS) -I. test/unit/modunit_pico_tftp.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a + @$(CC) -o $(PREFIX)/test/modunit_sntp_client.elf $(CFLAGS) -I. test/unit/modunit_pico_sntp_client.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) + @$(CC) -o $(PREFIX)/test/modunit_ipfilter.elf $(CFLAGS) -I. test/unit/modunit_pico_ipfilter.c stack/pico_tree.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) + @$(CC) -o $(PREFIX)/test/modunit_aodv.elf $(CFLAGS) -I. test/unit/modunit_pico_aodv.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a + @$(CC) -o $(PREFIX)/test/modunit_fragments.elf $(CFLAGS) -I. test/unit/modunit_pico_fragments.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a + @$(CC) -o $(PREFIX)/test/modunit_queue.elf $(CFLAGS) -I. test/unit/modunit_queue.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) + @$(CC) -o $(PREFIX)/test/modunit_dev_ppp.elf $(CFLAGS) -I. test/unit/modunit_pico_dev_ppp.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a + +devunits: mod core lib + @echo -e "\n\t[UNIT TESTS SUITE: device drivers]" + @mkdir -p $(PREFIX)/test/unit/device/ + @echo -e "\t[CC] picotcp_mock.o" + @$(CC) -c -o $(PREFIX)/test/unit/device/picotcp_mock.o $(CFLAGS) -I stack -I modules -I includes -I test/unit test/unit/device/picotcp_mock.c + @$(CC) -c -o $(PREFIX)/test/unit/device/unit_dev_vde.o $(CFLAGS) -I stack -I modules -I includes -I test/unit test/unit/device/unit_dev_vde.c + @echo -e "\t[LD] $(PREFIX)/test/devunits" + @$(CC) -o $(PREFIX)/test/devunits $(CFLAGS) -I stack $(PREFIX)/test/unit/device/*.o -lcheck -lm -pthread -lrt + +units_mm: mod core lib + @echo -e "\n\t[UNIT TESTS SUITE]" + @mkdir -p $(PREFIX)/test + @echo -e "\t[CC] units_mm.o" + @$(CC) -c -o $(PREFIX)/test/units_mm.o test/unit/unit_mem_manager.c $(CFLAGS) -I stack -I modules -I includes -I test/unit + @echo -e "\t[LD] $(PREFIX)/test/units" + @$(CC) -o $(PREFIX)/test/units_mm $(CFLAGS) $(PREFIX)/test/units_mm.o -lcheck -lm -pthread -lrt + + +clean: + @echo -e "\t[CLEAN] $(PREFIX)/" + @rm -rf $(PREFIX) tags + +mbed: + @echo -e "\t[Creating PicoTCP.zip]" + @rm -f PicoTCP.zip + @cp include/pico_socket.h include/socket.tmp + @echo "#define MBED\n" > include/mbed.tmp + @cat include/mbed.tmp include/socket.tmp > include/pico_socket.h + @zip -0 PicoTCP.zip -r include modules stack -x include/arch/ include/arch/* include/pico_config.h include/*.tmp modules/pico_dev_* + @rm include/pico_socket.h include/mbed.tmp + @mv include/socket.tmp include/pico_socket.h + + +style: + @find . -iname "*.[c|h]" | xargs -x uncrustify --replace -l C -c uncrustify.cfg || true + @find . -iname "*unc-backup*" |xargs -x rm || true + +dummy: mod core lib $(DUMMY_EXTRA) + @echo testing configuration... + @$(CC) -c -o test/dummy.o test/dummy.c $(CFLAGS) + @$(CC) -o dummy test/dummy.o $(DUMMY_EXTRA) $(PREFIX)/lib/libpicotcp.a $(LDFLAGS) $(CFLAGS) + @echo done. + @rm -f test/dummy.o dummy + +ppptest: test/ppp.c lib + gcc -ggdb -c -o ppp.o test/ppp.c -I build/include/ -I build/modules/ $(CFLAGS) + gcc -o ppp ppp.o build/lib/libpicotcp.a $(LDFLAGS) $(CFLAGS) + rm -f ppp.o + + +FORCE: diff --git a/ext/picotcp/README.md b/ext/picotcp/README.md new file mode 100644 index 0000000..37c5d43 --- /dev/null +++ b/ext/picotcp/README.md @@ -0,0 +1,103 @@ +picoTCP + +--------------- + +Welcome to the one and only picoTCP repository. + +picoTCP is a small-footprint, modular TCP/IP stack designed for embedded systems and the Internet of Things. It's actively being developed by *[Altran Intelligent Systems](http://intelligent-systems.altran.com/)*. Textual information about picoTCP, you can find on the [about page of our website](http://picotcp.com/about). + +This code is released under the terms of GNU GPL v2 only. Some rights reserved. +Other licenses may apply at the sole discretion of the copyright holders. + +Learn how to use picoTCP in your project by going through the **Getting Started guide** on our [GitHub wiki](https://github.com/tass-belgium/picotcp/wiki). + +For more information visit the [picoTCP website](http://www.picotcp.com), send us an email or contact us on [Twitter](https://twitter.com/picotcp), [Facebook](https://www.facebook.com/picoTCP) or [Reddit](http://www.reddit.com/r/picotcp/). + +Wondering about picoTCP's code quality? Check [our TiCS score](http://tics.picotcp.com:42506/tiobeweb/TICS/TqiDashboard.html#axes=Project%28%29&metric=tqi&sel=Project%28PicoTCP_rel%29) + + +--------------- + +Continuous integration + +Jenkins Functional tests: +[![Jenkins autotest](http://jenkins.picotcp.com:8080/buildStatus/icon?job=picoTCP_Rel/PicoTCP_rel_autotest)](http://jenkins.picotcp.com:8080/job/picoTCP_Rel/job/PicoTCP_rel_autotest) + +Jenkins Unit tests : +[![Jenkins unit tests](http://jenkins.picotcp.com:8080/buildStatus/icon?job=picoTCP_Rel/PicoTCP_rel_unit_tests)](http://jenkins.picotcp.com:8080/job/picoTCP_Rel/job/PicoTCP_rel_unit_tests) + +Jenkins RFC compliance : +[![Jenkins RFC Compliance](http://jenkins.picotcp.com:8080/buildStatus/icon?job=picoTCP_Rel/PicoTCP_rel_RF_mbed)](http://jenkins.picotcp.com:8080/job/picoTCP_Rel/job/PicoTCP_rel_RF_mbed) + +Jenkins TICS quality : +[![Jenkins TICS](http://jenkins.picotcp.com:8080/buildStatus/icon?job=picoTCP_Rel/PicoTCP_rel_TICS)](http://jenkins.picotcp.com:8080/job/picoTCP_Rel/job/PicoTCP_rel_TICS/) + +Coverity Scan Build status: +[![Coverity Scan Build Status](https://scan.coverity.com/projects/7944/badge.svg)](https://scan.coverity.com/projects/7944) + +--------------- + +Works with ... +## Platforms +* ARM Cortex-M series + * ST Micro STM + * NXP LPC + * TI Stellaris + * Freescale K64F +* ARM ARM9-series + * ST Micro STR9 +* Texas Instruments + * MSP430 +* Microchip + * PIC24 +* Atmel + * AVR 8bit +* Linux + * User space (TUN/TAP) + * Kernel space +* Windows + * User space (TAP) + +## Network drivers +* BCM43362 (IEEE 802.11) +* MRF24WG (IEEE 802.11) +* LPC Ethernet ENET/EMAC (IEEE 802.3) +* Stellaris Ethernet (IEEE 802.3) +* STM32 Ethernet (IEEE 802.3) +* Wiznet W5100 (IEEE 802.3) +* USB CDC-ECM (CDC1.2) +* PPP +* Virtual drivers + * TUN/TAP + * VDE + * Libpcap + +## (RT)OSes +* No OS / Bare metal +* FreeRTOS +* mbed-RTOS +* Frosted +* linux / POSIX +* MS DOS +* MS Windows + +## Libraries +* wolfSSL +* mbedTLS +* Mongoose RESTful library +* MicroPython +* HTTP library +* ZeroMQ (WIP) +* MQTT + +## Compilers +* GCC +* Clang +* TCC +* ARM-RCVT +* IAR +* XC-16 +* MSP-GCC +* AVR-GCC + +Your favorite not in the list? Check out the wiki for information and examples on how to port picoTCP to a new platform! diff --git a/ext/picotcp/include/arch/pico_arm9.h b/ext/picotcp/include/arch/pico_arm9.h new file mode 100644 index 0000000..0d2d2e3 --- /dev/null +++ b/ext/picotcp/include/arch/pico_arm9.h @@ -0,0 +1,35 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + *********************************************************************/ +#define dbg(...) do {} while(0) + +/******************/ + +/*** MACHINE CONFIGURATION ***/ +/* Temporary (POSIX) stuff. */ +#include +#include + +extern volatile uint32_t __str9_tick; + +#define pico_native_malloc(x) calloc(x, 1) +#define pico_native_free(x) free(x) + +static inline unsigned long PICO_TIME(void) +{ + register uint32_t tick = __str9_tick; + return tick / 1000; +} + +static inline unsigned long PICO_TIME_MS(void) +{ + return __str9_tick; +} + +static inline void PICO_IDLE(void) +{ + unsigned long tick_now = __str9_tick; + while(tick_now == __str9_tick) ; +} + diff --git a/ext/picotcp/include/arch/pico_avr.h b/ext/picotcp/include/arch/pico_avr.h new file mode 100644 index 0000000..cd7e4ab --- /dev/null +++ b/ext/picotcp/include/arch/pico_avr.h @@ -0,0 +1,39 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + *********************************************************************/ +#define dbg(...) do {} while(0) +/* #define dbg printf */ + +/*************************/ + +/*** MACHINE CONFIGURATION ***/ +/* Temporary (POSIX) stuff. */ +#include +#include +#include +#include "pico_mm.h" + +extern volatile uint32_t __avr_tick; + +#define pico_zalloc(x) calloc(x, 1) +#define pico_free(x) free(x) + +static inline unsigned long PICO_TIME(void) +{ + register uint32_t tick = __avr_tick; + return tick / 1000; +} + +static inline unsigned long PICO_TIME_MS(void) +{ + return __avr_tick; +} + +static inline void PICO_IDLE(void) +{ + unsigned long tick_now = __avr_tick; + while(tick_now == __avr_tick) ; +} + diff --git a/ext/picotcp/include/arch/pico_cortex_m.h b/ext/picotcp/include/arch/pico_cortex_m.h new file mode 100644 index 0000000..cfd12f3 --- /dev/null +++ b/ext/picotcp/include/arch/pico_cortex_m.h @@ -0,0 +1,12 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + *********************************************************************/ +#ifndef _INCLUDE_PICO_CORTEX_M +#define _INCLUDE_PICO_CORTEX_M + +#include "pico_generic_gcc.h" + +#endif /* PICO_CORTEX_M */ + diff --git a/ext/picotcp/include/arch/pico_esp8266.h b/ext/picotcp/include/arch/pico_esp8266.h new file mode 100644 index 0000000..5419e1f --- /dev/null +++ b/ext/picotcp/include/arch/pico_esp8266.h @@ -0,0 +1,55 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2014-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + *********************************************************************/ +#ifndef _INCLUDE_PICO_ESP8266 +#define _INCLUDE_PICO_ESP8266 + +#include + +#include +#include +#include +#include "pico_constants.h" + +/* -------------- DEBUG ------------- */ + +/* #define dbg(...) */ +#define dbg printf + +/* -------------- MEMORY ------------- */ +extern void *pvPortMalloc( size_t xWantedSize ); +extern void vPortFree( void *pv ); + +#define pico_free vPortFree + +static inline void *pico_zalloc(size_t size) +{ + void *ptr = (void *)pvPortMalloc(size); + + if(ptr) + memset(ptr, 0u, size); + + return ptr; +} + +/* -------------- TIME ------------- */ + +extern volatile uint32_t esp_tick; + +static inline pico_time PICO_TIME_MS(void) { + return (pico_time) esp_tick; +} + +static inline pico_time PICO_TIME(void) { + return PICO_TIME_MS() / 1000; +} + +static inline void PICO_IDLE(void) { + uint32_t now = esp_tick; + while (now == esp_tick) + ; +} + +#endif diff --git a/ext/picotcp/include/arch/pico_generic_gcc.h b/ext/picotcp/include/arch/pico_generic_gcc.h new file mode 100644 index 0000000..5958e3f --- /dev/null +++ b/ext/picotcp/include/arch/pico_generic_gcc.h @@ -0,0 +1,102 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + *********************************************************************/ +#ifndef _INCLUDE_PICO_GCC +#define _INCLUDE_PICO_GCC + +#include +#include +#include +#include "pico_constants.h" + +/* monotonically increasing tick, + * typically incremented every millisecond in a systick interrupt */ +extern volatile unsigned int pico_ms_tick; + +#define dbg(...) + +#ifdef PICO_SUPPORT_PTHREAD + #define PICO_SUPPORT_MUTEX +#endif + +#ifdef PICO_SUPPORT_RTOS + #define PICO_SUPPORT_MUTEX + +extern void *pico_mutex_init(void); +extern void pico_mutex_lock(void*); +extern void pico_mutex_unlock(void*); +extern void *pvPortMalloc( size_t xSize ); +extern void vPortFree( void *pv ); + + #define pico_free(x) vPortFree(x) + #define free(x) vPortFree(x) + +static inline void *pico_zalloc(size_t size) +{ + void *ptr = pvPortMalloc(size); + + if(ptr) + memset(ptr, 0u, size); + + return ptr; +} + +static inline pico_time PICO_TIME_MS() +{ + return pico_ms_tick; +} + +static inline pico_time PICO_TIME() +{ + return pico_ms_tick / 1000; +} + +static inline void PICO_IDLE(void) +{ + pico_time now = PICO_TIME_MS(); + while(now == PICO_TIME_MS()) ; +} + +#else /* NO RTOS SUPPORT */ + + #ifdef MEM_MEAS +/* These functions should be implemented elsewhere */ +extern void *memmeas_zalloc(size_t size); +extern void memmeas_free(void *); + #define pico_free(x) memmeas_free(x) + #define pico_zalloc(x) memmeas_zalloc(x) + #else +/* Use plain C-lib malloc and free */ + #define pico_free(x) free(x) +static inline void *pico_zalloc(size_t size) +{ + void *ptr = malloc(size); + if(ptr) + memset(ptr, 0u, size); + + return ptr; +} + #endif + +static inline pico_time PICO_TIME_MS(void) +{ + return (pico_time)pico_ms_tick; +} + +static inline pico_time PICO_TIME(void) +{ + return (pico_time)(PICO_TIME_MS() / 1000); +} + +static inline void PICO_IDLE(void) +{ + unsigned int now = pico_ms_tick; + while(now == pico_ms_tick) ; +} + +#endif /* IFNDEF RTOS */ + +#endif /* PICO_GCC */ + diff --git a/ext/picotcp/include/arch/pico_linux.h b/ext/picotcp/include/arch/pico_linux.h new file mode 100644 index 0000000..3910c25 --- /dev/null +++ b/ext/picotcp/include/arch/pico_linux.h @@ -0,0 +1,33 @@ +#ifndef PICO_SUPPORT_LINUX +#define PICO_SUPPORT_LINUX + +#include "linux/types.h" +#include "linux/mm.h" +#include "linux/slab.h" +#include "linux/jiffies.h" + +#define dbg printk + +#define pico_zalloc(x) kcalloc(x, 1, GFP_ATOMIC) /* All allocations are GFP_ATOMIC for now */ +#define pico_free(x) kfree(x) + + +static inline unsigned long PICO_TIME(void) +{ + return (unsigned long)(jiffies_to_msecs(jiffies) / 1000); +} + +static inline unsigned long PICO_TIME_MS(void) +{ + return (unsigned long)jiffies_to_msecs(jiffies); +} + +static inline void PICO_IDLE(void) +{ + unsigned long now = jiffies; + while (now == jiffies) { + ; + } +} + +#endif diff --git a/ext/picotcp/include/arch/pico_mbed.h b/ext/picotcp/include/arch/pico_mbed.h new file mode 100644 index 0000000..3c3a272 --- /dev/null +++ b/ext/picotcp/include/arch/pico_mbed.h @@ -0,0 +1,185 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + File: pico_mbed.h + Author: Toon Peters + *********************************************************************/ + +#ifndef PICO_SUPPORT_MBED +#define PICO_SUPPORT_MBED +#include +#include +/* #include "mbed.h" */ +/* #include "serial_api.h" */ + +/* #define TIME_PRESCALE */ +/* #define PICO_MEASURE_STACK */ +/* #define MEMORY_MEASURE */ +/* + Debug needs initialization: + * void serial_init (serial_t *obj, PinName tx, PinName rx); + * void serial_baud (serial_t *obj, int baudrate); + * void serial_format (serial_t *obj, int data_bits, SerialParity parity, int stop_bits); + */ + +#define dbg(...) + +/* + #define MEMORY_MEASURE + #define JENKINS_DEBUG + */ + +/* Intended for Mr. Jenkins endurance test loggings */ +#ifdef JENKINS_DEBUG +#include "PicoTerm.h" +#define jenkins_dbg ptm_dbg +#endif + +#ifdef PICO_MEASURE_STACK + +extern int freeStack; +#define STACK_TOTAL_WORDS 1000u +#define STACK_PATTERN (0xC0CAC01Au) + +void stack_fill_pattern(void *ptr); +void stack_count_free_words(void *ptr); +int stack_get_free_words(void); +#else +#define stack_fill_pattern(...) do {} while(0) +#define stack_count_free_words(...) do {} while(0) +#define stack_get_free_words() (0) +#endif + +#ifdef MEMORY_MEASURE /* in case, comment out the two defines above me. */ +extern uint32_t max_mem; +extern uint32_t cur_mem; + +struct mem_chunk_stats { +#ifdef MEMORY_MEASURE_ADV + uint32_t signature; + void *mem; +#endif + uint32_t size; +}; + +static inline void *pico_zalloc(int x) +{ + struct mem_chunk_stats *stats; + if ((cur_mem + x) > (10 * 1024)) + return NULL; + + stats = (struct mem_chunk_stats *)calloc(x + sizeof(struct mem_chunk_stats), 1); +#ifdef MEMORY_MEASURE_ADV + stats->signature = 0xdeadbeef; + stats->mem = ((uint8_t *)stats) + sizeof(struct mem_chunk_stats); +#endif + stats->size = x; + + /* Intended for Mr. Jenkins endurance test loggings */ + #ifdef JENKINS_DEBUG + if (!stats) { + jenkins_dbg(">> OUT OF MEM\n"); + while(1) ; + ; + } + + #endif + cur_mem += x; + if (cur_mem > max_mem) { + max_mem = cur_mem; + /* printf("max mem: %lu\n", max_mem); */ + } + +#ifdef MEMORY_MEASURE_ADV + return (void*)(stats->mem); +#else + return (void*) (((uint8_t *)stats) + sizeof(struct mem_chunk_stats)); +#endif +} + +static inline void pico_free(void *x) +{ + struct mem_chunk_stats *stats = (struct mem_chunk_stats *) ((uint8_t *)x - sizeof(struct mem_chunk_stats)); + + #ifdef JENKINS_DEBUG + #ifdef MEMORY_MEASURE_ADV + if ((stats->signature != 0xdeadbeef) || (x != stats->mem)) { + jenkins_dbg(">> FREE ERROR: caller is %p\n", __builtin_return_address(0)); + while(1) ; + ; + } + + #endif + + #endif + + cur_mem -= stats->size; + memset(stats, 0, sizeof(struct mem_chunk_stats)); + free(stats); +} +#else + +#define pico_zalloc(x) calloc(x, 1) +#define pico_free(x) free(x) + +#endif + +#define PICO_SUPPORT_MUTEX +extern void *pico_mutex_init(void); +extern void pico_mutex_lock(void*); +extern void pico_mutex_unlock(void*); +extern void pico_mutex_deinit(void*); + +extern uint32_t os_time; +extern pico_time local_time; +extern uint32_t last_os_time; + +#ifdef TIME_PRESCALE +extern int32_t prescale_time; +#endif + +#define UPDATE_LOCAL_TIME() do {local_time = local_time + ((pico_time)os_time - (pico_time)last_os_time);last_os_time = os_time;} while(0) + +static inline pico_time PICO_TIME(void) +{ + UPDATE_LOCAL_TIME(); + #ifdef TIME_PRESCALE + return (prescale_time < 0) ? (pico_time)(local_time / 1000 << (-prescale_time)) : \ + (pico_time)(local_time / 1000 >> prescale_time); + #else + return (pico_time)(local_time / 1000); + #endif +} + +static inline pico_time PICO_TIME_MS(void) +{ + UPDATE_LOCAL_TIME(); + #ifdef TIME_PRESCALE + return (prescale_time < 0) ? (pico_time)(local_time << (-prescale_time)) : \ + (pico_time)(local_time >> prescale_time); + #else + return (pico_time)local_time; + #endif +} + +static inline void PICO_IDLE(void) +{ + /* TODO needs implementation */ +} +/* + static inline void PICO_DEBUG(const char * formatter, ... ) + { + char buffer[256]; + char *ptr; + va_list args; + va_start(args, formatter); + vsnprintf(buffer, 256, formatter, args); + ptr = buffer; + while(*ptr != '\0') + serial_putc(serial_t *obj, (int) (*(ptr++))); + va_end(args); + //TODO implement serial_t + }*/ + +#endif diff --git a/ext/picotcp/include/arch/pico_msp430.h b/ext/picotcp/include/arch/pico_msp430.h new file mode 100644 index 0000000..27e3e5a --- /dev/null +++ b/ext/picotcp/include/arch/pico_msp430.h @@ -0,0 +1,38 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + *********************************************************************/ +#ifndef _INCLUDE_PICO_LPC +#define _INCLUDE_PICO_LPC + +#include +#include +#include +#include "pico_constants.h" + +extern pico_time msp430_time_s(void); +extern pico_time msp430_time_ms(void); +extern void *malloc(size_t); +extern void free(void *); + + +#define PICO_TIME() msp430_time_s() +#define PICO_TIME_MS() msp430_time_ms() +#define PICO_IDLE() do {} while(0) + +#define pico_free(x) free(x) + +static inline void *pico_zalloc(size_t size) +{ + void *ptr = malloc(size); + + if(ptr) + memset(ptr, 0u, size); + + return ptr; +} + +#define dbg(...) + +#endif diff --git a/ext/picotcp/include/arch/pico_none.h b/ext/picotcp/include/arch/pico_none.h new file mode 100644 index 0000000..fee74f9 --- /dev/null +++ b/ext/picotcp/include/arch/pico_none.h @@ -0,0 +1,22 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + *********************************************************************/ + +#ifndef PICO_SUPPORT_ARCHNONE +#define PICO_SUPPORT_ARCHNONE + +#include +#include +#include +#include + +#define dbg(...) do {} while(0) +#define pico_zalloc(x) NULL +#define pico_free(x) do {} while(0) +#define PICO_TIME() 666 +#define PICO_TIME_MS() 666000 +#define PICO_IDLE() do {} while(0) + +#endif /* PICO_SUPPORT_ARCHNONE */ + diff --git a/ext/picotcp/include/arch/pico_pic24.h b/ext/picotcp/include/arch/pico_pic24.h new file mode 100644 index 0000000..27ff39e --- /dev/null +++ b/ext/picotcp/include/arch/pico_pic24.h @@ -0,0 +1,100 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + *********************************************************************/ +#ifndef PICO_SUPPORT_PIC24 +#define PICO_SUPPORT_PIC24 +#define dbg printf +/* #define dbg(...) */ + +/*************************/ + +/*** MACHINE CONFIGURATION ***/ +#include +#include + +/* #include "phalox_development_board.h" */ + +#ifndef __PIC24F__ +#define __PIC24F__ +#endif + +/* + #ifndef __PIC24FJ256GA106__ + #define __PIC24FJ256GA106__ + #endif + */ + +#ifndef PICO_MAX_SOCKET_FRAMES +#define PICO_MAX_SOCKET_FRAMES 16 +#endif + +/* Device header file */ + +#if defined(__PIC24E__) +# include +#elif defined(__PIC24F__) +# include +#elif defined(__PIC24H__) +# include +#endif + + +#define TIMBASE_INT_E IEC0bits.T2IE + +#ifdef PICO_SUPPORT_DEBUG_MEMORY +static inline void *pico_zalloc(int len) +{ + /* dbg("%s: Alloc object of len %d, caller: %p\n", __FUNCTION__, len, __builtin_return_address(0)); */ + return calloc(len, 1); +} + +static inline void pico_free(void *tgt) +{ + /* dbg("%s: Discarded object @%p, caller: %p\n", __FUNCTION__, tgt, __builtin_return_address(0)); */ + free(tgt); +} +#else +# define pico_zalloc(x) calloc(x, 1) +# define pico_free(x) free(x) +#endif + +extern void *pvPortMalloc( size_t xWantedSize ); +extern volatile pico_time __pic24_tick; + +static inline unsigned long PICO_TIME(void) +{ + unsigned long tick; + /* Disable timer interrupts */ + TIMBASE_INT_E = 0; + tick = __pic24_tick; + /* Enable timer interrupts */ + TIMBASE_INT_E = 1; + return tick / 1000; +} + +static inline unsigned long PICO_TIME_MS(void) +{ + unsigned long tick; + /* Disable timer interrupts */ + TIMBASE_INT_E = 0; + tick = __pic24_tick; + /* Enable timer interrupts */ + TIMBASE_INT_E = 1; + return tick; +} + +static inline void PICO_IDLE(void) +{ + unsigned long tick_now; + /* Disable timer interrupts */ + TIMBASE_INT_E = 0; + tick_now = (unsigned long)pico_tick; + /* Enable timer interrupts */ + TIMBASE_INT_E = 1; + /* Doesn't matter that this call isn't interrupt safe, */ + /* we just check for the value to change */ + while(tick_now == __pic24_tick) ; +} + +#endif diff --git a/ext/picotcp/include/arch/pico_posix.h b/ext/picotcp/include/arch/pico_posix.h new file mode 100644 index 0000000..30ce884 --- /dev/null +++ b/ext/picotcp/include/arch/pico_posix.h @@ -0,0 +1,130 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + *********************************************************************/ + +#ifndef PICO_SUPPORT_POSIX +#define PICO_SUPPORT_POSIX + +#include +#include +#include +#include + +/* + #define MEMORY_MEASURE + #define TIME_PRESCALE + #define PICO_SUPPORT_THREADING + */ +#define dbg printf + +#define stack_fill_pattern(...) do {} while(0) +#define stack_count_free_words(...) do {} while(0) +#define stack_get_free_words() (0) + +/* measure allocated memory */ +#ifdef MEMORY_MEASURE +extern uint32_t max_mem; +extern uint32_t cur_mem; + +static inline void *pico_zalloc(int x) +{ + uint32_t *ptr; + if ((cur_mem + x) > (10 * 1024)) + return NULL; + + ptr = (uint32_t *)calloc(x + 4, 1); + *ptr = (uint32_t)x; + cur_mem += x; + if (cur_mem > max_mem) { + max_mem = cur_mem; + } + + return (void*)(ptr + 1); +} + +static inline void pico_free(void *x) +{ + uint32_t *ptr = (uint32_t*)(((uint8_t *)x) - 4); + cur_mem -= *ptr; + free(ptr); +} +#else +#define pico_zalloc(x) calloc(x, 1) +#define pico_free(x) free(x) +#endif + +/* time prescaler */ +#ifdef TIME_PRESCALE +extern int32_t prescale_time; +#endif + +#if defined(PICO_SUPPORT_RTOS) || defined (PICO_SUPPORT_PTHREAD) +/* pico_ms_tick must be defined */ +extern volatile unsigned long int pico_ms_tick; + + +static inline uint32_t PICO_TIME(void) +{ + return pico_ms_tick / 1000; +} + +static inline uint32_t PICO_TIME_MS(void) +{ + return pico_ms_tick; +} + +#else + +static inline uint32_t PICO_TIME(void) +{ + struct timeval t; + gettimeofday(&t, NULL); + #ifdef TIME_PRESCALE + return (prescale_time < 0) ? (uint32_t)(t.tv_sec / 1000 << (-prescale_time)) : \ + (uint32_t)(t.tv_sec / 1000 >> prescale_time); + #else + return (uint32_t)t.tv_sec; + #endif +} + +static inline uint32_t PICO_TIME_MS(void) +{ + struct timeval t; + gettimeofday(&t, NULL); + #ifdef TIME_PRESCALER + uint32_t tmp = ((t.tv_sec * 1000) + (t.tv_usec / 1000)); + return (prescale_time < 0) ? (uint32_t)(tmp / 1000 << (-prescale_time)) : \ + (uint32_t)(tmp / 1000 >> prescale_time); + #else + return (uint32_t)((t.tv_sec * 1000) + (t.tv_usec / 1000)); + #endif +} +#endif + +#ifdef PICO_SUPPORT_THREADING +#define PICO_SUPPORT_MUTEX +/* mutex implementations */ +extern void *pico_mutex_init(void); +extern void pico_mutex_lock(void *mux); +extern void pico_mutex_unlock(void *mux); + +/* semaphore implementations (only used in wrapper code) */ +extern void *pico_sem_init(void); +extern void pico_sem_destroy(void *sem); +extern void pico_sem_post(void *sem); +/* returns -1 on timeout (in ms), else returns 0 */ +/* if timeout < 0, the semaphore waits forever */ +extern int pico_sem_wait(void *sem, int timeout); + +/* thread implementations */ +extern void *pico_thread_create(void *(*routine)(void *), void *arg); +#endif /* PICO_SUPPORT_THREADING */ + +static inline void PICO_IDLE(void) +{ + usleep(5000); +} + +#endif /* PICO_SUPPORT_POSIX */ + diff --git a/ext/picotcp/include/heap.h b/ext/picotcp/include/heap.h new file mode 100644 index 0000000..88f495d --- /dev/null +++ b/ext/picotcp/include/heap.h @@ -0,0 +1,83 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + *********************************************************************/ + +#define DECLARE_HEAP(type, orderby) \ + struct heap_ ## type { \ + uint32_t size; \ + uint32_t n; \ + type *top; \ + }; \ + typedef struct heap_ ## type heap_ ## type; \ + static inline int heap_insert(struct heap_ ## type *heap, type * el) \ + { \ + uint32_t i; \ + type *newTop; \ + if (++heap->n >= heap->size) { \ + newTop = PICO_ZALLOC((heap->n + 1) * sizeof(type)); \ + if(!newTop) { \ + heap->n--; \ + return -1; \ + } \ + if (heap->top) { \ + memcpy(newTop, heap->top, heap->n * sizeof(type)); \ + PICO_FREE(heap->top); \ + } \ + heap->top = newTop; \ + heap->size++; \ + } \ + if (heap->n == 1) { \ + memcpy(&heap->top[1], el, sizeof(type)); \ + return 0; \ + } \ + for (i = heap->n; ((i > 1) && (heap->top[i / 2].orderby > el->orderby)); i /= 2) { \ + memcpy(&heap->top[i], &heap->top[i / 2], sizeof(type)); \ + } \ + memcpy(&heap->top[i], el, sizeof(type)); \ + return 0; \ + } \ + static inline int heap_peek(struct heap_ ## type *heap, type * first) \ + { \ + type *last; \ + uint32_t i, child; \ + if(heap->n == 0) { \ + return -1; \ + } \ + memcpy(first, &heap->top[1], sizeof(type)); \ + last = &heap->top[heap->n--]; \ + for(i = 1; (i * 2u) <= heap->n; i = child) { \ + child = 2u * i; \ + if ((child != heap->n) && \ + (heap->top[child + 1]).orderby \ + < (heap->top[child]).orderby) \ + child++; \ + if (last->orderby > \ + heap->top[child].orderby) \ + memcpy(&heap->top[i], &heap->top[child], \ + sizeof(type)); \ + else \ + break; \ + } \ + memcpy(&heap->top[i], last, sizeof(type)); \ + return 0; \ + } \ + static inline type *heap_first(heap_ ## type * heap) \ + { \ + if (heap->n == 0) \ + return NULL; \ + return &heap->top[1]; \ + } \ + static inline heap_ ## type *heap_init(void) \ + { \ + heap_ ## type * p = (heap_ ## type *)PICO_ZALLOC(sizeof(heap_ ## type)); \ + return p; \ + } \ + /*static inline void heap_destroy(heap_ ## type * h) \ + { \ + PICO_FREE(h->top); \ + PICO_FREE(h); \ + } \*/ + + diff --git a/ext/picotcp/include/pico_addressing.h b/ext/picotcp/include/pico_addressing.h new file mode 100644 index 0000000..de62281 --- /dev/null +++ b/ext/picotcp/include/pico_addressing.h @@ -0,0 +1,52 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + *********************************************************************/ +#ifndef INCLUDE_PICO_ADDRESSING +#define INCLUDE_PICO_ADDRESSING + +#include "pico_config.h" + +PACKED_STRUCT_DEF pico_ip4 +{ + uint32_t addr; +}; + +PACKED_STRUCT_DEF pico_ip6 +{ + uint8_t addr[16]; +}; + +union pico_address +{ + struct pico_ip4 ip4; + struct pico_ip6 ip6; +}; + +PACKED_STRUCT_DEF pico_eth +{ + uint8_t addr[6]; + uint8_t padding[2]; +}; + +extern const uint8_t PICO_ETHADDR_ALL[]; + + +PACKED_STRUCT_DEF pico_trans +{ + uint16_t sport; + uint16_t dport; + +}; + +/* Here are some protocols. */ +#define PICO_PROTO_IPV4 0 +#define PICO_PROTO_ICMP4 1 +#define PICO_PROTO_IGMP 2 +#define PICO_PROTO_TCP 6 +#define PICO_PROTO_UDP 17 +#define PICO_PROTO_IPV6 41 +#define PICO_PROTO_ICMP6 58 + +#endif diff --git a/ext/picotcp/include/pico_config.h b/ext/picotcp/include/pico_config.h new file mode 100644 index 0000000..3e5047f --- /dev/null +++ b/ext/picotcp/include/pico_config.h @@ -0,0 +1,234 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + *********************************************************************/ +#include "pico_defines.h" +#ifndef INCLUDE_PICO_CONFIG +#define INCLUDE_PICO_CONFIG +#ifndef __KERNEL__ +#include +#include +#include +#else +#include +#endif + +#if defined __IAR_SYSTEMS_ICC__ || defined ATOP +# define PACKED_STRUCT_DEF __packed struct +# define PEDANTIC_STRUCT_DEF __packed struct +# define PACKED_UNION_DEF __packed union +# define WEAK +#elif defined __WATCOMC__ +# define PACKED_STRUCT_DEF _Packed struct +# define PEDANTIC_STRUCT_DEF struct +# define PACKED_UNION_DEF _Packed union +# define WEAK +#else +# define PACKED_STRUCT_DEF struct __attribute__((packed)) +# define PEDANTIC_STRUCT_DEF struct +# define PACKED_UNION_DEF union /* Sane compilers do not require packed unions */ +# define WEAK __attribute__((weak)) +# ifdef __GNUC__ +# define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) +# if ((GCC_VERSION >= 40800)) +# define BYTESWAP_GCC +# endif +# endif +#endif + +#ifdef PICO_BIGENDIAN + +# define PICO_IDETH_IPV4 0x0800 +# define PICO_IDETH_ARP 0x0806 +# define PICO_IDETH_IPV6 0x86DD + +# define PICO_ARP_REQUEST 0x0001 +# define PICO_ARP_REPLY 0x0002 +# define PICO_ARP_HTYPE_ETH 0x0001 + +#define short_be(x) (x) +#define long_be(x) (x) +#define long_long_be(x) (x) + +static inline uint16_t short_from(void *_p) +{ + unsigned char *p = (unsigned char *)_p; + uint16_t r, p0, p1; + p0 = p[0]; + p1 = p[1]; + r = (p0 << 8) + p1; + return r; +} + +static inline uint32_t long_from(void *_p) +{ + unsigned char *p = (unsigned char *)_p; + uint32_t r, p0, p1, p2, p3; + p0 = p[0]; + p1 = p[1]; + p2 = p[2]; + p3 = p[3]; + r = (p0 << 24) + (p1 << 16) + (p2 << 8) + p3; + return r; +} + +#else + +static inline uint16_t short_from(void *_p) +{ + unsigned char *p = (unsigned char *)_p; + uint16_t r, _p0, _p1; + _p0 = p[0]; + _p1 = p[1]; + r = (uint16_t)((_p1 << 8u) + _p0); + return r; +} + +static inline uint32_t long_from(void *_p) +{ + unsigned char *p = (unsigned char *)_p; + uint32_t r, _p0, _p1, _p2, _p3; + _p0 = p[0]; + _p1 = p[1]; + _p2 = p[2]; + _p3 = p[3]; + r = (_p3 << 24) + (_p2 << 16) + (_p1 << 8) + _p0; + return r; +} + + +# define PICO_IDETH_IPV4 0x0008 +# define PICO_IDETH_ARP 0x0608 +# define PICO_IDETH_IPV6 0xDD86 + +# define PICO_ARP_REQUEST 0x0100 +# define PICO_ARP_REPLY 0x0200 +# define PICO_ARP_HTYPE_ETH 0x0100 + +# ifndef BYTESWAP_GCC +static inline uint16_t short_be(uint16_t le) +{ + return (uint16_t)(((le & 0xFFu) << 8) | ((le >> 8u) & 0xFFu)); +} + +static inline uint32_t long_be(uint32_t le) +{ + uint8_t *b = (uint8_t *)≤ + uint32_t be = 0; + uint32_t b0, b1, b2; + b0 = b[0]; + b1 = b[1]; + b2 = b[2]; + be = b[3] + (b2 << 8) + (b1 << 16) + (b0 << 24); + return be; +} +static inline uint64_t long_long_be(uint64_t le) +{ + uint8_t *b = (uint8_t *)≤ + uint64_t be = 0; + uint64_t b0, b1, b2, b3, b4, b5, b6; + b0 = b[0]; + b1 = b[1]; + b2 = b[2]; + b3 = b[3]; + b4 = b[4]; + b5 = b[5]; + b6 = b[6]; + be = b[7] + (b6 << 8) + (b5 << 16) + (b4 << 24) + (b3 << 32) + (b2 << 40) + (b1 << 48) + (b0 << 56); + return be; +} +# else +/* + extern uint32_t __builtin_bswap32(uint32_t); + extern uint16_t __builtin_bswap16(uint16_t); + extern uint64_t __builtin_bswap64(uint64_t); + */ + +static inline uint32_t long_be(uint32_t le) +{ + return (uint32_t)__builtin_bswap32(le); +} + +static inline uint16_t short_be(uint16_t le) +{ + return (uint16_t)__builtin_bswap16(le); +} + +static inline uint64_t long_long_be(uint64_t le) +{ + return (uint64_t)__builtin_bswap64(le); +} + +# endif /* BYTESWAP_GCC */ +#endif + + +/* Mockables */ +#if defined UNIT_TEST +# define MOCKABLE __attribute__((weak)) +#else +# define MOCKABLE +#endif + +#include "pico_constants.h" +#include "pico_mm.h" + +#define IGNORE_PARAMETER(x) ((void)x) + +#define PICO_MEM_DEFAULT_SLAB_SIZE 1600 +#define PICO_MEM_PAGE_SIZE 4096 +#define PICO_MEM_PAGE_LIFETIME 100 +#define PICO_MIN_HEAP_SIZE 600 +#define PICO_MIN_SLAB_SIZE 1200 +#define PICO_MAX_SLAB_SIZE 1600 +#define PICO_MEM_MINIMUM_OBJECT_SIZE 4 + + +/*** *** *** *** *** *** *** + *** PLATFORM SPECIFIC *** + *** *** *** *** *** *** ***/ +#if defined PICO_PORT_CUSTOM +# include "pico_port.h" +#elif defined CORTEX_M4_HARDFLOAT +# include "arch/pico_cortex_m.h" +#elif defined CORTEX_M4_SOFTFLOAT +# include "arch/pico_cortex_m.h" +#elif defined CORTEX_M3 +# include "arch/pico_cortex_m.h" +#elif defined PIC24 +# include "arch/pico_pic24.h" +#elif defined MSP430 +# include "arch/pico_msp430.h" +#elif defined MBED_TEST +# include "arch/pico_mbed.h" +#elif defined AVR +# include "arch/pico_avr.h" +#elif defined ARM9 +# include "arch/pico_arm9.h" +#elif defined ESP8266 +# include "arch/pico_esp8266.h" +#elif defined MT7681 +# include "arch/pico_generic_gcc.h" +#elif defined FAULTY +# include "../test/pico_faulty.h" +#elif defined ARCHNONE +# include "arch/pico_none.h" +#elif defined GENERIC +# include "arch/pico_generic_gcc.h" +#elif defined __KERNEL__ +# include "arch/pico_linux.h" +/* #elif defined ... */ +#else +# include "arch/pico_posix.h" +#endif + +#ifdef PICO_SUPPORT_MM +#define PICO_ZALLOC(x) pico_mem_zalloc(x) +#define PICO_FREE(x) pico_mem_free(x) +#else +#define PICO_ZALLOC(x) pico_zalloc(x) +#define PICO_FREE(x) pico_free(x) +#endif /* PICO_SUPPORT_MM */ + +#endif diff --git a/ext/picotcp/include/pico_constants.h b/ext/picotcp/include/pico_constants.h new file mode 100644 index 0000000..f043408 --- /dev/null +++ b/ext/picotcp/include/pico_constants.h @@ -0,0 +1,54 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + *********************************************************************/ +#ifndef INCLUDE_PICO_CONST +#define INCLUDE_PICO_CONST +/* Included from pico_config.h */ + +/** Non-endian dependant constants */ +#define PICO_SIZE_IP4 4 +#define PICO_SIZE_IP6 16 +#define PICO_SIZE_ETH 6 +#define PICO_SIZE_TRANS 8 + +/** Endian-dependant constants **/ +typedef uint64_t pico_time; +extern volatile uint64_t pico_tick; + + +/*** *** *** *** *** *** *** + *** ARP CONFIG *** + *** *** *** *** *** *** ***/ + +#include "pico_addressing.h" + +/* Maximum amount of accepted ARP requests per burst interval */ +#define PICO_ARP_MAX_RATE 1 +/* Duration of the burst interval in milliseconds */ +#define PICO_ARP_INTERVAL 1000 + +/* Add well-known host numbers here. (bigendian constants only beyond this point) */ +#define PICO_IP4_ANY (0x00000000U) +#define PICO_IP4_BCAST (0xffffffffU) + +/* defined in modules/pico_ipv6.c */ +#ifdef PICO_SUPPORT_IPV6 +extern const uint8_t PICO_IPV6_ANY[PICO_SIZE_IP6]; +#endif + +static inline uint32_t pico_hash(const void *buf, uint32_t size) +{ + uint32_t hash = 5381; + uint32_t i; + const uint8_t *ptr = (const uint8_t *)buf; + for(i = 0; i < size; i++) + hash = ((hash << 5) + hash) + ptr[i]; /* hash * 33 + char */ + return hash; +} + +/* Debug */ +/* #define PICO_SUPPORT_DEBUG_MEMORY */ +/* #define PICO_SUPPORT_DEBUG_TOOLS */ +#endif diff --git a/ext/picotcp/include/pico_device.h b/ext/picotcp/include/pico_device.h new file mode 100644 index 0000000..2b61d5f --- /dev/null +++ b/ext/picotcp/include/pico_device.h @@ -0,0 +1,60 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + *********************************************************************/ +#ifndef INCLUDE_PICO_DEVICE +#define INCLUDE_PICO_DEVICE +#include "pico_queue.h" +#include "pico_frame.h" +#include "pico_addressing.h" +#include "pico_tree.h" +extern struct pico_tree Device_tree; +#include "pico_ipv6_nd.h" +#define MAX_DEVICE_NAME 16 + + +struct pico_ethdev { + struct pico_eth mac; +}; + +struct pico_device { + char name[MAX_DEVICE_NAME]; + uint32_t hash; + uint32_t overhead; + uint32_t mtu; + struct pico_ethdev *eth; /* Null if non-ethernet */ + struct pico_queue *q_in; + struct pico_queue *q_out; + int (*link_state)(struct pico_device *self); + int (*send)(struct pico_device *self, void *buf, int len); /* Send function. Return 0 if busy */ + int (*poll)(struct pico_device *self, int loop_score); + void (*destroy)(struct pico_device *self); + int (*dsr)(struct pico_device *self, int loop_score); + int __serving_interrupt; + /* used to signal the upper layer the number of events arrived since the last processing */ + volatile int eventCnt; + #ifdef PICO_SUPPORT_IPV6 + struct pico_nd_hostvars hostvars; + #endif +}; + +#ifdef __cplusplus +extern "C" { +#endif +int pico_device_init(struct pico_device *dev, const char *name, uint8_t *mac); +#ifdef __cplusplus +} +#endif + +void pico_device_destroy(struct pico_device *dev); +int pico_devices_loop(int loop_score, int direction); +struct pico_device*pico_get_device(const char*name); +int32_t pico_device_broadcast(struct pico_frame *f); +int pico_device_link_state(struct pico_device *dev); +int pico_device_ipv6_random_ll(struct pico_device *dev); +#ifdef PICO_SUPPORT_IPV6 +struct pico_ipv6_link *pico_ipv6_link_add_local(struct pico_device *dev, const struct pico_ip6 *prefix); +#endif + +#endif diff --git a/ext/picotcp/include/pico_eth.h b/ext/picotcp/include/pico_eth.h new file mode 100644 index 0000000..89846c7 --- /dev/null +++ b/ext/picotcp/include/pico_eth.h @@ -0,0 +1,21 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + *********************************************************************/ +#ifndef INCLUDE_PICO_ETH +#define INCLUDE_PICO_ETH +#include "pico_addressing.h" +#include "pico_ipv4.h" +#include "pico_ipv6.h" + + +PACKED_STRUCT_DEF pico_eth_hdr { + uint8_t daddr[6]; + uint8_t saddr[6]; + uint16_t proto; +}; + +#define PICO_SIZE_ETHHDR 14 + +#endif diff --git a/ext/picotcp/include/pico_frame.h b/ext/picotcp/include/pico_frame.h new file mode 100644 index 0000000..18b2d4d --- /dev/null +++ b/ext/picotcp/include/pico_frame.h @@ -0,0 +1,122 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + *********************************************************************/ +#ifndef INCLUDE_PICO_FRAME +#define INCLUDE_PICO_FRAME +#include "pico_config.h" + + +#define PICO_FRAME_FLAG_BCAST (0x01) +#define PICO_FRAME_FLAG_EXT_BUFFER (0x02) +#define PICO_FRAME_FLAG_EXT_USAGE_COUNTER (0x04) +#define PICO_FRAME_FLAG_SACKED (0x80) +#define IS_BCAST(f) ((f->flags & PICO_FRAME_FLAG_BCAST) == PICO_FRAME_FLAG_BCAST) + + +struct pico_socket; + + +struct pico_frame { + + /* Connector for queues */ + struct pico_frame *next; + + /* Start of the whole buffer, total frame length. */ + unsigned char *buffer; + uint32_t buffer_len; + + /* For outgoing packets: this is the meaningful buffer. */ + unsigned char *start; + uint32_t len; + + /* Pointer to usage counter */ + uint32_t *usage_count; + + /* Pointer to protocol headers */ + uint8_t *datalink_hdr; + + uint8_t *net_hdr; + uint16_t net_len; + uint8_t *transport_hdr; + uint16_t transport_len; + uint8_t *app_hdr; + uint16_t app_len; + + /* Pointer to the phisical device this packet belongs to. + * Should be valid in both routing directions + */ + struct pico_device *dev; + + pico_time timestamp; + + /* Failures due to bad datalink addressing. */ + uint16_t failure_count; + + /* Protocol over IP */ + uint8_t proto; + + /* PICO_FRAME_FLAG_* */ + uint8_t flags; + + /* Pointer to payload */ + unsigned char *payload; + uint16_t payload_len; + +#if defined(PICO_SUPPORT_IPV4FRAG) || defined(PICO_SUPPORT_IPV6FRAG) + /* Payload fragmentation info */ + uint16_t frag; +#endif + + /* Pointer to socket */ + struct pico_socket *sock; + + /* Pointer to transport info, used to store remote UDP endpoint (IP + port) */ + void *info; + + /*Priority. "best-effort" priority, the default value is 0. Priority can be in between -10 and +10*/ + int8_t priority; + uint8_t transport_flags_saved; + + /* Callback to notify listener when the buffer has been discarded */ + void (*notify_free)(uint8_t *); + + uint8_t send_ttl; /* Special TTL/HOPS value, 0 = auto assign */ + uint8_t send_tos; /* Type of service */ +}; + +/** frame alloc/dealloc/copy **/ +void pico_frame_discard(struct pico_frame *f); +struct pico_frame *pico_frame_copy(struct pico_frame *f); +struct pico_frame *pico_frame_deepcopy(struct pico_frame *f); +struct pico_frame *pico_frame_alloc(uint32_t size); +int pico_frame_grow(struct pico_frame *f, uint32_t size); +struct pico_frame *pico_frame_alloc_skeleton(uint32_t size, int ext_buffer); +int pico_frame_skeleton_set_buffer(struct pico_frame *f, void *buf); +uint16_t pico_checksum(void *inbuf, uint32_t len); +uint16_t pico_dualbuffer_checksum(void *b1, uint32_t len1, void *b2, uint32_t len2); + +static inline int pico_is_digit(char c) +{ + if (c < '0' || c > '9') + return 0; + + return 1; +} + +static inline int pico_is_hex(char c) +{ + if (c >= '0' && c <= '9') + return 1; + + if (c >= 'a' && c <= 'f') + return 1; + + if (c >= 'A' && c <= 'F') + return 1; + + return 0; +} + +#endif diff --git a/ext/picotcp/include/pico_md5.h b/ext/picotcp/include/pico_md5.h new file mode 100644 index 0000000..0ba89c4 --- /dev/null +++ b/ext/picotcp/include/pico_md5.h @@ -0,0 +1,17 @@ +/********************************************************************* + * PicoTCP. Copyright (c) 2015 Altran Intelligent Systems. Some rights reserved. + * See LICENSE and COPYING for usage. + * + * Authors: Daniele Lacamera + * *********************************************************************/ + +#ifndef PICO_MD5_INCLUDE +#define PICO_MD5_INCLUDE + +#include +#include + +void pico_md5sum(uint8_t *dst, const uint8_t *src, size_t len); +void pico_register_md5sum(void (*md5)(uint8_t *, const uint8_t *, size_t)); + +#endif /* PICO_MD5_INCLUDE */ diff --git a/ext/picotcp/include/pico_module_eth.h b/ext/picotcp/include/pico_module_eth.h new file mode 100644 index 0000000..c86680d --- /dev/null +++ b/ext/picotcp/include/pico_module_eth.h @@ -0,0 +1,33 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + *********************************************************************/ +#ifndef PICO_MODULE_IPV4_H +#define PICO_MODULE_IPV4_H + +struct pico_arp_entry { + struct eth dest; +#ifdef PICO_CONFIG_IPV4 + struct ipv4 addr_ipv4; +#endif + RB_ENTRY(pico_arp_entry) node; +}; + +/* Configured device */ +struct pico_eth_link { + struct pico_device *dev; + struct eth address; + struct eth netmask; + RB_ENTRY(pico_eth_link) node; +}; + +#ifndef IS_MODULE_ETH +# define _mod extern +#else +# define _mod +#endif +_mod struct pico_module pico_module_eth; +#undef _mod + +#endif diff --git a/ext/picotcp/include/pico_protocol.h b/ext/picotcp/include/pico_protocol.h new file mode 100644 index 0000000..fd7c122 --- /dev/null +++ b/ext/picotcp/include/pico_protocol.h @@ -0,0 +1,95 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + *********************************************************************/ +#ifndef INCLUDE_PICO_PROTOCOL +#define INCLUDE_PICO_PROTOCOL +#include "pico_config.h" +#include "pico_queue.h" + +#define PICO_LOOP_DIR_IN 1 +#define PICO_LOOP_DIR_OUT 2 + +enum pico_layer { + PICO_LAYER_DATALINK = 2, /* Ethernet only. */ + PICO_LAYER_NETWORK = 3, /* IPv4, IPv6, ARP. Arp is there because it communicates with L2 */ + PICO_LAYER_TRANSPORT = 4, /* UDP, TCP, ICMP */ + PICO_LAYER_SOCKET = 5 /* Socket management */ +}; + +enum pico_err_e { + PICO_ERR_NOERR = 0, + PICO_ERR_EPERM = 1, + PICO_ERR_ENOENT = 2, + /* ... */ + PICO_ERR_EINTR = 4, + PICO_ERR_EIO = 5, + PICO_ERR_ENXIO = 6, + /* ... */ + PICO_ERR_EAGAIN = 11, + PICO_ERR_ENOMEM = 12, + PICO_ERR_EACCESS = 13, + PICO_ERR_EFAULT = 14, + /* ... */ + PICO_ERR_EBUSY = 16, + PICO_ERR_EEXIST = 17, + /* ... */ + PICO_ERR_EINVAL = 22, + /* ... */ + PICO_ERR_ENONET = 64, + /* ... */ + PICO_ERR_EPROTO = 71, + /* ... */ + PICO_ERR_ENOPROTOOPT = 92, + PICO_ERR_EPROTONOSUPPORT = 93, + /* ... */ + PICO_ERR_EOPNOTSUPP = 95, + PICO_ERR_EADDRINUSE = 98, + PICO_ERR_EADDRNOTAVAIL = 99, + PICO_ERR_ENETDOWN = 100, + PICO_ERR_ENETUNREACH = 101, + /* ... */ + PICO_ERR_ECONNRESET = 104, + /* ... */ + PICO_ERR_EISCONN = 106, + PICO_ERR_ENOTCONN = 107, + PICO_ERR_ESHUTDOWN = 108, + /* ... */ + PICO_ERR_ETIMEDOUT = 110, + PICO_ERR_ECONNREFUSED = 111, + PICO_ERR_EHOSTDOWN = 112, + PICO_ERR_EHOSTUNREACH = 113, +}; + +typedef enum pico_err_e pico_err_t; +extern volatile pico_err_t pico_err; + +#define IS_IPV6(f) (f && f->net_hdr && ((((uint8_t *)(f->net_hdr))[0] & 0xf0) == 0x60)) +#define IS_IPV4(f) (f && f->net_hdr && ((((uint8_t *)(f->net_hdr))[0] & 0xf0) == 0x40)) + +#define MAX_PROTOCOL_NAME 16 + +struct pico_protocol { + char name[MAX_PROTOCOL_NAME]; + uint32_t hash; + enum pico_layer layer; + uint16_t proto_number; + struct pico_queue *q_in; + struct pico_queue *q_out; + struct pico_frame *(*alloc)(struct pico_protocol *self, uint16_t size); /* Frame allocation. */ + int (*push)(struct pico_protocol *self, struct pico_frame *p); /* Push function, for active outgoing pkts from above */ + int (*process_out)(struct pico_protocol *self, struct pico_frame *p); /* Send loop. */ + int (*process_in)(struct pico_protocol *self, struct pico_frame *p); /* Recv loop. */ + uint16_t (*get_mtu)(struct pico_protocol *self); +}; + +int pico_protocols_loop(int loop_score); +void pico_protocol_init(struct pico_protocol *p); + +int pico_protocol_datalink_loop(int loop_score, int direction); +int pico_protocol_network_loop(int loop_score, int direction); +int pico_protocol_transport_loop(int loop_score, int direction); +int pico_protocol_socket_loop(int loop_score, int direction); + +#endif diff --git a/ext/picotcp/include/pico_queue.h b/ext/picotcp/include/pico_queue.h new file mode 100644 index 0000000..6cbb979 --- /dev/null +++ b/ext/picotcp/include/pico_queue.h @@ -0,0 +1,166 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + *********************************************************************/ +#ifndef INCLUDE_PICO_QUEUE +#define INCLUDE_PICO_QUEUE +#include "pico_config.h" +#include "pico_frame.h" + +#define Q_LIMIT 0 + +#ifndef NULL +#define NULL ((void *)0) +#endif + +void *pico_mutex_init(void); +void pico_mutex_deinit(void *mutex); +void pico_mutex_lock(void *mutex); +int pico_mutex_lock_timeout(void *mutex, int timeout); +void pico_mutex_unlock(void *mutex); +void pico_mutex_unlock_ISR(void *mutex); + +struct pico_queue { + uint32_t frames; + uint32_t size; + uint32_t max_frames; + uint32_t max_size; + struct pico_frame *head; + struct pico_frame *tail; +#ifdef PICO_SUPPORT_MUTEX + void *mutex; +#endif + uint8_t shared; + uint16_t overhead; +}; + +#ifdef PICO_SUPPORT_MUTEX +#define PICOTCP_MUTEX_LOCK(x) { \ + if (x == NULL) \ + x = pico_mutex_init(); \ + pico_mutex_lock(x); \ +} +#define PICOTCP_MUTEX_UNLOCK(x) pico_mutex_unlock(x) +#define PICOTCP_MUTEX_DEL(x) pico_mutex_deinit(x) + +#else +#define PICOTCP_MUTEX_LOCK(x) do {} while(0) +#define PICOTCP_MUTEX_UNLOCK(x) do {} while(0) +#define PICOTCP_MUTEX_DEL(x) do {} while(0) +#endif + +#ifdef PICO_SUPPORT_DEBUG_TOOLS +static void debug_q(struct pico_queue *q) +{ + struct pico_frame *p = q->head; + dbg("%d: ", q->frames); + while(p) { + dbg("(%p)-->", p); + p = p->next; + } + dbg("X\n"); +} + +#else + +#define debug_q(x) do {} while(0) +#endif + +static inline int32_t pico_enqueue(struct pico_queue *q, struct pico_frame *p) +{ + if ((q->max_frames) && (q->max_frames <= q->frames)) + return -1; + +#if (Q_LIMIT != 0) + if ((Q_LIMIT < p->buffer_len + q->size)) + return -1; + +#endif + + if ((q->max_size) && (q->max_size < (p->buffer_len + q->size))) + return -1; + + if (q->shared) + PICOTCP_MUTEX_LOCK(q->mutex); + + p->next = NULL; + if (!q->head) { + q->head = p; + q->tail = p; + q->size = 0; + q->frames = 0; + } else { + q->tail->next = p; + q->tail = p; + } + + q->size += p->buffer_len + q->overhead; + q->frames++; + debug_q(q); + + if (q->shared) + PICOTCP_MUTEX_UNLOCK(q->mutex); + + return (int32_t)q->size; +} + +static inline struct pico_frame *pico_dequeue(struct pico_queue *q) +{ + struct pico_frame *p = q->head; + if (!p) + return NULL; + + if (q->frames < 1) + return NULL; + + if (q->shared) + PICOTCP_MUTEX_LOCK(q->mutex); + + q->head = p->next; + q->frames--; + q->size -= p->buffer_len - q->overhead; + if (q->head == NULL) + q->tail = NULL; + + debug_q(q); + + p->next = NULL; + if (q->shared) + PICOTCP_MUTEX_UNLOCK(q->mutex); + + return p; +} + +static inline struct pico_frame *pico_queue_peek(struct pico_queue *q) +{ + struct pico_frame *p = q->head; + if (q->frames < 1) + return NULL; + + debug_q(q); + return p; +} + +static inline void pico_queue_deinit(struct pico_queue *q) +{ + if (q->shared) { + PICOTCP_MUTEX_DEL(q->mutex); + } +} + +static inline void pico_queue_empty(struct pico_queue *q) +{ + struct pico_frame *p = pico_dequeue(q); + while(p) { + pico_frame_discard(p); + p = pico_dequeue(q); + } +} + +static inline void pico_queue_protect(struct pico_queue *q) +{ + q->shared = 1; +} + +#endif diff --git a/ext/picotcp/include/pico_socket.h b/ext/picotcp/include/pico_socket.h new file mode 100644 index 0000000..46ce459 --- /dev/null +++ b/ext/picotcp/include/pico_socket.h @@ -0,0 +1,271 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + *********************************************************************/ +#ifndef INCLUDE_PICO_SOCKET +#define INCLUDE_PICO_SOCKET +#include "pico_queue.h" +#include "pico_addressing.h" +#include "pico_config.h" +#include "pico_protocol.h" +#include "pico_tree.h" + +#ifdef __linux__ + #define PICO_DEFAULT_SOCKETQ (16 * 1024) /* Linux host, so we want full throttle */ +#else + #define PICO_DEFAULT_SOCKETQ (6 * 1024) /* seems like an acceptable default for small embedded systems */ +#endif + +#define PICO_SHUT_RD 1 +#define PICO_SHUT_WR 2 +#define PICO_SHUT_RDWR 3 + +#ifdef PICO_SUPPORT_IPV4 +# define IS_SOCK_IPV4(s) ((s->net == &pico_proto_ipv4)) +#else +# define IS_SOCK_IPV4(s) (0) +#endif + +#ifdef PICO_SUPPORT_IPV6 +# define IS_SOCK_IPV6(s) ((s->net == &pico_proto_ipv6)) +#else +# define IS_SOCK_IPV6(s) (0) +#endif + + +struct pico_sockport +{ + struct pico_tree socks; /* how you make the connection ? */ + uint16_t number; + uint16_t proto; +}; + + +struct pico_socket { + struct pico_protocol *proto; + struct pico_protocol *net; + + union pico_address local_addr; + union pico_address remote_addr; + + uint16_t local_port; + uint16_t remote_port; + + struct pico_queue q_in; + struct pico_queue q_out; + + void (*wakeup)(uint16_t ev, struct pico_socket *s); + + +#ifdef PICO_SUPPORT_TCP + /* For the TCP backlog queue */ + struct pico_socket *backlog; + struct pico_socket *next; + struct pico_socket *parent; + uint16_t max_backlog; + uint16_t number_of_pending_conn; +#endif +#ifdef PICO_SUPPORT_MCAST + struct pico_tree *MCASTListen; +#ifdef PICO_SUPPORT_IPV6 + struct pico_tree *MCASTListen_ipv6; +#endif +#endif + uint16_t ev_pending; + + struct pico_device *dev; + + /* Private field. */ + int id; + uint16_t state; + uint16_t opt_flags; + pico_time timestamp; + void *priv; +}; + +struct pico_remote_endpoint { + union pico_address remote_addr; + uint16_t remote_port; +}; + + +struct pico_ip_mreq { + union pico_address mcast_group_addr; + union pico_address mcast_link_addr; +}; +struct pico_ip_mreq_source { + union pico_address mcast_group_addr; + union pico_address mcast_source_addr; + union pico_address mcast_link_addr; +}; + + +#define PICO_SOCKET_STATE_UNDEFINED 0x0000u +#define PICO_SOCKET_STATE_SHUT_LOCAL 0x0001u +#define PICO_SOCKET_STATE_SHUT_REMOTE 0x0002u +#define PICO_SOCKET_STATE_BOUND 0x0004u +#define PICO_SOCKET_STATE_CONNECTED 0x0008u +#define PICO_SOCKET_STATE_CLOSING 0x0010u +#define PICO_SOCKET_STATE_CLOSED 0x0020u + +# define PICO_SOCKET_STATE_TCP 0xFF00u +# define PICO_SOCKET_STATE_TCP_UNDEF 0x00FFu +# define PICO_SOCKET_STATE_TCP_CLOSED 0x0100u +# define PICO_SOCKET_STATE_TCP_LISTEN 0x0200u +# define PICO_SOCKET_STATE_TCP_SYN_SENT 0x0300u +# define PICO_SOCKET_STATE_TCP_SYN_RECV 0x0400u +# define PICO_SOCKET_STATE_TCP_ESTABLISHED 0x0500u +# define PICO_SOCKET_STATE_TCP_CLOSE_WAIT 0x0600u +# define PICO_SOCKET_STATE_TCP_LAST_ACK 0x0700u +# define PICO_SOCKET_STATE_TCP_FIN_WAIT1 0x0800u +# define PICO_SOCKET_STATE_TCP_FIN_WAIT2 0x0900u +# define PICO_SOCKET_STATE_TCP_CLOSING 0x0a00u +# define PICO_SOCKET_STATE_TCP_TIME_WAIT 0x0b00u +# define PICO_SOCKET_STATE_TCP_ARRAYSIZ 0x0cu + + +/* Socket options */ +# define PICO_TCP_NODELAY 1 +# define PICO_SOCKET_OPT_TCPNODELAY 0x0000u + +# define PICO_IP_MULTICAST_EXCLUDE 0 +# define PICO_IP_MULTICAST_INCLUDE 1 +# define PICO_IP_MULTICAST_IF 32 +# define PICO_IP_MULTICAST_TTL 33 +# define PICO_IP_MULTICAST_LOOP 34 +# define PICO_IP_ADD_MEMBERSHIP 35 +# define PICO_IP_DROP_MEMBERSHIP 36 +# define PICO_IP_UNBLOCK_SOURCE 37 +# define PICO_IP_BLOCK_SOURCE 38 +# define PICO_IP_ADD_SOURCE_MEMBERSHIP 39 +# define PICO_IP_DROP_SOURCE_MEMBERSHIP 40 + +# define PICO_SOCKET_OPT_MULTICAST_LOOP 1 +# define PICO_SOCKET_OPT_KEEPIDLE 4 +# define PICO_SOCKET_OPT_KEEPINTVL 5 +# define PICO_SOCKET_OPT_KEEPCNT 6 + +#define PICO_SOCKET_OPT_LINGER 13 + +# define PICO_SOCKET_OPT_RCVBUF 52 +# define PICO_SOCKET_OPT_SNDBUF 53 + + +/* Constants */ +# define PICO_IP_DEFAULT_MULTICAST_TTL 1 +# define PICO_IP_DEFAULT_MULTICAST_LOOP 1 + +#define PICO_SOCKET_TIMEOUT 5000u /* 5 seconds */ +#define PICO_SOCKET_LINGER_TIMEOUT 3000u /* 3 seconds */ +#define PICO_SOCKET_BOUND_TIMEOUT 30000u /* 30 seconds */ + +#define PICO_SOCKET_SHUTDOWN_WRITE 0x01u +#define PICO_SOCKET_SHUTDOWN_READ 0x02u +#define TCPSTATE(s) ((s)->state & PICO_SOCKET_STATE_TCP) + +#define PICO_SOCK_EV_RD 1u +#define PICO_SOCK_EV_WR 2u +#define PICO_SOCK_EV_CONN 4u +#define PICO_SOCK_EV_CLOSE 8u +#define PICO_SOCK_EV_FIN 0x10u +#define PICO_SOCK_EV_ERR 0x80u + +struct pico_msginfo { + struct pico_device *dev; + uint8_t ttl; + uint8_t tos; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +struct pico_socket *pico_socket_open(uint16_t net, uint16_t proto, void (*wakeup)(uint16_t ev, struct pico_socket *s)); + +int pico_socket_read(struct pico_socket *s, void *buf, int len); +int pico_socket_write(struct pico_socket *s, const void *buf, int len); + +int pico_socket_sendto(struct pico_socket *s, const void *buf, int len, void *dst, uint16_t remote_port); +int pico_socket_sendto_extended(struct pico_socket *s, const void *buf, const int len, + void *dst, uint16_t remote_port, struct pico_msginfo *msginfo); + +int pico_socket_recvfrom(struct pico_socket *s, void *buf, int len, void *orig, uint16_t *local_port); +int pico_socket_recvfrom_extended(struct pico_socket *s, void *buf, int len, void *orig, + uint16_t *remote_port, struct pico_msginfo *msginfo); + +int pico_socket_send(struct pico_socket *s, const void *buf, int len); +int pico_socket_recv(struct pico_socket *s, void *buf, int len); + +int pico_socket_bind(struct pico_socket *s, void *local_addr, uint16_t *port); +int pico_socket_getname(struct pico_socket *s, void *local_addr, uint16_t *port, uint16_t *proto); +int pico_socket_getpeername(struct pico_socket *s, void *remote_addr, uint16_t *port, uint16_t *proto); + +int pico_socket_connect(struct pico_socket *s, const void *srv_addr, uint16_t remote_port); + +int pico_socket_listen(struct pico_socket *s, const int backlog); + +struct pico_socket *pico_socket_accept(struct pico_socket *s, void *orig, uint16_t *port); +int8_t pico_socket_del(struct pico_socket *s); + +int pico_socket_setoption(struct pico_socket *s, int option, void *value); +int pico_socket_getoption(struct pico_socket *s, int option, void *value); + +int pico_socket_shutdown(struct pico_socket *s, int mode); +int pico_socket_close(struct pico_socket *s); + +#ifdef __cplusplus +} +#endif + +struct pico_frame *pico_socket_frame_alloc(struct pico_socket *s, uint16_t len); + +#ifdef PICO_SUPPORT_IPV4 +# define is_sock_ipv4(x) (x->net == &pico_proto_ipv4) +#else +# define is_sock_ipv4(x) (0) +#endif + +#ifdef PICO_SUPPORT_IPV6 +# define is_sock_ipv6(x) (x->net == &pico_proto_ipv6) +#else +# define is_sock_ipv6(x) (0) +#endif + +#ifdef PICO_SUPPORT_UDP +# define is_sock_udp(x) (x->proto == &pico_proto_udp) +#else +# define is_sock_udp(x) (0) +#endif + +#ifdef PICO_SUPPORT_TCP +# define is_sock_tcp(x) (x->proto == &pico_proto_tcp) +#else +# define is_sock_tcp(x) (0) +#endif + +/* Interface towards transport protocol */ +int pico_transport_process_in(struct pico_protocol *self, struct pico_frame *f); +struct pico_socket *pico_socket_clone(struct pico_socket *facsimile); +int8_t pico_socket_add(struct pico_socket *s); +int pico_transport_error(struct pico_frame *f, uint8_t proto, int code); + +/* Socket loop */ +int pico_sockets_loop(int loop_score); +struct pico_socket*pico_sockets_find(uint16_t local, uint16_t remote); +/* Port check */ +int pico_is_port_free(uint16_t proto, uint16_t port, void *addr, void *net); + +struct pico_sockport *pico_get_sockport(uint16_t proto, uint16_t port); + +uint32_t pico_socket_get_mss(struct pico_socket *s); +int pico_socket_set_family(struct pico_socket *s, uint16_t family); + +int pico_count_sockets(uint8_t proto); + +#define PICO_SOCKET_SETOPT_EN(socket, index) (socket->opt_flags |= (1 << index)) +#define PICO_SOCKET_SETOPT_DIS(socket, index) (socket->opt_flags &= (uint16_t) ~(1 << index)) +#define PICO_SOCKET_GETOPT(socket, index) ((socket->opt_flags & (1u << index)) != 0) + + +#endif diff --git a/ext/picotcp/include/pico_socket_multicast.h b/ext/picotcp/include/pico_socket_multicast.h new file mode 100644 index 0000000..44c30c5 --- /dev/null +++ b/ext/picotcp/include/pico_socket_multicast.h @@ -0,0 +1,10 @@ +#ifndef PICO_SOCKET_MULTICAST_H +#define PICO_SOCKET_MULTICAST_H +int pico_socket_mcast_filter(struct pico_socket *s, union pico_address *mcast_group, union pico_address *src); +void pico_multicast_delete(struct pico_socket *s); +int pico_setsockopt_mcast(struct pico_socket *s, int option, void *value); +int pico_getsockopt_mcast(struct pico_socket *s, int option, void *value); +int pico_udp_get_mc_ttl(struct pico_socket *s, uint8_t *ttl); +int pico_udp_set_mc_ttl(struct pico_socket *s, void *_ttl); + +#endif diff --git a/ext/picotcp/include/pico_stack.h b/ext/picotcp/include/pico_stack.h new file mode 100644 index 0000000..498975b --- /dev/null +++ b/ext/picotcp/include/pico_stack.h @@ -0,0 +1,93 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + *********************************************************************/ +#ifndef INCLUDE_PICO_STACK +#define INCLUDE_PICO_STACK + +#include "pico_config.h" +#include "pico_frame.h" + +#define PICO_MAX_TIMERS 20 + +#define PICO_ETH_MRU (1514u) +#define PICO_IP_MRU (1500u) + +/* ===== RECEIVING FUNCTIONS (from dev up to socket) ===== */ + +/* TRANSPORT LEVEL */ +/* interface towards network */ +int32_t pico_transport_receive(struct pico_frame *f, uint8_t proto); + +/* NETWORK LEVEL */ +/* interface towards ethernet */ +int32_t pico_network_receive(struct pico_frame *f); + +#ifdef __cplusplus +extern "C" { +#endif + +/* LOWEST LEVEL: interface towards devices. */ +/* Device driver will call this function which returns immediately. + * Incoming packet will be processed later on in the dev loop. + * The zerocopy version will associate the current buffer to the newly created frame. + * Warning: the buffer used in the zerocopy version MUST have been allocated using PICO_ZALLOC() + */ +int32_t pico_stack_recv(struct pico_device *dev, uint8_t *buffer, uint32_t len); +int32_t pico_stack_recv_zerocopy(struct pico_device *dev, uint8_t *buffer, uint32_t len); +int32_t pico_stack_recv_zerocopy_ext_buffer(struct pico_device *dev, uint8_t *buffer, uint32_t len); +int32_t pico_stack_recv_zerocopy_ext_buffer_notify(struct pico_device *dev, uint8_t *buffer, uint32_t len, void (*notify_free)(uint8_t *buffer)); + +/* ===== SENDING FUNCTIONS (from socket down to dev) ===== */ + +int32_t pico_network_send(struct pico_frame *f); +int32_t pico_sendto_dev(struct pico_frame *f); + +#ifdef PICO_SUPPORT_ETH +int32_t pico_ethernet_send(struct pico_frame *f); + +/* The pico_ethernet_receive() function is used by + * those devices supporting ETH in order to push packets up + * into the stack. + */ +/* DATALINK LEVEL */ +int32_t pico_ethernet_receive(struct pico_frame *f); +#else +/* When ETH is not supported by the stack... */ +# define pico_ethernet_send(f) (-1) +# define pico_ethernet_receive(f) (-1) +#endif + +/* ----- Initialization ----- */ +int pico_stack_init(void); + +/* ----- Loop Function. ----- */ +void pico_stack_tick(void); +void pico_stack_loop(void); + +#ifdef __cplusplus +} +#endif + +/* ---- Notifications for stack errors */ +int pico_notify_socket_unreachable(struct pico_frame *f); +int pico_notify_proto_unreachable(struct pico_frame *f); +int pico_notify_dest_unreachable(struct pico_frame *f); +int pico_notify_ttl_expired(struct pico_frame *f); +int pico_notify_frag_expired(struct pico_frame *f); +int pico_notify_pkt_too_big(struct pico_frame *f); + +/* Various. */ +int pico_source_is_local(struct pico_frame *f); +int pico_frame_dst_is_unicast(struct pico_frame *f); +void pico_store_network_origin(void *src, struct pico_frame *f); +uint32_t pico_timer_add(pico_time expire, void (*timer)(pico_time, void *), void *arg); +void pico_timer_cancel(uint32_t id); +uint32_t pico_rand(void); +void pico_rand_feed(uint32_t feed); +void pico_to_lowercase(char *str); +int pico_address_compare(union pico_address *a, union pico_address *b, uint16_t proto); +int32_t pico_seq_compare(uint32_t a, uint32_t b); + +#endif diff --git a/ext/picotcp/include/pico_tree.h b/ext/picotcp/include/pico_tree.h new file mode 100644 index 0000000..8667a1f --- /dev/null +++ b/ext/picotcp/include/pico_tree.h @@ -0,0 +1,93 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + Author: Andrei Carp + *********************************************************************/ + +#ifndef PICO_RBTREE_H +#define PICO_RBTREE_H + +#include "pico_config.h" + +/* This is used to declare a new tree, leaf root by default */ +#define PICO_TREE_DECLARE(name, compareFunction) \ + struct pico_tree name = \ + { \ + &LEAF, \ + compareFunction \ + } + +#define USE_PICO_PAGE0_ZALLOC (1) +#define USE_PICO_ZALLOC (2) + +struct pico_tree_node +{ + void*keyValue; /* generic key */ + struct pico_tree_node*parent; + struct pico_tree_node*leftChild; + struct pico_tree_node*rightChild; + uint8_t color; +}; + +struct pico_tree +{ + struct pico_tree_node *root; /* root of the tree */ + + /* this function directly provides the keys as parameters not the nodes. */ + int (*compare)(void*keyA, void*keyB); +}; + +extern struct pico_tree_node LEAF; /* generic leaf node */ + +#ifdef PICO_SUPPORT_MM +void *pico_tree_insert_implementation(struct pico_tree *tree, void *key, uint8_t allocator); +void *pico_tree_delete_implementation(struct pico_tree *tree, void *key, uint8_t allocator); +#endif + + +/* + * Manipulation functions + */ +void *pico_tree_insert(struct pico_tree *tree, void *key); +void *pico_tree_delete(struct pico_tree *tree, void *key); +void *pico_tree_findKey(struct pico_tree *tree, void *key); +void pico_tree_drop(struct pico_tree *tree); +int pico_tree_empty(struct pico_tree *tree); +struct pico_tree_node *pico_tree_findNode(struct pico_tree *tree, void *key); + +void *pico_tree_first(struct pico_tree *tree); +void *pico_tree_last(struct pico_tree *tree); +/* + * Traverse functions + */ +struct pico_tree_node *pico_tree_lastNode(struct pico_tree_node *node); +struct pico_tree_node *pico_tree_firstNode(struct pico_tree_node *node); +struct pico_tree_node *pico_tree_next(struct pico_tree_node *node); +struct pico_tree_node *pico_tree_prev(struct pico_tree_node *node); + +/* + * For each macros + */ + +#define pico_tree_foreach(idx, tree) \ + for ((idx) = pico_tree_firstNode((tree)->root); \ + (idx) != &LEAF; \ + (idx) = pico_tree_next(idx)) + +#define pico_tree_foreach_reverse(idx, tree) \ + for ((idx) = pico_tree_lastNode((tree)->root); \ + (idx) != &LEAF; \ + (idx) = pico_tree_prev(idx)) + +#define pico_tree_foreach_safe(idx, tree, idx2) \ + for ((idx) = pico_tree_firstNode((tree)->root); \ + ((idx) != &LEAF) && ((idx2) = pico_tree_next(idx), 1); \ + (idx) = (idx2)) + +#define pico_tree_foreach_reverse_safe(idx, tree, idx2) \ + for ((idx) = pico_tree_lastNode((tree)->root); \ + ((idx) != &LEAF) && ((idx2) = pico_tree_prev(idx), 1); \ + (idx) = (idx2)) + +#endif diff --git a/ext/picotcp/mkdeps.sh b/ext/picotcp/mkdeps.sh new file mode 100755 index 0000000..e313ef2 --- /dev/null +++ b/ext/picotcp/mkdeps.sh @@ -0,0 +1,16 @@ +#!/bin/bash +PREFIX=$1 +shift +echo "/* PicoTCP - Definition file - DO NOT EDIT */" >$PREFIX/include/pico_defines.h +echo "/* This file is automatically generated at compile time */" >>$PREFIX/include/pico_defines.h +echo "#ifndef PICO_DEFINES_H" >>$PREFIX/include/pico_defines.h +echo "#define PICO_DEFINES_H" >>$PREFIX/include/pico_defines.h +echo >>$PREFIX/include/pico_defines.h + +for i in $@; do + if (echo $i | grep "^-D" |grep PICO_SUPPORT >/dev/null); then + my_def=`echo $i |sed -e "s/-D//g"` + echo "#define $my_def" >> $PREFIX/include/pico_defines.h + fi +done +echo "#endif" >>$PREFIX/include/pico_defines.h diff --git a/ext/picotcp/modcheck.py b/ext/picotcp/modcheck.py new file mode 100755 index 0000000..05d2075 --- /dev/null +++ b/ext/picotcp/modcheck.py @@ -0,0 +1,50 @@ +#!/usr/bin/python +import os,sys +import subprocess + +f = open('MODTREE') +mods = {} +commands = [] + +def get_deps(mod): + if not mod in mods.keys(): + return [] + deps = mods[mod] + retlist = [mod] + for i in deps.split(' '): + retlist.append(i) + for j in get_deps(i): + retlist.append(j) + return retlist + + +while(True): + r = f.readline() + if r == '': + break + if r != '\n': + strings = r.split(':') + mod = strings[0] + deps = strings[1].rstrip('\n') + mods[mod] = deps.strip(' ') + +for k,v in mods.iteritems(): + command = 'make dummy ' + deps = get_deps(k) + for i in mods.keys(): + if i in deps: + command += i + "=1 " + else: + command += i + "=0 " + commands.append(command) + +nul = open('/dev/null', 'w') +for i in commands: + print 'Checking config:\n\t%s' % i + os.system('make clean >/dev/null') + args = i.split(' ') + subprocess.call(['make','clean'], shell=True, stdout=nul, stderr=nul) + subprocess.call(args[-1], shell=True,stdout=nul, stderr=nul)==0 or sys.exit(1) + print "CONFIG OK!" + print +sys.exit(0) diff --git a/ext/picotcp/modules/pico_aodv.c b/ext/picotcp/modules/pico_aodv.c new file mode 100644 index 0000000..b0751da --- /dev/null +++ b/ext/picotcp/modules/pico_aodv.c @@ -0,0 +1,674 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + . + + Author: Daniele Lacamera + *********************************************************************/ +#include +#include +#include +#include +#include + +#include +#ifdef PICO_SUPPORT_IPV4 + +#define pico_aodv_dbg(...) do {} while(0) +/* #define pico_aodv_dbg dbg */ + +#define AODV_MAX_PKT (64) +static const struct pico_ip4 HOST_NETMASK = { + 0xffffffff +}; +static struct pico_ip4 all_bcast = { + .addr = 0xFFFFFFFFu +}; + +static const struct pico_ip4 ANY_HOST = { + 0x0 +}; + +static uint32_t pico_aodv_local_id = 0; +static int aodv_node_compare(void *ka, void *kb) +{ + struct pico_aodv_node *a = ka, *b = kb; + if (a->dest.ip4.addr < b->dest.ip4.addr) + return -1; + + if (b->dest.ip4.addr < a->dest.ip4.addr) + return 1; + + return 0; +} + +static int aodv_dev_cmp(void *ka, void *kb) +{ + struct pico_device *a = ka, *b = kb; + if (a->hash < b->hash) + return -1; + + if (a->hash > b->hash) + return 1; + + return 0; +} + +PICO_TREE_DECLARE(aodv_nodes, aodv_node_compare); +PICO_TREE_DECLARE(aodv_devices, aodv_dev_cmp); + +static struct pico_socket *aodv_socket = NULL; + +static struct pico_aodv_node *get_node_by_addr(const union pico_address *addr) +{ + struct pico_aodv_node search; + memcpy(&search.dest, addr, sizeof(union pico_address)); + return pico_tree_findKey(&aodv_nodes, &search); + +} + +static void pico_aodv_set_dev(struct pico_device *dev) +{ + pico_ipv4_route_set_bcast_link(pico_ipv4_link_by_dev(dev)); +} + + +static int aodv_peer_refresh(struct pico_aodv_node *node, uint32_t seq) +{ + if ((0 == (node->flags & PICO_AODV_NODE_SYNC)) || (pico_seq_compare(seq, node->dseq) > 0)) { + node->dseq = seq; + node->flags |= PICO_AODV_NODE_SYNC; + node->last_seen = PICO_TIME_MS(); + return 0; + } + + return -1; +} + +static void aodv_elect_route(struct pico_aodv_node *node, union pico_address *gw, uint8_t metric, struct pico_device *dev) +{ + metric++; + if (!(PICO_AODV_ACTIVE(node)) || metric < node->metric) { + pico_ipv4_route_del(node->dest.ip4, HOST_NETMASK, node->metric); + if (!gw) { + pico_ipv4_route_add(node->dest.ip4, HOST_NETMASK, ANY_HOST, 1, pico_ipv4_link_by_dev(dev)); + node->metric = 1; + } else { + node->metric = metric; + pico_ipv4_route_add(node->dest.ip4, HOST_NETMASK, gw->ip4, metric, NULL); + } + } +} + +static struct pico_aodv_node *aodv_peer_new(const union pico_address *addr) +{ + struct pico_aodv_node *node = PICO_ZALLOC(sizeof(struct pico_aodv_node)); + if (!node) + return NULL; + + memcpy(&node->dest, addr, sizeof(union pico_address)); + pico_tree_insert(&aodv_nodes, node); + return node; +} + + +static struct pico_aodv_node *aodv_peer_eval(union pico_address *addr, uint32_t seq, int valid_seq) +{ + struct pico_aodv_node *node = NULL; + node = get_node_by_addr(addr); + if (!node) { + node = aodv_peer_new(addr); + } + + if (!valid_seq) + return node; + + if (node && (aodv_peer_refresh(node, long_be(seq)) == 0)) + return node; + + return NULL; +} + +static void aodv_forward(void *pkt, struct pico_msginfo *info, int reply) +{ + struct pico_aodv_node *orig; + union pico_address orig_addr; + struct pico_tree_node *index; + struct pico_device *dev; + pico_time now; + int size; + + pico_aodv_dbg("Forwarding %s packet\n", reply ? "REPLY" : "REQUEST"); + + if (reply) { + struct pico_aodv_rrep *rep = (struct pico_aodv_rrep *)pkt; + orig_addr.ip4.addr = rep->dest; + rep->hop_count++; + pico_aodv_dbg("RREP hop count: %d\n", rep->hop_count); + size = sizeof(struct pico_aodv_rrep); + } else { + struct pico_aodv_rreq *req = (struct pico_aodv_rreq *)pkt; + orig_addr.ip4.addr = req->orig; + req->hop_count++; + size = sizeof(struct pico_aodv_rreq); + } + + orig = get_node_by_addr(&orig_addr); + if (!orig) + orig = aodv_peer_new(&orig_addr); + + if (!orig) + return; + + now = PICO_TIME_MS(); + + pico_aodv_dbg("Forwarding %s: last fwd_time: %llu now: %llu ttl: %d ==== \n", reply ? "REPLY" : "REQUEST", + orig->fwd_time, now, info->ttl); + if (((orig->fwd_time == 0) || ((now - orig->fwd_time) > AODV_NODE_TRAVERSAL_TIME)) && (--info->ttl > 0)) { + orig->fwd_time = now; + info->dev = NULL; + pico_tree_foreach(index, &aodv_devices){ + dev = index->keyValue; + pico_aodv_set_dev(dev); + pico_socket_sendto_extended(aodv_socket, pkt, size, &all_bcast, short_be(PICO_AODV_PORT), info); + pico_aodv_dbg("Forwarding %s: complete! ==== \n", reply ? "REPLY" : "REQUEST"); + } + } +} + +static uint32_t aodv_lifetime(struct pico_aodv_node *node) +{ + uint32_t lifetime; + pico_time now = PICO_TIME_MS(); + if (!node->last_seen) + node->last_seen = now; + + if ((now - node->last_seen) > AODV_ACTIVE_ROUTE_TIMEOUT) + return 0; + + lifetime = AODV_ACTIVE_ROUTE_TIMEOUT - (uint32_t)(now - node->last_seen); + return lifetime; +} + +static void aodv_send_reply(struct pico_aodv_node *node, struct pico_aodv_rreq *req, int node_is_local, struct pico_msginfo *info) +{ + struct pico_aodv_rrep reply; + union pico_address dest; + union pico_address oaddr; + struct pico_aodv_node *orig; + oaddr.ip4.addr = req->orig; + orig = get_node_by_addr(&oaddr); + reply.type = AODV_TYPE_RREP; + reply.dest = req->dest; + reply.dseq = req->dseq; + reply.orig = req->orig; + if (!orig) + return; + + reply.hop_count = (uint8_t)(orig->metric - 1u); + + + dest.ip4.addr = 0xFFFFFFFF; /* wide broadcast */ + + if (short_be(req->req_flags) & AODV_RREQ_FLAG_G) { + dest.ip4.addr = req->orig; + } else { + pico_aodv_set_dev(info->dev); + } + + if (node_is_local) { + reply.lifetime = long_be(AODV_MY_ROUTE_TIMEOUT); + reply.dseq = long_be(++pico_aodv_local_id); + pico_socket_sendto(aodv_socket, &reply, sizeof(reply), &dest, short_be(PICO_AODV_PORT)); + } else if (((short_be(req->req_flags) & AODV_RREQ_FLAG_D) == 0) && (node->flags & PICO_AODV_NODE_SYNC)) { + reply.lifetime = long_be(aodv_lifetime(node)); + reply.dseq = long_be(node->dseq); + pico_aodv_dbg("Generating RREP for node %x, id=%x\n", reply.dest, reply.dseq); + pico_socket_sendto(aodv_socket, &reply, sizeof(reply), &dest, short_be(PICO_AODV_PORT)); + } + + pico_aodv_dbg("no rrep generated.\n"); +} + +/* Parser functions */ + +static int aodv_send_req(struct pico_aodv_node *node); + +static void aodv_reverse_path_discover(pico_time now, void *arg) +{ + struct pico_aodv_node *origin = (struct pico_aodv_node *)arg; + (void)now; + pico_aodv_dbg("Sending G RREQ to ORIGIN (metric = %d).\n", origin->metric); + origin->ring_ttl = origin->metric; + aodv_send_req(origin); +} + +static void aodv_recv_valid_rreq(struct pico_aodv_node *node, struct pico_aodv_rreq *req, struct pico_msginfo *info) +{ + struct pico_device *dev; + dev = pico_ipv4_link_find(&node->dest.ip4); + pico_aodv_dbg("Valid req.\n"); + if (dev || PICO_AODV_ACTIVE(node)) { + /* if destination is ourselves, or we have a possible route: Send reply. */ + aodv_send_reply(node, req, dev != NULL, info); + if (dev) { + /* if really for us, we need to build the return route. Initiate a gratuitous request. */ + union pico_address origin_addr; + struct pico_aodv_node *origin; + origin_addr.ip4.addr = req->orig; + origin = get_node_by_addr(&origin_addr); + if (origin) { + origin->flags |= PICO_AODV_NODE_ROUTE_DOWN; + pico_timer_add(AODV_PATH_DISCOVERY_TIME, aodv_reverse_path_discover, origin); + } + } + + pico_aodv_dbg("Replied.\n"); + } else { + /* destination unknown. Evaluate forwarding. */ + pico_aodv_dbg(" == Forwarding == .\n"); + aodv_forward(req, info, 0); + } +} + + +static void aodv_parse_rreq(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) +{ + struct pico_aodv_rreq *req = (struct pico_aodv_rreq *) buf; + struct pico_aodv_node *node = NULL; + struct pico_device *dev; + union pico_address orig, dest; + (void)from; + if (len != (int)sizeof(struct pico_aodv_rreq)) + return; + + orig.ip4.addr = req->orig; + dev = pico_ipv4_link_find(&orig.ip4); + if (dev) { + pico_aodv_dbg("RREQ <-- myself\n"); + return; + } + + node = aodv_peer_eval(&orig, req->oseq, 1); + if (!node) { + pico_aodv_dbg("RREQ: Neighbor is not valid. oseq=%d\n", long_be(req->oseq)); + return; + } + + if (req->hop_count > 0) + aodv_elect_route(node, from, req->hop_count, msginfo->dev); + else + aodv_elect_route(node, NULL, 0, msginfo->dev); + + dest.ip4.addr = req->dest; + node = aodv_peer_eval(&dest, req->dseq, !(req->req_flags & short_be(AODV_RREQ_FLAG_U))); + if (!node) { + node = aodv_peer_new(&dest); + pico_aodv_dbg("RREQ: New peer! %08x\n", dest.ip4.addr); + } + + if (!node) + return; + + aodv_recv_valid_rreq(node, req, msginfo); +} + +static void aodv_parse_rrep(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) +{ + struct pico_aodv_rrep *rep = (struct pico_aodv_rrep *) buf; + struct pico_aodv_node *node = NULL; + union pico_address dest; + union pico_address orig; + struct pico_device *dev = NULL; + if (len != (int)sizeof(struct pico_aodv_rrep)) + return; + + dest.ip4.addr = rep->dest; + orig.ip4.addr = rep->orig; + dev = pico_ipv4_link_find(&dest.ip4); + + if (dev) /* Our reply packet got rebounced, no useful information here, no need to fwd. */ + return; + + pico_aodv_dbg("::::::::::::: Parsing RREP for node %08x\n", rep->dest); + node = aodv_peer_eval(&dest, rep->dseq, 1); + if (node) { + pico_aodv_dbg("::::::::::::: Node found. Electing route and forwarding.\n"); + dest.ip4.addr = node->dest.ip4.addr; + if (rep->hop_count > 0) + aodv_elect_route(node, from, rep->hop_count, msginfo->dev); + else + aodv_elect_route(node, NULL, 0, msginfo->dev); + + /* If we are the final destination for the reply (orig), no need to forward. */ + if (pico_ipv4_link_find(&orig.ip4)) { + node->flags |= PICO_AODV_NODE_ROUTE_UP; + } else { + aodv_forward(rep, msginfo, 1); + } + } +} + +static void aodv_parse_rerr(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) +{ + if ((uint32_t)len < sizeof(struct pico_aodv_rerr) || + (((uint32_t)len - sizeof(struct pico_aodv_rerr)) % sizeof(struct pico_aodv_unreachable)) > 0) + return; + + (void)from; + (void)buf; + (void)len; + (void)msginfo; + /* TODO: invalidate routes. This only makes sense if we are using HELLO messages. */ +} + +static void aodv_parse_rack(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) +{ + if (len != (int)sizeof(struct pico_aodv_rack)) + return; + + (void)from; + (void)buf; + (void)len; + (void)msginfo; +} + +struct aodv_parser_s { + void (*call)(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo); +}; + +struct aodv_parser_s aodv_parser[5] = { + {.call = NULL}, + {.call = aodv_parse_rreq }, + {.call = aodv_parse_rrep }, + {.call = aodv_parse_rerr }, + {.call = aodv_parse_rack } +}; + + +static void pico_aodv_parse(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) +{ + struct pico_aodv_node *node; + uint8_t hopcount = 0; + if ((buf[0] < 1) || (buf[0] > 4)) { + /* Type is invalid. Discard silently. */ + return; + } + + if (buf[0] == AODV_TYPE_RREQ) { + hopcount = ((struct pico_aodv_rreq *)buf)->hop_count; + } + + if (buf[0] == AODV_TYPE_RREP) { + hopcount = ((struct pico_aodv_rrep *)buf)->hop_count; + } + + node = aodv_peer_eval(from, 0, 0); + if (!node) + node = aodv_peer_new(from); + + if (node && (hopcount == 0)) { + aodv_elect_route(node, NULL, hopcount, msginfo->dev); + } + + pico_aodv_dbg("Received AODV packet, ttl = %d\n", msginfo->ttl); + aodv_parser[buf[0]].call(from, buf, len, msginfo); +} + +static void pico_aodv_socket_callback(uint16_t ev, struct pico_socket *s) +{ + static uint8_t aodv_pkt[AODV_MAX_PKT]; + static union pico_address from; + static struct pico_msginfo msginfo; + uint16_t sport; + int r; + if (s != aodv_socket) + return; + + if (ev & PICO_SOCK_EV_RD) { + r = pico_socket_recvfrom_extended(s, aodv_pkt, AODV_MAX_PKT, &from, &sport, &msginfo); + if (r <= 0) + return; + + pico_aodv_dbg("Received AODV packet: %d bytes \n", r); + + pico_aodv_parse(&from, aodv_pkt, r, &msginfo); + } +} + +static void aodv_make_rreq(struct pico_aodv_node *node, struct pico_aodv_rreq *req) +{ + memset(req, 0, sizeof(struct pico_aodv_rreq)); + req->type = AODV_TYPE_RREQ; + + if (0 == (node->flags & PICO_AODV_NODE_SYNC)) { + req->req_flags |= short_be(AODV_RREQ_FLAG_U); /* no known dseq, mark as U */ + req->dseq = 0; /* Unknown */ + } else { + req->dseq = long_be(node->dseq); + req->req_flags |= short_be(AODV_RREQ_FLAG_G); /* RFC3561 $6.3: we SHOULD set G flag as originators */ + } + + /* Hop count = 0; */ + req->rreq_id = long_be(++pico_aodv_local_id); + req->dest = node->dest.ip4.addr; + req->oseq = long_be(pico_aodv_local_id); +} + +static void aodv_retrans_rreq(pico_time now, void *arg) +{ + struct pico_aodv_node *node = (struct pico_aodv_node *)arg; + struct pico_device *dev; + struct pico_tree_node *index; + static struct pico_aodv_rreq rreq; + struct pico_ipv4_link *ip4l = NULL; + struct pico_msginfo info = { + .dev = NULL, .tos = 0, .ttl = AODV_TTL_START + }; + (void)now; + + memset(&rreq, 0, sizeof(rreq)); + + if (node->flags & PICO_AODV_NODE_ROUTE_UP) { + pico_aodv_dbg("------------------------------------------------------ Node %08x already active.\n", node->dest.ip4.addr); + return; + } + + if (node->ring_ttl > AODV_TTL_THRESHOLD) { + node->ring_ttl = AODV_NET_DIAMETER; + pico_aodv_dbg("----------- DIAMETER reached.\n"); + } + + + if (node->rreq_retry > AODV_RREQ_RETRIES) { + node->rreq_retry = 0; + node->ring_ttl = 0; + pico_aodv_dbg("Node is unreachable.\n"); + node->flags &= (uint16_t)(~PICO_AODV_NODE_ROUTE_DOWN); + return; + } + + if (node->ring_ttl == AODV_NET_DIAMETER) { + node->rreq_retry++; + pico_aodv_dbg("Retry #%d\n", node->rreq_retry); + } + + aodv_make_rreq(node, &rreq); + info.ttl = (uint8_t)node->ring_ttl; + pico_tree_foreach(index, &aodv_devices){ + dev = index->keyValue; + pico_aodv_set_dev(dev); + ip4l = pico_ipv4_link_by_dev(dev); + if (ip4l) { + rreq.orig = ip4l->address.addr; + pico_socket_sendto_extended(aodv_socket, &rreq, sizeof(rreq), &all_bcast, short_be(PICO_AODV_PORT), &info); + } + } + if (node->ring_ttl < AODV_NET_DIAMETER) + node->ring_ttl = (uint8_t)(node->ring_ttl + AODV_TTL_INCREMENT); + + pico_timer_add((pico_time)AODV_RING_TRAVERSAL_TIME(node->ring_ttl), aodv_retrans_rreq, node); +} + +static int aodv_send_req(struct pico_aodv_node *node) +{ + struct pico_device *dev; + struct pico_tree_node *index; + static struct pico_aodv_rreq rreq; + int n = 0; + struct pico_ipv4_link *ip4l = NULL; + struct pico_msginfo info = { + .dev = NULL, .tos = 0, .ttl = AODV_TTL_START + }; + memset(&rreq, 0, sizeof(rreq)); + + if (PICO_AODV_ACTIVE(node)) + return 0; + + node->flags |= PICO_AODV_NODE_REQUESTING; + + if (pico_tree_empty(&aodv_devices)) + return n; + + if (!aodv_socket) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + if (node->flags & PICO_AODV_NODE_ROUTE_DOWN) { + info.ttl = node->metric; + } + + aodv_make_rreq(node, &rreq); + pico_tree_foreach(index, &aodv_devices) { + dev = index->keyValue; + pico_aodv_set_dev(dev); + ip4l = pico_ipv4_link_by_dev(dev); + if (ip4l) { + rreq.orig = ip4l->address.addr; + pico_socket_sendto_extended(aodv_socket, &rreq, sizeof(rreq), &all_bcast, short_be(PICO_AODV_PORT), &info); + n++; + } + } + pico_timer_add((pico_time)AODV_RING_TRAVERSAL_TIME(1), aodv_retrans_rreq, node); + return n; +} + +static void pico_aodv_expired(struct pico_aodv_node *node) +{ + node->flags |= PICO_AODV_NODE_UNREACH; + node->flags &= (uint8_t)(~PICO_AODV_NODE_ROUTE_UP); + node->flags &= (uint8_t)(~PICO_AODV_NODE_ROUTE_DOWN); + pico_ipv4_route_del(node->dest.ip4, HOST_NETMASK, node->metric); + node->ring_ttl = 0; + /* TODO: send err */ + +} + +static void pico_aodv_collector(pico_time now, void *arg) +{ + struct pico_tree_node *index; + struct pico_aodv_node *node; + (void)arg; + (void)now; + pico_tree_foreach(index, &aodv_nodes){ + node = index->keyValue; + if (PICO_AODV_ACTIVE(node)) { + uint32_t lifetime = aodv_lifetime(node); + if (lifetime == 0) + pico_aodv_expired(node); + } + } + pico_timer_add(AODV_HELLO_INTERVAL, pico_aodv_collector, NULL); +} + +MOCKABLE int pico_aodv_init(void) +{ + struct pico_ip4 any = { + 0 + }; + uint16_t port = short_be(PICO_AODV_PORT); + if (aodv_socket) { + pico_err = PICO_ERR_EADDRINUSE; + return -1; + } + + aodv_socket = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_UDP, pico_aodv_socket_callback); + if (!aodv_socket) + return -1; + + if (pico_socket_bind(aodv_socket, &any, &port) != 0) { + uint16_t err = pico_err; + pico_socket_close(aodv_socket); + pico_err = err; + aodv_socket = NULL; + return -1; + } + + pico_aodv_local_id = pico_rand(); + pico_timer_add(AODV_HELLO_INTERVAL, pico_aodv_collector, NULL); + return 0; +} + + +int pico_aodv_add(struct pico_device *dev) +{ + return (pico_tree_insert(&aodv_devices, dev)) ? (0) : (-1); +} + +void pico_aodv_refresh(const union pico_address *addr) +{ + struct pico_aodv_node *node = get_node_by_addr(addr); + if (node) { + node->last_seen = PICO_TIME_MS(); + } +} + +int pico_aodv_lookup(const union pico_address *addr) +{ + struct pico_aodv_node *node = get_node_by_addr(addr); + if (!node) + node = aodv_peer_new(addr); + + if (!node) + return -1; + + if ((node->flags & PICO_AODV_NODE_ROUTE_UP) || (node->flags & PICO_AODV_NODE_ROUTE_DOWN)) + return 0; + + if (node->ring_ttl < AODV_TTL_START) { + node->ring_ttl = AODV_TTL_START; + aodv_send_req(node); + return 0; + } + + pico_err = PICO_ERR_EINVAL; + return -1; +} + +#else + +int pico_aodv_init(void) +{ + return -1; +} + +int pico_aodv_add(struct pico_device *dev) +{ + (void)dev; + return -1; +} + +int pico_aodv_lookup(const union pico_address *addr) +{ + (void)addr; + return -1; +} + +void pico_aodv_refresh(const union pico_address *addr) +{ + (void)addr; +} + +#endif diff --git a/ext/picotcp/modules/pico_aodv.h b/ext/picotcp/modules/pico_aodv.h new file mode 100644 index 0000000..0937ed1 --- /dev/null +++ b/ext/picotcp/modules/pico_aodv.h @@ -0,0 +1,130 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + . + + Author: Daniele Lacamera + *********************************************************************/ +#ifndef PICO_AODV_H_ +#define PICO_AODV_H_ + +/* RFC3561 */ +#define PICO_AODV_PORT (654) + +/* RFC3561 $10 */ +#define AODV_ACTIVE_ROUTE_TIMEOUT (8000u) /* Conservative value for link breakage detection */ +#define AODV_DELETE_PERIOD (5 * AODV_ACTIVE_ROUTE_TIMEOUT) /* Recommended value K = 5 */ +#define AODV_ALLOWED_HELLO_LOSS (4) /* conservative */ +#define AODV_NET_DIAMETER ((uint8_t)(35)) +#define AODV_RREQ_RETRIES (2) +#define AODV_NODE_TRAVERSAL_TIME (40) +#define AODV_HELLO_INTERVAL (1000) +#define AODV_LOCAL_ADD_TTL 2 +#define AODV_RREQ_RATELIMIT (10) +#define AODV_TIMEOUT_BUFFER (2) +#define AODV_TTL_START ((uint8_t)(1)) +#define AODV_TTL_INCREMENT 2 +#define AODV_TTL_THRESHOLD ((uint8_t)(7)) +#define AODV_RERR_RATELIMIT (10) +#define AODV_MAX_REPAIR_TTL ((uint8_t)(AODV_NET_DIAMETER / 3)) +#define AODV_MY_ROUTE_TIMEOUT (2 * AODV_ACTIVE_ROUTE_TIMEOUT) +#define AODV_NET_TRAVERSAL_TIME (2 * AODV_NODE_TRAVERSAL_TIME * AODV_NET_DIAMETER) +#define AODV_BLACKLIST_TIMEOUT (AODV_RREQ_RETRIES * AODV_NET_TRAVERSAL_TIME) +#define AODV_NEXT_HOP_WAIT (AODV_NODE_TRAVERSAL_TIME + 10) +#define AODV_PATH_DISCOVERY_TIME (2 * AODV_NET_TRAVERSAL_TIME) +#define AODV_RING_TRAVERSAL_TIME(ttl) (2 * AODV_NODE_TRAVERSAL_TIME * (ttl + AODV_TIMEOUT_BUFFER)) +/* End section RFC3561 $10 */ + + +#define AODV_TYPE_RREQ 1 +#define AODV_TYPE_RREP 2 +#define AODV_TYPE_RERR 3 +#define AODV_TYPE_RACK 4 + +PACKED_STRUCT_DEF pico_aodv_rreq +{ + uint8_t type; + uint16_t req_flags; + uint8_t hop_count; + uint32_t rreq_id; + uint32_t dest; + uint32_t dseq; + uint32_t orig; + uint32_t oseq; +}; + +#define AODV_RREQ_FLAG_J 0x8000 +#define AODV_RREQ_FLAG_R 0x4000 +#define AODV_RREQ_FLAG_G 0x2000 +#define AODV_RREQ_FLAG_D 0x1000 +#define AODV_RREQ_FLAG_U 0x0800 +#define AODV_RREQ_FLAG_RESERVED 0x07FF + +PACKED_STRUCT_DEF pico_aodv_rrep +{ + uint8_t type; + uint8_t rep_flags; + uint8_t prefix_sz; + uint8_t hop_count; + uint32_t dest; + uint32_t dseq; + uint32_t orig; + uint32_t lifetime; +}; + +#define AODV_RREP_MAX_PREFIX 0x1F +#define AODV_RREP_FLAG_R 0x80 +#define AODV_RREP_FLAG_A 0x40 +#define AODV_RREP_FLAG_RESERVED 0x3F + +#define PICO_AODV_NODE_NEW 0x0000 +#define PICO_AODV_NODE_SYNC 0x0001 +#define PICO_AODV_NODE_REQUESTING 0x0002 +#define PICO_AODV_NODE_ROUTE_UP 0x0004 +#define PICO_AODV_NODE_ROUTE_DOWN 0x0008 +#define PICO_AODV_NODE_IDLING 0x0010 +#define PICO_AODV_NODE_UNREACH 0x0020 + +#define PICO_AODV_ACTIVE(node) ((node->flags & PICO_AODV_NODE_ROUTE_UP) && (node->flags & PICO_AODV_NODE_ROUTE_DOWN)) + + +struct pico_aodv_node +{ + union pico_address dest; + pico_time last_seen; + pico_time fwd_time; + uint32_t dseq; + uint16_t flags; + uint8_t metric; + uint8_t ring_ttl; + uint8_t rreq_retry; +}; + +PACKED_STRUCT_DEF pico_aodv_unreachable +{ + uint32_t addr; + uint32_t dseq; +}; + +PACKED_STRUCT_DEF pico_aodv_rerr +{ + uint8_t type; + uint16_t rerr_flags; + uint8_t dst_count; + uint32_t unreach_addr; + uint32_t unreach_dseq; + struct pico_aodv_unreachable unreach[1]; /* unrechable nodes: must be at least 1. See dst_count field above */ +}; + +PACKED_STRUCT_DEF pico_aodv_rack +{ + uint8_t type; + uint8_t reserved; +}; + +int pico_aodv_init(void); +int pico_aodv_add(struct pico_device *dev); +int pico_aodv_lookup(const union pico_address *addr); +void pico_aodv_refresh(const union pico_address *addr); +#endif diff --git a/ext/picotcp/modules/pico_arp.c b/ext/picotcp/modules/pico_arp.c new file mode 100644 index 0000000..850ca35 --- /dev/null +++ b/ext/picotcp/modules/pico_arp.c @@ -0,0 +1,537 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + . + + Authors: Daniele Lacamera + *********************************************************************/ + + +#include "pico_config.h" +#include "pico_arp.h" +#include "pico_tree.h" +#include "pico_ipv4.h" +#include "pico_device.h" +#include "pico_stack.h" + +extern const uint8_t PICO_ETHADDR_ALL[6]; +#define PICO_ARP_TIMEOUT 600000llu +#define PICO_ARP_RETRY 300lu +#define PICO_ARP_MAX_PENDING 5 + +#ifdef DEBUG_ARP + #define arp_dbg dbg +#else + #define arp_dbg(...) do {} while(0) +#endif + +static int max_arp_reqs = PICO_ARP_MAX_RATE; +static struct pico_frame *frames_queued[PICO_ARP_MAX_PENDING] = { 0 }; + +static void pico_arp_queued_trigger(void) +{ + int i; + struct pico_frame *f; + for (i = 0; i < PICO_ARP_MAX_PENDING; i++) + { + f = frames_queued[i]; + if (f) { + if (!pico_ethernet_send(f)) + { + pico_frame_discard(f); + frames_queued[i] = NULL; + } + } + } +} + +static void update_max_arp_reqs(pico_time now, void *unused) +{ + IGNORE_PARAMETER(now); + IGNORE_PARAMETER(unused); + if (max_arp_reqs < PICO_ARP_MAX_RATE) + max_arp_reqs++; + + pico_timer_add(PICO_ARP_INTERVAL / PICO_ARP_MAX_RATE, &update_max_arp_reqs, NULL); +} + +void pico_arp_init(void) +{ + pico_timer_add(PICO_ARP_INTERVAL / PICO_ARP_MAX_RATE, &update_max_arp_reqs, NULL); +} + +PACKED_STRUCT_DEF pico_arp_hdr +{ + uint16_t htype; + uint16_t ptype; + uint8_t hsize; + uint8_t psize; + uint16_t opcode; + uint8_t s_mac[PICO_SIZE_ETH]; + struct pico_ip4 src; + uint8_t d_mac[PICO_SIZE_ETH]; + struct pico_ip4 dst; +}; + + + +/* Callback handler for ip conflict service (e.g. IPv4 SLAAC) + * Whenever the IP address registered here is seen in the network, + * the callback is awaken to take countermeasures against IP collisions. + * + */ + +struct arp_service_ipconflict { + struct pico_eth mac; + struct pico_ip4 ip; + void (*conflict)(int); +}; + +static struct arp_service_ipconflict conflict_ipv4; + + + +#define PICO_SIZE_ARPHDR ((sizeof(struct pico_arp_hdr))) + +/* Arp Entries for the tables. */ +struct pico_arp { +/* CAREFUL MAN! ARP entry MUST begin with a pico_eth structure, + * due to in-place casting!!! */ + struct pico_eth eth; + struct pico_ip4 ipv4; + int arp_status; + pico_time timestamp; + struct pico_device *dev; + uint32_t timer; +}; + + + +/*****************/ +/** ARP TREE **/ +/*****************/ + +/* Routing destination */ + +static int arp_compare(void *ka, void *kb) +{ + struct pico_arp *a = ka, *b = kb; + return pico_ipv4_compare(&a->ipv4, &b->ipv4); +} + +PICO_TREE_DECLARE(arp_tree, arp_compare); + +/*********************/ +/** END ARP TREE **/ +/*********************/ + +struct pico_eth *pico_arp_lookup(struct pico_ip4 *dst) +{ + struct pico_arp search, *found; + search.ipv4.addr = dst->addr; + found = pico_tree_findKey(&arp_tree, &search); + if (found && (found->arp_status != PICO_ARP_STATUS_STALE)) + return &found->eth; + + return NULL; +} + +struct pico_ip4 *pico_arp_reverse_lookup(struct pico_eth *dst) +{ + struct pico_arp*search; + struct pico_tree_node *index; + pico_tree_foreach(index, &arp_tree){ + search = index->keyValue; + if(memcmp(&(search->eth.addr), &dst->addr, 6) == 0) + return &search->ipv4; + } + return NULL; +} + +static void pico_arp_unreachable(struct pico_ip4 *a) +{ + int i; + struct pico_frame *f; + struct pico_ipv4_hdr *hdr; + struct pico_ip4 dst; + for (i = 0; i < PICO_ARP_MAX_PENDING; i++) + { + f = frames_queued[i]; + if (f) { + hdr = (struct pico_ipv4_hdr *) f->net_hdr; + dst = pico_ipv4_route_get_gateway(&hdr->dst); + if (!dst.addr) + dst.addr = hdr->dst.addr; + + if (dst.addr == a->addr) { + if (!pico_source_is_local(f)) { + pico_notify_dest_unreachable(f); + } + + pico_frame_discard(f); + frames_queued[i] = NULL; + } + } + } +} + +static void pico_arp_retry(struct pico_frame *f, struct pico_ip4 *where) +{ + if (++f->failure_count < 4) { + arp_dbg ("================= ARP REQUIRED: %d =============\n\n", f->failure_count); + /* check if dst is local (gateway = 0), or if to use gateway */ + pico_arp_request(f->dev, where, PICO_ARP_QUERY); + } else { + pico_arp_unreachable(where); + } +} + +struct pico_eth *pico_arp_get(struct pico_frame *f) +{ + struct pico_eth *a4; + struct pico_ip4 gateway; + struct pico_ip4 *where; + struct pico_ipv4_hdr *hdr = (struct pico_ipv4_hdr *) f->net_hdr; + struct pico_ipv4_link *l; + if (!hdr) + return NULL; + + l = pico_ipv4_link_get(&hdr->dst); + if(l) { + /* address belongs to ourself */ + return &l->dev->eth->mac; + } + + gateway = pico_ipv4_route_get_gateway(&hdr->dst); + /* check if dst is local (gateway = 0), or if to use gateway */ + if (gateway.addr != 0) + where = &gateway; + else + where = &hdr->dst; + + a4 = pico_arp_lookup(where); /* check if dst ip mac in cache */ + + if (!a4) + pico_arp_retry(f, where); + + return a4; +} + + +void pico_arp_postpone(struct pico_frame *f) +{ + int i; + for (i = 0; i < PICO_ARP_MAX_PENDING; i++) + { + if (!frames_queued[i]) { + frames_queued[i] = pico_frame_copy(f); + return; + } + } + /* Not possible to enqueue: caller will discard packet */ +} + + +#ifdef DEBUG_ARP +void dbg_arp(void) +{ + struct pico_arp *a; + struct pico_tree_node *index; + + pico_tree_foreach(index, &arp_tree) { + a = index->keyValue; + arp_dbg("ARP to %08x, mac: %02x:%02x:%02x:%02x:%02x:%02x\n", a->ipv4.addr, a->eth.addr[0], a->eth.addr[1], a->eth.addr[2], a->eth.addr[3], a->eth.addr[4], a->eth.addr[5] ); + } +} +#endif + +static void arp_expire(pico_time now, void *_stale) +{ + struct pico_arp *stale = (struct pico_arp *) _stale; + if (now >= (stale->timestamp + PICO_ARP_TIMEOUT)) { + stale->arp_status = PICO_ARP_STATUS_STALE; + arp_dbg("ARP: Setting arp_status to STALE\n"); + pico_arp_request(stale->dev, &stale->ipv4, PICO_ARP_QUERY); + } else { + /* Timer must be rescheduled, ARP entry has been renewed lately. + * No action required to refresh the entry, will check on the next timeout */ + pico_timer_add(PICO_ARP_TIMEOUT + stale->timestamp - now, arp_expire, stale); + } +} + +static void pico_arp_add_entry(struct pico_arp *entry) +{ + entry->arp_status = PICO_ARP_STATUS_REACHABLE; + entry->timestamp = PICO_TIME(); + + pico_tree_insert(&arp_tree, entry); + arp_dbg("ARP ## reachable.\n"); + pico_arp_queued_trigger(); + pico_timer_add(PICO_ARP_TIMEOUT, arp_expire, entry); +} + +int pico_arp_create_entry(uint8_t *hwaddr, struct pico_ip4 ipv4, struct pico_device *dev) +{ + struct pico_arp*arp = PICO_ZALLOC(sizeof(struct pico_arp)); + if(!arp) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + memcpy(arp->eth.addr, hwaddr, 6); + arp->ipv4.addr = ipv4.addr; + arp->dev = dev; + + pico_arp_add_entry(arp); + + return 0; +} + +static void pico_arp_check_conflict(struct pico_arp_hdr *hdr) +{ + if (conflict_ipv4.conflict) + { + if((conflict_ipv4.ip.addr == hdr->src.addr) && + (memcmp(hdr->s_mac, conflict_ipv4.mac.addr, PICO_SIZE_ETH) != 0)) + conflict_ipv4.conflict(PICO_ARP_CONFLICT_REASON_CONFLICT ); + if((hdr->src.addr == 0) && (hdr->dst.addr == conflict_ipv4.ip.addr) ) + conflict_ipv4.conflict(PICO_ARP_CONFLICT_REASON_PROBE ); + } +} + +static struct pico_arp *pico_arp_lookup_entry(struct pico_frame *f) +{ + struct pico_arp search; + struct pico_arp *found = NULL; + struct pico_arp_hdr *hdr = (struct pico_arp_hdr *) f->net_hdr; + /* Populate a new arp entry */ + search.ipv4.addr = hdr->src.addr; + + /* Search for already existing entry */ + found = pico_tree_findKey(&arp_tree, &search); + if (found) { + if (found->arp_status == PICO_ARP_STATUS_STALE) { + /* Replace if stale */ + pico_tree_delete(&arp_tree, found); + pico_arp_add_entry(found); + } else { + /* Update mac address */ + memcpy(found->eth.addr, hdr->s_mac, PICO_SIZE_ETH); + arp_dbg("ARP entry updated!\n"); + + /* Refresh timestamp, this will force a reschedule on the next timeout*/ + found->timestamp = PICO_TIME(); + } + } + + return found; +} + + +static int pico_arp_check_incoming_hdr_type(struct pico_arp_hdr *h) +{ + /* Check the hardware type and protocol */ + if ((h->htype != PICO_ARP_HTYPE_ETH) || (h->ptype != PICO_IDETH_IPV4)) + return -1; + + return 0; +} + +static int pico_arp_check_incoming_hdr(struct pico_frame *f, struct pico_ip4 *dst_addr) +{ + struct pico_arp_hdr *hdr = (struct pico_arp_hdr *) f->net_hdr; + if (!hdr) + return -1; + + dst_addr->addr = hdr->dst.addr; + if (pico_arp_check_incoming_hdr_type(hdr) < 0) + return -1; + + /* The source mac address must not be a multicast or broadcast address */ + if (hdr->s_mac[0] & 0x01) + return -1; + + return 0; +} + +static void pico_arp_reply_on_request(struct pico_frame *f, struct pico_ip4 me) +{ + struct pico_arp_hdr *hdr; + struct pico_eth_hdr *eh; + + hdr = (struct pico_arp_hdr *) f->net_hdr; + eh = (struct pico_eth_hdr *)f->datalink_hdr; + if (hdr->opcode != PICO_ARP_REQUEST) + return; + + hdr->opcode = PICO_ARP_REPLY; + memcpy(hdr->d_mac, hdr->s_mac, PICO_SIZE_ETH); + memcpy(hdr->s_mac, f->dev->eth->mac.addr, PICO_SIZE_ETH); + hdr->dst.addr = hdr->src.addr; + hdr->src.addr = me.addr; + + /* Prepare eth header for arp reply */ + memcpy(eh->daddr, eh->saddr, PICO_SIZE_ETH); + memcpy(eh->saddr, f->dev->eth->mac.addr, PICO_SIZE_ETH); + f->start = f->datalink_hdr; + f->len = PICO_SIZE_ETHHDR + PICO_SIZE_ARPHDR; + f->dev->send(f->dev, f->start, (int)f->len); +} + +static int pico_arp_check_flooding(struct pico_frame *f, struct pico_ip4 me) +{ + struct pico_device *link_dev; + struct pico_arp_hdr *hdr; + hdr = (struct pico_arp_hdr *) f->net_hdr; + + /* Prevent ARP flooding */ + link_dev = pico_ipv4_link_find(&me); + if ((link_dev == f->dev) && (hdr->opcode == PICO_ARP_REQUEST)) { + if (max_arp_reqs == 0) + return -1; + else + max_arp_reqs--; + } + + /* Check if we are the target IP address */ + if (link_dev != f->dev) + return -1; + + return 0; +} + +static int pico_arp_process_in(struct pico_frame *f, struct pico_arp_hdr *hdr, struct pico_arp *found) +{ + struct pico_ip4 me; + if (pico_arp_check_incoming_hdr(f, &me) < 0) { + pico_frame_discard(f); + return -1; + } + + if (pico_arp_check_flooding(f, me) < 0) { + pico_frame_discard(f); + return -1; + } + + /* If no existing entry was found, create a new entry, or fail trying. */ + if ((!found) && (pico_arp_create_entry(hdr->s_mac, hdr->src, f->dev) < 0)) { + pico_frame_discard(f); + return -1; + } + + /* If the packet is a request, send a reply */ + pico_arp_reply_on_request(f, me); + +#ifdef DEBUG_ARP + dbg_arp(); +#endif + pico_frame_discard(f); + return 0; +} + +int pico_arp_receive(struct pico_frame *f) +{ + struct pico_arp_hdr *hdr; + struct pico_arp *found = NULL; + + hdr = (struct pico_arp_hdr *) f->net_hdr; + if (!hdr) + return -1; + + pico_arp_check_conflict(hdr); + found = pico_arp_lookup_entry(f); + return pico_arp_process_in(f, hdr, found); + +} + +static int32_t pico_arp_request_xmit(struct pico_device *dev, struct pico_frame *f, struct pico_ip4 *src, struct pico_ip4 *dst, uint8_t type) +{ + struct pico_arp_hdr *ah = (struct pico_arp_hdr *) (f->start + PICO_SIZE_ETHHDR); + int ret; + + /* Fill arp header */ + ah->htype = PICO_ARP_HTYPE_ETH; + ah->ptype = PICO_IDETH_IPV4; + ah->hsize = PICO_SIZE_ETH; + ah->psize = PICO_SIZE_IP4; + ah->opcode = PICO_ARP_REQUEST; + memcpy(ah->s_mac, dev->eth->mac.addr, PICO_SIZE_ETH); + + switch (type) { + case PICO_ARP_ANNOUNCE: + ah->src.addr = dst->addr; + ah->dst.addr = dst->addr; + break; + case PICO_ARP_PROBE: + ah->src.addr = 0; + ah->dst.addr = dst->addr; + break; + case PICO_ARP_QUERY: + ah->src.addr = src->addr; + ah->dst.addr = dst->addr; + break; + default: + pico_frame_discard(f); + return -1; + } + arp_dbg("Sending arp request.\n"); + ret = dev->send(dev, f->start, (int) f->len); + pico_frame_discard(f); + return ret; +} + +int32_t pico_arp_request(struct pico_device *dev, struct pico_ip4 *dst, uint8_t type) +{ + struct pico_frame *q = pico_frame_alloc(PICO_SIZE_ETHHDR + PICO_SIZE_ARPHDR); + struct pico_eth_hdr *eh; + struct pico_ip4 *src = NULL; + + if (!q) + return -1; + + if (type == PICO_ARP_QUERY) + { + src = pico_ipv4_source_find(dst); + if (!src) { + pico_frame_discard(q); + return -1; + } + } + + arp_dbg("QUERY: %08x\n", dst->addr); + + eh = (struct pico_eth_hdr *)q->start; + + /* Fill eth header */ + memcpy(eh->saddr, dev->eth->mac.addr, PICO_SIZE_ETH); + memcpy(eh->daddr, PICO_ETHADDR_ALL, PICO_SIZE_ETH); + eh->proto = PICO_IDETH_ARP; + + return pico_arp_request_xmit(dev, q, src, dst, type); +} + +int pico_arp_get_neighbors(struct pico_device *dev, struct pico_ip4 *neighbors, int maxlen) +{ + struct pico_arp*search; + struct pico_tree_node *index; + int i = 0; + pico_tree_foreach(index, &arp_tree){ + search = index->keyValue; + if (search->dev == dev) { + neighbors[i++].addr = search->ipv4.addr; + if (i >= maxlen) + return i; + } + } + return i; +} + +void pico_arp_register_ipconflict(struct pico_ip4 *ip, struct pico_eth *mac, void (*cb)(int reason)) +{ + conflict_ipv4.conflict = cb; + conflict_ipv4.ip.addr = ip->addr; + if (mac != NULL) + memcpy(conflict_ipv4.mac.addr, mac, 6); +} + diff --git a/ext/picotcp/modules/pico_arp.h b/ext/picotcp/modules/pico_arp.h new file mode 100644 index 0000000..36f7210 --- /dev/null +++ b/ext/picotcp/modules/pico_arp.h @@ -0,0 +1,35 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + *********************************************************************/ +#ifndef INCLUDE_PICO_ARP +#define INCLUDE_PICO_ARP +#include "pico_eth.h" +#include "pico_device.h" + +int pico_arp_receive(struct pico_frame *); + + +struct pico_eth *pico_arp_get(struct pico_frame *f); +int32_t pico_arp_request(struct pico_device *dev, struct pico_ip4 *dst, uint8_t type); + +#define PICO_ARP_STATUS_REACHABLE 0x00 +#define PICO_ARP_STATUS_PERMANENT 0x01 +#define PICO_ARP_STATUS_STALE 0x02 + +#define PICO_ARP_QUERY 0x00 +#define PICO_ARP_PROBE 0x01 +#define PICO_ARP_ANNOUNCE 0x02 + +#define PICO_ARP_CONFLICT_REASON_CONFLICT 0 +#define PICO_ARP_CONFLICT_REASON_PROBE 1 + +struct pico_eth *pico_arp_lookup(struct pico_ip4 *dst); +struct pico_ip4 *pico_arp_reverse_lookup(struct pico_eth *dst); +int pico_arp_create_entry(uint8_t*hwaddr, struct pico_ip4 ipv4, struct pico_device*dev); +int pico_arp_get_neighbors(struct pico_device *dev, struct pico_ip4 *neighbors, int maxlen); +void pico_arp_register_ipconflict(struct pico_ip4 *ip, struct pico_eth *mac, void (*cb)(int reason)); +void pico_arp_postpone(struct pico_frame *f); +void pico_arp_init(void); +#endif diff --git a/ext/picotcp/modules/pico_dev_loop.c b/ext/picotcp/modules/pico_dev_loop.c new file mode 100644 index 0000000..4416d0b --- /dev/null +++ b/ext/picotcp/modules/pico_dev_loop.c @@ -0,0 +1,66 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + Authors: Daniele Lacamera + *********************************************************************/ + + +#include "pico_device.h" +#include "pico_dev_loop.h" +#include "pico_stack.h" + + +#define LOOP_MTU 1500 +static uint8_t l_buf[LOOP_MTU]; +static int l_bufsize = 0; + + +static int pico_loop_send(struct pico_device *dev, void *buf, int len) +{ + IGNORE_PARAMETER(dev); + if (len > LOOP_MTU) + return 0; + + if (l_bufsize == 0) { + memcpy(l_buf, buf, (size_t)len); + l_bufsize += len; + return len; + } + + return 0; +} + +static int pico_loop_poll(struct pico_device *dev, int loop_score) +{ + if (loop_score <= 0) + return 0; + + if (l_bufsize > 0) { + pico_stack_recv(dev, l_buf, (uint32_t)l_bufsize); + l_bufsize = 0; + loop_score--; + } + + return loop_score; +} + + +struct pico_device *pico_loop_create(void) +{ + struct pico_device *loop = PICO_ZALLOC(sizeof(struct pico_device)); + if (!loop) + return NULL; + + if( 0 != pico_device_init(loop, "loop", NULL)) { + dbg ("Loop init failed.\n"); + pico_device_destroy(loop); + return NULL; + } + + loop->send = pico_loop_send; + loop->poll = pico_loop_poll; + dbg("Device %s created.\n", loop->name); + return loop; +} + diff --git a/ext/picotcp/modules/pico_dev_loop.h b/ext/picotcp/modules/pico_dev_loop.h new file mode 100644 index 0000000..6cb8de1 --- /dev/null +++ b/ext/picotcp/modules/pico_dev_loop.h @@ -0,0 +1,15 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + *********************************************************************/ +#ifndef INCLUDE_PICO_LOOP +#define INCLUDE_PICO_LOOP +#include "pico_config.h" +#include "pico_device.h" + +void pico_loop_destroy(struct pico_device *loop); +struct pico_device *pico_loop_create(void); + +#endif + diff --git a/ext/picotcp/modules/pico_dev_mock.c b/ext/picotcp/modules/pico_dev_mock.c new file mode 100644 index 0000000..59fae01 --- /dev/null +++ b/ext/picotcp/modules/pico_dev_mock.c @@ -0,0 +1,307 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + Authors: Frederik Van Slycken + *********************************************************************/ + + +#include "pico_device.h" +#include "pico_dev_mock.h" +#include "pico_stack.h" +#include "pico_tree.h" + +#define MOCK_MTU 1500 + + + +/* Tree for finding mock_device based on pico_device* */ + + +static int mock_dev_cmp(void *ka, void *kb) +{ + struct mock_device *a = ka, *b = kb; + if (a->dev < b->dev) + return -1; + + if (a->dev > b->dev) + return 1; + + return 0; +} + +PICO_TREE_DECLARE(mock_device_tree, mock_dev_cmp); + +static int pico_mock_send(struct pico_device *dev, void *buf, int len) +{ + struct mock_device search = { + .dev = dev + }; + struct mock_device*mock = pico_tree_findKey(&mock_device_tree, &search); + struct mock_frame*frame; + + if(!mock) + return 0; + + if (len > MOCK_MTU) + return 0; + + frame = PICO_ZALLOC(sizeof(struct mock_frame)); + if(!frame) { + return 0; + } + + if(mock->out_head == NULL) + mock->out_head = frame; + else + mock->out_tail->next = frame; + + mock->out_tail = frame; + + mock->out_tail->buffer = PICO_ZALLOC((uint32_t)len); + if(!mock->out_tail->buffer) + return 0; + + memcpy(mock->out_tail->buffer, buf, (uint32_t)len); + mock->out_tail->len = len; + + return len; + +} + +static int pico_mock_poll(struct pico_device *dev, int loop_score) +{ + struct mock_device search = { + .dev = dev + }; + struct mock_device*mock = pico_tree_findKey(&mock_device_tree, &search); + struct mock_frame*nxt; + + if(!mock) + return 0; + + if (loop_score <= 0) + return 0; + + while(mock->in_head != NULL && loop_score > 0) + { + pico_stack_recv(dev, mock->in_head->buffer, (uint32_t)mock->in_head->len); + loop_score--; + + PICO_FREE(mock->in_head->buffer); + + if(mock->in_tail == mock->in_head) { + PICO_FREE(mock->in_head); + mock->in_tail = mock->in_head = NULL; + return loop_score; + } + + nxt = mock->in_head->next; + PICO_FREE(mock->in_head); + mock->in_head = nxt; + } + return loop_score; +} + +int pico_mock_network_read(struct mock_device*mock, void *buf, int len) +{ + struct mock_frame*nxt; + if(mock->out_head == NULL) + return 0; + + if(len > mock->out_head->len - mock->out_head->read) + len = mock->out_head->len - mock->out_head->read; + + memcpy(buf, mock->out_head->buffer, (uint32_t)len); + + if(len + mock->out_head->read != mock->out_head->len) { + mock->out_head->read += len; + return len; + } + + PICO_FREE(mock->out_head->buffer); + + if(mock->out_tail == mock->out_head) { + PICO_FREE(mock->out_head); + mock->out_tail = mock->out_head = NULL; + return len; + } + + nxt = mock->out_head->next; + PICO_FREE(mock->out_head); + mock->out_head = nxt; + + return len; +} + +int pico_mock_network_write(struct mock_device*mock, const void *buf, int len) +{ + struct mock_frame*frame; + if (len > MOCK_MTU) + return 0; + + frame = PICO_ZALLOC(sizeof(struct mock_frame)); + if(!frame) { + return 0; + } + + if(mock->in_head == NULL) + mock->in_head = frame; + else + mock->in_tail->next = frame; + + mock->in_tail = frame; + + mock->in_tail->buffer = PICO_ZALLOC((uint32_t)len); + if(!mock->in_tail->buffer) + return 0; + + memcpy(mock->in_tail->buffer, buf, (uint32_t)len); + mock->in_tail->len = len; + + return len; + +} + +/* Public interface: create/destroy. */ + +void pico_mock_destroy(struct pico_device *dev) +{ + struct mock_device search = { + .dev = dev + }; + struct mock_device*mock = pico_tree_findKey(&mock_device_tree, &search); + struct mock_frame*nxt; + + if(!mock) + return; + + nxt = mock->in_head; + while(nxt != NULL) { + mock->in_head = mock->in_head->next; + PICO_FREE(nxt); + nxt = mock->in_head; + } + nxt = mock->out_head; + while(nxt != NULL) { + mock->out_head = mock->out_head->next; + PICO_FREE(nxt); + nxt = mock->out_head; + } + pico_tree_delete(&mock_device_tree, mock); +} + +struct mock_device *pico_mock_create(uint8_t*mac) +{ + + struct mock_device*mock = PICO_ZALLOC(sizeof(struct mock_device)); + if(!mock) + return NULL; + + mock->dev = PICO_ZALLOC(sizeof(struct pico_device)); + if (!mock->dev) { + PICO_FREE(mock); + return NULL; + } + + if(mac != NULL) { + mock->mac = PICO_ZALLOC(6 * sizeof(uint8_t)); + if(!mock->mac) { + PICO_FREE(mock->mac); + PICO_FREE(mock); + return NULL; + } + + memcpy(mock->mac, mac, 6); + } + + if( 0 != pico_device_init((struct pico_device *)mock->dev, "mock", mac)) { + dbg ("Loop init failed.\n"); + pico_mock_destroy((struct pico_device *)mock->dev); + if(mock->mac != NULL) + PICO_FREE(mock->mac); + + PICO_FREE(mock); + return NULL; + } + + mock->dev->send = pico_mock_send; + mock->dev->poll = pico_mock_poll; + mock->dev->destroy = pico_mock_destroy; + dbg("Device %s created.\n", mock->dev->name); + + pico_tree_insert(&mock_device_tree, mock); + return mock; +} + +/* + * a few utility functions that check certain fields + */ + +uint32_t mock_get_sender_ip4(struct mock_device*mock, void*buf, int len) +{ + uint32_t ret; + int start = mock->mac ? 14 : 0; + if(start + 16 > len) { + dbg("out of range!\n"); + return 0; + } + + memcpy(&ret, buf + start + 12, 4); + return ret; +} + +/* + * TODO + * find a way to create ARP replies + * + * create the other utility functions, e.g. + * -is_arp_request + * -create_arp_reply + * -get_destination_ip4 + * -get_ip4_total_length + * -is_ip4_checksum_valid + * -is_tcp_syn + * -create_tcp_synack + * -is_tcp_checksum_valid + * etc. + * + */ + +int mock_ip_protocol(struct mock_device*mock, void*buf, int len) +{ + uint8_t type; + int start = mock->mac ? 14 : 0; + if(start + 10 > len) { + return 0; + } + + memcpy(&type, buf + start + 9, 1); + return type; +} + +/* note : this function doesn't check if the IP header has any options */ +int mock_icmp_type(struct mock_device*mock, void*buf, int len) +{ + uint8_t type; + int start = mock->mac ? 14 : 0; + if(start + 21 > len) { + return 0; + } + + memcpy(&type, buf + start + 20, 1); + return type; +} + +/* note : this function doesn't check if the IP header has any options */ +int mock_icmp_code(struct mock_device*mock, void*buf, int len) +{ + uint8_t type; + int start = mock->mac ? 14 : 0; + if(start + 22 > len) { + return 0; + } + + memcpy(&type, buf + start + 21, 1); + return type; +} diff --git a/ext/picotcp/modules/pico_dev_mock.h b/ext/picotcp/modules/pico_dev_mock.h new file mode 100644 index 0000000..dfc2cfc --- /dev/null +++ b/ext/picotcp/modules/pico_dev_mock.h @@ -0,0 +1,47 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + *********************************************************************/ +#ifndef INCLUDE_PICO_MOCK +#define INCLUDE_PICO_MOCK +#include "pico_config.h" +#include "pico_device.h" + + +struct mock_frame { + uint8_t*buffer; + int len; + int read; + + struct mock_frame*next; +}; + +struct mock_device { + struct pico_device*dev; + struct mock_frame*in_head; + struct mock_frame*in_tail; + struct mock_frame*out_head; + struct mock_frame*out_tail; + + uint8_t*mac; + +}; + +struct mock_device; +/* A mockup-device for the purpose of testing. It provides a couple of extra "network"-functions, which represent the network-side of the device. A network_send will result in mock_poll reading something, a network_read will see if the stack has sent anything through our mock-device. */ +void pico_mock_destroy(struct pico_device *dev); +struct mock_device *pico_mock_create(uint8_t*mac); + +int pico_mock_network_read(struct mock_device*mock, void *buf, int len); +int pico_mock_network_write(struct mock_device*mock, const void *buf, int len); + +/* TODO */ +/* we could use a few checking functions, e.g. one to see if it's a valid IP packet, if it's TCP, if the IP-address matches,... */ +/* That would be useful to avoid having to manually create buffers of what you expect, probably with masks for things that are random,... */ +uint32_t mock_get_sender_ip4(struct mock_device*mock, void*buf, int len); + +int mock_ip_protocol(struct mock_device*mock, void*buf, int len); +int mock_icmp_type(struct mock_device*mock, void*buf, int len); +int mock_icmp_code(struct mock_device*mock, void*buf, int len); +#endif diff --git a/ext/picotcp/modules/pico_dev_null.c b/ext/picotcp/modules/pico_dev_null.c new file mode 100644 index 0000000..5fed494 --- /dev/null +++ b/ext/picotcp/modules/pico_dev_null.c @@ -0,0 +1,60 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + Authors: Daniele Lacamera + *********************************************************************/ + + +#include "pico_device.h" +#include "pico_dev_null.h" +#include "pico_stack.h" + +struct pico_device_null { + struct pico_device dev; + int statistics_frames_out; +}; + +#define NULL_MTU 0 + +static int pico_null_send(struct pico_device *dev, void *buf, int len) +{ + struct pico_device_null *null = (struct pico_device_null *) dev; + IGNORE_PARAMETER(buf); + + /* Increase the statistic count */ + null->statistics_frames_out++; + + /* Discard the frame content silently. */ + return len; +} + +static int pico_null_poll(struct pico_device *dev, int loop_score) +{ + /* We never have packet to receive, no score is used. */ + IGNORE_PARAMETER(dev); + return loop_score; +} + +/* Public interface: create/destroy. */ + + +struct pico_device *pico_null_create(char *name) +{ + struct pico_device_null *null = PICO_ZALLOC(sizeof(struct pico_device_null)); + + if (!null) + return NULL; + + if( 0 != pico_device_init((struct pico_device *)null, name, NULL)) { + return NULL; + } + + null->dev.overhead = 0; + null->statistics_frames_out = 0; + null->dev.send = pico_null_send; + null->dev.poll = pico_null_poll; + dbg("Device %s created.\n", null->dev.name); + return (struct pico_device *)null; +} + diff --git a/ext/picotcp/modules/pico_dev_null.h b/ext/picotcp/modules/pico_dev_null.h new file mode 100644 index 0000000..a0eb98e --- /dev/null +++ b/ext/picotcp/modules/pico_dev_null.h @@ -0,0 +1,15 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + *********************************************************************/ +#ifndef INCLUDE_PICO_NULL +#define INCLUDE_PICO_NULL +#include "pico_config.h" +#include "pico_device.h" + +void pico_null_destroy(struct pico_device *null); +struct pico_device *pico_null_create(char *name); + +#endif + diff --git a/ext/picotcp/modules/pico_dev_pcap.c b/ext/picotcp/modules/pico_dev_pcap.c new file mode 100644 index 0000000..f62fb14 --- /dev/null +++ b/ext/picotcp/modules/pico_dev_pcap.c @@ -0,0 +1,96 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + Authors: Daniele Lacamera + *********************************************************************/ + + +#include +#include "pico_device.h" +#include "pico_dev_pcap.h" +#include "pico_stack.h" + +#include + +struct pico_device_pcap { + struct pico_device dev; + pcap_t *conn; +}; + +#define VDE_MTU 2048 + +static int pico_pcap_send(struct pico_device *dev, void *buf, int len) +{ + struct pico_device_pcap *pcap = (struct pico_device_pcap *) dev; + /* dbg("[%s] send %d bytes.\n", dev->name, len); */ + return pcap_inject(pcap->conn, buf, (uint32_t)len); +} + +static void pico_dev_pcap_cb(u_char *u, const struct pcap_pkthdr *h, const u_char *data) +{ + struct pico_device *dev = (struct pico_device *)u; + const uint8_t *buf = (const uint8_t *)data; + pico_stack_recv(dev, buf, (uint32_t)h->len); +} + + +static int pico_pcap_poll(struct pico_device *dev, int loop_score) +{ + struct pico_device_pcap *pcap = (struct pico_device_pcap *) dev; + loop_score -= pcap_dispatch(pcap->conn, loop_score, pico_dev_pcap_cb, (u_char *) pcap); + return loop_score; +} + +/* Public interface: create/destroy. */ + +void pico_pcap_destroy(struct pico_device *dev) +{ + struct pico_device_pcap *pcap = (struct pico_device_pcap *) dev; + pcap_close(pcap->conn); +} + +#define PICO_PCAP_MODE_LIVE 0 +#define PICO_PCAP_MODE_STORED 1 + +static struct pico_device *pico_pcap_create(char *if_file_name, char *name, uint8_t *mac, int mode) +{ + struct pico_device_pcap *pcap = PICO_ZALLOC(sizeof(struct pico_device_pcap)); + char errbuf[2000]; + if (!pcap) + return NULL; + + if( 0 != pico_device_init((struct pico_device *)pcap, name, mac)) { + dbg ("Pcap init failed.\n"); + pico_pcap_destroy((struct pico_device *)pcap); + return NULL; + } + + pcap->dev.overhead = 0; + + if (mode == PICO_PCAP_MODE_LIVE) + pcap->conn = pcap_open_live(if_file_name, 2000, 100, 10, errbuf); + else + pcap->conn = pcap_open_offline(if_file_name, errbuf); + + if (!pcap->conn) { + pico_pcap_destroy((struct pico_device *)pcap); + return NULL; + } + + pcap->dev.send = pico_pcap_send; + pcap->dev.poll = pico_pcap_poll; + pcap->dev.destroy = pico_pcap_destroy; + dbg("Device %s created.\n", pcap->dev.name); + return (struct pico_device *)pcap; +} + +struct pico_device *pico_pcap_create_fromfile(char *filename, char *name, uint8_t *mac) +{ + return pico_pcap_create(filename, name, mac, PICO_PCAP_MODE_STORED); +} + +struct pico_device *pico_pcap_create_live(char *ifname, char *name, uint8_t *mac) +{ + return pico_pcap_create(ifname, name, mac, PICO_PCAP_MODE_LIVE); +} diff --git a/ext/picotcp/modules/pico_dev_pcap.h b/ext/picotcp/modules/pico_dev_pcap.h new file mode 100644 index 0000000..887be27 --- /dev/null +++ b/ext/picotcp/modules/pico_dev_pcap.h @@ -0,0 +1,19 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + + Author: Daniele Lacamera + *********************************************************************/ +#ifndef INCLUDE_PICO_PCAP +#define INCLUDE_PICO_PCAP +#include "pico_config.h" +#include "pico_device.h" +#include + +void pico_pcap_destroy(struct pico_device *pcap); +struct pico_device *pico_pcap_create_live(char *ifname, char *name, uint8_t *mac); +struct pico_device *pico_pcap_create_fromfile(char *filename, char *name, uint8_t *mac); + +#endif + diff --git a/ext/picotcp/modules/pico_dev_ppp.c b/ext/picotcp/modules/pico_dev_ppp.c new file mode 100644 index 0000000..8c67f19 --- /dev/null +++ b/ext/picotcp/modules/pico_dev_ppp.c @@ -0,0 +1,2294 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. + See LICENSE and COPYING for usage. + + Authors: Serge Gadeyne, Daniele Lacamera, Maxime Vincent + + *********************************************************************/ + + +#include +#include +#include + +#include "pico_device.h" +#include "pico_dev_ppp.h" +#include "pico_stack.h" +#include "pico_ipv4.h" +#include "pico_md5.h" +#include "pico_dns_client.h" + +#define ppp_dbg(...) do {} while(0) +/* #define ppp_dbg dbg */ + +/* We should define this in a global header. */ +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +#define PICO_PPP_MRU 1514 /* RFC default MRU */ +#define PICO_PPP_MTU 1500 +#define PPP_MAXPKT 2048 +#define PPP_MAX_APN 134 +#define PPP_MAX_USERNAME 134 +#define PPP_MAX_PASSWORD 134 +#define PPP_HDR_SIZE 3u +#define PPP_PROTO_SLOT_SIZE 2u +#define PPP_FCS_SIZE 2u +#define PPP_PROTO_LCP short_be(0xc021) +#define PPP_PROTO_IP short_be(0x0021) +#define PPP_PROTO_PAP short_be(0xc023) +#define PPP_PROTO_CHAP short_be(0xc223) +#define PPP_PROTO_IPCP short_be(0x8021) + +#define PICO_CONF_REQ 1 +#define PICO_CONF_ACK 2 +#define PICO_CONF_NAK 3 +#define PICO_CONF_REJ 4 +#define PICO_CONF_TERM 5 +#define PICO_CONF_TERM_ACK 6 +#define PICO_CONF_CODE_REJ 7 +#define PICO_CONF_PROTO_REJ 8 +#define PICO_CONF_ECHO_REQ 9 +#define PICO_CONF_ECHO_REP 10 +#define PICO_CONF_DISCARD_REQ 11 + +#define LCPOPT_MRU 1u /* param size: 4, fixed: MRU */ +#define LCPOPT_AUTH 3u /* param size: 4-5: AUTH proto */ +#define LCPOPT_QUALITY 4u /* unused for now */ +#define LCPOPT_MAGIC 5u /* param size: 6, fixed: Magic */ +#define LCPOPT_PROTO_COMP 7u /* param size: 0, flag */ +#define LCPOPT_ADDRCTL_COMP 8u /* param size: 0, flag */ + +#define CHAP_MD5_SIZE 16u +#define CHAP_CHALLENGE 1 +#define CHAP_RESPONSE 2 +#define CHAP_SUCCESS 3 +#define CHAP_FAILURE 4 +#define CHALLENGE_SIZE(ppp, ch) ((size_t)((1 + strlen(ppp->password) + short_be((ch)->len)))) + +#define PAP_AUTH_REQ 1 +#define PAP_AUTH_ACK 2 +#define PAP_AUTH_NAK 3 + + +#define PICO_PPP_DEFAULT_TIMER (3) /* seconds */ +#define PICO_PPP_DEFAULT_MAX_TERMINATE (2) +#define PICO_PPP_DEFAULT_MAX_CONFIGURE (10) +#define PICO_PPP_DEFAULT_MAX_FAILURE (5) +#define PICO_PPP_DEFAULT_MAX_DIALTIME (20) + +#define IPCP_ADDR_LEN 6u +#define IPCP_VJ_LEN 6u +#define IPCP_OPT_IP 0x03 +#define IPCP_OPT_VJ 0x02 +#define IPCP_OPT_DNS1 0x81 +#define IPCP_OPT_NBNS1 0x82 +#define IPCP_OPT_DNS2 0x83 +#define IPCP_OPT_NBNS2 0x84 + +static uint8_t LCPOPT_LEN[9] = { + 0, 4, 0, 4, 4, 6, 2, 2, 2 +}; + + +/* Protocol defines */ +static const unsigned char AT_S3 = 0x0du; +static const unsigned char AT_S4 = 0x0au; +static const unsigned char PPPF_FLAG_SEQ = 0x7eu; +static const unsigned char PPPF_CTRL_ESC = 0x7du; +static const unsigned char PPPF_ADDR = 0xffu; +static const unsigned char PPPF_CTRL = 0x03u; + +static int ppp_devnum = 0; +static uint8_t ppp_recv_buf[PPP_MAXPKT]; + +PACKED_STRUCT_DEF pico_lcp_hdr { + uint8_t code; + uint8_t id; + uint16_t len; +}; + +PACKED_STRUCT_DEF pico_chap_hdr { + uint8_t code; + uint8_t id; + uint16_t len; +}; + +PACKED_STRUCT_DEF pico_pap_hdr { + uint8_t code; + uint8_t id; + uint16_t len; +}; + +PACKED_STRUCT_DEF pico_ipcp_hdr { + uint8_t code; + uint8_t id; + uint16_t len; +}; + +#ifdef DEBUG_PPP +static int fifo_fd = -1; +#endif + +enum ppp_modem_state { + PPP_MODEM_STATE_INITIAL = 0, + PPP_MODEM_STATE_RESET, + PPP_MODEM_STATE_ECHO, + PPP_MODEM_STATE_CREG, + PPP_MODEM_STATE_CGREG, + PPP_MODEM_STATE_CGDCONT, + PPP_MODEM_STATE_CGATT, + PPP_MODEM_STATE_DIAL, + PPP_MODEM_STATE_CONNECTED, + PPP_MODEM_STATE_MAX +}; + +enum ppp_modem_event { + PPP_MODEM_EVENT_START = 0, + PPP_MODEM_EVENT_STOP, + PPP_MODEM_EVENT_OK, + PPP_MODEM_EVENT_CONNECT, + PPP_MODEM_EVENT_TIMEOUT, + PPP_MODEM_EVENT_MAX +}; + +enum ppp_lcp_state { + PPP_LCP_STATE_INITIAL = 0, + PPP_LCP_STATE_STARTING, + PPP_LCP_STATE_CLOSED, + PPP_LCP_STATE_STOPPED, + PPP_LCP_STATE_CLOSING, + PPP_LCP_STATE_STOPPING, + PPP_LCP_STATE_REQ_SENT, + PPP_LCP_STATE_ACK_RCVD, + PPP_LCP_STATE_ACK_SENT, + PPP_LCP_STATE_OPENED, + PPP_LCP_STATE_MAX +}; + +enum ppp_lcp_event { + PPP_LCP_EVENT_UP = 0, + PPP_LCP_EVENT_DOWN, + PPP_LCP_EVENT_OPEN, + PPP_LCP_EVENT_CLOSE, + PPP_LCP_EVENT_TO_POS, + PPP_LCP_EVENT_TO_NEG, + PPP_LCP_EVENT_RCR_POS, + PPP_LCP_EVENT_RCR_NEG, + PPP_LCP_EVENT_RCA, + PPP_LCP_EVENT_RCN, + PPP_LCP_EVENT_RTR, + PPP_LCP_EVENT_RTA, + PPP_LCP_EVENT_RUC, + PPP_LCP_EVENT_RXJ_POS, + PPP_LCP_EVENT_RXJ_NEG, + PPP_LCP_EVENT_RXR, + PPP_LCP_EVENT_MAX +}; + +enum ppp_auth_state { + PPP_AUTH_STATE_INITIAL = 0, + PPP_AUTH_STATE_STARTING, + PPP_AUTH_STATE_RSP_SENT, + PPP_AUTH_STATE_REQ_SENT, + PPP_AUTH_STATE_AUTHENTICATED, + PPP_AUTH_STATE_MAX +}; + +enum ppp_auth_event { + PPP_AUTH_EVENT_UP_NONE = 0, + PPP_AUTH_EVENT_UP_PAP, + PPP_AUTH_EVENT_UP_CHAP, + PPP_AUTH_EVENT_DOWN, + PPP_AUTH_EVENT_RAC, + PPP_AUTH_EVENT_RAA, + PPP_AUTH_EVENT_RAN, + PPP_AUTH_EVENT_TO, + PPP_AUTH_EVENT_MAX +}; + +enum ppp_ipcp_state { + PPP_IPCP_STATE_INITIAL = 0, + PPP_IPCP_STATE_REQ_SENT, + PPP_IPCP_STATE_ACK_RCVD, + PPP_IPCP_STATE_ACK_SENT, + PPP_IPCP_STATE_OPENED, + PPP_IPCP_STATE_MAX +}; + +enum ppp_ipcp_event { + PPP_IPCP_EVENT_UP = 0, + PPP_IPCP_EVENT_DOWN, + PPP_IPCP_EVENT_RCR_POS, + PPP_IPCP_EVENT_RCR_NEG, + PPP_IPCP_EVENT_RCA, + PPP_IPCP_EVENT_RCN, + PPP_IPCP_EVENT_TO, + PPP_IPCP_EVENT_MAX +}; + +enum pico_ppp_state { + PPP_MODEM_RST = 0, + PPP_MODEM_CREG, + PPP_MODEM_CGREG, + PPP_MODEM_CGDCONT, + PPP_MODEM_CGATT, + PPP_MODEM_CONNECT, + /* From here on, PPP states */ + PPP_ESTABLISH, + PPP_AUTH, + PPP_NETCONFIG, + PPP_NETWORK, + PPP_TERMINATE, + /* MAXSTATE is the last one */ + PPP_MODEM_MAXSTATE +}; + + +#define IPCP_ALLOW_IP 0x01u +#define IPCP_ALLOW_DNS1 0x02u +#define IPCP_ALLOW_DNS2 0x04u +#define IPCP_ALLOW_NBNS1 0x08u +#define IPCP_ALLOW_NBNS2 0x10u + +struct pico_device_ppp { + struct pico_device dev; + int autoreconnect; + enum ppp_modem_state modem_state; + enum ppp_lcp_state lcp_state; + enum ppp_auth_state auth_state; + enum ppp_ipcp_state ipcp_state; + enum pico_ppp_state state; + char apn[PPP_MAX_APN]; + char password[PPP_MAX_PASSWORD]; + char username[PPP_MAX_USERNAME]; + uint16_t lcpopt_local; + uint16_t lcpopt_peer; + uint8_t *pkt; + uint32_t len; + uint16_t rej; + uint16_t auth; + int (*serial_recv)(struct pico_device *dev, void *buf, int len); + int (*serial_send)(struct pico_device *dev, const void *buf, int len); + int (*serial_set_speed)(struct pico_device *dev, uint32_t speed); + uint32_t ipcp_allowed_fields; + uint32_t ipcp_ip; + uint32_t ipcp_dns1; + uint32_t ipcp_nbns1; + uint32_t ipcp_dns2; + uint32_t ipcp_nbns2; + uint32_t timer; + uint8_t timer_val; + uint8_t timer_count; + uint8_t frame_id; + uint8_t timer_on; + uint16_t mru; +}; + + +/* Unit test interceptor */ +static void (*mock_modem_state)(struct pico_device_ppp *ppp, enum ppp_modem_event event) = NULL; +static void (*mock_lcp_state)(struct pico_device_ppp *ppp, enum ppp_lcp_event event) = NULL; +static void (*mock_auth_state)(struct pico_device_ppp *ppp, enum ppp_auth_event event) = NULL; +static void (*mock_ipcp_state)(struct pico_device_ppp *ppp, enum ppp_ipcp_event event) = NULL; + +/* Debug prints */ +#ifdef PPP_DEBUG +static void lcp_optflags_print(struct pico_device_ppp *ppp, uint8_t *opts, uint32_t opts_len); +#endif + +#define PPP_TIMER_ON_MODEM 0x01u +#define PPP_TIMER_ON_LCPREQ 0x04u +#define PPP_TIMER_ON_LCPTERM 0x08u +#define PPP_TIMER_ON_AUTH 0x10u +#define PPP_TIMER_ON_IPCP 0x20u + +/* Escape and send */ +static int ppp_serial_send_escape(struct pico_device_ppp *ppp, void *buf, int len) +{ + uint8_t *in_buf = (uint8_t *)buf; + uint8_t *out_buf = NULL; + int esc_char_count = 0; + int newlen = 0, ret = -1; + int i, j; + +#ifdef PPP_DEBUG + { + uint32_t idx; + if (len > 0) { + ppp_dbg("PPP >>>> "); + for(idx = 0; idx < (uint32_t)len; idx++) { + ppp_dbg(" %02x", ((uint8_t *)buf)[idx]); + } + ppp_dbg("\n"); + } + } +#endif + + for (i = 1; i < (len - 1); i++) /* from 1 to len -1, as start/stop are not escaped */ + { + if (((in_buf[i] + 1u) >> 1) == 0x3Fu) + esc_char_count++; + } + if (!esc_char_count) { + return ppp->serial_send(&ppp->dev, buf, len); + } + + newlen = len + esc_char_count; + out_buf = PICO_ZALLOC((uint32_t)newlen); + if(!out_buf) + return -1; + + /* Start byte. */ + out_buf[0] = in_buf[0]; + for(i = 1, j = 1; i < (len - 1); i++) { + if (((in_buf[i] + 1u) >> 1) == 0x3Fu) { + out_buf[j++] = PPPF_CTRL_ESC; + out_buf[j++] = in_buf[i] ^ 0x20; + } else { + out_buf[j++] = in_buf[i]; + } + } + /* Stop byte. */ + out_buf[newlen - 1] = in_buf[len - 1]; + + ret = ppp->serial_send(&ppp->dev, out_buf, newlen); + + PICO_FREE(out_buf); + + if (ret == newlen) + return len; + + return ret; + +} + +static void lcp_timer_start(struct pico_device_ppp *ppp, uint8_t timer_type) +{ + uint8_t count = 0; + ppp->timer_on |= timer_type; + + if (ppp->timer_val == 0) { + ppp->timer_val = PICO_PPP_DEFAULT_TIMER; + } + + if (timer_type == PPP_TIMER_ON_LCPTERM) { + count = PICO_PPP_DEFAULT_MAX_TERMINATE; + } + + if (timer_type == PPP_TIMER_ON_LCPREQ) { + count = PICO_PPP_DEFAULT_MAX_CONFIGURE; + } + + if (timer_type == 0) { + ppp->timer_on |= PPP_TIMER_ON_LCPREQ; + ppp->timer_count = 0; + } + + if (ppp->timer_count == 0) + ppp->timer_count = count; +} + +static void lcp_zero_restart_count(struct pico_device_ppp *ppp) +{ + lcp_timer_start(ppp, 0); +} + +static void lcp_timer_stop(struct pico_device_ppp *ppp, uint8_t timer_type) +{ + ppp->timer_on = (uint8_t)ppp->timer_on & (uint8_t)(~timer_type); +} + + +#define PPP_FSM_MAX_ACTIONS 3 + +struct pico_ppp_fsm { + int next_state; + void (*event_handler[PPP_FSM_MAX_ACTIONS]) (struct pico_device_ppp *); +}; + +#define LCPOPT_SET_LOCAL(ppp, opt) ppp->lcpopt_local |= (uint16_t)(1u << opt) +#define LCPOPT_SET_PEER(ppp, opt) ppp->lcpopt_peer |= (uint16_t)(1u << opt) +#define LCPOPT_UNSET_LOCAL(ppp, opt) ppp->lcpopt_local &= (uint16_t)~(1u << opt) +#define LCPOPT_UNSET_LOCAL_MASK(ppp, opt) ppp->lcpopt_local &= (uint16_t)~(opt) +#define LCPOPT_UNSET_PEER(ppp, opt) ppp->lcpopt_peer &= (uint16_t)~(1u << opt) +#define LCPOPT_ISSET_LOCAL(ppp, opt) ((ppp->lcpopt_local & (uint16_t)(1u << opt)) != 0) +#define LCPOPT_ISSET_PEER(ppp, opt) ((ppp->lcpopt_peer & (uint16_t)(1u << opt)) != 0) + +static void evaluate_modem_state(struct pico_device_ppp *ppp, enum ppp_modem_event event); +static void evaluate_lcp_state(struct pico_device_ppp *ppp, enum ppp_lcp_event event); +static void evaluate_auth_state(struct pico_device_ppp *ppp, enum ppp_auth_event event); +static void evaluate_ipcp_state(struct pico_device_ppp *ppp, enum ppp_ipcp_event event); + + +static uint32_t ppp_ctl_packet_size(struct pico_device_ppp *ppp, uint16_t proto, uint32_t *size) +{ + uint32_t prefix = 0; + + IGNORE_PARAMETER(ppp); + IGNORE_PARAMETER(proto); + + prefix += PPP_HDR_SIZE; /* 7e ff 03 ... */ + prefix += PPP_PROTO_SLOT_SIZE; + *size += prefix; + *size += PPP_FCS_SIZE; + (*size)++; /* STOP byte 0x7e */ + return prefix; +} + +/* CRC16 / FCS Calculation */ +static uint16_t ppp_fcs_char(uint16_t old_crc, uint8_t data) +{ + uint16_t word = (old_crc ^ data) & (uint16_t)0x00FFu; + word = (uint16_t)(word ^ (uint16_t)((word << 4u) & (uint16_t)0x00FFu)); + word = (uint16_t)((word << 8u) ^ (word << 3u) ^ (word >> 4u)); + return ((old_crc >> 8u) ^ word); +} + +static uint16_t ppp_fcs_continue(uint16_t fcs, uint8_t *buf, uint32_t len) +{ + uint8_t *pos = buf; + for (pos = buf; pos < buf + len; pos++) + { + fcs = ppp_fcs_char(fcs, *pos); + } + return fcs; +} + +static uint16_t ppp_fcs_finish(uint16_t fcs) +{ + return fcs ^ 0xFFFF; +} + +static uint16_t ppp_fcs_start(uint8_t *buf, uint32_t len) +{ + uint16_t fcs = 0xFFFF; + return ppp_fcs_continue(fcs, buf, len); +} + +static int ppp_fcs_verify(uint8_t *buf, uint32_t len) +{ + uint16_t fcs = ppp_fcs_start(buf, len - 2); + fcs = ppp_fcs_finish(fcs); + if ((((fcs & 0xFF00u) >> 8) != buf[len - 1]) || ((fcs & 0xFFu) != buf[len - 2])) { + return -1; + } + + return 0; +} + +/* Serial send (DTE->DCE) functions */ +static int pico_ppp_ctl_send(struct pico_device *dev, uint16_t code, uint8_t *pkt, uint32_t len) +{ + struct pico_device_ppp *ppp = (struct pico_device_ppp *) dev; + uint16_t fcs; + uint8_t *ptr = pkt; + int i = 0; + + if (!ppp->serial_send) + return (int)len; + + /* PPP Header */ + ptr[i++] = PPPF_FLAG_SEQ; + ptr[i++] = PPPF_ADDR; + ptr[i++] = PPPF_CTRL; + /* protocol */ + ptr[i++] = (uint8_t)(code & 0xFFu); + ptr[i++] = (uint8_t)((code & 0xFF00u) >> 8); + + /* payload is already in place. Calculate FCS. */ + fcs = ppp_fcs_start(pkt + 1, len - 4); /* FCS excludes: start (1), FCS(2), stop(1), total 4 bytes */ + fcs = ppp_fcs_finish(fcs); + pkt[len - 3] = (uint8_t)(fcs & 0xFFu); + pkt[len - 2] = (uint8_t)((fcs & 0xFF00u) >> 8); + pkt[len - 1] = PPPF_FLAG_SEQ; + ppp_serial_send_escape(ppp, pkt, (int)len); + return (int)len; +} + +static uint8_t pico_ppp_data_buffer[PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE + PICO_PPP_MTU + PPP_FCS_SIZE + 1]; +static int pico_ppp_send(struct pico_device *dev, void *buf, int len) +{ + struct pico_device_ppp *ppp = (struct pico_device_ppp *) dev; + uint16_t fcs = 0; + int fcs_start; + int i = 0; + + ppp_dbg(" >>>>>>>>> PPP OUT\n"); + + if (ppp->ipcp_state != PPP_IPCP_STATE_OPENED) + return len; + + if (!ppp->serial_send) + return len; + + pico_ppp_data_buffer[i++] = PPPF_FLAG_SEQ; + if (!LCPOPT_ISSET_PEER(ppp, LCPOPT_ADDRCTL_COMP)) + { + pico_ppp_data_buffer[i++] = PPPF_ADDR; + pico_ppp_data_buffer[i++] = PPPF_CTRL; + } + + fcs_start = i; + + if (!LCPOPT_ISSET_PEER(ppp, LCPOPT_PROTO_COMP)) + { + pico_ppp_data_buffer[i++] = 0x00; + } + + pico_ppp_data_buffer[i++] = 0x21; + memcpy(pico_ppp_data_buffer + i, buf, (uint32_t)len); + i += len; + fcs = ppp_fcs_start(pico_ppp_data_buffer + fcs_start, (uint32_t)(i - fcs_start)); + fcs = ppp_fcs_finish(fcs); + pico_ppp_data_buffer[i++] = (uint8_t)(fcs & 0xFFu); + pico_ppp_data_buffer[i++] = (uint8_t)((fcs & 0xFF00u) >> 8); + pico_ppp_data_buffer[i++] = PPPF_FLAG_SEQ; + + ppp_serial_send_escape(ppp, pico_ppp_data_buffer, i); + return len; +} + + +/* FSM functions */ + +static void ppp_modem_start_timer(struct pico_device_ppp *ppp) +{ + ppp->timer_on = ppp->timer_on | PPP_TIMER_ON_MODEM; + ppp->timer_val = PICO_PPP_DEFAULT_TIMER; +} + +#define PPP_AT_CREG0 "ATZ\r\n" +static void ppp_modem_send_reset(struct pico_device_ppp *ppp) +{ + if (ppp->serial_send) + ppp->serial_send(&ppp->dev, PPP_AT_CREG0, strlen(PPP_AT_CREG0)); + + ppp_modem_start_timer(ppp); +} + +#define PPP_AT_CREG1 "ATE0\r\n" +static void ppp_modem_send_echo(struct pico_device_ppp *ppp) +{ + if (ppp->serial_send) + ppp->serial_send(&ppp->dev, PPP_AT_CREG1, strlen(PPP_AT_CREG1)); + + ppp_modem_start_timer(ppp); +} + +#define PPP_AT_CREG2 "AT+CREG=1\r\n" +static void ppp_modem_send_creg(struct pico_device_ppp *ppp) +{ + if (ppp->serial_send) + ppp->serial_send(&ppp->dev, PPP_AT_CREG2, strlen(PPP_AT_CREG2)); + + ppp_modem_start_timer(ppp); +} + +#define PPP_AT_CGREG "AT+CGREG=1\r\n" +static void ppp_modem_send_cgreg(struct pico_device_ppp *ppp) +{ + if (ppp->serial_send) + ppp->serial_send(&ppp->dev, PPP_AT_CGREG, strlen(PPP_AT_CGREG)); + + ppp_modem_start_timer(ppp); +} + + +#define PPP_AT_CGDCONT "AT+CGDCONT=1,\"IP\",\"%s\",,,\r\n" +static void ppp_modem_send_cgdcont(struct pico_device_ppp *ppp) +{ + char at_cgdcont[200]; + + if (ppp->serial_send) { + snprintf(at_cgdcont, 200, PPP_AT_CGDCONT, ppp->apn); + ppp->serial_send(&ppp->dev, at_cgdcont, (int)strlen(at_cgdcont)); + } + + ppp_modem_start_timer(ppp); +} + + +#define PPP_AT_CGATT "AT+CGATT=1\r\n" +static void ppp_modem_send_cgatt(struct pico_device_ppp *ppp) +{ + if (ppp->serial_send) + ppp->serial_send(&ppp->dev, PPP_AT_CGATT, strlen(PPP_AT_CGATT)); + + ppp_modem_start_timer(ppp); +} + +#ifdef PICOTCP_PPP_SUPPORT_QUERIES +#define PPP_AT_CGATT_Q "AT+CGATT?\r\n" +static void ppp_modem_send_cgatt_q(struct pico_device_ppp *ppp) +{ + if (ppp->serial_send) + ppp->serial_send(&ppp->dev, PPP_AT_CGATT_Q, strlen(PPP_AT_CGATT_Q)); + + ppp_modem_start_timer(ppp); +} +#define PPP_AT_CGDCONT_Q "AT+CGDCONT?\r\n" +static void ppp_modem_send_cgdcont_q(struct pico_device_ppp *ppp) +{ + if (ppp->serial_send) + ppp->serial_send(&ppp->dev, PPP_AT_CGDCONT_Q, strlen(PPP_AT_CGDCONT_Q)); + + ppp_modem_start_timer(ppp); +} + +#define PPP_AT_CGREG_Q "AT+CGREG?\r\n" +static void ppp_modem_send_cgreg_q(struct pico_device_ppp *ppp) +{ + if (ppp->serial_send) + ppp->serial_send(&ppp->dev, PPP_AT_CGREG_Q, strlen(PPP_AT_CGREG_Q)); + + ppp_modem_start_timer(ppp); +} + +#define PPP_AT_CREG3 "AT+CREG?\r\n" +static void ppp_modem_send_creg_q(struct pico_device_ppp *ppp) +{ + if (ppp->serial_send) + ppp->serial_send(&ppp->dev, PPP_AT_CREG3, strlen(PPP_AT_CREG3)); + + ppp_modem_start_timer(ppp); +} +#endif /* PICOTCP_PPP_SUPPORT_QUERIES */ + +#define PPP_AT_DIALIN "ATD*99***1#\r\n" +static void ppp_modem_send_dial(struct pico_device_ppp *ppp) +{ + if (ppp->serial_send) + ppp->serial_send(&ppp->dev, PPP_AT_DIALIN, strlen(PPP_AT_DIALIN)); + + ppp_modem_start_timer(ppp); + ppp->timer_val = PICO_PPP_DEFAULT_MAX_DIALTIME; +} + +static void ppp_modem_connected(struct pico_device_ppp *ppp) +{ + ppp_dbg("PPP: Modem connected to peer.\n"); + evaluate_lcp_state(ppp, PPP_LCP_EVENT_UP); +} + +#define PPP_ATH "+++ATH\r\n" +static void ppp_modem_disconnected(struct pico_device_ppp *ppp) +{ + ppp_dbg("PPP: Modem disconnected.\n"); + if (ppp->serial_send) + ppp->serial_send(&ppp->dev, PPP_ATH, strlen(PPP_ATH)); + + evaluate_lcp_state(ppp, PPP_LCP_EVENT_DOWN); +} + +static const struct pico_ppp_fsm ppp_modem_fsm[PPP_MODEM_STATE_MAX][PPP_MODEM_EVENT_MAX] = { + [PPP_MODEM_STATE_INITIAL] = { + [PPP_MODEM_EVENT_START] = { PPP_MODEM_STATE_RESET, {ppp_modem_send_reset} }, + [PPP_MODEM_EVENT_STOP] = { PPP_MODEM_STATE_INITIAL, {} }, + [PPP_MODEM_EVENT_OK] = { PPP_MODEM_STATE_INITIAL, {} }, + [PPP_MODEM_EVENT_CONNECT] = { PPP_MODEM_STATE_INITIAL, {} }, + [PPP_MODEM_EVENT_TIMEOUT] = { PPP_MODEM_STATE_INITIAL, {ppp_modem_send_reset} } + }, + [PPP_MODEM_STATE_RESET] = { + [PPP_MODEM_EVENT_START] = { PPP_MODEM_STATE_RESET, {} }, + [PPP_MODEM_EVENT_STOP] = { PPP_MODEM_STATE_INITIAL, {} }, + [PPP_MODEM_EVENT_OK] = { PPP_MODEM_STATE_ECHO, { ppp_modem_send_echo } }, + [PPP_MODEM_EVENT_CONNECT] = { PPP_MODEM_STATE_RESET, {} }, + [PPP_MODEM_EVENT_TIMEOUT] = { PPP_MODEM_STATE_RESET, {ppp_modem_send_reset} } + }, + [PPP_MODEM_STATE_ECHO] = { + [PPP_MODEM_EVENT_START] = { PPP_MODEM_STATE_ECHO, {} }, + [PPP_MODEM_EVENT_STOP] = { PPP_MODEM_STATE_INITIAL, {} }, + [PPP_MODEM_EVENT_OK] = { PPP_MODEM_STATE_CREG, { ppp_modem_send_creg } }, + [PPP_MODEM_EVENT_CONNECT] = { PPP_MODEM_STATE_ECHO, {} }, + [PPP_MODEM_EVENT_TIMEOUT] = { PPP_MODEM_STATE_RESET, {ppp_modem_send_reset} } + }, + [PPP_MODEM_STATE_CREG] = { + [PPP_MODEM_EVENT_START] = { PPP_MODEM_STATE_CREG, {} }, + [PPP_MODEM_EVENT_STOP] = { PPP_MODEM_STATE_INITIAL, {} }, + [PPP_MODEM_EVENT_OK] = { PPP_MODEM_STATE_CGREG, { ppp_modem_send_cgreg } }, + [PPP_MODEM_EVENT_CONNECT] = { PPP_MODEM_STATE_CREG, {} }, + [PPP_MODEM_EVENT_TIMEOUT] = { PPP_MODEM_STATE_RESET, {ppp_modem_send_reset} } + }, + [PPP_MODEM_STATE_CGREG] = { + [PPP_MODEM_EVENT_START] = { PPP_MODEM_STATE_CGREG, {} }, + [PPP_MODEM_EVENT_STOP] = { PPP_MODEM_STATE_INITIAL, {} }, + [PPP_MODEM_EVENT_OK] = { PPP_MODEM_STATE_CGDCONT, { ppp_modem_send_cgdcont } }, + [PPP_MODEM_EVENT_CONNECT] = { PPP_MODEM_STATE_CGREG, {} }, + [PPP_MODEM_EVENT_TIMEOUT] = { PPP_MODEM_STATE_RESET, {ppp_modem_send_reset} } + }, + [PPP_MODEM_STATE_CGDCONT] = { + [PPP_MODEM_EVENT_START] = { PPP_MODEM_STATE_CGDCONT, {} }, + [PPP_MODEM_EVENT_STOP] = { PPP_MODEM_STATE_INITIAL, {} }, + [PPP_MODEM_EVENT_OK] = { PPP_MODEM_STATE_CGATT, { ppp_modem_send_cgatt } }, + [PPP_MODEM_EVENT_CONNECT] = { PPP_MODEM_STATE_CGDCONT, {} }, + [PPP_MODEM_EVENT_TIMEOUT] = { PPP_MODEM_STATE_RESET, {ppp_modem_send_reset} } + }, + [PPP_MODEM_STATE_CGATT] = { + [PPP_MODEM_EVENT_START] = { PPP_MODEM_STATE_CGATT, {} }, + [PPP_MODEM_EVENT_STOP] = { PPP_MODEM_STATE_INITIAL, {} }, + [PPP_MODEM_EVENT_OK] = { PPP_MODEM_STATE_DIAL, { ppp_modem_send_dial } }, + [PPP_MODEM_EVENT_CONNECT] = { PPP_MODEM_STATE_CGATT, {} }, + [PPP_MODEM_EVENT_TIMEOUT] = { PPP_MODEM_STATE_RESET, {ppp_modem_send_reset} } + }, + [PPP_MODEM_STATE_DIAL] = { + [PPP_MODEM_EVENT_START] = { PPP_MODEM_STATE_DIAL, {} }, + [PPP_MODEM_EVENT_STOP] = { PPP_MODEM_STATE_INITIAL, {} }, + [PPP_MODEM_EVENT_OK] = { PPP_MODEM_STATE_DIAL, {} }, + [PPP_MODEM_EVENT_CONNECT] = { PPP_MODEM_STATE_CONNECTED, { ppp_modem_connected } }, + [PPP_MODEM_EVENT_TIMEOUT] = { PPP_MODEM_STATE_RESET, {ppp_modem_send_reset} } + }, + [PPP_MODEM_STATE_CONNECTED] = { + [PPP_MODEM_EVENT_START] = { PPP_MODEM_STATE_CONNECTED, {} }, + [PPP_MODEM_EVENT_STOP] = { PPP_MODEM_STATE_INITIAL, { ppp_modem_disconnected } }, + [PPP_MODEM_EVENT_OK] = { PPP_MODEM_STATE_CONNECTED, {} }, + [PPP_MODEM_EVENT_CONNECT] = { PPP_MODEM_STATE_CONNECTED, {} }, + [PPP_MODEM_EVENT_TIMEOUT] = { PPP_MODEM_STATE_CONNECTED, {} } + } +}; +static void evaluate_modem_state(struct pico_device_ppp *ppp, enum ppp_modem_event event) +{ + const struct pico_ppp_fsm *fsm; + int i; + if (mock_modem_state) { + mock_modem_state(ppp, event); + return; + } + + fsm = &ppp_modem_fsm[ppp->modem_state][event]; + + ppp->modem_state = (enum ppp_modem_state)fsm->next_state; + + for (i = 0; i < PPP_FSM_MAX_ACTIONS; i++) { + if (fsm->event_handler[i]) + fsm->event_handler[i](ppp); + } +} + +static void ppp_modem_recv(struct pico_device_ppp *ppp, void *data, uint32_t len) +{ + IGNORE_PARAMETER(len); + + ppp_dbg("PPP: Recv: '%s'\n", (char *)data); + + if (strcmp(data, "OK") == 0) { + evaluate_modem_state(ppp, PPP_MODEM_EVENT_OK); + } + + if (strcmp(data, "ERROR") == 0) { + evaluate_modem_state(ppp, PPP_MODEM_EVENT_STOP); + } + + if (strncmp(data, "CONNECT", 7) == 0) { + evaluate_modem_state(ppp, PPP_MODEM_EVENT_CONNECT); + } +} + +static void lcp_send_configure_request(struct pico_device_ppp *ppp) +{ +# define MY_LCP_REQ_SIZE 12 /* Max value. */ + struct pico_lcp_hdr *req; + uint8_t *lcpbuf, *opts; + uint32_t size = MY_LCP_REQ_SIZE; + uint32_t prefix; + uint32_t optsize = 0; + + prefix = ppp_ctl_packet_size(ppp, PPP_PROTO_LCP, &size); + lcpbuf = PICO_ZALLOC(size); + if (!lcpbuf) + return; + + req = (struct pico_lcp_hdr *)(lcpbuf + prefix); + + opts = lcpbuf + prefix + (sizeof(struct pico_lcp_hdr)); + /* uint8_t my_pkt[] = { 0x7e, 0xff, 0x03, 0xc0, 0x21, 0x01, 0x00, 0x00, 0x06, 0x07, 0x02, 0x64, 0x7b, 0x7e }; */ + + ppp_dbg("Sending LCP CONF REQ\n"); + req->code = PICO_CONF_REQ; + req->id = ppp->frame_id++; + + if (LCPOPT_ISSET_LOCAL(ppp, LCPOPT_PROTO_COMP)) { + opts[optsize++] = LCPOPT_PROTO_COMP; + opts[optsize++] = LCPOPT_LEN[LCPOPT_PROTO_COMP]; + } + + if (LCPOPT_ISSET_LOCAL(ppp, LCPOPT_MRU)) { + opts[optsize++] = LCPOPT_MRU; + opts[optsize++] = LCPOPT_LEN[LCPOPT_MRU]; + opts[optsize++] = (uint8_t)((ppp->mru >> 8) & 0xFF); + opts[optsize++] = (uint8_t)(ppp->mru & 0xFF); + } else { + ppp->mru = PICO_PPP_MRU; + } + + if (LCPOPT_ISSET_LOCAL(ppp, LCPOPT_ADDRCTL_COMP)) { + opts[optsize++] = LCPOPT_ADDRCTL_COMP; + opts[optsize++] = LCPOPT_LEN[LCPOPT_ADDRCTL_COMP]; + } + + req->len = short_be((uint16_t)((unsigned long)optsize + sizeof(struct pico_lcp_hdr))); + +#ifdef PPP_DEBUG + lcp_optflags_print(ppp, opts, optsize); +#endif + + pico_ppp_ctl_send(&ppp->dev, PPP_PROTO_LCP, + lcpbuf, /* Start of PPP packet */ + (uint32_t)(prefix + /* PPP Header, etc. */ + sizeof(struct pico_lcp_hdr) + /* LCP HDR */ + optsize + /* Actual options size */ + PPP_FCS_SIZE + /* FCS at the end of the frame */ + 1u) /* STOP Byte */ + ); + PICO_FREE(lcpbuf); + ppp->timer_val = PICO_PPP_DEFAULT_TIMER; + lcp_timer_start(ppp, PPP_TIMER_ON_LCPREQ); +} + +#ifdef PPP_DEBUG +static void lcp_optflags_print(struct pico_device_ppp *ppp, uint8_t *opts, uint32_t opts_len) +{ + uint8_t *p = opts; + int off; + IGNORE_PARAMETER(ppp); + ppp_dbg("Parsing options:\n"); + while(p < (opts + opts_len)) { + int i; + + ppp_dbg("-- LCP opt: %d - len: %d - data:", p[0], p[1]); + for (i=0; imru = (uint16_t)((p[2] << 8) + p[3]); + break; + case LCPOPT_AUTH: + ppp_dbg("Setting AUTH to %02x%02x\n", p[2], p[3]); + ppp->auth = (uint16_t)((p[2] << 8) + p[3]); + break; + default: + break; + } + } + + off = p[1]; /* opt length field */ + if (!off) + break; + + p += off; + } +#ifdef PPP_DEBUG + lcp_optflags_print(ppp, pkt + sizeof(struct pico_lcp_hdr), (uint32_t)(len - sizeof(struct pico_lcp_hdr)) ); +#endif + return flags; +} + + +static void lcp_send_configure_ack(struct pico_device_ppp *ppp) +{ + uint8_t ack[ppp->len + PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE + sizeof(struct pico_lcp_hdr) + PPP_FCS_SIZE + 1]; + struct pico_lcp_hdr *ack_hdr = (struct pico_lcp_hdr *) (ack + PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE); + struct pico_lcp_hdr *lcpreq = (struct pico_lcp_hdr *)ppp->pkt; + memcpy(ack + PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE, ppp->pkt, ppp->len); + ack_hdr->code = PICO_CONF_ACK; + ack_hdr->id = lcpreq->id; + ack_hdr->len = lcpreq->len; + ppp_dbg("Sending LCP CONF ACK\n"); + pico_ppp_ctl_send(&ppp->dev, PPP_PROTO_LCP, ack, + PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE + /* PPP Header, etc. */ + short_be(lcpreq->len) + /* Actual options size + hdr (whole lcp packet) */ + PPP_FCS_SIZE + /* FCS at the end of the frame */ + 1 /* STOP Byte */ + ); +} + +static void lcp_send_terminate_request(struct pico_device_ppp *ppp) +{ + uint8_t term[PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE + sizeof(struct pico_lcp_hdr) + PPP_FCS_SIZE + 1]; + struct pico_lcp_hdr *term_hdr = (struct pico_lcp_hdr *) (term + PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE); + term_hdr->code = PICO_CONF_TERM; + term_hdr->id = ppp->frame_id++; + term_hdr->len = short_be((uint16_t)sizeof(struct pico_lcp_hdr)); + ppp_dbg("Sending LCP TERMINATE REQUEST\n"); + pico_ppp_ctl_send(&ppp->dev, PPP_PROTO_LCP, term, + PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE + /* PPP Header, etc. */ + sizeof(struct pico_lcp_hdr) + /* Actual options size + hdr (whole lcp packet) */ + PPP_FCS_SIZE + /* FCS at the end of the frame */ + 1 /* STOP Byte */ + ); + lcp_timer_start(ppp, PPP_TIMER_ON_LCPTERM); +} + +static void lcp_send_terminate_ack(struct pico_device_ppp *ppp) +{ + uint8_t ack[ppp->len + PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE + sizeof(struct pico_lcp_hdr) + PPP_FCS_SIZE + 1]; + struct pico_lcp_hdr *ack_hdr = (struct pico_lcp_hdr *) (ack + PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE); + struct pico_lcp_hdr *lcpreq = (struct pico_lcp_hdr *)ppp->pkt; + memcpy(ack + PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE, ppp->pkt, ppp->len); + ack_hdr->code = PICO_CONF_TERM_ACK; + ack_hdr->id = lcpreq->id; + ack_hdr->len = lcpreq->len; + ppp_dbg("Sending LCP TERM ACK\n"); + pico_ppp_ctl_send(&ppp->dev, PPP_PROTO_LCP, ack, + PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE + /* PPP Header, etc. */ + short_be(lcpreq->len) + /* Actual options size + hdr (whole lcp packet) */ + PPP_FCS_SIZE + /* FCS at the end of the frame */ + 1 /* STOP Byte */ + ); +} + +static void lcp_send_configure_nack(struct pico_device_ppp *ppp) +{ + uint8_t reject[64]; + uint8_t *p = ppp->pkt + sizeof(struct pico_lcp_hdr); + struct pico_lcp_hdr *lcpreq = (struct pico_lcp_hdr *)ppp->pkt; + struct pico_lcp_hdr *lcprej = (struct pico_lcp_hdr *)(reject + PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE); + uint8_t *dst_opts = reject + PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE + sizeof(struct pico_lcp_hdr); + uint32_t dstopts_len = 0; + ppp_dbg("CONF_NACK: rej = %04X\n", ppp->rej); + while (p < (ppp->pkt + ppp->len)) { + uint8_t i = 0; + if ((1u << p[0]) & ppp->rej || (p[0] > 8u)) { /* Reject anything we dont support or with option id >8 */ + ppp_dbg("rejecting option %d -- ", p[0]); + dst_opts[dstopts_len++] = p[0]; + + ppp_dbg("len: %d -- ", p[1]); + dst_opts[dstopts_len++] = p[1]; + + ppp_dbg("data: "); + for(i = 0; i < p[1]-2u; i++) { /* length includes type, length and data fields */ + dst_opts[dstopts_len++] = p[2 + i]; + ppp_dbg("%02X ", p[2+i]); + } + ppp_dbg("\n"); + } + + p += p[1]; + } + lcprej->code = PICO_CONF_REJ; + lcprej->id = lcpreq->id; + lcprej->len = short_be((uint16_t)(dstopts_len + sizeof(struct pico_lcp_hdr))); + + ppp_dbg("Sending LCP CONF REJ\n"); +#ifdef PPP_DEBUG + lcp_optflags_print(ppp, dst_opts, dstopts_len); +#endif + + pico_ppp_ctl_send(&ppp->dev, PPP_PROTO_LCP, reject, + (uint32_t)(PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE + /* PPP Header, etc. */ + sizeof(struct pico_lcp_hdr) + /* LCP HDR */ + dstopts_len + /* Actual options size */ + PPP_FCS_SIZE + /* FCS at the end of the frame */ + 1u) /* STOP Byte */ + ); +} + +static void lcp_process_in(struct pico_device_ppp *ppp, uint8_t *pkt, uint32_t len) +{ + uint16_t optflags; + if (!ppp) + return; + if (pkt[0] == PICO_CONF_REQ) { + uint16_t rejected = 0; + ppp_dbg("Received LCP CONF REQ\n"); + optflags = lcp_optflags(ppp, pkt, len, 1u); + rejected = (uint16_t)(optflags & (~ppp->lcpopt_local)); + ppp->pkt = pkt; + ppp->len = len; + ppp->rej = rejected; + if (rejected) { + evaluate_lcp_state(ppp, PPP_LCP_EVENT_RCR_NEG); + } else { + ppp->lcpopt_peer = optflags; + evaluate_lcp_state(ppp, PPP_LCP_EVENT_RCR_POS); + } + + return; + } + + if (pkt[0] == PICO_CONF_ACK) { + ppp_dbg("Received LCP CONF ACK\nOptflags: %04x\n", lcp_optflags(NULL, pkt, len, 0u)); + evaluate_lcp_state(ppp, PPP_LCP_EVENT_RCA); + return; + } + + if (pkt[0] == PICO_CONF_NAK) { + /* Every instance of the received Configuration Options is recognizable, but some values are not acceptable */ + optflags = lcp_optflags(ppp, pkt, len, 1u); /* We want our options adjusted */ + ppp_dbg("Received LCP CONF NAK - changed optflags: %04X\n", optflags); + evaluate_lcp_state(ppp, PPP_LCP_EVENT_RCN); + return; + } + + if (pkt[0] == PICO_CONF_REJ) { + /* Some Configuration Options received in a Configure-Request are not recognizable or are not acceptable for negotiation */ + optflags = lcp_optflags(ppp, pkt, len, 0u); + ppp_dbg("Received LCP CONF REJ - will disable optflags: %04X\n", optflags); + /* Disable the options that are not supported by the peer */ + LCPOPT_UNSET_LOCAL_MASK(ppp, optflags); + evaluate_lcp_state(ppp, PPP_LCP_EVENT_RCN); + return; + } + + if (pkt[0] == PICO_CONF_ECHO_REQ) { + ppp_dbg("Received LCP ECHO REQ\n"); + evaluate_lcp_state(ppp, PPP_LCP_EVENT_RXR); + return; + } +} + +static void pap_process_in(struct pico_device_ppp *ppp, uint8_t *pkt, uint32_t len) +{ + struct pico_pap_hdr *p = (struct pico_pap_hdr *)pkt; + (void)len; + if (!p) + return; + + if (ppp->auth != 0xc023) + return; + + switch(p->code) { + case PAP_AUTH_ACK: + ppp_dbg("PAP: Received Authentication OK!\n"); + evaluate_auth_state(ppp, PPP_AUTH_EVENT_RAA); + break; + case PAP_AUTH_NAK: + ppp_dbg("PAP: Received Authentication Reject!\n"); + evaluate_auth_state(ppp, PPP_AUTH_EVENT_RAN); + break; + + default: + ppp_dbg("PAP: Received invalid packet with code %d\n", p->code); + } +} + + +static void chap_process_in(struct pico_device_ppp *ppp, uint8_t *pkt, uint32_t len) +{ + struct pico_chap_hdr *ch = (struct pico_chap_hdr *)pkt; + + if (!pkt) + return; + + if (ppp->auth != 0xc223) + return; + + switch(ch->code) { + case CHAP_CHALLENGE: + ppp_dbg("Received CHAP CHALLENGE\n"); + ppp->pkt = pkt; + ppp->len = len; + evaluate_auth_state(ppp, PPP_AUTH_EVENT_RAC); + break; + case CHAP_SUCCESS: + ppp_dbg("Received CHAP SUCCESS\n"); + evaluate_auth_state(ppp, PPP_AUTH_EVENT_RAA); + break; + case CHAP_FAILURE: + ppp_dbg("Received CHAP FAILURE\n"); + evaluate_auth_state(ppp, PPP_AUTH_EVENT_RAN); + break; + } +} + + +static void ipcp_send_ack(struct pico_device_ppp *ppp) +{ + uint8_t ack[ppp->len + PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE + sizeof(struct pico_lcp_hdr) + PPP_FCS_SIZE + 1]; + struct pico_ipcp_hdr *ack_hdr = (struct pico_ipcp_hdr *) (ack + PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE); + struct pico_ipcp_hdr *ipcpreq = (struct pico_ipcp_hdr *)ppp->pkt; + memcpy(ack + PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE, ppp->pkt, ppp->len); + ack_hdr->code = PICO_CONF_ACK; + ack_hdr->id = ipcpreq->id; + ack_hdr->len = ipcpreq->len; + ppp_dbg("Sending IPCP CONF ACK\n"); + pico_ppp_ctl_send(&ppp->dev, PPP_PROTO_IPCP, ack, + PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE + /* PPP Header, etc. */ + short_be(ipcpreq->len) + /* Actual options size + hdr (whole ipcp packet) */ + PPP_FCS_SIZE + /* FCS at the end of the frame */ + 1 /* STOP Byte */ + ); +} + +static inline uint32_t ipcp_request_options_size(struct pico_device_ppp *ppp) +{ + uint32_t size = 0; + +/* if (ppp->ipcp_ip) */ + size += IPCP_ADDR_LEN; +/* if (ppp->ipcp_dns1) */ + size += IPCP_ADDR_LEN; +/* if (ppp->ipcp_dns2) */ + size += IPCP_ADDR_LEN; + if (ppp->ipcp_nbns1) + size += IPCP_ADDR_LEN; + + if (ppp->ipcp_nbns2) + size += IPCP_ADDR_LEN; + + return size; +} + +static int ipcp_request_add_address(uint8_t *dst, uint8_t tag, uint32_t arg) +{ + uint32_t addr = long_be(arg); + dst[0] = tag; + dst[1] = IPCP_ADDR_LEN; + dst[2] = (uint8_t)((addr & 0xFF000000u) >> 24); + dst[3] = (uint8_t)((addr & 0x00FF0000u) >> 16); + dst[4] = (uint8_t)((addr & 0x0000FF00u) >> 8); + dst[5] = (addr & 0x000000FFu); + return IPCP_ADDR_LEN; +} + +static void ipcp_request_fill(struct pico_device_ppp *ppp, uint8_t *opts) +{ + if (ppp->ipcp_allowed_fields & IPCP_ALLOW_IP) + opts += ipcp_request_add_address(opts, IPCP_OPT_IP, ppp->ipcp_ip); + if (ppp->ipcp_allowed_fields & IPCP_ALLOW_DNS1) + opts += ipcp_request_add_address(opts, IPCP_OPT_DNS1, ppp->ipcp_dns1); + if (ppp->ipcp_allowed_fields & IPCP_ALLOW_DNS2) + opts += ipcp_request_add_address(opts, IPCP_OPT_DNS2, ppp->ipcp_dns2); + if ((ppp->ipcp_allowed_fields & IPCP_ALLOW_NBNS1) && (ppp->ipcp_nbns1)) + opts += ipcp_request_add_address(opts, IPCP_OPT_NBNS1, ppp->ipcp_nbns1); + if ((ppp->ipcp_allowed_fields & IPCP_ALLOW_NBNS2) && (ppp->ipcp_nbns2)) + opts += ipcp_request_add_address(opts, IPCP_OPT_NBNS2, ppp->ipcp_nbns2); +} + +static void ipcp_send_req(struct pico_device_ppp *ppp) +{ + uint8_t ipcp_req[PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE + sizeof(struct pico_ipcp_hdr) + ipcp_request_options_size(ppp) + PPP_FCS_SIZE + 1]; + uint32_t prefix = PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE; + struct pico_ipcp_hdr *ih = (struct pico_ipcp_hdr *) (ipcp_req + prefix); + uint8_t *p = ipcp_req + prefix + sizeof(struct pico_ipcp_hdr); + uint16_t len = (uint16_t)(ipcp_request_options_size(ppp) + sizeof(struct pico_ipcp_hdr)); + + ih->id = ppp->frame_id++; + ih->code = PICO_CONF_REQ; + ih->len = short_be(len); + ipcp_request_fill(ppp, p); + + ppp_dbg("Sending IPCP CONF REQ, ipcp size = %d\n", len); + pico_ppp_ctl_send(&ppp->dev, PPP_PROTO_IPCP, + ipcp_req, /* Start of PPP packet */ + (uint32_t)(prefix + /* PPP Header, etc. */ + (uint32_t)len + /* IPCP Header + options */ + PPP_FCS_SIZE + /* FCS at the end of the frame */ + 1u) /* STOP Byte */ + ); +} + +static void ipcp_reject_vj(struct pico_device_ppp *ppp, uint8_t *comp_req) +{ + uint8_t ipcp_req[PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE + sizeof(struct pico_ipcp_hdr) + IPCP_VJ_LEN + PPP_FCS_SIZE + 1]; + uint32_t prefix = PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE; + struct pico_ipcp_hdr *ih = (struct pico_ipcp_hdr *) (ipcp_req + prefix); + uint8_t *p = ipcp_req + prefix + sizeof(struct pico_ipcp_hdr); + uint32_t i; + + ih->id = ppp->frame_id++; + ih->code = PICO_CONF_REQ; + ih->len = short_be(IPCP_VJ_LEN + sizeof(struct pico_ipcp_hdr)); + for(i = 0; i < IPCP_OPT_VJ; i++) + p[i] = comp_req[i + sizeof(struct pico_ipcp_hdr)]; + ppp_dbg("Sending IPCP CONF REJ VJ\n"); + pico_ppp_ctl_send(&ppp->dev, PPP_PROTO_IPCP, + ipcp_req, /* Start of PPP packet */ + (uint32_t)(prefix + /* PPP Header, etc. */ + sizeof(struct pico_ipcp_hdr) + /* LCP HDR */ + IPCP_VJ_LEN + /* Actual options size */ + PPP_FCS_SIZE + /* FCS at the end of the frame */ + 1u) /* STOP Byte */ + ); +} + +static void ppp_ipv4_conf(struct pico_device_ppp *ppp) +{ + struct pico_ip4 ip; + struct pico_ip4 nm; + struct pico_ip4 dns1; + struct pico_ip4 dns2; + struct pico_ip4 any = { + 0 + }; + ip.addr = ppp->ipcp_ip; + nm.addr = 0xFFFFFF00; + pico_ipv4_link_add(&ppp->dev, ip, nm); + pico_ipv4_route_add(any, any, any, 1, pico_ipv4_link_by_dev(&ppp->dev)); + + dns1.addr = ppp->ipcp_dns1; + dns2.addr = ppp->ipcp_dns2; + pico_dns_client_nameserver(&dns1, PICO_DNS_NS_ADD); + pico_dns_client_nameserver(&dns2, PICO_DNS_NS_ADD); +} + + +static void ipcp_process_in(struct pico_device_ppp *ppp, uint8_t *pkt, uint32_t len) +{ + struct pico_ipcp_hdr *ih = (struct pico_ipcp_hdr *)pkt; + uint8_t *p = pkt + sizeof(struct pico_ipcp_hdr); + int reject = 0; + while (p < pkt + len) { + if (p[0] == IPCP_OPT_VJ) { + reject++; + } + + if (p[0] == IPCP_OPT_IP) { + if (ih->code != PICO_CONF_REJ) + ppp->ipcp_ip = long_be((uint32_t)((p[2] << 24) + (p[3] << 16) + (p[4] << 8) + p[5])); + else { + ppp->ipcp_allowed_fields &= (~IPCP_ALLOW_IP); + ppp->ipcp_ip = 0; + } + } + + if (p[0] == IPCP_OPT_DNS1) { + if (ih->code != PICO_CONF_REJ) + ppp->ipcp_dns1 = long_be((uint32_t)((p[2] << 24) + (p[3] << 16) + (p[4] << 8) + p[5])); + else { + ppp->ipcp_allowed_fields &= (~IPCP_ALLOW_DNS1); + ppp->ipcp_dns1 = 0; + } + } + + if (p[0] == IPCP_OPT_NBNS1) { + if (ih->code != PICO_CONF_REJ) + ppp->ipcp_nbns1 = long_be((uint32_t)((p[2] << 24) + (p[3] << 16) + (p[4] << 8) + p[5])); + else { + ppp->ipcp_allowed_fields &= (~IPCP_ALLOW_NBNS1); + ppp->ipcp_nbns1 = 0; + } + } + + if (p[0] == IPCP_OPT_DNS2) { + if (ih->code != PICO_CONF_REJ) + ppp->ipcp_dns2 = long_be((uint32_t)((p[2] << 24) + (p[3] << 16) + (p[4] << 8) + p[5])); + else { + ppp->ipcp_allowed_fields &= (~IPCP_ALLOW_DNS2); + ppp->ipcp_dns2 = 0; + } + } + + if (p[0] == IPCP_OPT_NBNS2) { + if (ih->code != PICO_CONF_REJ) + ppp->ipcp_nbns2 = long_be((uint32_t)((p[2] << 24) + (p[3] << 16) + (p[4] << 8) + p[5])); + else { + ppp->ipcp_allowed_fields &= (~IPCP_ALLOW_NBNS2); + ppp->ipcp_nbns2 = 0; + } + } + + p += p[1]; + } + if (reject) { + ipcp_reject_vj(ppp, p); + return; + } + + ppp->pkt = pkt; + ppp->len = len; + + switch(ih->code) { + case PICO_CONF_ACK: + ppp_dbg("Received IPCP CONF ACK\n"); + evaluate_ipcp_state(ppp, PPP_IPCP_EVENT_RCA); + break; + case PICO_CONF_REQ: + ppp_dbg("Received IPCP CONF REQ\n"); + evaluate_ipcp_state(ppp, PPP_IPCP_EVENT_RCR_POS); + break; + case PICO_CONF_NAK: + ppp_dbg("Received IPCP CONF NAK\n"); + evaluate_ipcp_state(ppp, PPP_IPCP_EVENT_RCN); + break; + case PICO_CONF_REJ: + ppp_dbg("Received IPCP CONF REJ\n"); + + evaluate_ipcp_state(ppp, PPP_IPCP_EVENT_RCN); + break; + } +} + +static void ipcp6_process_in(struct pico_device_ppp *ppp, uint8_t *pkt, uint32_t len) +{ + IGNORE_PARAMETER(ppp); + IGNORE_PARAMETER(pkt); + IGNORE_PARAMETER(len); +} + +static void ppp_process_packet_payload(struct pico_device_ppp *ppp, uint8_t *pkt, uint32_t len) +{ + if (pkt[0] == 0xc0) { + /* Link control packet */ + if (pkt[1] == 0x21) { + /* LCP */ + lcp_process_in(ppp, pkt + 2, len - 2); + } + + if (pkt[1] == 0x23) { + /* PAP */ + pap_process_in(ppp, pkt + 2, len - 2); + } + + return; + } + + if ((pkt[0] == 0xc2) && (pkt[1] == 0x23)) { + /* CHAP */ + chap_process_in(ppp, pkt + 2, len - 2); + return; + } + + if (pkt[0] == 0x80) { + /* IP assignment (IPCP/IPCP6) */ + if (pkt[1] == 0x21) { + /* IPCP */ + ipcp_process_in(ppp, pkt + 2, len - 2); + } + + if (pkt[1] == 0x57) { + /* IPCP6 */ + ipcp6_process_in(ppp, pkt + 2, len - 2); + } + + return; + } + + if (pkt[0] == 0x00) { + /* Uncompressed protocol: leading zero. */ + pkt++; + len--; + } + + if ((pkt[0] == 0x21) || (pkt[0] == 0x57)) { + /* IPv4 /v6 Data */ + pico_stack_recv(&ppp->dev, pkt + 1, len - 1); + return; + } + + ppp_dbg("PPP: Unrecognized protocol %02x%02x\n", pkt[0], pkt[1]); +} + +static void ppp_process_packet(struct pico_device_ppp *ppp, uint8_t *pkt, uint32_t len) +{ + /* Verify incoming FCS */ + if (ppp_fcs_verify(pkt, len) != 0) + return; + + /* Remove trailing FCS */ + len -= 2; + + /* Remove ADDR/CTRL, then process */ + if ((pkt[0] == PPPF_ADDR) && (pkt[1] == PPPF_CTRL)) { + pkt += 2; + len -= 2; + } + + ppp_process_packet_payload(ppp, pkt, len); + +} + +static void ppp_recv_data(struct pico_device_ppp *ppp, void *data, uint32_t len) +{ + uint8_t *pkt = (uint8_t *)data; + +#ifdef PPP_DEBUG + uint32_t idx; + if (len > 0) { + ppp_dbg("PPP <<<<< "); + for(idx = 0; idx < len; idx++) { + ppp_dbg(" %02x", ((uint8_t *)data)[idx]); + } + ppp_dbg("\n"); + } +#endif + + ppp_process_packet(ppp, pkt, len); +} + +static void lcp_this_layer_up(struct pico_device_ppp *ppp) +{ + ppp_dbg("PPP: LCP up.\n"); + + switch (ppp->auth) { + case 0x0000: + evaluate_auth_state(ppp, PPP_AUTH_EVENT_UP_NONE); + break; + case 0xc023: + evaluate_auth_state(ppp, PPP_AUTH_EVENT_UP_PAP); + break; + case 0xc223: + evaluate_auth_state(ppp, PPP_AUTH_EVENT_UP_CHAP); + break; + default: + ppp_dbg("PPP: Unknown authentication protocol.\n"); + break; + } +} + +static void lcp_this_layer_down(struct pico_device_ppp *ppp) +{ + ppp_dbg("PPP: LCP down.\n"); + evaluate_auth_state(ppp, PPP_AUTH_EVENT_DOWN); +} + +static void lcp_this_layer_started(struct pico_device_ppp *ppp) +{ + ppp_dbg("PPP: LCP started.\n"); + evaluate_modem_state(ppp, PPP_MODEM_EVENT_START); +} + +static void lcp_this_layer_finished(struct pico_device_ppp *ppp) +{ + ppp_dbg("PPP: LCP finished.\n"); + evaluate_modem_state(ppp, PPP_MODEM_EVENT_STOP); +} + +static void lcp_initialize_restart_count(struct pico_device_ppp *ppp) +{ + lcp_timer_start(ppp, PPP_TIMER_ON_LCPREQ); +} + +static void lcp_send_code_reject(struct pico_device_ppp *ppp) +{ + IGNORE_PARAMETER(ppp); +} + +static void lcp_send_echo_reply(struct pico_device_ppp *ppp) +{ + uint8_t reply[ppp->len + PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE + sizeof(struct pico_lcp_hdr) + PPP_FCS_SIZE + 1]; + struct pico_lcp_hdr *reply_hdr = (struct pico_lcp_hdr *) (reply + PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE); + struct pico_lcp_hdr *lcpreq = (struct pico_lcp_hdr *)ppp->pkt; + memcpy(reply + PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE, ppp->pkt, ppp->len); + reply_hdr->code = PICO_CONF_ECHO_REP; + reply_hdr->id = lcpreq->id; + reply_hdr->len = lcpreq->len; + ppp_dbg("Sending LCP ECHO REPLY\n"); + pico_ppp_ctl_send(&ppp->dev, PPP_PROTO_LCP, reply, + PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE + /* PPP Header, etc. */ + short_be(lcpreq->len) + /* Actual options size + hdr (whole lcp packet) */ + PPP_FCS_SIZE + /* FCS at the end of the frame */ + 1 /* STOP Byte */ + ); +} + +static const struct pico_ppp_fsm ppp_lcp_fsm[PPP_LCP_STATE_MAX][PPP_LCP_EVENT_MAX] = { + [PPP_LCP_STATE_INITIAL] = { + [PPP_LCP_EVENT_UP] = { PPP_LCP_STATE_CLOSED, {} }, + [PPP_LCP_EVENT_DOWN] = { PPP_LCP_STATE_INITIAL, {} }, + [PPP_LCP_EVENT_OPEN] = { PPP_LCP_STATE_STARTING, { lcp_this_layer_started } }, + [PPP_LCP_EVENT_CLOSE] = { PPP_LCP_STATE_INITIAL, {} }, + [PPP_LCP_EVENT_TO_POS] = { PPP_LCP_STATE_INITIAL, {} }, + [PPP_LCP_EVENT_TO_NEG] = { PPP_LCP_STATE_INITIAL, {} }, + [PPP_LCP_EVENT_RCR_POS] = { PPP_LCP_STATE_INITIAL, {} }, + [PPP_LCP_EVENT_RCR_NEG] = { PPP_LCP_STATE_INITIAL, {} }, + [PPP_LCP_EVENT_RCA] = { PPP_LCP_STATE_INITIAL, {} }, + [PPP_LCP_EVENT_RCN] = { PPP_LCP_STATE_INITIAL, {} }, + [PPP_LCP_EVENT_RTR] = { PPP_LCP_STATE_INITIAL, {} }, + [PPP_LCP_EVENT_RTA] = { PPP_LCP_STATE_INITIAL, {} }, + [PPP_LCP_EVENT_RUC] = { PPP_LCP_STATE_INITIAL, {} }, + [PPP_LCP_EVENT_RXJ_POS] = { PPP_LCP_STATE_INITIAL, {} }, + [PPP_LCP_EVENT_RXJ_NEG] = { PPP_LCP_STATE_INITIAL, {} }, + [PPP_LCP_EVENT_RXR] = { PPP_LCP_STATE_INITIAL, {} } + }, + [PPP_LCP_STATE_STARTING] = { + [PPP_LCP_EVENT_UP] = { PPP_LCP_STATE_REQ_SENT, { lcp_send_configure_request } }, + [PPP_LCP_EVENT_DOWN] = { PPP_LCP_STATE_STARTING, {} }, + [PPP_LCP_EVENT_OPEN] = { PPP_LCP_STATE_STARTING, {} }, + [PPP_LCP_EVENT_CLOSE] = { PPP_LCP_STATE_INITIAL, { lcp_this_layer_finished } }, + [PPP_LCP_EVENT_TO_POS] = { PPP_LCP_STATE_STARTING, {} }, + [PPP_LCP_EVENT_TO_NEG] = { PPP_LCP_STATE_STARTING, {} }, + [PPP_LCP_EVENT_RCR_POS] = { PPP_LCP_STATE_STARTING, {} }, + [PPP_LCP_EVENT_RCR_NEG] = { PPP_LCP_STATE_STARTING, {} }, + [PPP_LCP_EVENT_RCA] = { PPP_LCP_STATE_STARTING, {} }, + [PPP_LCP_EVENT_RCN] = { PPP_LCP_STATE_STARTING, {} }, + [PPP_LCP_EVENT_RTR] = { PPP_LCP_STATE_STARTING, {} }, + [PPP_LCP_EVENT_RTA] = { PPP_LCP_STATE_STARTING, {} }, + [PPP_LCP_EVENT_RUC] = { PPP_LCP_STATE_STARTING, {} }, + [PPP_LCP_EVENT_RXJ_POS] = { PPP_LCP_STATE_STARTING, {} }, + [PPP_LCP_EVENT_RXJ_NEG] = { PPP_LCP_STATE_STARTING, {} }, + [PPP_LCP_EVENT_RXR] = { PPP_LCP_STATE_STARTING, {} } + }, + [PPP_LCP_STATE_CLOSED] = { + [PPP_LCP_EVENT_UP] = { PPP_LCP_STATE_CLOSED, {} }, + [PPP_LCP_EVENT_DOWN] = { PPP_LCP_STATE_INITIAL, {} }, + [PPP_LCP_EVENT_OPEN] = { PPP_LCP_STATE_REQ_SENT, { lcp_send_configure_request} }, + [PPP_LCP_EVENT_CLOSE] = { PPP_LCP_STATE_CLOSED, {} }, + [PPP_LCP_EVENT_TO_POS] = { PPP_LCP_STATE_CLOSED, {} }, + [PPP_LCP_EVENT_TO_NEG] = { PPP_LCP_STATE_CLOSED, {} }, + [PPP_LCP_EVENT_RCR_POS] = { PPP_LCP_STATE_CLOSED, { lcp_send_terminate_ack } }, + [PPP_LCP_EVENT_RCR_NEG] = { PPP_LCP_STATE_CLOSED, { lcp_send_terminate_ack } }, + [PPP_LCP_EVENT_RCA] = { PPP_LCP_STATE_CLOSED, { lcp_send_terminate_ack } }, + [PPP_LCP_EVENT_RCN] = { PPP_LCP_STATE_CLOSED, { lcp_send_terminate_ack } }, + [PPP_LCP_EVENT_RTR] = { PPP_LCP_STATE_CLOSED, { lcp_send_terminate_ack } }, + [PPP_LCP_EVENT_RTA] = { PPP_LCP_STATE_CLOSED, {} }, + [PPP_LCP_EVENT_RUC] = { PPP_LCP_STATE_CLOSED, { lcp_send_code_reject } }, + [PPP_LCP_EVENT_RXJ_POS] = { PPP_LCP_STATE_CLOSED, {} }, + [PPP_LCP_EVENT_RXJ_NEG] = { PPP_LCP_STATE_CLOSED, { lcp_this_layer_finished } }, + [PPP_LCP_EVENT_RXR] = { PPP_LCP_STATE_CLOSED, {} } + }, + [PPP_LCP_STATE_STOPPED] = { + [PPP_LCP_EVENT_UP] = { PPP_LCP_STATE_STOPPED, {} }, + [PPP_LCP_EVENT_DOWN] = { PPP_LCP_STATE_STARTING, { lcp_this_layer_started } }, + [PPP_LCP_EVENT_OPEN] = { PPP_LCP_STATE_STOPPED, {}}, + [PPP_LCP_EVENT_CLOSE] = { PPP_LCP_STATE_CLOSED, {}}, + [PPP_LCP_EVENT_TO_POS] = { PPP_LCP_STATE_STOPPED, {} }, + [PPP_LCP_EVENT_TO_NEG] = { PPP_LCP_STATE_STOPPED, {} }, + [PPP_LCP_EVENT_RCR_POS] = { PPP_LCP_STATE_ACK_SENT, + { lcp_send_configure_request, lcp_send_configure_ack}}, + [PPP_LCP_EVENT_RCR_NEG] = { PPP_LCP_STATE_REQ_SENT, + { lcp_send_configure_request, lcp_send_configure_nack}}, + [PPP_LCP_EVENT_RCA] = { PPP_LCP_STATE_STOPPED, { lcp_send_terminate_ack } }, + [PPP_LCP_EVENT_RCN] = { PPP_LCP_STATE_STOPPED, { lcp_send_terminate_ack } }, + [PPP_LCP_EVENT_RTR] = { PPP_LCP_STATE_STOPPED, { lcp_send_terminate_ack } }, + [PPP_LCP_EVENT_RTA] = { PPP_LCP_STATE_STOPPED, {} }, + [PPP_LCP_EVENT_RUC] = { PPP_LCP_STATE_STOPPED, { lcp_send_code_reject } }, + [PPP_LCP_EVENT_RXJ_POS] = { PPP_LCP_STATE_STOPPED, {} }, + [PPP_LCP_EVENT_RXJ_NEG] = { PPP_LCP_STATE_STOPPED, { lcp_this_layer_finished } }, + [PPP_LCP_EVENT_RXR] = { PPP_LCP_STATE_STOPPED, {} } + }, + [PPP_LCP_STATE_CLOSING] = { + [PPP_LCP_EVENT_UP] = { PPP_LCP_STATE_CLOSING, {} }, + [PPP_LCP_EVENT_DOWN] = { PPP_LCP_STATE_INITIAL, {} }, + [PPP_LCP_EVENT_OPEN] = { PPP_LCP_STATE_STOPPING, {} }, + [PPP_LCP_EVENT_CLOSE] = { PPP_LCP_STATE_CLOSING, {} }, + [PPP_LCP_EVENT_TO_POS] = { PPP_LCP_STATE_CLOSING, { lcp_send_terminate_request } }, + [PPP_LCP_EVENT_TO_NEG] = { PPP_LCP_STATE_CLOSED, { lcp_this_layer_finished } }, + [PPP_LCP_EVENT_RCR_POS] = { PPP_LCP_STATE_CLOSING, {} }, + [PPP_LCP_EVENT_RCR_NEG] = { PPP_LCP_STATE_CLOSING, {} }, + [PPP_LCP_EVENT_RCA] = { PPP_LCP_STATE_CLOSING, {} }, + [PPP_LCP_EVENT_RCN] = { PPP_LCP_STATE_CLOSING, {} }, + [PPP_LCP_EVENT_RTR] = { PPP_LCP_STATE_CLOSING, { lcp_send_terminate_ack } }, + [PPP_LCP_EVENT_RTA] = { PPP_LCP_STATE_CLOSED, { lcp_this_layer_finished } }, + [PPP_LCP_EVENT_RUC] = { PPP_LCP_STATE_CLOSING, { lcp_send_code_reject } }, + [PPP_LCP_EVENT_RXJ_POS] = { PPP_LCP_STATE_CLOSING, {} }, + [PPP_LCP_EVENT_RXJ_NEG] = { PPP_LCP_STATE_CLOSED, { lcp_this_layer_finished } }, + [PPP_LCP_EVENT_RXR] = { PPP_LCP_STATE_CLOSING, {} } + }, + [PPP_LCP_STATE_STOPPING] = { + [PPP_LCP_EVENT_UP] = { PPP_LCP_STATE_STOPPING, {} }, + [PPP_LCP_EVENT_DOWN] = { PPP_LCP_STATE_STARTING, {} }, + [PPP_LCP_EVENT_OPEN] = { PPP_LCP_STATE_STOPPING, {} }, + [PPP_LCP_EVENT_CLOSE] = { PPP_LCP_STATE_CLOSING, {} }, + [PPP_LCP_EVENT_TO_POS] = { PPP_LCP_STATE_STOPPING, { lcp_send_terminate_request } }, + [PPP_LCP_EVENT_TO_NEG] = { PPP_LCP_STATE_STOPPED, { lcp_this_layer_finished } }, + [PPP_LCP_EVENT_RCR_POS] = { PPP_LCP_STATE_STOPPING, {} }, + [PPP_LCP_EVENT_RCR_NEG] = { PPP_LCP_STATE_STOPPING, {} }, + [PPP_LCP_EVENT_RCA] = { PPP_LCP_STATE_STOPPING, {} }, + [PPP_LCP_EVENT_RCN] = { PPP_LCP_STATE_STOPPING, {} }, + [PPP_LCP_EVENT_RTR] = { PPP_LCP_STATE_STOPPING, { lcp_send_terminate_ack } }, + [PPP_LCP_EVENT_RTA] = { PPP_LCP_STATE_STOPPED, { lcp_this_layer_finished } }, + [PPP_LCP_EVENT_RUC] = { PPP_LCP_STATE_STOPPING, { lcp_send_code_reject } }, + [PPP_LCP_EVENT_RXJ_POS] = { PPP_LCP_STATE_STOPPING, {} }, + [PPP_LCP_EVENT_RXJ_NEG] = { PPP_LCP_STATE_STOPPED, { lcp_this_layer_finished } }, + [PPP_LCP_EVENT_RXR] = { PPP_LCP_STATE_STOPPING, {} } + }, + [PPP_LCP_STATE_REQ_SENT] = { + [PPP_LCP_EVENT_UP] = { PPP_LCP_STATE_REQ_SENT, {} }, + [PPP_LCP_EVENT_DOWN] = { PPP_LCP_STATE_STARTING, {} }, + [PPP_LCP_EVENT_OPEN] = { PPP_LCP_STATE_REQ_SENT, {} }, + [PPP_LCP_EVENT_CLOSE] = { PPP_LCP_STATE_CLOSING, { lcp_send_terminate_request } }, + [PPP_LCP_EVENT_TO_POS] = { PPP_LCP_STATE_REQ_SENT, { lcp_send_configure_request } }, + [PPP_LCP_EVENT_TO_NEG] = { PPP_LCP_STATE_STOPPED, { lcp_this_layer_finished } }, + [PPP_LCP_EVENT_RCR_POS] = { PPP_LCP_STATE_ACK_SENT, { lcp_send_configure_ack } }, + [PPP_LCP_EVENT_RCR_NEG] = { PPP_LCP_STATE_REQ_SENT, { lcp_send_configure_nack } }, + [PPP_LCP_EVENT_RCA] = { PPP_LCP_STATE_ACK_RCVD, { lcp_initialize_restart_count } }, + [PPP_LCP_EVENT_RCN] = { PPP_LCP_STATE_REQ_SENT, { lcp_send_configure_request} }, + [PPP_LCP_EVENT_RTR] = { PPP_LCP_STATE_REQ_SENT, { lcp_send_terminate_ack } }, + [PPP_LCP_EVENT_RTA] = { PPP_LCP_STATE_REQ_SENT, {} }, + [PPP_LCP_EVENT_RUC] = { PPP_LCP_STATE_REQ_SENT, { lcp_send_code_reject } }, + [PPP_LCP_EVENT_RXJ_POS] = { PPP_LCP_STATE_REQ_SENT, {} }, + [PPP_LCP_EVENT_RXJ_NEG] = { PPP_LCP_STATE_STOPPED, { lcp_this_layer_finished } }, + [PPP_LCP_EVENT_RXR] = { PPP_LCP_STATE_REQ_SENT, {} } + }, + [PPP_LCP_STATE_ACK_RCVD] = { + [PPP_LCP_EVENT_UP] = { PPP_LCP_STATE_ACK_RCVD, {} }, + [PPP_LCP_EVENT_DOWN] = { PPP_LCP_STATE_STARTING, {} }, + [PPP_LCP_EVENT_OPEN] = { PPP_LCP_STATE_ACK_RCVD, {} }, + [PPP_LCP_EVENT_CLOSE] = { PPP_LCP_STATE_CLOSING, { lcp_send_terminate_request} }, + [PPP_LCP_EVENT_TO_POS] = { PPP_LCP_STATE_REQ_SENT, { lcp_send_configure_request } }, + [PPP_LCP_EVENT_TO_NEG] = { PPP_LCP_STATE_STOPPED, { lcp_this_layer_finished } }, + [PPP_LCP_EVENT_RCR_POS] = { PPP_LCP_STATE_OPENED, { lcp_send_configure_ack, lcp_this_layer_up} }, + [PPP_LCP_EVENT_RCR_NEG] = { PPP_LCP_STATE_ACK_RCVD, { lcp_send_configure_nack } }, + [PPP_LCP_EVENT_RCA] = { PPP_LCP_STATE_REQ_SENT, { lcp_send_configure_request } }, + [PPP_LCP_EVENT_RCN] = { PPP_LCP_STATE_REQ_SENT, { lcp_send_configure_request } }, + [PPP_LCP_EVENT_RTR] = { PPP_LCP_STATE_REQ_SENT, { lcp_send_terminate_ack } }, + [PPP_LCP_EVENT_RTA] = { PPP_LCP_STATE_REQ_SENT, {} }, + [PPP_LCP_EVENT_RUC] = { PPP_LCP_STATE_ACK_RCVD, { lcp_send_code_reject } }, + [PPP_LCP_EVENT_RXJ_POS] = { PPP_LCP_STATE_REQ_SENT, {} }, + [PPP_LCP_EVENT_RXJ_NEG] = { PPP_LCP_STATE_STOPPED, { lcp_this_layer_finished } }, + [PPP_LCP_EVENT_RXR] = { PPP_LCP_STATE_ACK_RCVD, {} } + }, + [PPP_LCP_STATE_ACK_SENT] = { + [PPP_LCP_EVENT_UP] = { PPP_LCP_STATE_ACK_SENT, {} }, + [PPP_LCP_EVENT_DOWN] = { PPP_LCP_STATE_STARTING, {} }, + [PPP_LCP_EVENT_OPEN] = { PPP_LCP_STATE_ACK_SENT, {} }, + [PPP_LCP_EVENT_CLOSE] = { PPP_LCP_STATE_CLOSING, { lcp_send_terminate_request} }, + [PPP_LCP_EVENT_TO_POS] = { PPP_LCP_STATE_ACK_SENT, { lcp_send_configure_request } }, + [PPP_LCP_EVENT_TO_NEG] = { PPP_LCP_STATE_STOPPED, { lcp_this_layer_finished } }, + [PPP_LCP_EVENT_RCR_POS] = { PPP_LCP_STATE_ACK_SENT, { lcp_send_configure_ack } }, + [PPP_LCP_EVENT_RCR_NEG] = { PPP_LCP_STATE_REQ_SENT, { lcp_send_configure_nack } }, + [PPP_LCP_EVENT_RCA] = { PPP_LCP_STATE_OPENED, { lcp_this_layer_up} }, + [PPP_LCP_EVENT_RCN] = { PPP_LCP_STATE_ACK_SENT, { lcp_send_configure_request} }, + [PPP_LCP_EVENT_RTR] = { PPP_LCP_STATE_REQ_SENT, { lcp_send_terminate_ack } }, + [PPP_LCP_EVENT_RTA] = { PPP_LCP_STATE_ACK_SENT, {} }, + [PPP_LCP_EVENT_RUC] = { PPP_LCP_STATE_ACK_SENT, { lcp_send_code_reject } }, + [PPP_LCP_EVENT_RXJ_POS] = { PPP_LCP_STATE_ACK_SENT, {} }, + [PPP_LCP_EVENT_RXJ_NEG] = { PPP_LCP_STATE_STOPPED, { lcp_this_layer_finished } }, + [PPP_LCP_EVENT_RXR] = { PPP_LCP_STATE_ACK_SENT, {} } + }, + [PPP_LCP_STATE_OPENED] = { + [PPP_LCP_EVENT_UP] = { PPP_LCP_STATE_OPENED, {} }, + [PPP_LCP_EVENT_DOWN] = { PPP_LCP_STATE_STARTING, {lcp_this_layer_down } }, + [PPP_LCP_EVENT_OPEN] = { PPP_LCP_STATE_OPENED, {} }, + [PPP_LCP_EVENT_CLOSE] = { PPP_LCP_STATE_CLOSING, + { lcp_this_layer_down, lcp_send_terminate_request }}, + [PPP_LCP_EVENT_TO_POS] = { PPP_LCP_STATE_OPENED, {} }, + [PPP_LCP_EVENT_TO_NEG] = { PPP_LCP_STATE_OPENED, {} }, + [PPP_LCP_EVENT_RCR_POS] = { PPP_LCP_STATE_ACK_SENT, + { lcp_this_layer_down, lcp_send_terminate_request, lcp_send_configure_ack }}, + [PPP_LCP_EVENT_RCR_NEG] = { PPP_LCP_STATE_REQ_SENT, + { lcp_this_layer_down, lcp_send_configure_request, lcp_send_configure_nack }}, + [PPP_LCP_EVENT_RCA] = { PPP_LCP_STATE_REQ_SENT, { lcp_this_layer_down, lcp_send_terminate_request } }, + [PPP_LCP_EVENT_RCN] = { PPP_LCP_STATE_REQ_SENT, { lcp_this_layer_down, lcp_send_terminate_request } }, + [PPP_LCP_EVENT_RTR] = { PPP_LCP_STATE_STOPPING, { lcp_this_layer_down, lcp_zero_restart_count, lcp_send_terminate_ack} }, + [PPP_LCP_EVENT_RTA] = { PPP_LCP_STATE_REQ_SENT, { lcp_this_layer_down, lcp_send_terminate_request} }, + [PPP_LCP_EVENT_RUC] = { PPP_LCP_STATE_OPENED, { lcp_send_code_reject } }, + [PPP_LCP_EVENT_RXJ_POS] = { PPP_LCP_STATE_OPENED, { } }, + [PPP_LCP_EVENT_RXJ_NEG] = { PPP_LCP_STATE_STOPPING, + {lcp_this_layer_down, lcp_send_terminate_request}}, + [PPP_LCP_EVENT_RXR] = { PPP_LCP_STATE_OPENED, { lcp_send_echo_reply} } + } +}; + +static void evaluate_lcp_state(struct pico_device_ppp *ppp, enum ppp_lcp_event event) +{ + const struct pico_ppp_fsm *fsm, *next_fsm_to; + int i; + if (!ppp) + return; + + if (mock_lcp_state) { + mock_lcp_state(ppp, event); + return; + } + + fsm = &ppp_lcp_fsm[ppp->lcp_state][event]; + ppp->lcp_state = (enum ppp_lcp_state)fsm->next_state; + /* RFC1661: The states in which the Restart timer is running are identifiable by + * the presence of TO events. + */ + next_fsm_to = &ppp_lcp_fsm[ppp->lcp_state][PPP_LCP_EVENT_TO_POS]; + if (!next_fsm_to->event_handler[0]) { + /* The Restart timer is stopped when transitioning + * from any state where the timer is running to a state where the timer + * is not running. + */ + lcp_timer_stop(ppp, PPP_TIMER_ON_LCPREQ); + lcp_timer_stop(ppp, PPP_TIMER_ON_LCPTERM); + } + + for (i = 0; i < PPP_FSM_MAX_ACTIONS; i++) { + if (fsm->event_handler[i]) + fsm->event_handler[i](ppp); + } +} + +static void auth(struct pico_device_ppp *ppp) +{ + ppp_dbg("PPP: Authenticated.\n"); + ppp->ipcp_allowed_fields = 0xFFFF; + evaluate_ipcp_state(ppp, PPP_IPCP_EVENT_UP); +} + +static void deauth(struct pico_device_ppp *ppp) +{ + ppp_dbg("PPP: De-authenticated.\n"); + evaluate_ipcp_state(ppp, PPP_IPCP_EVENT_DOWN); +} + +static void auth_abort(struct pico_device_ppp *ppp) +{ + ppp_dbg("PPP: Authentication failed!\n"); + ppp->timer_on = (uint8_t) (ppp->timer_on & (~PPP_TIMER_ON_AUTH)); + evaluate_lcp_state(ppp, PPP_LCP_EVENT_CLOSE); + +} + +static void auth_req(struct pico_device_ppp *ppp) +{ + uint16_t ppp_usr_len = 0; + uint16_t ppp_pwd_len = 0; + uint8_t *req = NULL, *p; + struct pico_pap_hdr *hdr; + uint16_t pap_len = 0; + uint8_t field_len = 0; + ppp_usr_len = (uint16_t)strlen(ppp->username); + ppp_pwd_len = (uint16_t)strlen(ppp->password); + + pap_len = (uint16_t)(sizeof(struct pico_pap_hdr) + 1u + 1u + ppp_usr_len + ppp_pwd_len); + + req = PICO_ZALLOC(PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE + pap_len + PPP_FCS_SIZE + 1); + if (!req) + return; + + hdr = (struct pico_pap_hdr *) (req + PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE); + + hdr->code = PAP_AUTH_REQ; + hdr->id = ppp->frame_id++; + hdr->len = short_be(pap_len); + + p = req + PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE + sizeof(struct pico_pap_hdr); + + /* Populate authentication domain */ + field_len = (uint8_t)(ppp_usr_len & 0xFF); + *p = field_len; + ++p; + if (ppp_usr_len > 0) { + memcpy(p, ppp->username, ppp_usr_len); + p += ppp_usr_len; + } + + /* Populate authentication password */ + field_len = (uint8_t)(ppp_pwd_len & 0xFF); + *p = field_len; + ++p; + if (ppp_pwd_len > 0) { + memcpy(p, ppp->password, ppp_pwd_len); + p += ppp_pwd_len; + } + ppp_dbg("PAP: Sending authentication request.\n"); + pico_ppp_ctl_send(&ppp->dev, PPP_PROTO_PAP, + req, /* Start of PPP packet */ + (uint32_t)( + PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE + /* PPP Header, etc. */ + pap_len + /* Authentication packet len */ + PPP_FCS_SIZE + /* FCS */ + 1) /* STOP Byte */ + ); + PICO_FREE(req); +} + +static void auth_rsp(struct pico_device_ppp *ppp) +{ + struct pico_chap_hdr *ch = (struct pico_chap_hdr *)ppp->pkt; + uint8_t resp[PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE + sizeof(struct pico_chap_hdr) + CHAP_MD5_SIZE + PPP_FCS_SIZE + 2]; + struct pico_chap_hdr *rh = (struct pico_chap_hdr *) (resp + PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE); + uint8_t *md5resp = resp + PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE + sizeof(struct pico_chap_hdr) + 1; + uint8_t *md5resp_len = resp + PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE + sizeof(struct pico_chap_hdr); + uint8_t *challenge; + uint32_t i = 0, pwdlen; + uint8_t *recvd_challenge_len = ppp->pkt + sizeof(struct pico_chap_hdr); + uint8_t *recvd_challenge = recvd_challenge_len + 1; + size_t challenge_size = CHALLENGE_SIZE(ppp, ch); + + challenge = PICO_ZALLOC(challenge_size); + + if (!challenge) + return; + + + pwdlen = (uint32_t)strlen(ppp->password); + challenge[i++] = ch->id; + memcpy(challenge + i, ppp->password, pwdlen); + i += pwdlen; + memcpy(challenge + i, recvd_challenge, *recvd_challenge_len); + i += *recvd_challenge_len; + pico_md5sum(md5resp, challenge, i); + PICO_FREE(challenge); + rh->id = ch->id; + rh->code = CHAP_RESPONSE; + rh->len = short_be(CHAP_MD5_SIZE + sizeof(struct pico_chap_hdr) + 1); + *md5resp_len = CHAP_MD5_SIZE; + ppp_dbg("Sending CHAP RESPONSE, \n"); + pico_ppp_ctl_send(&ppp->dev, PPP_PROTO_CHAP, + resp, /* Start of PPP packet */ + (uint32_t)( + PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE + /* PPP Header, etc. */ + sizeof(struct pico_chap_hdr) + /* CHAP HDR */ + 1 + /* Value length */ + CHAP_MD5_SIZE + /* Actual payload size */ + PPP_FCS_SIZE + /* FCS at the end of the frame */ + 1) /* STOP Byte */ + ); +} + +static void auth_start_timer(struct pico_device_ppp *ppp) +{ + ppp->timer_on = ppp->timer_on | PPP_TIMER_ON_AUTH; + ppp->timer_val = PICO_PPP_DEFAULT_TIMER; +} + +static const struct pico_ppp_fsm ppp_auth_fsm[PPP_AUTH_STATE_MAX][PPP_AUTH_EVENT_MAX] = { + [PPP_AUTH_STATE_INITIAL] = { + [PPP_AUTH_EVENT_UP_NONE] = { PPP_AUTH_STATE_AUTHENTICATED, {auth} }, + [PPP_AUTH_EVENT_UP_PAP] = { PPP_AUTH_STATE_REQ_SENT, {auth_req, auth_start_timer} }, + [PPP_AUTH_EVENT_UP_CHAP] = { PPP_AUTH_STATE_STARTING, {} }, + [PPP_AUTH_EVENT_DOWN] = { PPP_AUTH_STATE_INITIAL, {} }, + [PPP_AUTH_EVENT_RAC] = { PPP_AUTH_STATE_INITIAL, {} }, + [PPP_AUTH_EVENT_RAA] = { PPP_AUTH_STATE_INITIAL, {} }, + [PPP_AUTH_EVENT_RAN] = { PPP_AUTH_STATE_INITIAL, {auth_abort} }, + [PPP_AUTH_EVENT_TO] = { PPP_AUTH_STATE_INITIAL, {} } + }, + [PPP_AUTH_STATE_STARTING] = { + [PPP_AUTH_EVENT_UP_NONE] = { PPP_AUTH_STATE_STARTING, {} }, + [PPP_AUTH_EVENT_UP_PAP] = { PPP_AUTH_STATE_STARTING, {} }, + [PPP_AUTH_EVENT_UP_CHAP] = { PPP_AUTH_STATE_STARTING, {} }, + [PPP_AUTH_EVENT_DOWN] = { PPP_AUTH_STATE_INITIAL, {deauth} }, + [PPP_AUTH_EVENT_RAC] = { PPP_AUTH_STATE_RSP_SENT, {auth_rsp, auth_start_timer} }, + [PPP_AUTH_EVENT_RAA] = { PPP_AUTH_STATE_STARTING, {auth_start_timer} }, + [PPP_AUTH_EVENT_RAN] = { PPP_AUTH_STATE_STARTING, {auth_abort} }, + [PPP_AUTH_EVENT_TO] = { PPP_AUTH_STATE_INITIAL, {auth_req, auth_start_timer} } + }, + [PPP_AUTH_STATE_RSP_SENT] = { + [PPP_AUTH_EVENT_UP_NONE] = { PPP_AUTH_STATE_RSP_SENT, {} }, + [PPP_AUTH_EVENT_UP_PAP] = { PPP_AUTH_STATE_RSP_SENT, {} }, + [PPP_AUTH_EVENT_UP_CHAP] = { PPP_AUTH_STATE_RSP_SENT, {} }, + [PPP_AUTH_EVENT_DOWN] = { PPP_AUTH_STATE_INITIAL, {deauth} }, + [PPP_AUTH_EVENT_RAC] = { PPP_AUTH_STATE_RSP_SENT, {auth_rsp, auth_start_timer} }, + [PPP_AUTH_EVENT_RAA] = { PPP_AUTH_STATE_AUTHENTICATED, {auth} }, + [PPP_AUTH_EVENT_RAN] = { PPP_AUTH_STATE_STARTING, {auth_abort} }, + [PPP_AUTH_EVENT_TO] = { PPP_AUTH_STATE_STARTING, {auth_start_timer} } + }, + [PPP_AUTH_STATE_REQ_SENT] = { + [PPP_AUTH_EVENT_UP_NONE] = { PPP_AUTH_STATE_REQ_SENT, {} }, + [PPP_AUTH_EVENT_UP_PAP] = { PPP_AUTH_STATE_REQ_SENT, {} }, + [PPP_AUTH_EVENT_UP_CHAP] = { PPP_AUTH_STATE_REQ_SENT, {} }, + [PPP_AUTH_EVENT_DOWN] = { PPP_AUTH_STATE_INITIAL, {deauth} }, + [PPP_AUTH_EVENT_RAC] = { PPP_AUTH_STATE_REQ_SENT, {} }, + [PPP_AUTH_EVENT_RAA] = { PPP_AUTH_STATE_AUTHENTICATED, {auth} }, + [PPP_AUTH_EVENT_RAN] = { PPP_AUTH_STATE_REQ_SENT, {auth_abort} }, + [PPP_AUTH_EVENT_TO] = { PPP_AUTH_STATE_REQ_SENT, {auth_req, auth_start_timer} } + }, + [PPP_AUTH_STATE_AUTHENTICATED] = { + [PPP_AUTH_EVENT_UP_NONE] = { PPP_AUTH_STATE_AUTHENTICATED, {} }, + [PPP_AUTH_EVENT_UP_PAP] = { PPP_AUTH_STATE_AUTHENTICATED, {} }, + [PPP_AUTH_EVENT_UP_CHAP] = { PPP_AUTH_STATE_AUTHENTICATED, {} }, + [PPP_AUTH_EVENT_DOWN] = { PPP_AUTH_STATE_INITIAL, {deauth} }, + [PPP_AUTH_EVENT_RAC] = { PPP_AUTH_STATE_RSP_SENT, {auth_rsp} }, + [PPP_AUTH_EVENT_RAA] = { PPP_AUTH_STATE_AUTHENTICATED, {} }, + [PPP_AUTH_EVENT_RAN] = { PPP_AUTH_STATE_AUTHENTICATED, {} }, + [PPP_AUTH_EVENT_TO] = { PPP_AUTH_STATE_AUTHENTICATED, {} }, + } +}; + +static void evaluate_auth_state(struct pico_device_ppp *ppp, enum ppp_auth_event event) +{ + const struct pico_ppp_fsm *fsm; + int i; + if (mock_auth_state) { + mock_auth_state(ppp, event); + return; + } + + fsm = &ppp_auth_fsm[ppp->auth_state][event]; + + ppp->auth_state = (enum ppp_auth_state)fsm->next_state; + for (i = 0; i < PPP_FSM_MAX_ACTIONS; i++) { + if (fsm->event_handler[i]) + fsm->event_handler[i](ppp); + } +} + +static void ipcp_send_nack(struct pico_device_ppp *ppp) +{ + IGNORE_PARAMETER(ppp); +} + +static void ipcp_bring_up(struct pico_device_ppp *ppp) +{ + ppp_dbg("PPP: IPCP up.\n"); + + if (ppp->ipcp_ip) { + char my_ip[16], my_dns[16]; + pico_ipv4_to_string(my_ip, ppp->ipcp_ip); + ppp_dbg("Received IP config %s\n", my_ip); + pico_ipv4_to_string(my_dns, ppp->ipcp_dns1); + ppp_dbg("Received DNS: %s\n", my_dns); + ppp_ipv4_conf(ppp); + } +} + +static void ipcp_bring_down(struct pico_device_ppp *ppp) +{ + IGNORE_PARAMETER(ppp); + + ppp_dbg("PPP: IPCP down.\n"); +} + +static void ipcp_start_timer(struct pico_device_ppp *ppp) +{ + ppp->timer_on = ppp->timer_on | PPP_TIMER_ON_IPCP; + ppp->timer_val = PICO_PPP_DEFAULT_TIMER * PICO_PPP_DEFAULT_MAX_FAILURE; +} + +static const struct pico_ppp_fsm ppp_ipcp_fsm[PPP_IPCP_STATE_MAX][PPP_IPCP_EVENT_MAX] = { + [PPP_IPCP_STATE_INITIAL] = { + [PPP_IPCP_EVENT_UP] = { PPP_IPCP_STATE_REQ_SENT, {ipcp_send_req, ipcp_start_timer} }, + [PPP_IPCP_EVENT_DOWN] = { PPP_IPCP_STATE_INITIAL, {} }, + [PPP_IPCP_EVENT_RCR_POS] = { PPP_IPCP_STATE_INITIAL, {} }, + [PPP_IPCP_EVENT_RCR_NEG] = { PPP_IPCP_STATE_INITIAL, {} }, + [PPP_IPCP_EVENT_RCA] = { PPP_IPCP_STATE_INITIAL, {} }, + [PPP_IPCP_EVENT_RCN] = { PPP_IPCP_STATE_INITIAL, {} }, + [PPP_IPCP_EVENT_TO] = { PPP_IPCP_STATE_INITIAL, {} } + }, + [PPP_IPCP_STATE_REQ_SENT] = { + [PPP_IPCP_EVENT_UP] = { PPP_IPCP_STATE_REQ_SENT, {} }, + [PPP_IPCP_EVENT_DOWN] = { PPP_IPCP_STATE_INITIAL, {} }, + [PPP_IPCP_EVENT_RCR_POS] = { PPP_IPCP_STATE_ACK_SENT, {ipcp_send_ack} }, + [PPP_IPCP_EVENT_RCR_NEG] = { PPP_IPCP_STATE_REQ_SENT, {ipcp_send_nack} }, + [PPP_IPCP_EVENT_RCA] = { PPP_IPCP_STATE_ACK_RCVD, {} }, + [PPP_IPCP_EVENT_RCN] = { PPP_IPCP_STATE_REQ_SENT, {ipcp_send_req, ipcp_start_timer} }, + [PPP_IPCP_EVENT_TO] = { PPP_IPCP_STATE_REQ_SENT, {ipcp_send_req, ipcp_start_timer} } + }, + [PPP_IPCP_STATE_ACK_RCVD] = { + [PPP_IPCP_EVENT_UP] = { PPP_IPCP_STATE_ACK_RCVD, {} }, + [PPP_IPCP_EVENT_DOWN] = { PPP_IPCP_STATE_INITIAL, {} }, + [PPP_IPCP_EVENT_RCR_POS] = { PPP_IPCP_STATE_OPENED, {ipcp_send_ack, ipcp_bring_up} }, + [PPP_IPCP_EVENT_RCR_NEG] = { PPP_IPCP_STATE_ACK_RCVD, {ipcp_send_nack} }, + [PPP_IPCP_EVENT_RCA] = { PPP_IPCP_STATE_REQ_SENT, {ipcp_send_req, ipcp_start_timer} }, + [PPP_IPCP_EVENT_RCN] = { PPP_IPCP_STATE_REQ_SENT, {ipcp_send_req, ipcp_start_timer} }, + [PPP_IPCP_EVENT_TO] = { PPP_IPCP_STATE_ACK_RCVD, {ipcp_send_req, ipcp_start_timer} } + }, + [PPP_IPCP_STATE_ACK_SENT] = { + [PPP_IPCP_EVENT_UP] = { PPP_IPCP_STATE_ACK_SENT, {} }, + [PPP_IPCP_EVENT_DOWN] = { PPP_IPCP_STATE_INITIAL, {} }, + [PPP_IPCP_EVENT_RCR_POS] = { PPP_IPCP_STATE_ACK_SENT, {ipcp_send_ack} }, + [PPP_IPCP_EVENT_RCR_NEG] = { PPP_IPCP_STATE_REQ_SENT, {ipcp_send_nack} }, + [PPP_IPCP_EVENT_RCA] = { PPP_IPCP_STATE_OPENED, {ipcp_bring_up} }, + [PPP_IPCP_EVENT_RCN] = { PPP_IPCP_STATE_ACK_SENT, {ipcp_send_req, ipcp_start_timer} }, + [PPP_IPCP_EVENT_TO] = { PPP_IPCP_STATE_ACK_SENT, {ipcp_send_req, ipcp_start_timer} } + }, + [PPP_IPCP_STATE_OPENED] = { + [PPP_IPCP_EVENT_UP] = { PPP_IPCP_STATE_OPENED, {} }, + [PPP_IPCP_EVENT_DOWN] = { PPP_IPCP_STATE_INITIAL, {ipcp_bring_down} }, + [PPP_IPCP_EVENT_RCR_POS] = { PPP_IPCP_STATE_ACK_SENT, {ipcp_bring_down, ipcp_send_req, ipcp_send_ack} }, + [PPP_IPCP_EVENT_RCR_NEG] = { PPP_IPCP_STATE_REQ_SENT, {ipcp_bring_down, ipcp_send_req, ipcp_send_nack} }, + [PPP_IPCP_EVENT_RCA] = { PPP_IPCP_STATE_REQ_SENT, {ipcp_send_req} }, + [PPP_IPCP_EVENT_RCN] = { PPP_IPCP_STATE_REQ_SENT, {ipcp_send_req} }, + [PPP_IPCP_EVENT_TO] = { PPP_IPCP_STATE_OPENED, {} } + } +}; + +static void evaluate_ipcp_state(struct pico_device_ppp *ppp, enum ppp_ipcp_event event) +{ + const struct pico_ppp_fsm *fsm; + int i; + if (mock_ipcp_state) { + mock_ipcp_state(ppp, event); + return; + } + + fsm = &ppp_ipcp_fsm[ppp->ipcp_state][event]; + + ppp->ipcp_state = (enum ppp_ipcp_state)fsm->next_state; + for (i = 0; i < PPP_FSM_MAX_ACTIONS; i++) { + if (fsm->event_handler[i]) + fsm->event_handler[i](ppp); + } +} + +static int pico_ppp_poll(struct pico_device *dev, int loop_score) +{ + struct pico_device_ppp *ppp = (struct pico_device_ppp *) dev; + static uint32_t len = 0; + int r; + if (ppp->serial_recv) { + do { + r = ppp->serial_recv(&ppp->dev, &ppp_recv_buf[len], 1); + if (r <= 0) + break; + + if (ppp->modem_state == PPP_MODEM_STATE_CONNECTED) { + static int control_escape = 0; + + if (ppp_recv_buf[len] == PPPF_FLAG_SEQ) { + if (control_escape) { + /* Illegal sequence, discard frame */ + ppp_dbg("Illegal sequence, ppp_recv_buf[%d] = %d\n", len, ppp_recv_buf[len]); + control_escape = 0; + len = 0; + } + + if (len > 1) { + ppp_recv_data(ppp, ppp_recv_buf, len); + loop_score--; + len = 0; + } + } else if (control_escape) { + ppp_recv_buf[len] ^= 0x20; + control_escape = 0; + len++; + } else if (ppp_recv_buf[len] == PPPF_CTRL_ESC) { + control_escape = 1; + } else { + len++; + } + } else { + static int s3 = 0; + + if (ppp_recv_buf[len] == AT_S3) { + s3 = 1; + if (len > 0) { + ppp_recv_buf[len] = '\0'; + ppp_modem_recv(ppp, ppp_recv_buf, len); + len = 0; + } + } else if (ppp_recv_buf[len] == AT_S4) { + if (!s3) { + len++; + } + + s3 = 0; + } else { + s3 = 0; + len++; + } + } + } while ((r > 0) && (len < ARRAY_SIZE(ppp_recv_buf)) && (loop_score > 0)); + } + + return loop_score; +} + +/* Public interface: create/destroy. */ + +static int pico_ppp_link_state(struct pico_device *dev) +{ + struct pico_device_ppp *ppp = (struct pico_device_ppp *)dev; + if (ppp->ipcp_state == PPP_IPCP_STATE_OPENED) + return 1; + + return 0; +} + +void pico_ppp_destroy(struct pico_device *ppp) +{ + if (!ppp) + return; + + /* Perform custom cleanup here before calling 'pico_device_destroy' + * or register a custom cleanup function during initialization + * by setting 'ppp->dev.destroy'. */ + + pico_device_destroy(ppp); +} + +static void check_to_modem(struct pico_device_ppp *ppp) +{ + if (ppp->timer_on & PPP_TIMER_ON_MODEM) { + if (ppp->timer_val == 0) { + ppp->timer_on = (uint8_t) (ppp->timer_on & (~PPP_TIMER_ON_MODEM)); + evaluate_modem_state(ppp, PPP_MODEM_EVENT_TIMEOUT); + } + } +} + +static void check_to_lcp(struct pico_device_ppp *ppp) +{ + if (ppp->timer_on & (PPP_TIMER_ON_LCPREQ | PPP_TIMER_ON_LCPTERM)) { + if (ppp->timer_val == 0) { + if (ppp->timer_count == 0) + evaluate_lcp_state(ppp, PPP_LCP_EVENT_TO_NEG); + else{ + evaluate_lcp_state(ppp, PPP_LCP_EVENT_TO_POS); + ppp->timer_count--; + } + } + } +} + +static void check_to_auth(struct pico_device_ppp *ppp) +{ + if (ppp->timer_on & PPP_TIMER_ON_AUTH) { + if (ppp->timer_val == 0) { + ppp->timer_on = (uint8_t) (ppp->timer_on & (~PPP_TIMER_ON_AUTH)); + evaluate_auth_state(ppp, PPP_AUTH_EVENT_TO); + } + } +} + +static void check_to_ipcp(struct pico_device_ppp *ppp) +{ + if (ppp->timer_on & PPP_TIMER_ON_IPCP) { + if (ppp->timer_val == 0) { + ppp->timer_on = (uint8_t) (ppp->timer_on & (~PPP_TIMER_ON_IPCP)); + evaluate_ipcp_state(ppp, PPP_IPCP_EVENT_TO); + } + } +} + +static void pico_ppp_tick(pico_time t, void *arg) +{ + struct pico_device_ppp *ppp = (struct pico_device_ppp *) arg; + (void)t; + if (ppp->timer_val > 0) + ppp->timer_val--; + + check_to_modem(ppp); + check_to_lcp(ppp); + check_to_auth(ppp); + check_to_ipcp(ppp); + + if (ppp->autoreconnect && ppp->lcp_state == PPP_LCP_STATE_INITIAL) { + ppp_dbg("(Re)connecting...\n"); + evaluate_lcp_state(ppp, PPP_LCP_EVENT_OPEN); + } + + pico_timer_add(1000, pico_ppp_tick, arg); +} + +struct pico_device *pico_ppp_create(void) +{ + struct pico_device_ppp *ppp = PICO_ZALLOC(sizeof(struct pico_device_ppp)); + char devname[MAX_DEVICE_NAME]; + + if (!ppp) + return NULL; + + snprintf(devname, MAX_DEVICE_NAME, "ppp%d", ppp_devnum++); + + if( 0 != pico_device_init((struct pico_device *)ppp, devname, NULL)) { + return NULL; + } + + ppp->dev.overhead = PPP_HDR_SIZE; + ppp->dev.mtu = PICO_PPP_MTU; + ppp->dev.send = pico_ppp_send; + ppp->dev.poll = pico_ppp_poll; + ppp->dev.link_state = pico_ppp_link_state; + ppp->frame_id = (uint8_t)(pico_rand() % 0xFF); + + ppp->modem_state = PPP_MODEM_STATE_INITIAL; + ppp->lcp_state = PPP_LCP_STATE_INITIAL; + ppp->auth_state = PPP_AUTH_STATE_INITIAL; + ppp->ipcp_state = PPP_IPCP_STATE_INITIAL; + + ppp->timer = pico_timer_add(1000, pico_ppp_tick, ppp); + ppp->mru = PICO_PPP_MRU; + + LCPOPT_SET_LOCAL(ppp, LCPOPT_MRU); + LCPOPT_SET_LOCAL(ppp, LCPOPT_AUTH); /* We support authentication, even if it's not part of the req */ + LCPOPT_SET_LOCAL(ppp, LCPOPT_PROTO_COMP); + LCPOPT_SET_LOCAL(ppp, LCPOPT_ADDRCTL_COMP); + + + ppp_dbg("Device %s created.\n", ppp->dev.name); + return (struct pico_device *)ppp; +} + +int pico_ppp_connect(struct pico_device *dev) +{ + struct pico_device_ppp *ppp = (struct pico_device_ppp *)dev; + ppp->autoreconnect = 1; + return 0; +} + +int pico_ppp_disconnect(struct pico_device *dev) +{ + struct pico_device_ppp *ppp = (struct pico_device_ppp *)dev; + ppp->autoreconnect = 0; + evaluate_lcp_state(ppp, PPP_LCP_EVENT_CLOSE); + return 0; +} + +int pico_ppp_set_serial_read(struct pico_device *dev, int (*sread)(struct pico_device *, void *, int)) +{ + struct pico_device_ppp *ppp = (struct pico_device_ppp *)dev; + + if (!dev) + return -1; + + ppp->serial_recv = sread; + return 0; +} + +int pico_ppp_set_serial_write(struct pico_device *dev, int (*swrite)(struct pico_device *, const void *, int)) +{ + struct pico_device_ppp *ppp = (struct pico_device_ppp *)dev; + + if (!dev) + return -1; + + ppp->serial_send = swrite; + return 0; +} + +int pico_ppp_set_serial_set_speed(struct pico_device *dev, int (*sspeed)(struct pico_device *, uint32_t)) +{ + struct pico_device_ppp *ppp = (struct pico_device_ppp *)dev; + + if (!dev) + return -1; + + ppp->serial_set_speed = sspeed; + return 0; +} + +int pico_ppp_set_apn(struct pico_device *dev, const char *apn) +{ + struct pico_device_ppp *ppp = (struct pico_device_ppp *)dev; + + if (!dev) + return -1; + + if (!apn) + return -1; + + strncpy(ppp->apn, apn, sizeof(ppp->apn) - 1); + return 0; +} + +int pico_ppp_set_username(struct pico_device *dev, const char *username) +{ + struct pico_device_ppp *ppp = (struct pico_device_ppp *)dev; + + if (!dev) + return -1; + + if (!username) + return -1; + + strncpy(ppp->username, username, sizeof(ppp->username) - 1); + return 0; +} + +int pico_ppp_set_password(struct pico_device *dev, const char *password) +{ + struct pico_device_ppp *ppp = (struct pico_device_ppp *)dev; + + if (!dev) + return -1; + + if (!password) + return -1; + + strncpy(ppp->password, password, sizeof(ppp->password) - 1); + return 0; +} diff --git a/ext/picotcp/modules/pico_dev_ppp.h b/ext/picotcp/modules/pico_dev_ppp.h new file mode 100644 index 0000000..6110e50 --- /dev/null +++ b/ext/picotcp/modules/pico_dev_ppp.h @@ -0,0 +1,26 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. + See LICENSE and COPYING for usage. + + *********************************************************************/ +#ifndef INCLUDE_PICO_PPP +#define INCLUDE_PICO_PPP + +#include "pico_config.h" +#include "pico_device.h" + +void pico_ppp_destroy(struct pico_device *ppp); +struct pico_device *pico_ppp_create(void); + +int pico_ppp_connect(struct pico_device *dev); +int pico_ppp_disconnect(struct pico_device *dev); + +int pico_ppp_set_serial_read(struct pico_device *dev, int (*sread)(struct pico_device *, void *, int)); +int pico_ppp_set_serial_write(struct pico_device *dev, int (*swrite)(struct pico_device *, const void *, int)); +int pico_ppp_set_serial_set_speed(struct pico_device *dev, int (*sspeed)(struct pico_device *, uint32_t)); + +int pico_ppp_set_apn(struct pico_device *dev, const char *apn); +int pico_ppp_set_username(struct pico_device *dev, const char *username); +int pico_ppp_set_password(struct pico_device *dev, const char *password); + +#endif /* INCLUDE_PICO_PPP */ diff --git a/ext/picotcp/modules/pico_dev_tap.c b/ext/picotcp/modules/pico_dev_tap.c new file mode 100644 index 0000000..b80ea6d --- /dev/null +++ b/ext/picotcp/modules/pico_dev_tap.c @@ -0,0 +1,220 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + Authors: Daniele Lacamera + *********************************************************************/ + + +#include +#include +#include +#include +#include "pico_device.h" +#include "pico_dev_tap.h" +#include "pico_stack.h" + +#ifndef __FreeBSD__ +#include +#endif + +#include + +struct pico_device_tap { + struct pico_device dev; + int fd; +}; + +#define TUN_MTU 2048 + +// We only support one global link state - we only have two USR signals, we +// can't spread these out over an arbitrary amount of devices. When you unplug +// one tap, you unplug all of them. + +static int link_state = 0; + +static void sig_handler(int signo) +{ + if (signo == SIGUSR1) + link_state = 0; + if (signo == SIGUSR2) + link_state = 1; +} + +static int tap_link_state(__attribute__((unused)) struct pico_device *self) +{ + return link_state; +} + + +static int pico_tap_send(struct pico_device *dev, void *buf, int len) +{ + struct pico_device_tap *tap = (struct pico_device_tap *) dev; + return (int)write(tap->fd, buf, (uint32_t)len); +} + +static int pico_tap_poll(struct pico_device *dev, int loop_score) +{ + struct pico_device_tap *tap = (struct pico_device_tap *) dev; + struct pollfd pfd; + unsigned char buf[TUN_MTU]; + int len; + pfd.fd = tap->fd; + pfd.events = POLLIN; + do { + if (poll(&pfd, 1, 0) <= 0) + return loop_score; + + len = (int)read(tap->fd, buf, TUN_MTU); + if (len > 0) { + loop_score--; + pico_stack_recv(dev, buf, (uint32_t)len); + } + } while(loop_score > 0); + return 0; +} + +/* Public interface: create/destroy. */ + +void pico_tap_destroy(struct pico_device *dev) +{ + struct pico_device_tap *tap = (struct pico_device_tap *) dev; + if(tap->fd > 0) + close(tap->fd); +} + +#ifndef __FreeBSD__ +static int tap_open(char *name) +{ + struct ifreq ifr; + int tap_fd; + if((tap_fd = open("/dev/net/tun", O_RDWR)) < 0) { + return(-1); + } + + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_flags = IFF_TAP | IFF_NO_PI; + strncpy(ifr.ifr_name, name, IFNAMSIZ); + if(ioctl(tap_fd, TUNSETIFF, &ifr) < 0) { + return(-1); + } + + return tap_fd; +} +#else +static int tap_open(char *name) +{ + int tap_fd; + (void)name; + tap_fd = open("/dev/tap0", O_RDWR); + return tap_fd; +} +#endif + + +#ifndef __FreeBSD__ +static int tap_get_mac(char *name, uint8_t *mac) +{ + int sck; + struct ifreq eth; + int retval = -1; + + + + + sck = socket(AF_INET, SOCK_DGRAM, 0); + if(sck < 0) { + return retval; + } + + memset(ð, 0, sizeof(struct ifreq)); + strcpy(eth.ifr_name, name); + /* call the IOCTL */ + if (ioctl(sck, SIOCGIFHWADDR, ð) < 0) { + perror("ioctl(SIOCGIFHWADDR)"); + return -1; + ; + } + + memcpy (mac, ð.ifr_hwaddr.sa_data, 6); + + + close(sck); + return 0; + +} +#else +#include +#include +#include +static int tap_get_mac(char *name, uint8_t *mac) +{ + struct sockaddr_dl *sdl; + struct ifaddrs *ifap, *root; + if (getifaddrs(&ifap) != 0) + return -1; + + root = ifap; + while(ifap) { + if (strcmp(name, ifap->ifa_name) == 0) + sdl = (struct sockaddr_dl *) ifap->ifa_addr; + + if (sdl->sdl_type == IFT_ETHER) { + memcpy(mac, LLADDR(sdl), 6); + freeifaddrs(root); + return 0; + } + + ifap = ifap->ifa_next; + } + freeifaddrs(root); + return 0; +} +#endif + +struct pico_device *pico_tap_create(char *name) +{ + struct pico_device_tap *tap = PICO_ZALLOC(sizeof(struct pico_device_tap)); + uint8_t mac[6] = {}; + struct sigaction sa; + + if (!tap) + return NULL; + + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sa.sa_handler = sig_handler; + + if ((sigaction(SIGUSR1, &sa, NULL) == 0) && + (sigaction(SIGUSR2, &sa, NULL) == 0)) + tap->dev.link_state = &tap_link_state; + + tap->dev.overhead = 0; + tap->fd = tap_open(name); + if (tap->fd < 0) { + dbg("Tap creation failed.\n"); + pico_tap_destroy((struct pico_device *)tap); + return NULL; + } + + if (tap_get_mac(name, mac) < 0) { + dbg("Tap mac query failed.\n"); + pico_tap_destroy((struct pico_device *)tap); + return NULL; + } + + mac[5]++; + + if( 0 != pico_device_init((struct pico_device *)tap, name, mac)) { + dbg("Tap init failed.\n"); + pico_tap_destroy((struct pico_device *)tap); + return NULL; + } + + tap->dev.send = pico_tap_send; + tap->dev.poll = pico_tap_poll; + tap->dev.destroy = pico_tap_destroy; + dbg("Device %s created.\n", tap->dev.name); + return (struct pico_device *)tap; +} + diff --git a/ext/picotcp/modules/pico_dev_tap.h b/ext/picotcp/modules/pico_dev_tap.h new file mode 100644 index 0000000..ebc21fe --- /dev/null +++ b/ext/picotcp/modules/pico_dev_tap.h @@ -0,0 +1,15 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + *********************************************************************/ +#ifndef INCLUDE_PICO_TAP +#define INCLUDE_PICO_TAP +#include "pico_config.h" +#include "pico_device.h" + +void pico_tap_destroy(struct pico_device *tap); +struct pico_device *pico_tap_create(char *name); + +#endif + diff --git a/ext/picotcp/modules/pico_dev_tap_windows.c b/ext/picotcp/modules/pico_dev_tap_windows.c new file mode 100644 index 0000000..2e003f8 --- /dev/null +++ b/ext/picotcp/modules/pico_dev_tap_windows.c @@ -0,0 +1,1083 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2014-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + Authors: Maxime Vincent + Based on the OpenVPN tun.c driver, under GPL + + NOTES: This is the Windows-only driver, a Linux-equivalent is available, too + You need to have an OpenVPN TUN/TAP network adapter installed, first + This driver is barely working: + * Only TAP-mode is supported (TUN is not) + * it will simply open the first TAP device it can find + * there is memory being allocated that's never freed + * there is no destroy function, yet + * it has only been tested on a Windows 7 machine + *********************************************************************/ + +#include "pico_device.h" +#include "pico_dev_null.h" +#include "pico_stack.h" +#include "pico_dev_tap_windows.h" + +#include +#include +#include +#include "pico_dev_tap_windows_private.h" + +/* + * Debugging info + */ +#define dbg_tap_info(...) /* tap info messages */ +#define dbg_tap(...) /* first level debug */ +#define dbg_win32(...) /* second level detailed win32 debug */ +#define dbg_reg(...) /* third level: registry debug */ + +/* + * Tunnel types + */ +#define DEV_TYPE_UNDEF 0 +#define DEV_TYPE_NULL 1 +#define DEV_TYPE_TUN 2 /* point-to-point IP tunnel */ +#define DEV_TYPE_TAP 3 /* ethernet (802.3) tunnel */ + + +/* + * We try to do all Win32 I/O using overlapped + * (i.e. asynchronous) I/O for a performance win. + */ +struct overlapped_io { +# define IOSTATE_INITIAL 0 +# define IOSTATE_QUEUED 1 /* overlapped I/O has been queued */ +# define IOSTATE_IMMEDIATE_RETURN 2 /* I/O function returned immediately without queueing */ + int iostate; + OVERLAPPED overlapped; + DWORD size; + DWORD flags; + int status; + int addr_defined; + uint8_t *buf_init; + uint32_t buf_init_len; + uint8_t *buf; + uint32_t buf_len; +}; + +struct rw_handle { + HANDLE read; + HANDLE write; +}; + +struct tuntap +{ + int type; /* DEV_TYPE_x as defined in proto.h */ + int ipv6; + int persistent_if; /* if existed before, keep on program end */ + char *actual_name; /* actual name of TUN/TAP dev, usually including unit number */ + int post_open_mtu; + uint8_t mac[6]; + + /* Windows stuff */ + DWORD adapter_index; /*adapter index for TAP-Windows adapter, ~0 if undefined */ + HANDLE hand; + struct overlapped_io reads; /* for overlapped IO */ + struct overlapped_io writes; + struct rw_handle rw_handle; + +}; + + +struct pico_device_tap { + struct pico_device dev; + int statistics_frames_out; + struct tuntap *tt; +}; + + +/* + * Private function prototypes + */ +const struct tap_reg *get_tap_reg (void); +const struct panel_reg *get_panel_reg (void); + + +/* + * Private functions + */ + +/* Get TAP info from Windows registry */ +const struct tap_reg *get_tap_reg (void) +{ + HKEY adapter_key; + LONG status; + DWORD len; + struct tap_reg *first = NULL; + struct tap_reg *last = NULL; + int i = 0; + + status = RegOpenKeyEx( + HKEY_LOCAL_MACHINE, + ADAPTER_KEY, + 0, + KEY_READ, + &adapter_key); + + if (status != ERROR_SUCCESS) + { + dbg_reg("Error opening registry key: %s\n", ADAPTER_KEY); + return NULL; + } + + while (1) + { + char enum_name[256]; + char unit_string[256]; + HKEY unit_key; + char component_id_string[] = "ComponentId"; + char component_id[256]; + char net_cfg_instance_id_string[] = "NetCfgInstanceId"; + char net_cfg_instance_id[256]; + DWORD data_type; + + len = sizeof (enum_name); + status = RegEnumKeyEx( + adapter_key, + i, + enum_name, + &len, + NULL, + NULL, + NULL, + NULL); + if (status == ERROR_NO_MORE_ITEMS) + break; + else if (status != ERROR_SUCCESS) + dbg_reg("Error enumerating registry subkeys of key: %s.\n", ADAPTER_KEY); + + snprintf (unit_string, sizeof(unit_string), "%s\\%s", + ADAPTER_KEY, enum_name); + + status = RegOpenKeyEx( + HKEY_LOCAL_MACHINE, + unit_string, + 0, + KEY_READ, + &unit_key); + + if (status != ERROR_SUCCESS) + { + dbg_reg("Error opening registry key: %s\n", unit_string); + } + else + { + len = sizeof (component_id); + status = RegQueryValueEx( + unit_key, + component_id_string, + NULL, + &data_type, + (LPBYTE)component_id, + &len); + + if (status != ERROR_SUCCESS || data_type != REG_SZ) + { + dbg_reg("Error opening registry key: %s\\%s\n", unit_string, component_id_string); + } + else + { + len = sizeof (net_cfg_instance_id); + status = RegQueryValueEx( + unit_key, + net_cfg_instance_id_string, + NULL, + &data_type, + (LPBYTE)net_cfg_instance_id, + &len); + + if (status == ERROR_SUCCESS && data_type == REG_SZ) + { + if (!strcmp (component_id, TAP_WIN_COMPONENT_ID)) + { + struct tap_reg *reg; + reg = PICO_ZALLOC(sizeof(struct tap_reg), 1); + /* ALLOC_OBJ_CLEAR_GC (reg, struct tap_reg, gc); */ + if (!reg) + return NULL; + + /* reg->guid = string_alloc (net_cfg_instance_id, gc); */ + reg->guid = PICO_ZALLOC (strlen(net_cfg_instance_id) + 1, 1); + if (!(reg->guid)) + { + PICO_FREE(reg); + return NULL; + } + + strcpy((char *)reg->guid, net_cfg_instance_id); + /* link into return list */ + if (!first) + first = reg; + + if (last) + last->next = reg; + + last = reg; + } + } + } + + RegCloseKey (unit_key); + } + + ++i; + } + RegCloseKey (adapter_key); + return first; +} + +/* Get Panel info from Windows registry */ +const struct panel_reg *get_panel_reg (void) +{ + LONG status; + HKEY network_connections_key; + DWORD len; + struct panel_reg *first = NULL; + struct panel_reg *last = NULL; + int i = 0; + + status = RegOpenKeyEx( + HKEY_LOCAL_MACHINE, + NETWORK_CONNECTIONS_KEY, + 0, + KEY_READ, + &network_connections_key); + + if (status != ERROR_SUCCESS) + { + dbg_reg("Error opening registry key: %s\n", NETWORK_CONNECTIONS_KEY); + return NULL; + } + + while (1) + { + char enum_name[256]; + char connection_string[256]; + HKEY connection_key; + WCHAR name_data[256]; + DWORD name_type; + const WCHAR name_string[] = L"Name"; + + len = sizeof (enum_name); + status = RegEnumKeyEx( + network_connections_key, + i, + enum_name, + &len, + NULL, + NULL, + NULL, + NULL); + if (status == ERROR_NO_MORE_ITEMS) + break; + else if (status != ERROR_SUCCESS) + dbg_reg("Error enumerating registry subkeys of key: %s.\n", NETWORK_CONNECTIONS_KEY); + + snprintf (connection_string, sizeof(connection_string), "%s\\%s\\Connection", NETWORK_CONNECTIONS_KEY, enum_name); + + status = RegOpenKeyEx( + HKEY_LOCAL_MACHINE, + connection_string, + 0, + KEY_READ, + &connection_key); + if (status != ERROR_SUCCESS) + dbg_reg("Error opening registry key: %s\n", connection_string); + else + { + len = sizeof (name_data); + status = RegQueryValueExW( + connection_key, + name_string, + NULL, + &name_type, + (LPBYTE) name_data, + &len); + + if (status != ERROR_SUCCESS || name_type != REG_SZ) + dbg_reg("Error opening registry key: %s\\%s\\%S\n", NETWORK_CONNECTIONS_KEY, connection_string, name_string); + else + { + int n; + LPSTR name; + struct panel_reg *reg; + + /* ALLOC_OBJ_CLEAR_GC (reg, struct panel_reg, gc); */ + reg = PICO_ZALLOC(sizeof(struct panel_reg), 1); + if (!reg) + return NULL; + + n = WideCharToMultiByte (CP_UTF8, 0, name_data, -1, NULL, 0, NULL, NULL); + /* name = gc_malloc (n, false, gc); */ + name = PICO_ZALLOC(n, 1); + if (!name) + { + PICO_FREE(reg); + return NULL; + } + + WideCharToMultiByte (CP_UTF8, 0, name_data, -1, name, n, NULL, NULL); + reg->name = name; + /* reg->guid = string_alloc (enum_name, gc); */ + reg->guid = PICO_ZALLOC(strlen(enum_name) + 1, 1); + if (!reg->guid) + { + PICO_FREE((void *)reg->name); + PICO_FREE((void *)reg); + return NULL; + } + + strcpy((char *)reg->guid, enum_name); + + /* link into return list */ + if (!first) + first = reg; + + if (last) + last->next = reg; + + last = reg; + } + + RegCloseKey (connection_key); + } + + ++i; + } + RegCloseKey (network_connections_key); + + return first; +} + + +void show_tap_win_adapters (void) +{ + int warn_panel_null = 0; + int warn_panel_dup = 0; + int warn_tap_dup = 0; + + int links; + + const struct tap_reg *tr; + const struct tap_reg *tr1; + const struct panel_reg *pr; + + const struct tap_reg *tap_reg = get_tap_reg (); + const struct panel_reg *panel_reg = get_panel_reg (); + + if (!(tap_reg && panel_reg)) + return; + + dbg_tap_info("Available TAP-WIN32 adapters [name, GUID]:\n"); + + /* loop through each TAP-Windows adapter registry entry */ + for (tr = tap_reg; tr != NULL; tr = tr->next) + { + links = 0; + + /* loop through each network connections entry in the control panel */ + for (pr = panel_reg; pr != NULL; pr = pr->next) + { + if (!strcmp (tr->guid, pr->guid)) + { + dbg_tap_info("\t>> '%s' %s\n", pr->name, tr->guid); + ++links; + } + } + if (links > 1) + { + warn_panel_dup = 1; + } + else if (links == 0) + { + /* a TAP adapter exists without a link from the network + connections control panel */ + warn_panel_null = 1; + dbg_tap_info("\t>> [NULL] %s\n", tr->guid); + } + } + /* check for TAP-Windows adapter duplicated GUIDs */ + for (tr = tap_reg; tr != NULL; tr = tr->next) + { + for (tr1 = tap_reg; tr1 != NULL; tr1 = tr1->next) + { + if (tr != tr1 && !strcmp (tr->guid, tr1->guid)) + warn_tap_dup = 1; + } + } + /* warn on registry inconsistencies */ + if (warn_tap_dup) + dbg_tap_info("WARNING: Some TAP-Windows adapters have duplicate GUIDs\n"); + + if (warn_panel_dup) + dbg_tap_info("WARNING: Some TAP-Windows adapters have duplicate links from the Network Connections control panel\n"); + + if (warn_panel_null) + dbg_tap_info("WARNING: Some TAP-Windows adapters have no link from the Network Connections control panel\n"); +} + + +/* Get the GUID of the first TAP device found */ +const char *get_first_device_guid(const struct tap_reg *tap_reg, const struct panel_reg *panel_reg, char *name) +{ + const struct tap_reg *tr; + const struct panel_reg *pr; + /* loop through each TAP-Windows adapter registry entry */ + for (tr = tap_reg; tr != NULL; tr = tr->next) + { + /* loop through each network connections entry in the control panel */ + for (pr = panel_reg; pr != NULL; pr = pr->next) + { + if (!strcmp (tr->guid, pr->guid)) + { + dbg_tap_info("Using first TAP device: '%s' %s\n", pr->name, tr->guid); + if (name) + strcpy(name, pr->name); + + return tr->guid; + } + } + } + return NULL; +} + + + +int open_tun (const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt) +{ + char device_path[256]; + const char *device_guid = NULL; + DWORD len; + + dbg_tap_info("open_tun, tt->ipv6=%d\n", tt->ipv6 ); + + if (!(tt->type == DEV_TYPE_TAP || tt->type == DEV_TYPE_TUN)) + { + dbg_tap_info("Unknown virtual device type: '%s'\n", dev); + return -1; + } + + /* + * Lookup the device name in the registry, using the --dev-node high level name. + */ + { + const struct tap_reg *tap_reg = get_tap_reg(); + const struct panel_reg *panel_reg = get_panel_reg(); + char name[256]; + + if (!(tap_reg && panel_reg)) + { + dbg_tap_info("No TUN/TAP devices found\n"); + return -1; + } + + /* Get the device GUID for the device specified with --dev-node. */ + device_guid = get_first_device_guid (tap_reg, panel_reg, name); + + if (!device_guid) + dbg_tap_info("TAP-Windows adapter '%s' not found\n", dev_node); + + /* Open Windows TAP-Windows adapter */ + snprintf (device_path, sizeof(device_path), "%s%s%s", + USERMODEDEVICEDIR, + device_guid, + TAP_WIN_SUFFIX); + + tt->hand = CreateFile ( + device_path, + GENERIC_READ | GENERIC_WRITE, + 0, /* was: FILE_SHARE_READ */ + 0, + OPEN_EXISTING, + FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, + 0 + ); + + if (tt->hand == INVALID_HANDLE_VALUE) + dbg_tap_info("CreateFile failed on TAP device: %s\n", device_path); + + /* translate high-level device name into a device instance + GUID using the registry */ + tt->actual_name = PICO_ZALLOC(strlen(name) + 1); + if (tt->actual_name) + strcpy(tt->actual_name, name); + } + + dbg_tap_info("TAP-WIN32 device [%s] opened: %s\n", tt->actual_name, device_path); + /* TODO TODO TODO */ + /* tt->adapter_index = get_adapter_index (device_guid); */ + + /* get driver version info */ + { + ULONG info[3]; + /* TODO TODO TODO */ + /* CLEAR (info); */ + if (DeviceIoControl (tt->hand, TAP_WIN_IOCTL_GET_VERSION, &info, sizeof (info), &info, sizeof (info), &len, NULL)) + { + dbg_tap_info ("TAP-Windows Driver Version %d.%d %s\n", + (int) info[0], + (int) info[1], + (info[2] ? "(DEBUG)" : "")); + + } + + if (!(info[0] == TAP_WIN_MIN_MAJOR && info[1] >= TAP_WIN_MIN_MINOR)) + dbg_tap_info ("ERROR: This version of " PACKAGE_NAME " requires a TAP-Windows driver that is at least version %d.%d \ + -- If you recently upgraded your " PACKAGE_NAME " distribution, \ + a reboot is probably required at this point to get Windows to see the new driver.\n", + TAP_WIN_MIN_MAJOR, + TAP_WIN_MIN_MINOR); + + /* usage of numeric constants is ugly, but this is really tied to + * *this* version of the driver + */ + if ( tt->ipv6 && tt->type == DEV_TYPE_TUN && info[0] == 9 && info[1] < 8) + { + dbg_tap_info("WARNING: Tap-Win32 driver version %d.%d does not support IPv6 in TUN mode. IPv6 will be disabled. \ + Upgrade to Tap-Win32 9.8 (2.2-beta3 release or later) or use TAP mode to get IPv6\n", (int) info[0], (int) info[1] ); + tt->ipv6 = 0; + } + + /* tap driver 9.8 (2.2.0 and 2.2.1 release) is buggy + */ + if ( tt->type == DEV_TYPE_TUN && info[0] == 9 && info[1] == 8) + { + dbg_tap_info("ERROR: Tap-Win32 driver version %d.%d is buggy regarding small IPv4 packets in TUN mode. Upgrade to Tap-Win32 9.9 (2.2.2 release or later) or use TAP mode\n", (int) info[0], (int) info[1] ); + } + } + + /* get driver MTU */ + { + ULONG mtu; + if (DeviceIoControl (tt->hand, TAP_WIN_IOCTL_GET_MTU, + &mtu, sizeof (mtu), + &mtu, sizeof (mtu), &len, NULL)) + { + tt->post_open_mtu = (int) mtu; + dbg_tap_info("TAP-Windows MTU=%d\n", (int) mtu); + } + } + + + /* get driver MAC */ + { + uint8_t mac[6] = { + 0, 0, 0, 0, 0, 0 + }; + if (DeviceIoControl (tt->hand, TAP_WIN_IOCTL_GET_MAC, + mac, sizeof (mac), + mac, sizeof (mac), &len, NULL)) + { + dbg_tap_info("TAP-Windows MAC=[%x,%x,%x,%x,%x,%x]\n", mac[0], mac[1], mac[2], + mac[2], mac[4], mac[5]); + memcpy(tt->mac, mac, sizeof(mac)); + } + } + + /* set point-to-point mode if TUN device */ + + if (tt->type == DEV_TYPE_TUN) + { + dbg_tap_info("TUN type not supported for now...\n"); + return -1; + } + else if (tt->type == DEV_TYPE_TAP) + { /* TAP DEVICE */ + dbg_tap_info("TODO: Set Point-to-point through DeviceIoControl\n"); + } + + /* set driver media status to 'connected' */ + { + ULONG status = TRUE; + if (!DeviceIoControl (tt->hand, TAP_WIN_IOCTL_SET_MEDIA_STATUS, + &status, sizeof (status), + &status, sizeof (status), &len, NULL)) + dbg_tap_info("WARNING: The TAP-Windows driver rejected a TAP_WIN_IOCTL_SET_MEDIA_STATUS DeviceIoControl call."); + } + + return 0; +} + + + + +/* TODO: Closing a TUN device is currently not implemented */ +/* + void close_tun (struct tuntap *tt) + { + (void)tt; + } + */ + + +int tap_win_getinfo (const struct tuntap *tt, char *buf, int bufsize) +{ + if (tt && tt->hand != NULL && buf != NULL) + { + DWORD len; + if (DeviceIoControl (tt->hand, TAP_WIN_IOCTL_GET_INFO, + buf, bufsize, + buf, bufsize, + &len, NULL)) + { + return 0; + } + } + + return -1; +} + +void tun_show_debug (struct tuntap *tt, char *buf, int bufsize) +{ + if (tt && tt->hand != NULL && buf != NULL) + { + DWORD len; + while (DeviceIoControl (tt->hand, TAP_WIN_IOCTL_GET_LOG_LINE, + buf, bufsize, + buf, bufsize, + &len, NULL)) + { + dbg_tap_info("TAP-Windows: %s\n", buf); + } + } +} + + +/* returns the state */ +int tun_read_queue (struct tuntap *tt, uint8_t *buffer, int maxsize) +{ + if (tt->reads.iostate == IOSTATE_INITIAL) + { + DWORD len = 1500; + BOOL status; + int err; + + /* reset buf to its initial state */ + tt->reads.buf = tt->reads.buf_init; + tt->reads.buf_len = tt->reads.buf_init_len; + + len = maxsize ? maxsize : (tt->reads.buf_len); + if (len > (tt->reads.buf_len)) /* clip to buffer len */ + len = tt->reads.buf_len; + + /* the overlapped read will signal this event on I/O completion */ + if (!ResetEvent (tt->reads.overlapped.hEvent)) + dbg_tap("ResetEvent failed\n"); + + status = ReadFile( + tt->hand, + buffer, + len, + &tt->reads.size, + &tt->reads.overlapped + ); + + if (status) /* operation completed immediately? */ + { + /* since we got an immediate return, we must signal the event object ourselves */ + /* ASSERT (SetEvent (tt->reads.overlapped.hEvent)); */ + if (!SetEvent (tt->reads.overlapped.hEvent)) + dbg_tap("SetEvent failed\n"); + + tt->reads.iostate = IOSTATE_IMMEDIATE_RETURN; + tt->reads.status = 0; + + dbg_win32 ("WIN32 I/O: TAP Read immediate return [%d,%d]\n", + (int) len, + (int) tt->reads.size); + } + else + { + err = GetLastError (); + if (err == ERROR_IO_PENDING) /* operation queued? */ + { + tt->reads.iostate = IOSTATE_QUEUED; + tt->reads.status = err; + dbg_win32 ("WIN32 I/O: TAP Read queued [%d]\n", (int) len); + } + else /* error occurred */ + { + if (!SetEvent (tt->reads.overlapped.hEvent)) + dbg_tap("SetEvent failed\n"); + + tt->reads.iostate = IOSTATE_IMMEDIATE_RETURN; + tt->reads.status = err; + dbg_tap ("WIN32 I/O: TAP Read error [%d] : %d\n", (int) len, (int) err); + } + } + } + + return tt->reads.iostate; +} + +/* Finalize any pending overlapped IO's */ +int tun_finalize(HANDLE h, struct overlapped_io *io, uint8_t **buf, uint32_t *buf_len) +{ + int ret = -1; + BOOL status; + + switch (io->iostate) + { + case IOSTATE_QUEUED: + status = GetOverlappedResult( + h, + &io->overlapped, + &io->size, + 0u + ); + if (status) + { + /* successful return for a queued operation */ + if (buf) + { + *buf = io->buf; + *buf_len = io->buf_len; + } + + ret = io->size; + io->iostate = IOSTATE_INITIAL; + + if (!ResetEvent (io->overlapped.hEvent)) + dbg_tap("ResetEvent in finalize failed!\n"); + + dbg_win32 ("WIN32 I/O: TAP Completion success: QUEUED! [%d]\n", ret); + } + else + { + /* error during a queued operation */ + /* error, or just not completed? */ + ret = 0; + if (GetLastError() != ERROR_IO_INCOMPLETE) + { + /* if no error (i.e. just not finished yet), + then DON'T execute this code */ + io->iostate = IOSTATE_INITIAL; + if (!ResetEvent (io->overlapped.hEvent)) + dbg_tap("ResetEvent in finalize failed!\n"); + + dbg_tap("WIN32 I/O: TAP Completion error\n"); + ret = -1; /* There actually was an error */ + } + } + + break; + + case IOSTATE_IMMEDIATE_RETURN: + io->iostate = IOSTATE_INITIAL; + if (!ResetEvent (io->overlapped.hEvent)) + dbg_tap("ResetEvent in finalize failed!\n"); + + if (io->status) + { + /* error return for a non-queued operation */ + SetLastError (io->status); + ret = -1; + dbg_tap("WIN32 I/O: TAP Completion non-queued error\n"); + } + else + { + /* successful return for a non-queued operation */ + if (buf) + *buf = io->buf; + + ret = io->size; + dbg_win32 ("WIN32 I/O: TAP Completion non-queued success [%d]\n", ret); + } + + break; + + case IOSTATE_INITIAL: /* were we called without proper queueing? */ + SetLastError (ERROR_INVALID_FUNCTION); + ret = -1; + dbg_tap ("WIN32 I/O: TAP Completion BAD STATE\n"); + break; + + default: + dbg_tap ("Some weird case happened..\n"); + } + + if (buf) + *buf_len = ret; + + return ret; +} + + + +/* returns the amount of bytes written */ +int tun_write_queue (struct tuntap *tt, uint8_t *buf, uint32_t buf_len) +{ + if (tt->writes.iostate == IOSTATE_INITIAL) + { + BOOL status; + int err; + + /* make a private copy of buf */ + tt->writes.buf = tt->writes.buf_init; + tt->writes.buf_len = buf_len; + memcpy(tt->writes.buf, buf, buf_len); + + /* the overlapped write will signal this event on I/O completion */ + if (!ResetEvent (tt->writes.overlapped.hEvent)) + dbg_tap("ResetEvent in write_queue failed!\n"); + + status = WriteFile( + tt->hand, + tt->writes.buf, + tt->writes.buf_len, + &tt->writes.size, + &tt->writes.overlapped + ); + + if (status) /* operation completed immediately? */ + { + tt->writes.iostate = IOSTATE_IMMEDIATE_RETURN; + + /* since we got an immediate return, we must signal the event object ourselves */ + if (!SetEvent (tt->writes.overlapped.hEvent)) + dbg_tap("SetEvent in write_queue failed!\n"); + + tt->writes.status = 0; + + dbg_win32 ("WIN32 I/O: TAP Write immediate return [%d,%d]\n", + (int)(tt->writes.buf_len), + (int)tt->writes.size); + } + else + { + err = GetLastError (); + if (err == ERROR_IO_PENDING) /* operation queued? */ + { + tt->writes.iostate = IOSTATE_QUEUED; + tt->writes.status = err; + dbg_win32("WIN32 I/O: TAP Write queued [%d]\n", + (tt->writes.buf_len)); + } + else /* error occurred */ + { + if (!SetEvent (tt->writes.overlapped.hEvent)) + dbg_tap("SetEvent in write_queue failed!\n"); + + tt->writes.iostate = IOSTATE_IMMEDIATE_RETURN; + tt->writes.status = err; + dbg_tap ("WIN32 I/O: TAP Write error [%d] : %d\n", (int) &tt->writes.buf_len, (int) err); + } + } + } + + return tt->writes.iostate; +} + +static inline int overlapped_io_active (struct overlapped_io *o) +{ + return o->iostate == IOSTATE_QUEUED || o->iostate == IOSTATE_IMMEDIATE_RETURN; +} + +/* if >= 0: returns the amount of bytes read, otherwise error! */ +static int tun_write_win32 (struct tuntap *tt, uint8_t *buf, uint32_t buf_len) +{ + int err = 0; + int status = 0; + if (overlapped_io_active (&tt->writes)) + { + status = tun_finalize (tt->hand, &tt->writes, NULL, 0); + if (status == 0) + { + /* busy, just wait, do not schedule a new write */ + return 0; + } + + if (status < 0) + err = GetLastError (); + } + + /* the overlapped IO is done, now we can schedule a new write */ + tun_write_queue (tt, buf, buf_len); + if (status < 0) + { + SetLastError (err); + return status; + } + else + return buf_len; +} + + +/* if >= 0: returns the amount of bytes read, otherwise error! */ +static int tun_read_win32 (struct tuntap *tt, uint8_t *buf, uint32_t buf_len) +{ + int err = 0; + int status = 0; + + + /* First, finish possible pending IOs */ + if (overlapped_io_active (&tt->reads)) + { + status = tun_finalize (tt->hand, &tt->reads, &buf, &buf_len); + if (status == 0) + { + /* busy, just wait, do not schedule a new read */ + return 0; + } + + if (status < 0) + { + dbg_tap ("tun_finalize status < 0: %d\n", status); + err = GetLastError (); + } + + if (status > 0) + { + return buf_len; + } + } + + /* If no pending IOs, schedule a new read */ + /* queue, or immediate return */ + if (IOSTATE_IMMEDIATE_RETURN == tun_read_queue(tt, buf, buf_len)) + { + return tt->reads.size; + } + + /* If the pending IOs gave an error, report it */ + if (status < 0) + { + SetLastError (err); + return status; + } + else + { + /* no errors, but the newly scheduled read is now pending */ + return 0; + } +} + + +static int read_tun_buffered(struct tuntap *tt, uint8_t *buf, uint32_t buf_len) +{ + return tun_read_win32 (tt, buf, buf_len); +} + +static int write_tun_buffered(struct tuntap *tt, uint8_t *buf, uint32_t buf_len) +{ + return tun_write_win32 (tt, buf, buf_len); +} + + +static int pico_tap_send(struct pico_device *dev, void *buf, int len) +{ + uint32_t bytes_sent = 0; + struct pico_device_tap *tap = (struct pico_device_tap *) dev; + + /* Increase the statistic count */ + tap->statistics_frames_out++; + + bytes_sent = write_tun_buffered (tap->tt, buf, len); + dbg_tap("TX> sent %d bytes\n", bytes_sent); + + /* Discard the frame content silently. */ + return bytes_sent; +} + +uint8_t recv_buffer[1500]; + +static int pico_tap_poll(struct pico_device *dev, int loop_score) +{ + struct pico_device_tap *tap = (struct pico_device_tap *) dev; + while (loop_score) + { + int bytes_read = read_tun_buffered(tap->tt, recv_buffer, 1500); + loop_score--; + if (bytes_read > 0) + { + dbg_tap("RX< recvd: %d bytes\n", bytes_read); + pico_stack_recv(dev, recv_buffer, bytes_read); + /* break; */ + } + else + break; + } + return loop_score; +} + + + +#define CLEAR(x) memset(&(x), 0, sizeof(x)) + +void overlapped_io_init (struct overlapped_io *o, int event_state) +{ + CLEAR (*o); + + /* manual reset event, initially set according to event_state */ + o->overlapped.hEvent = CreateEvent (NULL, TRUE, event_state, NULL); + if (o->overlapped.hEvent == NULL) + dbg_tap ("Error: overlapped_io_init: CreateEvent failed\n"); + + /* allocate buffer for overlapped I/O */ + o->buf_init = PICO_ZALLOC(1500); /* XXX: MTU */ + o->buf_init_len = 1500; /* XXX: MTU */ + if (!(o->buf_init)) + dbg_tap("buffer alloc failed!\n"); /* XXX: return -1 or so? */ + else + dbg_tap("overlapped_io_init buffer allocated!\n"); +} + +void init_tun_post (struct tuntap *tt) +{ + dbg_tap("TUN post init (for overlapped io)\n"); + overlapped_io_init (&tt->reads, FALSE); + overlapped_io_init (&tt->writes, TRUE); + tt->rw_handle.read = tt->reads.overlapped.hEvent; + tt->rw_handle.write = tt->writes.overlapped.hEvent; +} + + +/* + * Public interface: pico_tap_create + * TODO: pico_tap_destroy + */ + +struct pico_device *pico_tap_create(char *name, uint8_t *mac) +{ + struct pico_device_tap *tap = PICO_ZALLOC(sizeof(struct pico_device_tap)); + struct tuntap *tt = PICO_ZALLOC(sizeof(struct tuntap), 1); + + if (!(tap) || !(tt)) + return NULL; + + tap->dev.overhead = 0; + tap->statistics_frames_out = 0; + tap->dev.send = pico_tap_send; + tap->dev.poll = pico_tap_poll; + + show_tap_win_adapters(); + + tt->type = DEV_TYPE_TAP; + if (open_tun(NULL, NULL, "tap0", tt)) + { + dbg_tap("Failed to create TAP device!\n"); + PICO_FREE(tt); + PICO_FREE(tap); + return NULL; + } + + tap->tt = tt; + + if( 0 != pico_device_init((struct pico_device *)tap, name, mac)) { + return NULL; + } + + init_tun_post(tt); /* init overlapped io */ + + dbg_tap("Device %s created.\n", tap->dev.name); + + return (struct pico_device *)tap; +} + diff --git a/ext/picotcp/modules/pico_dev_tap_windows.h b/ext/picotcp/modules/pico_dev_tap_windows.h new file mode 100755 index 0000000..25bd4fc --- /dev/null +++ b/ext/picotcp/modules/pico_dev_tap_windows.h @@ -0,0 +1,17 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2014-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + *********************************************************************/ +#ifndef INCLUDE_PICO_TAP +#define INCLUDE_PICO_TAP +#include "pico_config.h" +#include "pico_device.h" + +/* will look for the first TAP device available, and use it */ +struct pico_device *pico_tap_create(char *name, uint8_t *mac); +/* TODO: not implemented yet */ +/* void pico_tap_destroy(struct pico_device *null); */ + +#endif + diff --git a/ext/picotcp/modules/pico_dev_tap_windows_private.h b/ext/picotcp/modules/pico_dev_tap_windows_private.h new file mode 100644 index 0000000..8f3b192 --- /dev/null +++ b/ext/picotcp/modules/pico_dev_tap_windows_private.h @@ -0,0 +1,89 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2014-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + Authors: Maxime Vincent + Based on the OpenVPN tun.c driver, under GPL + + NOTES: This is the Windows-only driver, a Linux-equivalent is available, too + You need to have an OpenVPN TUN/TAP network adapter installed, first + This driver is barely working: + * Only TAP-mode is supported (TUN is not) + * it will simply open the first TAP device it can find + * there is memory being allocated that's never freed + * there is no destroy function, yet + * it has only been tested on a Windows 7 machine + *********************************************************************/ + +#ifndef __PICO_DEV_TAP_WINDOWS_PRIVATE_H +#define __PICO_DEV_TAP_WINDOWS_PRIVATE_H + +/* Extra defines (vnz) */ +#define TAP_WIN_COMPONENT_ID "tap0901" +#define TAP_WIN_MIN_MAJOR 9 +#define TAP_WIN_MIN_MINOR 9 +#define PACKAGE_NAME "PicoTCP WinTAP" + +/* Extra structs */ +struct tap_reg +{ + const char *guid; + struct tap_reg *next; +}; + +struct panel_reg +{ + const char *name; + const char *guid; + struct panel_reg *next; +}; + + +/* + * ============= + * 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) +#define TAP_WIN_IOCTL_GET_LOG_LINE TAP_WIN_CONTROL_CODE (8, METHOD_BUFFERED) +#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) + +/* + * ================= + * 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 diff --git a/ext/picotcp/modules/pico_dev_tun.c b/ext/picotcp/modules/pico_dev_tun.c new file mode 100644 index 0000000..c5791d6 --- /dev/null +++ b/ext/picotcp/modules/pico_dev_tun.c @@ -0,0 +1,110 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + Authors: Daniele Lacamera + *********************************************************************/ + + +#include +#include +#include +#include +#include "pico_device.h" +#include "pico_dev_tun.h" +#include "pico_stack.h" + +#include + +struct pico_device_tun { + struct pico_device dev; + int fd; +}; + +#define TUN_MTU 2048 + +static int pico_tun_send(struct pico_device *dev, void *buf, int len) +{ + struct pico_device_tun *tun = (struct pico_device_tun *) dev; + return (int)write(tun->fd, buf, (uint32_t)len); +} + +static int pico_tun_poll(struct pico_device *dev, int loop_score) +{ + struct pico_device_tun *tun = (struct pico_device_tun *) dev; + struct pollfd pfd; + unsigned char buf[TUN_MTU]; + int len; + pfd.fd = tun->fd; + pfd.events = POLLIN; + do { + if (poll(&pfd, 1, 0) <= 0) + return loop_score; + + len = (int)read(tun->fd, buf, TUN_MTU); + if (len > 0) { + loop_score--; + pico_stack_recv(dev, buf, (uint32_t)len); + } + } while(loop_score > 0); + return 0; +} + +/* Public interface: create/destroy. */ + +void pico_tun_destroy(struct pico_device *dev) +{ + struct pico_device_tun *tun = (struct pico_device_tun *) dev; + if(tun->fd > 0) + close(tun->fd); +} + + +static int tun_open(char *name) +{ + struct ifreq ifr; + int tun_fd; + if((tun_fd = open("/dev/net/tun", O_RDWR)) < 0) { + return(-1); + } + + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_flags = IFF_TUN | IFF_NO_PI; + strncpy(ifr.ifr_name, name, IFNAMSIZ); + if(ioctl(tun_fd, TUNSETIFF, &ifr) < 0) { + return(-1); + } + + return tun_fd; +} + + + +struct pico_device *pico_tun_create(char *name) +{ + struct pico_device_tun *tun = PICO_ZALLOC(sizeof(struct pico_device_tun)); + + if (!tun) + return NULL; + + if( 0 != pico_device_init((struct pico_device *)tun, name, NULL)) { + dbg("Tun init failed.\n"); + pico_tun_destroy((struct pico_device *)tun); + return NULL; + } + + tun->dev.overhead = 0; + tun->fd = tun_open(name); + if (tun->fd < 0) { + dbg("Tun creation failed.\n"); + pico_tun_destroy((struct pico_device *)tun); + return NULL; + } + + tun->dev.send = pico_tun_send; + tun->dev.poll = pico_tun_poll; + tun->dev.destroy = pico_tun_destroy; + dbg("Device %s created.\n", tun->dev.name); + return (struct pico_device *)tun; +} + diff --git a/ext/picotcp/modules/pico_dev_tun.h b/ext/picotcp/modules/pico_dev_tun.h new file mode 100644 index 0000000..4dd477e --- /dev/null +++ b/ext/picotcp/modules/pico_dev_tun.h @@ -0,0 +1,15 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + *********************************************************************/ +#ifndef INCLUDE_PICO_TUN +#define INCLUDE_PICO_TUN +#include "pico_config.h" +#include "pico_device.h" + +void pico_tun_destroy(struct pico_device *tun); +struct pico_device *pico_tun_create(char *name); + +#endif + diff --git a/ext/picotcp/modules/pico_dev_vde.c b/ext/picotcp/modules/pico_dev_vde.c new file mode 100644 index 0000000..df07a3b --- /dev/null +++ b/ext/picotcp/modules/pico_dev_vde.c @@ -0,0 +1,115 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + Authors: Daniele Lacamera + *********************************************************************/ + +#ifndef UNIT_TEST +#include +#endif +#include "pico_device.h" +#include "pico_dev_vde.h" +#include "pico_stack.h" + +#include + +struct pico_device_vde { + struct pico_device dev; + char *sock; + VDECONN *conn; + uint32_t counter_in; + uint32_t counter_out; + uint32_t lost_in; + uint32_t lost_out; +}; + +#define VDE_MTU 65536 + +static int pico_vde_send(struct pico_device *dev, void *buf, int len) +{ + struct pico_device_vde *vde = (struct pico_device_vde *) dev; + /* dbg("[%s] send %d bytes.\n", dev->name, len); */ + if ((vde->lost_out == 0) || ((pico_rand() % 100) > vde->lost_out)) + return (int)vde_send(vde->conn, buf, (uint32_t)len, 0); + else + return len; /* Silently discarded "on the wire" */ + +} + +static int pico_vde_poll(struct pico_device *dev, int loop_score) +{ + struct pico_device_vde *vde = (struct pico_device_vde *) dev; + struct pollfd pfd; + unsigned char buf[VDE_MTU]; + int len; + pfd.fd = vde_datafd(vde->conn); + pfd.events = POLLIN; + do { + if (poll(&pfd, 1, 0) <= 0) + return loop_score; + + len = (int)vde_recv(vde->conn, buf, VDE_MTU, 0); + if (len > 0) { + /* dbg("Received pkt.\n"); */ + if ((vde->lost_in == 0) || ((pico_rand() % 100) > vde->lost_in)) { + loop_score--; + pico_stack_recv(dev, buf, (uint32_t)len); + } + } + } while(loop_score > 0); + return 0; +} + +/* Public interface: create/destroy. */ + +void pico_vde_destroy(struct pico_device *dev) +{ + struct pico_device_vde *vde = (struct pico_device_vde *) dev; + vde_close(vde->conn); + usleep(100000); + sync(); +} + +void pico_vde_set_packetloss(struct pico_device *dev, uint32_t in_pct, uint32_t out_pct) +{ + struct pico_device_vde *vde = (struct pico_device_vde *) dev; + vde->lost_in = in_pct; + vde->lost_out = out_pct; +} + + + +struct pico_device *pico_vde_create(char *sock, char *name, uint8_t *mac) +{ + struct pico_device_vde *vde = PICO_ZALLOC(sizeof(struct pico_device_vde)); + struct vde_open_args open_args = { + .mode = 0700 + }; + char vdename[] = "picotcp"; + + if (!vde) + return NULL; + + if( 0 != pico_device_init((struct pico_device *)vde, name, mac)) { + dbg ("Vde init failed.\n"); + pico_vde_destroy((struct pico_device *)vde); + return NULL; + } + + vde->dev.overhead = 0; + vde->sock = PICO_ZALLOC(strlen(sock) + 1); + memcpy(vde->sock, sock, strlen(sock)); + vde->conn = vde_open(sock, vdename, &open_args); + if (!vde->conn) { + pico_vde_destroy((struct pico_device *)vde); + return NULL; + } + + vde->dev.send = pico_vde_send; + vde->dev.poll = pico_vde_poll; + vde->dev.destroy = pico_vde_destroy; + dbg("Device %s created.\n", vde->dev.name); + return (struct pico_device *)vde; +} + diff --git a/ext/picotcp/modules/pico_dev_vde.h b/ext/picotcp/modules/pico_dev_vde.h new file mode 100644 index 0000000..c6c999e --- /dev/null +++ b/ext/picotcp/modules/pico_dev_vde.h @@ -0,0 +1,18 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + + *********************************************************************/ +#ifndef INCLUDE_PICO_VDE +#define INCLUDE_PICO_VDE +#include "pico_config.h" +#include "pico_device.h" +#include + +void pico_vde_destroy(struct pico_device *vde); +struct pico_device *pico_vde_create(char *sock, char *name, uint8_t *mac); +void pico_vde_set_packetloss(struct pico_device *dev, uint32_t in_pct, uint32_t out_pct); + +#endif + diff --git a/ext/picotcp/modules/pico_dhcp_client.c b/ext/picotcp/modules/pico_dhcp_client.c new file mode 100644 index 0000000..f834dd6 --- /dev/null +++ b/ext/picotcp/modules/pico_dhcp_client.c @@ -0,0 +1,990 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + Authors: Kristof Roelants, Frederik Van Slycken + *********************************************************************/ + + +#include "pico_dhcp_client.h" +#include "pico_stack.h" +#include "pico_config.h" +#include "pico_device.h" +#include "pico_ipv4.h" +#include "pico_socket.h" +#include "pico_eth.h" + +#if (defined PICO_SUPPORT_DHCPC && defined PICO_SUPPORT_UDP) +#define dhcpc_dbg(...) do {} while(0) +/* #define dhcpc_dbg dbg */ + +/* timer values */ +#define DHCP_CLIENT_REINIT 6000 /* msec */ +#define DHCP_CLIENT_RETRANS 4 /* sec */ +#define DHCP_CLIENT_RETRIES 3 + +#define DHCP_CLIENT_TIMER_STOPPED 0 +#define DHCP_CLIENT_TIMER_STARTED 1 + +/* maximum size of a DHCP message */ +#define DHCP_CLIENT_MAXMSGZISE (PICO_IP_MRU - PICO_SIZE_IP4HDR) +#define PICO_DHCP_HOSTNAME_MAXLEN 64U + +static char dhcpc_host_name[PICO_DHCP_HOSTNAME_MAXLEN] = ""; +static char dhcpc_domain_name[PICO_DHCP_HOSTNAME_MAXLEN] = ""; + + +enum dhcp_client_state { + DHCP_CLIENT_STATE_INIT_REBOOT = 0, + DHCP_CLIENT_STATE_REBOOTING, + DHCP_CLIENT_STATE_INIT, + DHCP_CLIENT_STATE_SELECTING, + DHCP_CLIENT_STATE_REQUESTING, + DHCP_CLIENT_STATE_BOUND, + DHCP_CLIENT_STATE_RENEWING, + DHCP_CLIENT_STATE_REBINDING +}; + + +#define PICO_DHCPC_TIMER_INIT 0 +#define PICO_DHCPC_TIMER_REQUEST 1 +#define PICO_DHCPC_TIMER_RENEW 2 +#define PICO_DHCPC_TIMER_REBIND 3 +#define PICO_DHCPC_TIMER_T1 4 +#define PICO_DHCPC_TIMER_T2 5 +#define PICO_DHCPC_TIMER_LEASE 6 +#define PICO_DHCPC_TIMER_ARRAY_SIZE 7 + +struct dhcp_client_timer +{ + uint8_t state; + unsigned int type; + uint32_t xid; +}; + +struct pico_dhcp_client_cookie +{ + uint8_t event; + uint8_t retry; + uint32_t xid; + uint32_t *uid; + enum dhcp_client_state state; + void (*cb)(void*dhcpc, int code); + pico_time init_timestamp; + struct pico_socket *s; + struct pico_ip4 address; + struct pico_ip4 netmask; + struct pico_ip4 gateway; + struct pico_ip4 nameserver[2]; + struct pico_ip4 server_id; + struct pico_device *dev; + struct dhcp_client_timer *timer[PICO_DHCPC_TIMER_ARRAY_SIZE]; + uint32_t t1_time; + uint32_t t2_time; + uint32_t lease_time; + uint32_t renew_time; + uint32_t rebind_time; +}; + +static int pico_dhcp_client_init(struct pico_dhcp_client_cookie *dhcpc); +static int reset(struct pico_dhcp_client_cookie *dhcpc, uint8_t *buf); +static int8_t pico_dhcp_client_msg(struct pico_dhcp_client_cookie *dhcpc, uint8_t msg_type); +static void pico_dhcp_client_wakeup(uint16_t ev, struct pico_socket *s); +static void pico_dhcp_state_machine(uint8_t event, struct pico_dhcp_client_cookie *dhcpc, uint8_t *buf); + +static const struct pico_ip4 bcast_netmask = { + .addr = 0xFFFFFFFF +}; + +static struct pico_ip4 inaddr_any = { + 0 +}; + + +static int dhcp_cookies_cmp(void *ka, void *kb) +{ + struct pico_dhcp_client_cookie *a = ka, *b = kb; + if (a->xid == b->xid) + return 0; + + return (a->xid < b->xid) ? (-1) : (1); +} +PICO_TREE_DECLARE(DHCPCookies, dhcp_cookies_cmp); + +static struct pico_dhcp_client_cookie *pico_dhcp_client_add_cookie(uint32_t xid, struct pico_device *dev, void (*cb)(void *dhcpc, int code), uint32_t *uid) +{ + struct pico_dhcp_client_cookie *dhcpc = NULL, *found = NULL, test = { + 0 + }; + + test.xid = xid; + found = pico_tree_findKey(&DHCPCookies, &test); + if (found) { + pico_err = PICO_ERR_EAGAIN; + return NULL; + } + + dhcpc = PICO_ZALLOC(sizeof(struct pico_dhcp_client_cookie)); + if (!dhcpc) { + pico_err = PICO_ERR_ENOMEM; + return NULL; + } + + dhcpc->state = DHCP_CLIENT_STATE_INIT; + dhcpc->xid = xid; + dhcpc->uid = uid; + *(dhcpc->uid) = 0; + dhcpc->cb = cb; + dhcpc->dev = dev; + + pico_tree_insert(&DHCPCookies, dhcpc); + return dhcpc; +} + +static void pico_dhcp_client_stop_timers(struct pico_dhcp_client_cookie *dhcpc); +static int pico_dhcp_client_del_cookie(uint32_t xid) +{ + struct pico_dhcp_client_cookie test = { + 0 + }, *found = NULL; + + test.xid = xid; + found = pico_tree_findKey(&DHCPCookies, &test); + if (!found) + return -1; + + pico_dhcp_client_stop_timers(found); + pico_socket_close(found->s); + found->s = NULL; + pico_ipv4_link_del(found->dev, found->address); + pico_tree_delete(&DHCPCookies, found); + PICO_FREE(found); + return 0; +} + +static struct pico_dhcp_client_cookie *pico_dhcp_client_find_cookie(uint32_t xid) +{ + struct pico_dhcp_client_cookie test = { + 0 + }, *found = NULL; + + test.xid = xid; + found = pico_tree_findKey(&DHCPCookies, &test); + if (found) + return found; + else + return NULL; +} + +static void pico_dhcp_client_timer_handler(pico_time now, void *arg); +static void pico_dhcp_client_reinit(pico_time now, void *arg); +static struct dhcp_client_timer *pico_dhcp_timer_add(uint8_t type, uint32_t time, struct pico_dhcp_client_cookie *ck) +{ + struct dhcp_client_timer *t; + + t = PICO_ZALLOC(sizeof(struct dhcp_client_timer)); + if (!t) + return NULL; + + t->state = DHCP_CLIENT_TIMER_STARTED; + t->xid = ck->xid; + t->type = type; + pico_timer_add(time, pico_dhcp_client_timer_handler, t); + if (ck->timer[type]) { + ck->timer[type]->state = DHCP_CLIENT_TIMER_STOPPED; + } + + ck->timer[type] = t; + return t; +} + +static int dhcp_get_timer_event(struct pico_dhcp_client_cookie *dhcpc, unsigned int type) +{ + const int events[PICO_DHCPC_TIMER_ARRAY_SIZE] = + { + PICO_DHCP_EVENT_RETRANSMIT, + PICO_DHCP_EVENT_RETRANSMIT, + PICO_DHCP_EVENT_RETRANSMIT, + PICO_DHCP_EVENT_RETRANSMIT, + PICO_DHCP_EVENT_T1, + PICO_DHCP_EVENT_T2, + PICO_DHCP_EVENT_LEASE + }; + + if (type == PICO_DHCPC_TIMER_REQUEST) { + if (++dhcpc->retry > DHCP_CLIENT_RETRIES) { + reset(dhcpc, NULL); + return PICO_DHCP_EVENT_NONE; + } + } else if (type < PICO_DHCPC_TIMER_T1) { + dhcpc->retry++; + } + + return events[type]; +} + +static void pico_dhcp_client_timer_handler(pico_time now, void *arg) +{ + struct dhcp_client_timer *t = (struct dhcp_client_timer *)arg; + struct pico_dhcp_client_cookie *dhcpc; + + if (!t) + return; + + (void) now; + if (t->state != DHCP_CLIENT_TIMER_STOPPED) { + dhcpc = pico_dhcp_client_find_cookie(t->xid); + if (dhcpc && dhcpc->timer) { + t->state = DHCP_CLIENT_TIMER_STOPPED; + if ((t->type == PICO_DHCPC_TIMER_INIT) && (dhcpc->state < DHCP_CLIENT_STATE_SELECTING)) { + pico_dhcp_client_reinit(now, dhcpc); + } else if (t->type != PICO_DHCPC_TIMER_INIT) { + dhcpc->event = (uint8_t)dhcp_get_timer_event(dhcpc, t->type); + if (dhcpc->event != PICO_DHCP_EVENT_NONE) + pico_dhcp_state_machine(dhcpc->event, dhcpc, NULL); + } + } + } +} + +static void pico_dhcp_client_timer_stop(struct pico_dhcp_client_cookie *dhcpc, int type) +{ + if (dhcpc->timer[type]) { + dhcpc->timer[type]->state = DHCP_CLIENT_TIMER_STOPPED; + } + +} + + +static void pico_dhcp_client_reinit(pico_time now, void *arg) +{ + struct pico_dhcp_client_cookie *dhcpc = (struct pico_dhcp_client_cookie *)arg; + (void) now; + + if (dhcpc->s) { + pico_socket_close(dhcpc->s); + dhcpc->s = NULL; + } + + if (++dhcpc->retry > DHCP_CLIENT_RETRIES) { + pico_err = PICO_ERR_EAGAIN; + if (dhcpc->cb) + dhcpc->cb(dhcpc, PICO_DHCP_ERROR); + + pico_dhcp_client_del_cookie(dhcpc->xid); + return; + } + + pico_dhcp_client_init(dhcpc); + return; +} + + +static void pico_dhcp_client_stop_timers(struct pico_dhcp_client_cookie *dhcpc) +{ + int i; + dhcpc->retry = 0; + for (i = 0; i < PICO_DHCPC_TIMER_ARRAY_SIZE; i++) + pico_dhcp_client_timer_stop(dhcpc, i); +} + +static void pico_dhcp_client_start_init_timer(struct pico_dhcp_client_cookie *dhcpc) +{ + uint32_t time = 0; + /* timer value is doubled with every retry (exponential backoff) */ + time = (uint32_t) (DHCP_CLIENT_RETRANS << dhcpc->retry); + pico_dhcp_timer_add(PICO_DHCPC_TIMER_INIT, time * 1000, dhcpc); +} + +static void pico_dhcp_client_start_requesting_timer(struct pico_dhcp_client_cookie *dhcpc) +{ + uint32_t time = 0; + + /* timer value is doubled with every retry (exponential backoff) */ + time = (uint32_t)(DHCP_CLIENT_RETRANS << dhcpc->retry); + pico_dhcp_timer_add(PICO_DHCPC_TIMER_REQUEST, time * 1000, dhcpc); +} + +static void pico_dhcp_client_start_renewing_timer(struct pico_dhcp_client_cookie *dhcpc) +{ + uint32_t halftime = 0; + + /* wait one-half of the remaining time until T2, down to a minimum of 60 seconds */ + /* (dhcpc->retry + 1): initial -> divide by 2, 1st retry -> divide by 4, 2nd retry -> divide by 8, etc */ + pico_dhcp_client_stop_timers(dhcpc); + halftime = dhcpc->renew_time >> (dhcpc->retry + 1); + if (halftime < 60) + halftime = 60; + + pico_dhcp_timer_add(PICO_DHCPC_TIMER_RENEW, halftime * 1000, dhcpc); + + return; +} + +static void pico_dhcp_client_start_rebinding_timer(struct pico_dhcp_client_cookie *dhcpc) +{ + uint32_t halftime = 0; + + pico_dhcp_client_stop_timers(dhcpc); + halftime = dhcpc->rebind_time >> (dhcpc->retry + 1); + if (halftime < 60) + halftime = 60; + + pico_dhcp_timer_add(PICO_DHCPC_TIMER_REBIND, halftime * 1000, dhcpc); + + return; +} + +static void pico_dhcp_client_start_reacquisition_timers(struct pico_dhcp_client_cookie *dhcpc) +{ + + pico_dhcp_client_stop_timers(dhcpc); + pico_dhcp_timer_add(PICO_DHCPC_TIMER_T1, dhcpc->t1_time * 1000, dhcpc); + pico_dhcp_timer_add(PICO_DHCPC_TIMER_T2, dhcpc->t2_time * 1000, dhcpc); + pico_dhcp_timer_add(PICO_DHCPC_TIMER_LEASE, dhcpc->lease_time * 1000, dhcpc); +} + +static int pico_dhcp_client_init(struct pico_dhcp_client_cookie *dhcpc) +{ + uint16_t port = PICO_DHCP_CLIENT_PORT; + if (!dhcpc) + return -1; + + /* adding a link with address 0.0.0.0 and netmask 0.0.0.0, + * automatically adds a route for a global broadcast */ + pico_ipv4_link_add(dhcpc->dev, inaddr_any, bcast_netmask); + if (!dhcpc->s) + dhcpc->s = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_UDP, &pico_dhcp_client_wakeup); + + if (!dhcpc->s) { + pico_dhcp_timer_add(PICO_DHCPC_TIMER_INIT, DHCP_CLIENT_REINIT, dhcpc); + return 0; + } + + dhcpc->s->dev = dhcpc->dev; + if (pico_socket_bind(dhcpc->s, &inaddr_any, &port) < 0) { + pico_socket_close(dhcpc->s); + dhcpc->s = NULL; + pico_dhcp_timer_add(PICO_DHCPC_TIMER_INIT, DHCP_CLIENT_REINIT, dhcpc); + return 0; + } + + if (pico_dhcp_client_msg(dhcpc, PICO_DHCP_MSG_DISCOVER) < 0) { + pico_socket_close(dhcpc->s); + dhcpc->s = NULL; + pico_dhcp_timer_add(PICO_DHCPC_TIMER_INIT, DHCP_CLIENT_REINIT, dhcpc); + return 0; + } + + dhcpc->retry = 0; + dhcpc->init_timestamp = PICO_TIME_MS(); + pico_dhcp_client_start_init_timer(dhcpc); + return 0; +} + +int pico_dhcp_initiate_negotiation(struct pico_device *dev, void (*cb)(void *dhcpc, int code), uint32_t *uid) +{ + uint8_t retry = 32; + uint32_t xid = 0; + struct pico_dhcp_client_cookie *dhcpc = NULL; + + if (!dev || !cb || !uid) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + if (!dev->eth) { + pico_err = PICO_ERR_EOPNOTSUPP; + return -1; + } + + /* attempt to generate a correct xid, else fail */ + do { + xid = pico_rand(); + } while (!xid && --retry); + + if (!xid) { + pico_err = PICO_ERR_EAGAIN; + return -1; + } + + dhcpc = pico_dhcp_client_add_cookie(xid, dev, cb, uid); + if (!dhcpc) + return -1; + + dhcpc_dbg("DHCP client: cookie with xid %u\n", dhcpc->xid); + *uid = xid; + return pico_dhcp_client_init(dhcpc); +} + +static void pico_dhcp_client_recv_params(struct pico_dhcp_client_cookie *dhcpc, struct pico_dhcp_opt *opt) +{ + do { + switch (opt->code) + { + case PICO_DHCP_OPT_PAD: + break; + + case PICO_DHCP_OPT_END: + break; + + case PICO_DHCP_OPT_MSGTYPE: + dhcpc->event = opt->ext.msg_type.type; + dhcpc_dbg("DHCP client: message type %u\n", dhcpc->event); + break; + + case PICO_DHCP_OPT_LEASETIME: + dhcpc->lease_time = long_be(opt->ext.lease_time.time); + dhcpc_dbg("DHCP client: lease time %u\n", dhcpc->lease_time); + break; + + case PICO_DHCP_OPT_RENEWALTIME: + dhcpc->t1_time = long_be(opt->ext.renewal_time.time); + dhcpc_dbg("DHCP client: renewal time %u\n", dhcpc->t1_time); + break; + + case PICO_DHCP_OPT_REBINDINGTIME: + dhcpc->t2_time = long_be(opt->ext.rebinding_time.time); + dhcpc_dbg("DHCP client: rebinding time %u\n", dhcpc->t2_time); + break; + + case PICO_DHCP_OPT_ROUTER: + dhcpc->gateway = opt->ext.router.ip; + dhcpc_dbg("DHCP client: router %08X\n", dhcpc->gateway.addr); + break; + + case PICO_DHCP_OPT_DNS: + dhcpc->nameserver[0] = opt->ext.dns1.ip; + dhcpc_dbg("DHCP client: dns1 %08X\n", dhcpc->nameserver[0].addr); + if (opt->len >= 8) { + dhcpc->nameserver[1] = opt->ext.dns2.ip; + dhcpc_dbg("DHCP client: dns1 %08X\n", dhcpc->nameserver[1].addr); + } + + break; + + case PICO_DHCP_OPT_NETMASK: + dhcpc->netmask = opt->ext.netmask.ip; + dhcpc_dbg("DHCP client: netmask %08X\n", dhcpc->netmask.addr); + break; + + case PICO_DHCP_OPT_SERVERID: + dhcpc->server_id = opt->ext.server_id.ip; + dhcpc_dbg("DHCP client: server ID %08X\n", dhcpc->server_id.addr); + break; + + case PICO_DHCP_OPT_OPTOVERLOAD: + dhcpc_dbg("DHCP client: WARNING option overload present (not processed)"); + break; + + case PICO_DHCP_OPT_HOSTNAME: + { + uint32_t maxlen = PICO_DHCP_HOSTNAME_MAXLEN; + if (opt->len < maxlen) + maxlen = opt->len; + strncpy(dhcpc_host_name, opt->ext.string.txt, maxlen); + } + break; + + case PICO_DHCP_OPT_DOMAINNAME: + { + uint32_t maxlen = PICO_DHCP_HOSTNAME_MAXLEN; + if (opt->len < maxlen) + maxlen = opt->len; + strncpy(dhcpc_domain_name, opt->ext.string.txt, maxlen); + } + break; + + default: + dhcpc_dbg("DHCP client: WARNING unsupported option %u\n", opt->code); + break; + } + } while (pico_dhcp_next_option(&opt)); + + /* default values for T1 and T2 when not provided */ + if (!dhcpc->t1_time) + dhcpc->t1_time = dhcpc->lease_time >> 1; + + if (!dhcpc->t2_time) + dhcpc->t2_time = (dhcpc->lease_time * 875) / 1000; + + return; +} + +static int recv_offer(struct pico_dhcp_client_cookie *dhcpc, uint8_t *buf) +{ + struct pico_dhcp_hdr *hdr = (struct pico_dhcp_hdr *)buf; + struct pico_dhcp_opt *opt = DHCP_OPT(hdr,0); + + pico_dhcp_client_recv_params(dhcpc, opt); + if ((dhcpc->event != PICO_DHCP_MSG_OFFER) || !dhcpc->server_id.addr || !dhcpc->netmask.addr || !dhcpc->lease_time) + return -1; + + dhcpc->address.addr = hdr->yiaddr; + + /* we skip state SELECTING, process first offer received */ + dhcpc->state = DHCP_CLIENT_STATE_REQUESTING; + dhcpc->retry = 0; + pico_dhcp_client_msg(dhcpc, PICO_DHCP_MSG_REQUEST); + pico_dhcp_client_start_requesting_timer(dhcpc); + return 0; +} + +static int recv_ack(struct pico_dhcp_client_cookie *dhcpc, uint8_t *buf) +{ + struct pico_dhcp_hdr *hdr = (struct pico_dhcp_hdr *)buf; + struct pico_dhcp_opt *opt = DHCP_OPT(hdr,0); + struct pico_ip4 address = { + 0 + }; + struct pico_ip4 any_address = { + 0 + }; + + struct pico_ipv4_link *l; + + pico_dhcp_client_recv_params(dhcpc, opt); + if ((dhcpc->event != PICO_DHCP_MSG_ACK) || !dhcpc->server_id.addr || !dhcpc->netmask.addr || !dhcpc->lease_time) + return -1; + + /* Issue #20 the server can transmit on ACK a different IP than the one in OFFER */ + /* RFC2131 ch 4.3.2 ... The client SHOULD use the parameters in the DHCPACK message for configuration */ + if (dhcpc->state == DHCP_CLIENT_STATE_REQUESTING) + dhcpc->address.addr = hdr->yiaddr; + + + /* close the socket used for address (re)acquisition */ + pico_socket_close(dhcpc->s); + dhcpc->s = NULL; + + /* Delete all the links before adding the address */ + pico_ipv4_link_del(dhcpc->dev, address); + l = pico_ipv4_link_by_dev(dhcpc->dev); + while(l) { + pico_ipv4_link_del(dhcpc->dev, l->address); + l = pico_ipv4_link_by_dev_next(dhcpc->dev, l); + } + pico_ipv4_link_add(dhcpc->dev, dhcpc->address, dhcpc->netmask); + + dbg("DHCP client: renewal time (T1) %u\n", (unsigned int)dhcpc->t1_time); + dbg("DHCP client: rebinding time (T2) %u\n", (unsigned int)dhcpc->t2_time); + dbg("DHCP client: lease time %u\n", (unsigned int)dhcpc->lease_time); + + /* If router option is received, use it as default gateway */ + if (dhcpc->gateway.addr != 0U) { + pico_ipv4_route_add(any_address, any_address, dhcpc->gateway, 1, NULL); + } + + dhcpc->retry = 0; + dhcpc->renew_time = dhcpc->t2_time - dhcpc->t1_time; + dhcpc->rebind_time = dhcpc->lease_time - dhcpc->t2_time; + pico_dhcp_client_start_reacquisition_timers(dhcpc); + + *(dhcpc->uid) = dhcpc->xid; + if (dhcpc->cb) + dhcpc->cb(dhcpc, PICO_DHCP_SUCCESS); + + dhcpc->state = DHCP_CLIENT_STATE_BOUND; + return 0; +} + +static int renew(struct pico_dhcp_client_cookie *dhcpc, uint8_t *buf) +{ + uint16_t port = PICO_DHCP_CLIENT_PORT; + (void) buf; + dhcpc->state = DHCP_CLIENT_STATE_RENEWING; + dhcpc->s = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_UDP, &pico_dhcp_client_wakeup); + if (!dhcpc->s) { + dhcpc_dbg("DHCP client ERROR: failure opening socket on renew, aborting DHCP! (%s)\n", strerror(pico_err)); + if (dhcpc->cb) + dhcpc->cb(dhcpc, PICO_DHCP_ERROR); + + return -1; + } + + if (pico_socket_bind(dhcpc->s, &dhcpc->address, &port) != 0) { + dhcpc_dbg("DHCP client ERROR: failure binding socket on renew, aborting DHCP! (%s)\n", strerror(pico_err)); + pico_socket_close(dhcpc->s); + dhcpc->s = NULL; + if (dhcpc->cb) + dhcpc->cb(dhcpc, PICO_DHCP_ERROR); + + return -1; + } + + dhcpc->retry = 0; + pico_dhcp_client_msg(dhcpc, PICO_DHCP_MSG_REQUEST); + pico_dhcp_client_start_renewing_timer(dhcpc); + + return 0; +} + +static int rebind(struct pico_dhcp_client_cookie *dhcpc, uint8_t *buf) +{ + (void) buf; + + dhcpc->state = DHCP_CLIENT_STATE_REBINDING; + dhcpc->retry = 0; + pico_dhcp_client_msg(dhcpc, PICO_DHCP_MSG_REQUEST); + pico_dhcp_client_start_rebinding_timer(dhcpc); + + return 0; +} + +static int reset(struct pico_dhcp_client_cookie *dhcpc, uint8_t *buf) +{ + struct pico_ip4 address = { + 0 + }; + (void) buf; + + if (dhcpc->state == DHCP_CLIENT_STATE_REQUESTING) + address.addr = PICO_IP4_ANY; + else + address.addr = dhcpc->address.addr; + + /* close the socket used for address (re)acquisition */ + pico_socket_close(dhcpc->s); + dhcpc->s = NULL; + /* delete the link with the currently in use address */ + pico_ipv4_link_del(dhcpc->dev, address); + + if (dhcpc->cb) + dhcpc->cb(dhcpc, PICO_DHCP_RESET); + + if (dhcpc->state < DHCP_CLIENT_STATE_BOUND) + { + /* pico_dhcp_client_timer_stop(dhcpc, PICO_DHCPC_TIMER_INIT); */ + } + + + dhcpc->state = DHCP_CLIENT_STATE_INIT; + pico_dhcp_client_stop_timers(dhcpc); + pico_dhcp_client_init(dhcpc); + return 0; +} + +static int retransmit(struct pico_dhcp_client_cookie *dhcpc, uint8_t *buf) +{ + (void) buf; + switch (dhcpc->state) + { + case DHCP_CLIENT_STATE_INIT: + pico_dhcp_client_msg(dhcpc, PICO_DHCP_MSG_DISCOVER); + pico_dhcp_client_start_init_timer(dhcpc); + break; + + case DHCP_CLIENT_STATE_REQUESTING: + pico_dhcp_client_msg(dhcpc, PICO_DHCP_MSG_REQUEST); + pico_dhcp_client_start_requesting_timer(dhcpc); + break; + + case DHCP_CLIENT_STATE_RENEWING: + pico_dhcp_client_msg(dhcpc, PICO_DHCP_MSG_REQUEST); + pico_dhcp_client_start_renewing_timer(dhcpc); + break; + + case DHCP_CLIENT_STATE_REBINDING: + pico_dhcp_client_msg(dhcpc, PICO_DHCP_MSG_DISCOVER); + pico_dhcp_client_start_rebinding_timer(dhcpc); + break; + + default: + dhcpc_dbg("DHCP client WARNING: retransmit in incorrect state (%u)!\n", dhcpc->state); + return -1; + } + return 0; +} + +struct dhcp_action_entry { + int (*offer)(struct pico_dhcp_client_cookie *dhcpc, uint8_t *buf); + int (*ack)(struct pico_dhcp_client_cookie *dhcpc, uint8_t *buf); + int (*nak)(struct pico_dhcp_client_cookie *dhcpc, uint8_t *buf); + int (*timer1)(struct pico_dhcp_client_cookie *dhcpc, uint8_t *buf); + int (*timer2)(struct pico_dhcp_client_cookie *dhcpc, uint8_t *buf); + int (*timer_lease)(struct pico_dhcp_client_cookie *dhcpc, uint8_t *buf); + int (*timer_retransmit)(struct pico_dhcp_client_cookie *dhcpc, uint8_t *buf); +}; + +static struct dhcp_action_entry dhcp_fsm[] = +{ /* event |offer |ack |nak |T1 |T2 |lease |retransmit */ +/* state init-reboot */ + { NULL, NULL, NULL, NULL, NULL, NULL, NULL }, +/* state rebooting */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL }, +/* state init */ { recv_offer, NULL, NULL, NULL, NULL, NULL, retransmit }, +/* state selecting */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL }, +/* state requesting */ { NULL, recv_ack, reset, NULL, NULL, NULL, retransmit }, +/* state bound */ { NULL, NULL, NULL, renew, NULL, NULL, NULL }, +/* state renewing */ { NULL, recv_ack, reset, NULL, rebind, NULL, retransmit }, +/* state rebinding */ { NULL, recv_ack, reset, NULL, NULL, reset, retransmit }, +}; + +/* TIMERS REMARK: + * In state bound we have T1, T2 and the lease timer running. If T1 goes off, we attempt to renew. + * If the renew succeeds a new T1, T2 and lease timer is started. The former T2 and lease timer is + * still running though. This poses no concerns as the T2 and lease event in state bound have a NULL + * pointer in the fsm. If the former T2 or lease timer goes off, nothing happens. Same situation + * applies for T2 and a succesfull rebind. */ + +static void pico_dhcp_state_machine(uint8_t event, struct pico_dhcp_client_cookie *dhcpc, uint8_t *buf) +{ + switch (event) + { + case PICO_DHCP_MSG_OFFER: + dhcpc_dbg("DHCP client: received OFFER\n"); + if (dhcp_fsm[dhcpc->state].offer) + dhcp_fsm[dhcpc->state].offer(dhcpc, buf); + + break; + + case PICO_DHCP_MSG_ACK: + dhcpc_dbg("DHCP client: received ACK\n"); + if (dhcp_fsm[dhcpc->state].ack) + dhcp_fsm[dhcpc->state].ack(dhcpc, buf); + + break; + + case PICO_DHCP_MSG_NAK: + dhcpc_dbg("DHCP client: received NAK\n"); + if (dhcp_fsm[dhcpc->state].nak) + dhcp_fsm[dhcpc->state].nak(dhcpc, buf); + + break; + + case PICO_DHCP_EVENT_T1: + dhcpc_dbg("DHCP client: received T1 timeout\n"); + if (dhcp_fsm[dhcpc->state].timer1) + dhcp_fsm[dhcpc->state].timer1(dhcpc, NULL); + + break; + + case PICO_DHCP_EVENT_T2: + dhcpc_dbg("DHCP client: received T2 timeout\n"); + if (dhcp_fsm[dhcpc->state].timer2) + dhcp_fsm[dhcpc->state].timer2(dhcpc, NULL); + + break; + + case PICO_DHCP_EVENT_LEASE: + dhcpc_dbg("DHCP client: received LEASE timeout\n"); + if (dhcp_fsm[dhcpc->state].timer_lease) + dhcp_fsm[dhcpc->state].timer_lease(dhcpc, NULL); + + break; + + case PICO_DHCP_EVENT_RETRANSMIT: + dhcpc_dbg("DHCP client: received RETRANSMIT timeout\n"); + if (dhcp_fsm[dhcpc->state].timer_retransmit) + dhcp_fsm[dhcpc->state].timer_retransmit(dhcpc, NULL); + + break; + + default: + dhcpc_dbg("DHCP client WARNING: unrecognized event (%u)!\n", dhcpc->event); + return; + } + return; +} + +static int16_t pico_dhcp_client_opt_parse(void *ptr, uint16_t len) +{ + uint32_t optlen = len - (uint32_t)sizeof(struct pico_dhcp_hdr); + struct pico_dhcp_hdr *hdr = (struct pico_dhcp_hdr *)ptr; + struct pico_dhcp_opt *opt = DHCP_OPT(hdr,0); + + if (hdr->dhcp_magic != PICO_DHCPD_MAGIC_COOKIE) + return -1; + + if (!pico_dhcp_are_options_valid(opt, (int32_t)optlen)) + return -1; + + do { + if (opt->code == PICO_DHCP_OPT_MSGTYPE) + return opt->ext.msg_type.type; + } while (pico_dhcp_next_option(&opt)); + + return -1; +} + +static int8_t pico_dhcp_client_msg(struct pico_dhcp_client_cookie *dhcpc, uint8_t msg_type) +{ + int32_t r = 0; + uint16_t optlen = 0, offset = 0; + struct pico_ip4 destination = { + .addr = 0xFFFFFFFF + }; + struct pico_dhcp_hdr *hdr = NULL; + + + /* RFC 2131 3.1.3: Request is always BROADCAST */ + + /* Set again default route for the bcast request */ + pico_ipv4_route_set_bcast_link(pico_ipv4_link_by_dev(dhcpc->dev)); + + switch (msg_type) + { + case PICO_DHCP_MSG_DISCOVER: + dhcpc_dbg("DHCP client: sent DHCPDISCOVER\n"); + optlen = PICO_DHCP_OPTLEN_MSGTYPE + PICO_DHCP_OPTLEN_MAXMSGSIZE + PICO_DHCP_OPTLEN_PARAMLIST + PICO_DHCP_OPTLEN_END; + hdr = PICO_ZALLOC((size_t)(sizeof(struct pico_dhcp_hdr) + optlen)); + if (!hdr) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + /* specific options */ + offset = (uint16_t)(offset + pico_dhcp_opt_maxmsgsize(DHCP_OPT(hdr,offset), DHCP_CLIENT_MAXMSGZISE)); + break; + + case PICO_DHCP_MSG_REQUEST: + optlen = PICO_DHCP_OPTLEN_MSGTYPE + PICO_DHCP_OPTLEN_MAXMSGSIZE + PICO_DHCP_OPTLEN_PARAMLIST + PICO_DHCP_OPTLEN_REQIP + PICO_DHCP_OPTLEN_SERVERID + + PICO_DHCP_OPTLEN_END; + hdr = PICO_ZALLOC(sizeof(struct pico_dhcp_hdr) + optlen); + if (!hdr) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + /* specific options */ + offset = (uint16_t)(offset + pico_dhcp_opt_maxmsgsize(DHCP_OPT(hdr,offset), DHCP_CLIENT_MAXMSGZISE)); + if (dhcpc->state == DHCP_CLIENT_STATE_REQUESTING) { + offset = (uint16_t)(offset + pico_dhcp_opt_reqip(DHCP_OPT(hdr,offset), &dhcpc->address)); + offset = (uint16_t)(offset + pico_dhcp_opt_serverid(DHCP_OPT(hdr,offset), &dhcpc->server_id)); + } + + break; + + default: + return -1; + } + + /* common options */ + offset = (uint16_t)(offset + pico_dhcp_opt_msgtype(DHCP_OPT(hdr,offset), msg_type)); + offset = (uint16_t)(offset + pico_dhcp_opt_paramlist(DHCP_OPT(hdr,offset))); + offset = (uint16_t)(offset + pico_dhcp_opt_end(DHCP_OPT(hdr,offset))); + + switch (dhcpc->state) + { + case DHCP_CLIENT_STATE_BOUND: + destination.addr = dhcpc->server_id.addr; + hdr->ciaddr = dhcpc->address.addr; + break; + + case DHCP_CLIENT_STATE_RENEWING: + destination.addr = dhcpc->server_id.addr; + hdr->ciaddr = dhcpc->address.addr; + break; + + case DHCP_CLIENT_STATE_REBINDING: + hdr->ciaddr = dhcpc->address.addr; + break; + + default: + /* do nothing */ + break; + } + + /* header information */ + hdr->op = PICO_DHCP_OP_REQUEST; + hdr->htype = PICO_DHCP_HTYPE_ETH; + hdr->hlen = PICO_SIZE_ETH; + hdr->xid = dhcpc->xid; + /* hdr->flags = short_be(PICO_DHCP_FLAG_BROADCAST); / * Nope: see bug #96! * / */ + hdr->dhcp_magic = PICO_DHCPD_MAGIC_COOKIE; + /* copy client hardware address */ + memcpy(hdr->hwaddr, &dhcpc->dev->eth->mac, PICO_SIZE_ETH); + + if (destination.addr == PICO_IP4_BCAST) + pico_ipv4_route_set_bcast_link(pico_ipv4_link_get(&dhcpc->address)); + + r = pico_socket_sendto(dhcpc->s, hdr, (int)(sizeof(struct pico_dhcp_hdr) + optlen), &destination, PICO_DHCPD_PORT); + PICO_FREE(hdr); + if (r < 0) + return -1; + + return 0; +} + +static void pico_dhcp_client_wakeup(uint16_t ev, struct pico_socket *s) +{ + + uint8_t *buf; + int r = 0; + struct pico_dhcp_hdr *hdr = NULL; + struct pico_dhcp_client_cookie *dhcpc = NULL; + + if ((ev & PICO_SOCK_EV_RD) == 0) + return; + + buf = PICO_ZALLOC(DHCP_CLIENT_MAXMSGZISE); + if (!buf) { + return; + } + + r = pico_socket_recvfrom(s, buf, DHCP_CLIENT_MAXMSGZISE, NULL, NULL); + if (r < 0) + goto out_discard_buf; + + /* If the 'xid' of an arriving message does not match the 'xid' + * of the most recent transmitted message, the message must be + * silently discarded. */ + hdr = (struct pico_dhcp_hdr *)buf; + dhcpc = pico_dhcp_client_find_cookie(hdr->xid); + if (!dhcpc) + goto out_discard_buf; + + dhcpc->event = (uint8_t)pico_dhcp_client_opt_parse(buf, (uint16_t)r); + pico_dhcp_state_machine(dhcpc->event, dhcpc, buf); + +out_discard_buf: + PICO_FREE(buf); +} + +void *pico_dhcp_get_identifier(uint32_t xid) +{ + return (void *)pico_dhcp_client_find_cookie(xid); +} + +struct pico_ip4 pico_dhcp_get_address(void*dhcpc) +{ + return ((struct pico_dhcp_client_cookie*)dhcpc)->address; +} + +struct pico_ip4 pico_dhcp_get_gateway(void*dhcpc) +{ + return ((struct pico_dhcp_client_cookie*)dhcpc)->gateway; +} + +struct pico_ip4 pico_dhcp_get_netmask(void *dhcpc) +{ + return ((struct pico_dhcp_client_cookie*)dhcpc)->netmask; +} + +struct pico_ip4 pico_dhcp_get_nameserver(void*dhcpc, int index) +{ + struct pico_ip4 fault = { + .addr = 0xFFFFFFFFU + }; + if ((index != 0) && (index != 1)) + return fault; + + return ((struct pico_dhcp_client_cookie*)dhcpc)->nameserver[index]; +} + +int pico_dhcp_client_abort(uint32_t xid) +{ + return pico_dhcp_client_del_cookie(xid); +} + + +char *pico_dhcp_get_hostname(void) +{ + return dhcpc_host_name; +} + +char *pico_dhcp_get_domain(void) +{ + return dhcpc_domain_name; +} + +#endif diff --git a/ext/picotcp/modules/pico_dhcp_client.h b/ext/picotcp/modules/pico_dhcp_client.h new file mode 100644 index 0000000..4dafb2a --- /dev/null +++ b/ext/picotcp/modules/pico_dhcp_client.h @@ -0,0 +1,32 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + . + + *********************************************************************/ +#ifndef INCLUDE_PICO_DHCP_CLIENT +#define INCLUDE_PICO_DHCP_CLIENT +#include "pico_defines.h" +#ifdef PICO_SUPPORT_UDP +#include "pico_dhcp_common.h" +#include "pico_addressing.h" +#include "pico_protocol.h" + +int pico_dhcp_initiate_negotiation(struct pico_device *device, void (*callback)(void*cli, int code), uint32_t *xid); +void *pico_dhcp_get_identifier(uint32_t xid); +struct pico_ip4 pico_dhcp_get_address(void *cli); +struct pico_ip4 pico_dhcp_get_gateway(void *cli); +struct pico_ip4 pico_dhcp_get_netmask(void *cli); +struct pico_ip4 pico_dhcp_get_nameserver(void*cli, int index); +int pico_dhcp_client_abort(uint32_t xid); +char *pico_dhcp_get_hostname(void); +char *pico_dhcp_get_domain(void); + +/* possible codes for the callback */ +#define PICO_DHCP_SUCCESS 0 +#define PICO_DHCP_ERROR 1 +#define PICO_DHCP_RESET 2 + +#endif +#endif diff --git a/ext/picotcp/modules/pico_dhcp_common.c b/ext/picotcp/modules/pico_dhcp_common.c new file mode 100644 index 0000000..7edb1e2 --- /dev/null +++ b/ext/picotcp/modules/pico_dhcp_common.c @@ -0,0 +1,190 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + . + + Authors: Frederik Van Slycken + *********************************************************************/ + +#include "pico_config.h" +#include "pico_stack.h" +#include "pico_dhcp_common.h" + +#if defined (PICO_SUPPORT_DHCPC) || defined (PICO_SUPPORT_DHCPD) +/* pico_dhcp_are_options_valid needs to be called first to prevent illegal memory access */ +/* The argument pointer is moved forward to the next option */ +struct pico_dhcp_opt *pico_dhcp_next_option(struct pico_dhcp_opt **ptr) +{ + uint8_t **p = (uint8_t **)ptr; + struct pico_dhcp_opt *opt = *ptr; + + if (opt->code == PICO_DHCP_OPT_END) + return NULL; + + if (opt->code == PICO_DHCP_OPT_PAD) { + *p += 1; + return *ptr; + } + + *p += (opt->len + 2); /* (len + 2) to account for code and len octet */ + return *ptr; +} + +uint8_t pico_dhcp_are_options_valid(void *ptr, int32_t len) +{ + uint8_t optlen = 0, *p = ptr; + + while (len > 0) { + switch (*p) + { + case PICO_DHCP_OPT_END: + return 1; + + case PICO_DHCP_OPT_PAD: + p++; + len--; + break; + + default: + p++; /* move pointer from code octet to len octet */ + len--; + if ((len <= 0) || (len - (*p + 1) < 0)) /* (*p + 1) to account for len octet */ + return 0; + + optlen = *p; + p += optlen + 1; + len -= optlen; + break; + } + } + return 0; +} + +uint8_t pico_dhcp_opt_netmask(void *ptr, struct pico_ip4 *ip) +{ + struct pico_dhcp_opt *opt = (struct pico_dhcp_opt *)ptr; + + /* option: netmask */ + opt->code = PICO_DHCP_OPT_NETMASK; + opt->len = PICO_DHCP_OPTLEN_NETMASK - PICO_DHCP_OPTLEN_HDR; + opt->ext.netmask.ip = *ip; + return PICO_DHCP_OPTLEN_NETMASK; +} + +uint8_t pico_dhcp_opt_router(void *ptr, struct pico_ip4 *ip) +{ + struct pico_dhcp_opt *opt = (struct pico_dhcp_opt *)ptr; + + /* option: router */ + opt->code = PICO_DHCP_OPT_ROUTER; + opt->len = PICO_DHCP_OPTLEN_ROUTER - PICO_DHCP_OPTLEN_HDR; + opt->ext.router.ip = *ip; + return PICO_DHCP_OPTLEN_ROUTER; +} + +uint8_t pico_dhcp_opt_dns(void *ptr, struct pico_ip4 *ip) +{ + struct pico_dhcp_opt *opt = (struct pico_dhcp_opt *)ptr; + + /* option: dns */ + opt->code = PICO_DHCP_OPT_DNS; + opt->len = PICO_DHCP_OPTLEN_DNS - PICO_DHCP_OPTLEN_HDR; + opt->ext.dns1.ip = *ip; + return PICO_DHCP_OPTLEN_DNS; +} + +uint8_t pico_dhcp_opt_broadcast(void *ptr, struct pico_ip4 *ip) +{ + struct pico_dhcp_opt *opt = (struct pico_dhcp_opt *)ptr; + + /* option: broadcast */ + opt->code = PICO_DHCP_OPT_BROADCAST; + opt->len = PICO_DHCP_OPTLEN_BROADCAST - PICO_DHCP_OPTLEN_HDR; + opt->ext.broadcast.ip = *ip; + return PICO_DHCP_OPTLEN_BROADCAST; +} + +uint8_t pico_dhcp_opt_reqip(void *ptr, struct pico_ip4 *ip) +{ + struct pico_dhcp_opt *opt = (struct pico_dhcp_opt *)ptr; + + /* option: request IP address */ + opt->code = PICO_DHCP_OPT_REQIP; + opt->len = PICO_DHCP_OPTLEN_REQIP - PICO_DHCP_OPTLEN_HDR; + opt->ext.req_ip.ip = *ip; + return PICO_DHCP_OPTLEN_REQIP; +} + +uint8_t pico_dhcp_opt_leasetime(void *ptr, uint32_t time) +{ + struct pico_dhcp_opt *opt = (struct pico_dhcp_opt *)ptr; + + /* option: lease time */ + opt->code = PICO_DHCP_OPT_LEASETIME; + opt->len = PICO_DHCP_OPTLEN_LEASETIME - PICO_DHCP_OPTLEN_HDR; + opt->ext.lease_time.time = time; + return PICO_DHCP_OPTLEN_LEASETIME; +} + +uint8_t pico_dhcp_opt_msgtype(void *ptr, uint8_t type) +{ + struct pico_dhcp_opt *opt = (struct pico_dhcp_opt *)ptr; + + /* option: message type */ + opt->code = PICO_DHCP_OPT_MSGTYPE; + opt->len = PICO_DHCP_OPTLEN_MSGTYPE - PICO_DHCP_OPTLEN_HDR; + opt->ext.msg_type.type = type; + return PICO_DHCP_OPTLEN_MSGTYPE; +} + +uint8_t pico_dhcp_opt_serverid(void *ptr, struct pico_ip4 *ip) +{ + struct pico_dhcp_opt *opt = (struct pico_dhcp_opt *)ptr; + + /* option: server identifier */ + opt->code = PICO_DHCP_OPT_SERVERID; + opt->len = PICO_DHCP_OPTLEN_SERVERID - PICO_DHCP_OPTLEN_HDR; + opt->ext.server_id.ip = *ip; + return PICO_DHCP_OPTLEN_SERVERID; +} + +uint8_t pico_dhcp_opt_paramlist(void *ptr) +{ + struct pico_dhcp_opt *opt = (struct pico_dhcp_opt *)ptr; + uint8_t *param_code = &(opt->ext.param_list.code[0]); + + /* option: parameter list */ + opt->code = PICO_DHCP_OPT_PARAMLIST; + opt->len = PICO_DHCP_OPTLEN_PARAMLIST - PICO_DHCP_OPTLEN_HDR; + param_code[0] = PICO_DHCP_OPT_NETMASK; + param_code[1] = PICO_DHCP_OPT_TIME; + param_code[2] = PICO_DHCP_OPT_ROUTER; + param_code[3] = PICO_DHCP_OPT_HOSTNAME; + param_code[4] = PICO_DHCP_OPT_RENEWALTIME; + param_code[5] = PICO_DHCP_OPT_REBINDINGTIME; + param_code[6] = PICO_DHCP_OPT_DNS; + return PICO_DHCP_OPTLEN_PARAMLIST; +} + +uint8_t pico_dhcp_opt_maxmsgsize(void *ptr, uint16_t size) +{ + struct pico_dhcp_opt *opt = (struct pico_dhcp_opt *)ptr; + + /* option: maximum message size */ + opt->code = PICO_DHCP_OPT_MAXMSGSIZE; + opt->len = PICO_DHCP_OPTLEN_MAXMSGSIZE - PICO_DHCP_OPTLEN_HDR; + opt->ext.max_msg_size.size = short_be(size); + return PICO_DHCP_OPTLEN_MAXMSGSIZE; +} + +uint8_t pico_dhcp_opt_end(void *ptr) +{ + uint8_t *opt = (uint8_t *)ptr; + + /* option: end of options */ + *opt = PICO_DHCP_OPT_END; + return PICO_DHCP_OPTLEN_END; +} + +#endif diff --git a/ext/picotcp/modules/pico_dhcp_common.h b/ext/picotcp/modules/pico_dhcp_common.h new file mode 100644 index 0000000..62c94da --- /dev/null +++ b/ext/picotcp/modules/pico_dhcp_common.h @@ -0,0 +1,191 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + . + + *********************************************************************/ +#ifndef INCLUDE_PICO_DHCP_COMMON +#define INCLUDE_PICO_DHCP_COMMON +#include "pico_config.h" +#include "pico_addressing.h" + +#define PICO_DHCPD_PORT (short_be(67)) +#define PICO_DHCP_CLIENT_PORT (short_be(68)) +#define PICO_DHCPD_MAGIC_COOKIE (long_be(0x63825363)) +#define PICO_DHCP_HTYPE_ETH 1 + +/* Macro to get DHCP option field */ +#define DHCP_OPT(hdr,off) ((struct pico_dhcp_opt *)(((uint8_t *)hdr)+sizeof(struct pico_dhcp_hdr) + off)) + +/* flags */ +#define PICO_DHCP_FLAG_BROADCAST 0x8000 + +/* options */ +#define PICO_DHCP_OPT_PAD 0x00 +#define PICO_DHCP_OPT_NETMASK 0x01 +#define PICO_DHCP_OPT_TIME 0x02 +#define PICO_DHCP_OPT_ROUTER 0x03 +#define PICO_DHCP_OPT_DNS 0x06 +#define PICO_DHCP_OPT_HOSTNAME 0x0c +#define PICO_DHCP_OPT_DOMAINNAME 0x0f +#define PICO_DHCP_OPT_MTU 0x1a +#define PICO_DHCP_OPT_BROADCAST 0x1c +#define PICO_DHCP_OPT_NETBIOSNS 0x2c +#define PICO_DHCP_OPT_NETBIOSSCOPE 0x2f +#define PICO_DHCP_OPT_REQIP 0x32 +#define PICO_DHCP_OPT_LEASETIME 0x33 +#define PICO_DHCP_OPT_OPTOVERLOAD 0x34 +#define PICO_DHCP_OPT_MSGTYPE 0x35 +#define PICO_DHCP_OPT_SERVERID 0x36 +#define PICO_DHCP_OPT_PARAMLIST 0x37 +#define PICO_DHCP_OPT_MESSAGE 0x38 +#define PICO_DHCP_OPT_MAXMSGSIZE 0x39 +#define PICO_DHCP_OPT_RENEWALTIME 0x3a +#define PICO_DHCP_OPT_REBINDINGTIME 0x3b +#define PICO_DHCP_OPT_VENDORID 0x3c +#define PICO_DHCP_OPT_CLIENTID 0x3d +#define PICO_DHCP_OPT_DOMAINSEARCH 0x77 +#define PICO_DHCP_OPT_STATICROUTE 0x79 +#define PICO_DHCP_OPT_END 0xFF + +/* options len */ +#define PICO_DHCP_OPTLEN_HDR 2 /* account for code and len field */ +#define PICO_DHCP_OPTLEN_NETMASK 6 +#define PICO_DHCP_OPTLEN_ROUTER 6 +#define PICO_DHCP_OPTLEN_DNS 6 +#define PICO_DHCP_OPTLEN_BROADCAST 6 +#define PICO_DHCP_OPTLEN_REQIP 6 +#define PICO_DHCP_OPTLEN_LEASETIME 6 +#define PICO_DHCP_OPTLEN_OPTOVERLOAD 3 +#define PICO_DHCP_OPTLEN_MSGTYPE 3 +#define PICO_DHCP_OPTLEN_SERVERID 6 +#define PICO_DHCP_OPTLEN_PARAMLIST 9 /* PicoTCP specific */ +#define PICO_DHCP_OPTLEN_MAXMSGSIZE 4 +#define PICO_DHCP_OPTLEN_RENEWALTIME 6 +#define PICO_DHCP_OPTLEN_REBINDINGTIME 6 +#define PICO_DHCP_OPTLEN_END 1 + +/* op codes */ +#define PICO_DHCP_OP_REQUEST 1 +#define PICO_DHCP_OP_REPLY 2 + +/* rfc message types */ +#define PICO_DHCP_MSG_DISCOVER 1 +#define PICO_DHCP_MSG_OFFER 2 +#define PICO_DHCP_MSG_REQUEST 3 +#define PICO_DHCP_MSG_DECLINE 4 +#define PICO_DHCP_MSG_ACK 5 +#define PICO_DHCP_MSG_NAK 6 +#define PICO_DHCP_MSG_RELEASE 7 +#define PICO_DHCP_MSG_INFORM 8 + +/* custom message types */ +#define PICO_DHCP_EVENT_T1 9 +#define PICO_DHCP_EVENT_T2 10 +#define PICO_DHCP_EVENT_LEASE 11 +#define PICO_DHCP_EVENT_RETRANSMIT 12 +#define PICO_DHCP_EVENT_NONE 0xff + +PACKED_STRUCT_DEF pico_dhcp_hdr +{ + uint8_t op; + uint8_t htype; + uint8_t hlen; + uint8_t hops; /* zero */ + uint32_t xid; /* store this in the request */ + uint16_t secs; /* ignore */ + uint16_t flags; + uint32_t ciaddr; /* client address - if asking for renewal */ + uint32_t yiaddr; /* your address (client) */ + uint32_t siaddr; /* dhcp offered address */ + uint32_t giaddr; /* relay agent, bootp. */ + uint8_t hwaddr[6]; + uint8_t hwaddr_padding[10]; + char hostname[64]; + char bootp_filename[128]; + uint32_t dhcp_magic; +}; + +PACKED_STRUCT_DEF pico_dhcp_opt +{ + uint8_t code; + uint8_t len; + PACKED_UNION_DEF dhcp_opt_ext_u { + PEDANTIC_STRUCT_DEF netmask_s { + struct pico_ip4 ip; + } netmask; + PEDANTIC_STRUCT_DEF router_s { + struct pico_ip4 ip; + } router; + PEDANTIC_STRUCT_DEF dns_s { + struct pico_ip4 ip; + } dns1; + struct dns_s dns2; + PEDANTIC_STRUCT_DEF broadcast_s { + struct pico_ip4 ip; + } broadcast; + PEDANTIC_STRUCT_DEF req_ip_s { + struct pico_ip4 ip; + } req_ip; + PEDANTIC_STRUCT_DEF lease_time_s { + uint32_t time; + } lease_time; + PEDANTIC_STRUCT_DEF opt_overload_s { + uint8_t value; + } opt_overload; + PEDANTIC_STRUCT_DEF tftp_server_s { + char name[1]; + } tftp_server; + PEDANTIC_STRUCT_DEF bootfile_s { + char name[1]; + } bootfile; + PEDANTIC_STRUCT_DEF msg_type_s { + uint8_t type; + } msg_type; + PEDANTIC_STRUCT_DEF server_id_s { + struct pico_ip4 ip; + } server_id; + PEDANTIC_STRUCT_DEF param_list_s { + uint8_t code[1]; + } param_list; + PEDANTIC_STRUCT_DEF message_s { + char error[1]; + } message; + PEDANTIC_STRUCT_DEF max_msg_size_s { + uint16_t size; + } max_msg_size; + PEDANTIC_STRUCT_DEF renewal_time_s { + uint32_t time; + } renewal_time; + PEDANTIC_STRUCT_DEF rebinding_time_s { + uint32_t time; + } rebinding_time; + PEDANTIC_STRUCT_DEF vendor_id_s { + uint8_t id[1]; + } vendor_id; + PEDANTIC_STRUCT_DEF client_id_s { + uint8_t id[1]; + } client_id; + PEDANTIC_STRUCT_DEF text_s { + char txt[1]; + } string; + } ext; +}; + +uint8_t dhcp_get_next_option(uint8_t *begin, uint8_t *data, int *len, uint8_t **nextopt); +struct pico_dhcp_opt *pico_dhcp_next_option(struct pico_dhcp_opt **ptr); +uint8_t pico_dhcp_are_options_valid(void *ptr, int32_t len); + +uint8_t pico_dhcp_opt_netmask(void *ptr, struct pico_ip4 *ip); +uint8_t pico_dhcp_opt_router(void *ptr, struct pico_ip4 *ip); +uint8_t pico_dhcp_opt_dns(void *ptr, struct pico_ip4 *ip); +uint8_t pico_dhcp_opt_broadcast(void *ptr, struct pico_ip4 *ip); +uint8_t pico_dhcp_opt_reqip(void *ptr, struct pico_ip4 *ip); +uint8_t pico_dhcp_opt_leasetime(void *ptr, uint32_t time); +uint8_t pico_dhcp_opt_msgtype(void *ptr, uint8_t type); +uint8_t pico_dhcp_opt_serverid(void *ptr, struct pico_ip4 *ip); +uint8_t pico_dhcp_opt_paramlist(void *ptr); +uint8_t pico_dhcp_opt_maxmsgsize(void *ptr, uint16_t size); +uint8_t pico_dhcp_opt_end(void *ptr); +#endif diff --git a/ext/picotcp/modules/pico_dhcp_server.c b/ext/picotcp/modules/pico_dhcp_server.c new file mode 100644 index 0000000..dbef3bb --- /dev/null +++ b/ext/picotcp/modules/pico_dhcp_server.c @@ -0,0 +1,411 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + + Authors: Frederik Van Slycken, Kristof Roelants + *********************************************************************/ + +#include "pico_dhcp_server.h" +#include "pico_config.h" +#include "pico_addressing.h" +#include "pico_socket.h" +#include "pico_udp.h" +#include "pico_stack.h" +#include "pico_arp.h" + +#if (defined PICO_SUPPORT_DHCPD && defined PICO_SUPPORT_UDP) + +#define dhcps_dbg(...) do {} while(0) +/* #define dhcps_dbg dbg */ + +/* default configurations */ +#define DHCP_SERVER_OPENDNS long_be(0xd043dede) /* OpenDNS DNS server 208.67.222.222 */ +#define DHCP_SERVER_POOL_START long_be(0x00000064) +#define DHCP_SERVER_POOL_END long_be(0x000000fe) +#define DHCP_SERVER_LEASE_TIME long_be(0x00000078) + +/* maximum size of a DHCP message */ +#define DHCP_SERVER_MAXMSGSIZE (PICO_IP_MRU - sizeof(struct pico_ipv4_hdr) - sizeof(struct pico_udp_hdr)) + +enum dhcp_server_state { + PICO_DHCP_STATE_DISCOVER = 0, + PICO_DHCP_STATE_OFFER, + PICO_DHCP_STATE_REQUEST, + PICO_DHCP_STATE_BOUND, + PICO_DHCP_STATE_RENEWING +}; + +struct pico_dhcp_server_negotiation { + uint32_t xid; + enum dhcp_server_state state; + struct pico_dhcp_server_setting *dhcps; + struct pico_ip4 ciaddr; + struct pico_eth hwaddr; + uint8_t bcast; +}; + +static inline int ip_address_is_in_dhcp_range(struct pico_dhcp_server_negotiation *n, uint32_t x) +{ + uint32_t ip_hostendian = long_be(x); + if (ip_hostendian < long_be(n->dhcps->pool_start)) + return 0; + + if (ip_hostendian > long_be(n->dhcps->pool_end)) + return 0; + + return 1; +} + + +static void pico_dhcpd_wakeup(uint16_t ev, struct pico_socket *s); + +static int dhcp_settings_cmp(void *ka, void *kb) +{ + struct pico_dhcp_server_setting *a = ka, *b = kb; + if (a->dev == b->dev) + return 0; + + return (a->dev < b->dev) ? (-1) : (1); +} +PICO_TREE_DECLARE(DHCPSettings, dhcp_settings_cmp); + +static int dhcp_negotiations_cmp(void *ka, void *kb) +{ + struct pico_dhcp_server_negotiation *a = ka, *b = kb; + if (a->xid == b->xid) + return 0; + + return (a->xid < b->xid) ? (-1) : (1); +} +PICO_TREE_DECLARE(DHCPNegotiations, dhcp_negotiations_cmp); + + +static inline void dhcps_set_default_pool_start_if_not_provided(struct pico_dhcp_server_setting *dhcps) +{ + if (!dhcps->pool_start) + dhcps->pool_start = (dhcps->server_ip.addr & dhcps->netmask.addr) | DHCP_SERVER_POOL_START; +} + +static inline void dhcps_set_default_pool_end_if_not_provided(struct pico_dhcp_server_setting *dhcps) +{ + if (!dhcps->pool_end) + dhcps->pool_end = (dhcps->server_ip.addr & dhcps->netmask.addr) | DHCP_SERVER_POOL_END; +} +static inline void dhcps_set_default_lease_time_if_not_provided(struct pico_dhcp_server_setting *dhcps) +{ + if (!dhcps->lease_time) + dhcps->lease_time = DHCP_SERVER_LEASE_TIME; +} + +static inline struct pico_dhcp_server_setting *dhcps_try_open_socket(struct pico_dhcp_server_setting *dhcps) +{ + uint16_t port = PICO_DHCPD_PORT; + dhcps->s = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_UDP, &pico_dhcpd_wakeup); + if (!dhcps->s) { + dhcps_dbg("DHCP server ERROR: failure opening socket (%s)\n", strerror(pico_err)); + PICO_FREE(dhcps); + return NULL; + } + + if (pico_socket_bind(dhcps->s, &dhcps->server_ip, &port) < 0) { + dhcps_dbg("DHCP server ERROR: failure binding socket (%s)\n", strerror(pico_err)); + PICO_FREE(dhcps); + return NULL; + } + + pico_tree_insert(&DHCPSettings, dhcps); + return dhcps; +} + +static struct pico_dhcp_server_setting *pico_dhcp_server_add_setting(struct pico_dhcp_server_setting *setting) +{ + struct pico_dhcp_server_setting *dhcps = NULL, *found = NULL, test = { + 0 + }; + struct pico_ipv4_link *link = NULL; + + link = pico_ipv4_link_get(&setting->server_ip); + if (!link) { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + + test.dev = setting->dev; + found = pico_tree_findKey(&DHCPSettings, &test); + if (found) { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + + dhcps = PICO_ZALLOC(sizeof(struct pico_dhcp_server_setting)); + if (!dhcps) { + pico_err = PICO_ERR_ENOMEM; + return NULL; + } + + dhcps->lease_time = setting->lease_time; + dhcps->pool_start = setting->pool_start; + dhcps->pool_next = setting->pool_next; + dhcps->pool_end = setting->pool_end; + dhcps->dev = link->dev; + dhcps->server_ip = link->address; + dhcps->netmask = link->netmask; + + /* default values if not provided */ + dhcps_set_default_lease_time_if_not_provided(dhcps); + dhcps_set_default_pool_end_if_not_provided(dhcps); + dhcps_set_default_pool_start_if_not_provided(dhcps); + + dhcps->pool_next = dhcps->pool_start; + + return dhcps_try_open_socket(dhcps); + +} + +static struct pico_dhcp_server_negotiation *pico_dhcp_server_find_negotiation(uint32_t xid) +{ + struct pico_dhcp_server_negotiation test = { + 0 + }, *found = NULL; + + test.xid = xid; + found = pico_tree_findKey(&DHCPNegotiations, &test); + if (found) + return found; + else + return NULL; +} + +static inline void dhcp_negotiation_set_ciaddr(struct pico_dhcp_server_negotiation *dhcpn) +{ + struct pico_ip4 *ciaddr = NULL; + ciaddr = pico_arp_reverse_lookup(&dhcpn->hwaddr); + if (!ciaddr) { + dhcpn->ciaddr.addr = dhcpn->dhcps->pool_next; + dhcpn->dhcps->pool_next = long_be(long_be(dhcpn->dhcps->pool_next) + 1); + pico_arp_create_entry(dhcpn->hwaddr.addr, dhcpn->ciaddr, dhcpn->dhcps->dev); + } else { + dhcpn->ciaddr = *ciaddr; + } +} + +static struct pico_dhcp_server_negotiation *pico_dhcp_server_add_negotiation(struct pico_device *dev, struct pico_dhcp_hdr *hdr) +{ + struct pico_dhcp_server_negotiation *dhcpn = NULL; + struct pico_dhcp_server_setting test = { + 0 + }; + + if (pico_dhcp_server_find_negotiation(hdr->xid)) + return NULL; + + dhcpn = PICO_ZALLOC(sizeof(struct pico_dhcp_server_negotiation)); + if (!dhcpn) { + pico_err = PICO_ERR_ENOMEM; + return NULL; + } + + dhcpn->xid = hdr->xid; + dhcpn->state = PICO_DHCP_STATE_DISCOVER; + dhcpn->bcast = ((short_be(hdr->flags) & PICO_DHCP_FLAG_BROADCAST) != 0) ? (1) : (0); + memcpy(dhcpn->hwaddr.addr, hdr->hwaddr, PICO_SIZE_ETH); + + test.dev = dev; + dhcpn->dhcps = pico_tree_findKey(&DHCPSettings, &test); + if (!dhcpn->dhcps) { + dhcps_dbg("DHCP server WARNING: received DHCP message on unconfigured link %s\n", dev->name); + PICO_FREE(dhcpn); + return NULL; + } + + dhcp_negotiation_set_ciaddr(dhcpn); + pico_tree_insert(&DHCPNegotiations, dhcpn); + return dhcpn; +} + +static void dhcpd_make_reply(struct pico_dhcp_server_negotiation *dhcpn, uint8_t msg_type) +{ + int r = 0, optlen = 0, offset = 0; + struct pico_ip4 broadcast = { + 0 + }, dns = { + 0 + }, destination = { + .addr = 0xFFFFFFFF + }; + struct pico_dhcp_hdr *hdr = NULL; + + dns.addr = DHCP_SERVER_OPENDNS; + broadcast.addr = dhcpn->dhcps->server_ip.addr | ~(dhcpn->dhcps->netmask.addr); + + optlen = PICO_DHCP_OPTLEN_MSGTYPE + PICO_DHCP_OPTLEN_SERVERID + PICO_DHCP_OPTLEN_LEASETIME + PICO_DHCP_OPTLEN_NETMASK + PICO_DHCP_OPTLEN_ROUTER + + PICO_DHCP_OPTLEN_BROADCAST + PICO_DHCP_OPTLEN_DNS + PICO_DHCP_OPTLEN_END; + hdr = PICO_ZALLOC(sizeof(struct pico_dhcp_hdr) + (uint32_t)optlen); + if (!hdr) { + return; + } + + hdr->op = PICO_DHCP_OP_REPLY; + hdr->htype = PICO_DHCP_HTYPE_ETH; + hdr->hlen = PICO_SIZE_ETH; + hdr->xid = dhcpn->xid; + hdr->yiaddr = dhcpn->ciaddr.addr; + hdr->siaddr = dhcpn->dhcps->server_ip.addr; + hdr->dhcp_magic = PICO_DHCPD_MAGIC_COOKIE; + memcpy(hdr->hwaddr, dhcpn->hwaddr.addr, PICO_SIZE_ETH); + + /* options */ + offset += pico_dhcp_opt_msgtype(DHCP_OPT(hdr,offset), msg_type); + offset += pico_dhcp_opt_serverid(DHCP_OPT(hdr,offset), &dhcpn->dhcps->server_ip); + offset += pico_dhcp_opt_leasetime(DHCP_OPT(hdr,offset), dhcpn->dhcps->lease_time); + offset += pico_dhcp_opt_netmask(DHCP_OPT(hdr,offset), &dhcpn->dhcps->netmask); + offset += pico_dhcp_opt_router(DHCP_OPT(hdr,offset), &dhcpn->dhcps->server_ip); + offset += pico_dhcp_opt_broadcast(DHCP_OPT(hdr,offset), &broadcast); + offset += pico_dhcp_opt_dns(DHCP_OPT(hdr,offset), &dns); + offset += pico_dhcp_opt_end(DHCP_OPT(hdr,offset)); + + if (dhcpn->bcast == 0) + destination.addr = hdr->yiaddr; + else { + hdr->flags |= short_be(PICO_DHCP_FLAG_BROADCAST); + destination.addr = broadcast.addr; + } + + r = pico_socket_sendto(dhcpn->dhcps->s, hdr, (int)(sizeof(struct pico_dhcp_hdr) + (uint32_t)optlen), &destination, PICO_DHCP_CLIENT_PORT); + if (r < 0) + dhcps_dbg("DHCP server WARNING: failure sending: %s!\n", strerror(pico_err)); + + PICO_FREE(hdr); + + return; +} + +static inline void parse_opt_msgtype(struct pico_dhcp_opt *opt, uint8_t *msgtype) +{ + if (opt->code == PICO_DHCP_OPT_MSGTYPE) { + *msgtype = opt->ext.msg_type.type; + dhcps_dbg("DHCP server: message type %u\n", msgtype); + } +} + +static inline void parse_opt_reqip(struct pico_dhcp_opt *opt, struct pico_ip4 *reqip) +{ + if (opt->code == PICO_DHCP_OPT_REQIP) + reqip->addr = opt->ext.req_ip.ip.addr; +} + +static inline void parse_opt_serverid(struct pico_dhcp_opt *opt, struct pico_ip4 *serverid) +{ + if (opt->code == PICO_DHCP_OPT_SERVERID) + *serverid = opt->ext.server_id.ip; +} + +static inline void dhcps_make_reply_to_request_msg(struct pico_dhcp_server_negotiation *dhcpn, int bound_valid_flag) +{ + if ((dhcpn->state == PICO_DHCP_STATE_BOUND) && bound_valid_flag) + dhcpd_make_reply(dhcpn, PICO_DHCP_MSG_ACK); + + if (dhcpn->state == PICO_DHCP_STATE_OFFER) { + dhcpn->state = PICO_DHCP_STATE_BOUND; + dhcpd_make_reply(dhcpn, PICO_DHCP_MSG_ACK); + } +} + +static inline void dhcps_make_reply_to_discover_or_request(struct pico_dhcp_server_negotiation *dhcpn, uint8_t msgtype, int bound_valid_flag) +{ + if (PICO_DHCP_MSG_DISCOVER == msgtype) { + dhcpd_make_reply(dhcpn, PICO_DHCP_MSG_OFFER); + dhcpn->state = PICO_DHCP_STATE_OFFER; + } else if (PICO_DHCP_MSG_REQUEST == msgtype) { + dhcps_make_reply_to_request_msg(dhcpn, bound_valid_flag); + } +} + +static inline void dhcps_parse_options_loop(struct pico_dhcp_server_negotiation *dhcpn, struct pico_dhcp_hdr *hdr) +{ + struct pico_dhcp_opt *opt = DHCP_OPT(hdr,0); + uint8_t msgtype = 0; + struct pico_ip4 reqip = { + 0 + }, server_id = { + 0 + }; + + do { + parse_opt_msgtype(opt, &msgtype); + parse_opt_reqip(opt, &reqip); + parse_opt_serverid(opt, &server_id); + } while (pico_dhcp_next_option(&opt)); + dhcps_make_reply_to_discover_or_request(dhcpn, msgtype, (!reqip.addr) && (!server_id.addr) && (hdr->ciaddr == dhcpn->ciaddr.addr)); +} + +static void pico_dhcp_server_recv(struct pico_socket *s, uint8_t *buf, uint32_t len) +{ + int32_t optlen = (int32_t)(len - sizeof(struct pico_dhcp_hdr)); + struct pico_dhcp_hdr *hdr = (struct pico_dhcp_hdr *)buf; + struct pico_dhcp_server_negotiation *dhcpn = NULL; + struct pico_device *dev = NULL; + + if (!pico_dhcp_are_options_valid(DHCP_OPT(hdr,0), optlen)) + return; + + dev = pico_ipv4_link_find(&s->local_addr.ip4); + dhcpn = pico_dhcp_server_find_negotiation(hdr->xid); + if (!dhcpn) + dhcpn = pico_dhcp_server_add_negotiation(dev, hdr); + + if (!ip_address_is_in_dhcp_range(dhcpn, dhcpn->ciaddr.addr)) + return; + + dhcps_parse_options_loop(dhcpn, hdr); + +} + +static void pico_dhcpd_wakeup(uint16_t ev, struct pico_socket *s) +{ + uint8_t buf[DHCP_SERVER_MAXMSGSIZE] = { + 0 + }; + int r = 0; + + if (ev != PICO_SOCK_EV_RD) + return; + + r = pico_socket_recvfrom(s, buf, DHCP_SERVER_MAXMSGSIZE, NULL, NULL); + if (r < 0) + return; + + pico_dhcp_server_recv(s, buf, (uint32_t)r); + return; +} + +int pico_dhcp_server_initiate(struct pico_dhcp_server_setting *setting) +{ + if (!setting || !setting->server_ip.addr) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + if (pico_dhcp_server_add_setting(setting) == NULL) + return -1; + + return 0; +} + +int pico_dhcp_server_destroy(struct pico_device *dev) +{ + struct pico_dhcp_server_setting *found, test = { 0 }; + test.dev = dev; + found = pico_tree_findKey(&DHCPSettings, &test); + if (!found) { + pico_err = PICO_ERR_ENOENT; + return -1; + } + + pico_tree_delete(&DHCPSettings, found); + PICO_FREE(found); + return 0; +} + +#endif /* PICO_SUPPORT_DHCP */ diff --git a/ext/picotcp/modules/pico_dhcp_server.h b/ext/picotcp/modules/pico_dhcp_server.h new file mode 100644 index 0000000..d5df820 --- /dev/null +++ b/ext/picotcp/modules/pico_dhcp_server.h @@ -0,0 +1,34 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + *********************************************************************/ +#ifndef INCLUDE_PICO_DHCP_SERVER +#define INCLUDE_PICO_DHCP_SERVER +#include "pico_defines.h" +#ifdef PICO_SUPPORT_UDP + +#include "pico_dhcp_common.h" +#include "pico_addressing.h" + +struct pico_dhcp_server_setting +{ + uint32_t pool_start; + uint32_t pool_next; + uint32_t pool_end; + uint32_t lease_time; + struct pico_device *dev; + struct pico_socket *s; + struct pico_ip4 server_ip; + struct pico_ip4 netmask; + uint8_t flags; /* unused atm */ +}; + +/* required field: IP address of the interface to serve, only IPs of this network will be served. */ +int pico_dhcp_server_initiate(struct pico_dhcp_server_setting *dhcps); + +/* To destroy an existing DHCP server configuration, running on a given interface */ +int pico_dhcp_server_destroy(struct pico_device *dev); + +#endif /* _INCLUDE_PICO_DHCP_SERVER */ +#endif diff --git a/ext/picotcp/modules/pico_dns_client.c b/ext/picotcp/modules/pico_dns_client.c new file mode 100644 index 0000000..806cff5 --- /dev/null +++ b/ext/picotcp/modules/pico_dns_client.c @@ -0,0 +1,808 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. + See LICENSE and COPYING for usage. + . + Authors: Kristof Roelants + *********************************************************************/ +#include "pico_config.h" +#include "pico_stack.h" +#include "pico_addressing.h" +#include "pico_socket.h" +#include "pico_ipv4.h" +#include "pico_ipv6.h" +#include "pico_dns_client.h" +#include "pico_dns_common.h" +#include "pico_tree.h" + +#ifdef PICO_SUPPORT_DNS_CLIENT + +#ifdef PICO_SUPPORT_IPV4 + +#define dns_dbg(...) do {} while(0) +/* #define dns_dbg dbg */ + +/* DNS response length */ +#define PICO_DNS_MAX_QUERY_LEN 255 +#define PICO_DNS_MAX_QUERY_LABEL_LEN 63 + +/* DNS client retransmission time (msec) + frequency */ +#define PICO_DNS_CLIENT_RETRANS 4000 +#define PICO_DNS_CLIENT_MAX_RETRANS 3 + +static void pico_dns_client_callback(uint16_t ev, struct pico_socket *s); +static void pico_dns_client_retransmission(pico_time now, void *arg); +static int pico_dns_client_getaddr_init(const char *url, uint16_t proto, void (*callback)(char *, void *), void *arg); + +struct pico_dns_ns +{ + struct pico_ip4 ns; /* nameserver */ +}; + +static int dns_ns_cmp(void *ka, void *kb) +{ + struct pico_dns_ns *a = ka, *b = kb; + return pico_ipv4_compare(&a->ns, &b->ns); +} +PICO_TREE_DECLARE(NSTable, dns_ns_cmp); + +struct pico_dns_query +{ + char *query; + uint16_t len; + uint16_t id; + uint16_t qtype; + uint16_t qclass; + uint8_t retrans; + struct pico_dns_ns q_ns; + struct pico_socket *s; + void (*callback)(char *, void *); + void *arg; +}; + +static int dns_query_cmp(void *ka, void *kb) +{ + struct pico_dns_query *a = ka, *b = kb; + if (a->id == b->id) + return 0; + + return (a->id < b->id) ? (-1) : (1); +} +PICO_TREE_DECLARE(DNSTable, dns_query_cmp); + +static int pico_dns_client_del_ns(struct pico_ip4 *ns_addr) +{ + struct pico_dns_ns test = {{0}}, *found = NULL; + + test.ns = *ns_addr; + found = pico_tree_findKey(&NSTable, &test); + if (!found) + return -1; + + pico_tree_delete(&NSTable, found); + PICO_FREE(found); + + /* no NS left, add default NS */ + if (pico_tree_empty(&NSTable)) + pico_dns_client_init(); + + return 0; +} + +static struct pico_dns_ns *pico_dns_client_add_ns(struct pico_ip4 *ns_addr) +{ + struct pico_dns_ns *dns = NULL, *found = NULL, test = {{0}}; + struct pico_ip4 zero = {0}; /* 0.0.0.0 */ + + /* Do not add 0.0.0.0 addresses, which some DHCP servers might reply */ + if (!pico_ipv4_compare(ns_addr, &zero)) + { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + + dns = PICO_ZALLOC(sizeof(struct pico_dns_ns)); + if (!dns) { + pico_err = PICO_ERR_ENOMEM; + return NULL; + } + + dns->ns = *ns_addr; + + found = pico_tree_insert(&NSTable, dns); + if (found) { /* nameserver already present */ + PICO_FREE(dns); + return found; + } + + /* default NS found, remove it */ + pico_string_to_ipv4(PICO_DNS_NS_DEFAULT, (uint32_t *)&test.ns.addr); + found = pico_tree_findKey(&NSTable, &test); + if (found && (found->ns.addr != ns_addr->addr)) + pico_dns_client_del_ns(&found->ns); + + return dns; +} + +static struct pico_dns_ns pico_dns_client_next_ns(struct pico_ip4 *ns_addr) +{ + struct pico_dns_ns dns = {{0}}, *nxtdns = NULL; + struct pico_tree_node *node = NULL, *nxtnode = NULL; + + dns.ns = *ns_addr; + node = pico_tree_findNode(&NSTable, &dns); + if (!node) + return dns; /* keep using current NS */ + + nxtnode = pico_tree_next(node); + nxtdns = nxtnode->keyValue; + if (!nxtdns) + nxtdns = (struct pico_dns_ns *)pico_tree_first(&NSTable); + + return *nxtdns; +} + +static struct pico_dns_query *pico_dns_client_add_query(struct pico_dns_header *hdr, uint16_t len, struct pico_dns_question_suffix *suffix, + void (*callback)(char *, void *), void *arg) +{ + struct pico_dns_query *q = NULL, *found = NULL; + + q = PICO_ZALLOC(sizeof(struct pico_dns_query)); + if (!q) + return NULL; + + q->query = (char *)hdr; + q->len = len; + q->id = short_be(hdr->id); + q->qtype = short_be(suffix->qtype); + q->qclass = short_be(suffix->qclass); + q->retrans = 1; + q->q_ns = *((struct pico_dns_ns *)pico_tree_first(&NSTable)); + q->callback = callback; + q->arg = arg; + q->s = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_UDP, &pico_dns_client_callback); + if (!q->s) { + PICO_FREE(q); + return NULL; + } + + found = pico_tree_insert(&DNSTable, q); + if (found) { + pico_err = PICO_ERR_EAGAIN; + pico_socket_close(q->s); + PICO_FREE(q); + return NULL; + } + + return q; +} + +static int pico_dns_client_del_query(uint16_t id) +{ + struct pico_dns_query test = { + 0 + }, *found = NULL; + + test.id = id; + found = pico_tree_findKey(&DNSTable, &test); + if (!found) + return -1; + + PICO_FREE(found->query); + pico_socket_close(found->s); + pico_tree_delete(&DNSTable, found); + PICO_FREE(found); + return 0; +} + +static struct pico_dns_query *pico_dns_client_find_query(uint16_t id) +{ + struct pico_dns_query test = { + 0 + }, *found = NULL; + + test.id = id; + found = pico_tree_findKey(&DNSTable, &test); + if (found) + return found; + else + return NULL; +} + +/* seek end of string */ +static char *pico_dns_client_seek(char *ptr) +{ + if (!ptr) + return NULL; + + while (*ptr != 0) + ptr++; + return ptr + 1; +} + +static struct pico_dns_query *pico_dns_client_idcheck(uint16_t id) +{ + struct pico_dns_query test = { + 0 + }; + + test.id = id; + return pico_tree_findKey(&DNSTable, &test); +} + +static int pico_dns_client_query_header(struct pico_dns_header *hdr) +{ + uint16_t id = 0; + uint8_t retry = 32; + + do { + id = (uint16_t)(pico_rand() & 0xFFFFU); + dns_dbg("DNS: generated id %u\n", id); + } while (retry-- && pico_dns_client_idcheck(id)); + if (!retry) + return -1; + + hdr->id = short_be(id); + pico_dns_fill_packet_header(hdr, 1, 0, 0, 0); /* 1 question, 0 answers */ + + return 0; +} + +static int pico_dns_client_check_header(struct pico_dns_header *pre) +{ + if (pre->qr != PICO_DNS_QR_RESPONSE || pre->opcode != PICO_DNS_OPCODE_QUERY || pre->rcode != PICO_DNS_RCODE_NO_ERROR) { + dns_dbg("DNS ERROR: OPCODE %d | TC %d | RCODE %d\n", pre->opcode, pre->tc, pre->rcode); + return -1; + } + + if (short_be(pre->ancount) < 1) { + dns_dbg("DNS ERROR: ancount < 1\n"); + return -1; + } + + return 0; +} + +static int pico_dns_client_check_qsuffix(struct pico_dns_question_suffix *suf, struct pico_dns_query *q) +{ + if (!suf) + return -1; + + if (short_be(suf->qtype) != q->qtype || short_be(suf->qclass) != q->qclass) { + dns_dbg("DNS ERROR: received qtype (%u) or qclass (%u) incorrect\n", short_be(suf->qtype), short_be(suf->qclass)); + return -1; + } + + return 0; +} + +static int pico_dns_client_check_url(struct pico_dns_header *resp, struct pico_dns_query *q) +{ + char *recv_name = (char*)(resp) + sizeof(struct pico_dns_header) + PICO_DNS_LABEL_INITIAL; + char *exp_name = (char *)(q->query) + sizeof(struct pico_dns_header) + PICO_DNS_LABEL_INITIAL; + if (strcasecmp(recv_name, exp_name) != 0) + return -1; + + return 0; +} + +static int pico_dns_client_check_asuffix(struct pico_dns_record_suffix *suf, struct pico_dns_query *q) +{ + if (!suf) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + if (short_be(suf->rtype) != q->qtype || short_be(suf->rclass) != q->qclass) { + dns_dbg("DNS WARNING: received qtype (%u) or qclass (%u) incorrect\n", short_be(suf->rtype), short_be(suf->rclass)); + return -1; + } + + if (long_be(suf->rttl) > PICO_DNS_MAX_TTL) { + dns_dbg("DNS WARNING: received TTL (%u) > MAX (%u)\n", short_be(suf->rttl), PICO_DNS_MAX_TTL); + return -1; + } + + return 0; +} + +static char *pico_dns_client_seek_suffix(char *suf, struct pico_dns_header *pre, struct pico_dns_query *q) +{ + struct pico_dns_record_suffix *asuffix = NULL; + uint16_t comp = 0, compression = 0; + uint16_t i = 0; + + if (!suf) + return NULL; + + while (i++ < short_be(pre->ancount)) { + comp = short_from(suf); + compression = short_be(comp); + switch (compression >> 14) + { + case PICO_DNS_POINTER: + while (compression >> 14 == PICO_DNS_POINTER) { + dns_dbg("DNS: pointer\n"); + suf += sizeof(uint16_t); + comp = short_from(suf); + compression = short_be(comp); + } + break; + + case PICO_DNS_LABEL: + dns_dbg("DNS: label\n"); + suf = pico_dns_client_seek(suf); + break; + + default: + dns_dbg("DNS ERROR: incorrect compression (%u) value\n", compression); + return NULL; + } + + asuffix = (struct pico_dns_record_suffix *)suf; + if (!asuffix) + break; + + if (pico_dns_client_check_asuffix(asuffix, q) < 0) { + suf += (sizeof(struct pico_dns_record_suffix) + short_be(asuffix->rdlength)); + continue; + } + + return suf; + } + return NULL; +} + +static int pico_dns_client_send(struct pico_dns_query *q) +{ + uint16_t *paramID = PICO_ZALLOC(sizeof(uint16_t)); + if (!paramID) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + dns_dbg("DNS: sending query to %08X\n", q->q_ns.ns.addr); + if (!q->s) + goto failure; + + if (pico_socket_connect(q->s, &q->q_ns.ns, short_be(PICO_DNS_NS_PORT)) < 0) + goto failure; + + pico_socket_send(q->s, q->query, q->len); + *paramID = q->id; + pico_timer_add(PICO_DNS_CLIENT_RETRANS, pico_dns_client_retransmission, paramID); + + return 0; + +failure: + PICO_FREE(paramID); + return -1; +} + +static void pico_dns_client_retransmission(pico_time now, void *arg) +{ + struct pico_dns_query *q = NULL; + struct pico_dns_query dummy; + IGNORE_PARAMETER(now); + + if(!arg) + return; + + /* search for the dns query and free used space */ + dummy.id = *(uint16_t *)arg; + q = (struct pico_dns_query *)pico_tree_findKey(&DNSTable, &dummy); + PICO_FREE(arg); + + /* dns query successful? */ + if (!q) { + return; + } + + q->retrans++; + if (q->retrans <= PICO_DNS_CLIENT_MAX_RETRANS) { + q->q_ns = pico_dns_client_next_ns(&q->q_ns.ns); + pico_dns_client_send(q); + } else { + pico_err = PICO_ERR_EIO; + q->callback(NULL, q->arg); + pico_dns_client_del_query(q->id); + } +} + +static int pico_dns_client_user_callback(struct pico_dns_record_suffix *asuffix, struct pico_dns_query *q) +{ + uint32_t ip = 0; + char *str = NULL; + char *rdata = (char *) asuffix + sizeof(struct pico_dns_record_suffix); + + switch (q->qtype) + { + case PICO_DNS_TYPE_A: + ip = long_from(rdata); + str = PICO_ZALLOC(PICO_DNS_IPV4_ADDR_LEN); + pico_ipv4_to_string(str, ip); + break; +#ifdef PICO_SUPPORT_IPV6 + case PICO_DNS_TYPE_AAAA: + { + struct pico_ip6 ip6; + memcpy(&ip6.addr, rdata, sizeof(struct pico_ip6)); + str = PICO_ZALLOC(PICO_DNS_IPV6_ADDR_LEN); + pico_ipv6_to_string(str, ip6.addr); + break; + } +#endif + case PICO_DNS_TYPE_PTR: + /* TODO: check for decompression / rdlength vs. decompressed length */ + pico_dns_notation_to_name(rdata, short_be(asuffix->rdlength)); + str = PICO_ZALLOC((size_t)(short_be(asuffix->rdlength) - + PICO_DNS_LABEL_INITIAL)); + if (!str) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + memcpy(str, rdata + PICO_DNS_LABEL_INITIAL, short_be(asuffix->rdlength) - PICO_DNS_LABEL_INITIAL); + break; + + default: + dns_dbg("DNS ERROR: incorrect qtype (%u)\n", q->qtype); + break; + } + + if (q->retrans) { + q->callback(str, q->arg); + q->retrans = 0; + pico_dns_client_del_query(q->id); + } + + if (str) + PICO_FREE(str); + + return 0; +} + +static char dns_response[PICO_IP_MRU] = { + 0 +}; + +static void pico_dns_try_fallback_cname(struct pico_dns_query *q, struct pico_dns_header *h, struct pico_dns_question_suffix *qsuffix) +{ + uint16_t type = q->qtype; + uint16_t proto = PICO_PROTO_IPV4; + struct pico_dns_record_suffix *asuffix = NULL; + char *p_asuffix = NULL; + char *cname_orig = NULL; + char *cname = NULL; + uint16_t cname_len; + + /* Try to use CNAME only if A or AAAA query is ongoing */ + if (type != PICO_DNS_TYPE_A && type != PICO_DNS_TYPE_AAAA) + return; + + if (type == PICO_DNS_TYPE_AAAA) + proto = PICO_PROTO_IPV6; + + q->qtype = PICO_DNS_TYPE_CNAME; + p_asuffix = (char *)qsuffix + sizeof(struct pico_dns_question_suffix); + p_asuffix = pico_dns_client_seek_suffix(p_asuffix, h, q); + if (!p_asuffix) { + return; + } + + /* Found CNAME response. Re-initiating query. */ + asuffix = (struct pico_dns_record_suffix *)p_asuffix; + cname = pico_dns_decompress_name((char *)asuffix + sizeof(struct pico_dns_record_suffix), (pico_dns_packet *)h); /* allocates memory! */ + cname_orig = cname; /* to free later */ + + if (cname == NULL) + return; + + cname_len = (uint16_t)(pico_dns_strlen(cname) + 1); + + pico_dns_notation_to_name(cname, cname_len); + if (cname[0] == '.') + cname++; + + dns_dbg("Restarting query for name '%s'\n", cname); + pico_dns_client_getaddr_init(cname, proto, q->callback, q->arg); + PICO_FREE(cname_orig); + pico_dns_client_del_query(q->id); +} + +static void pico_dns_client_callback(uint16_t ev, struct pico_socket *s) +{ + struct pico_dns_header *header = NULL; + char *domain; + struct pico_dns_question_suffix *qsuffix = NULL; + struct pico_dns_record_suffix *asuffix = NULL; + struct pico_dns_query *q = NULL; + char *p_asuffix = NULL; + + if (ev == PICO_SOCK_EV_ERR) { + dns_dbg("DNS: socket error received\n"); + return; + } + + if (ev & PICO_SOCK_EV_RD) { + if (pico_socket_read(s, dns_response, PICO_IP_MRU) < 0) + return; + } + + header = (struct pico_dns_header *)dns_response; + domain = (char *)header + sizeof(struct pico_dns_header); + qsuffix = (struct pico_dns_question_suffix *)pico_dns_client_seek(domain); + /* valid asuffix is determined dynamically later on */ + + if (pico_dns_client_check_header(header) < 0) + return; + + q = pico_dns_client_find_query(short_be(header->id)); + if (!q) + return; + + if (pico_dns_client_check_qsuffix(qsuffix, q) < 0) + return; + + if (pico_dns_client_check_url(header, q) < 0) + return; + + p_asuffix = (char *)qsuffix + sizeof(struct pico_dns_question_suffix); + p_asuffix = pico_dns_client_seek_suffix(p_asuffix, header, q); + if (!p_asuffix) { + pico_dns_try_fallback_cname(q, header, qsuffix); + return; + } + + asuffix = (struct pico_dns_record_suffix *)p_asuffix; + pico_dns_client_user_callback(asuffix, q); + + return; +} + +static int pico_dns_create_message(struct pico_dns_header **header, struct pico_dns_question_suffix **qsuffix, enum pico_dns_arpa arpa, const char *url, uint16_t *urlen, uint16_t *hdrlen) +{ + char *domain; + char inaddr_arpa[14]; + uint16_t strlen = 0, arpalen = 0; + + if (!url) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + if(arpa == PICO_DNS_ARPA4) { + strcpy(inaddr_arpa, ".in-addr.arpa"); + strlen = pico_dns_strlen(url); + } + +#ifdef PICO_SUPPORT_IPV6 + else if (arpa == PICO_DNS_ARPA6) { + strcpy(inaddr_arpa, ".IP6.ARPA"); + strlen = STRLEN_PTR_IP6; + } +#endif + else { + strcpy(inaddr_arpa, ""); + strlen = pico_dns_strlen(url); + } + + arpalen = pico_dns_strlen(inaddr_arpa); + *urlen = (uint16_t)(PICO_DNS_LABEL_INITIAL + strlen + arpalen + PICO_DNS_LABEL_ROOT); + *hdrlen = (uint16_t)(sizeof(struct pico_dns_header) + *urlen + sizeof(struct pico_dns_question_suffix)); + *header = PICO_ZALLOC(*hdrlen); + if (!*header) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + *header = (struct pico_dns_header *)*header; + domain = (char *) *header + sizeof(struct pico_dns_header); + *qsuffix = (struct pico_dns_question_suffix *)(domain + *urlen); + + if(arpa == PICO_DNS_ARPA4) { + memcpy(domain + PICO_DNS_LABEL_INITIAL, url, strlen); + pico_dns_mirror_addr(domain + PICO_DNS_LABEL_INITIAL); + memcpy(domain + PICO_DNS_LABEL_INITIAL + strlen, inaddr_arpa, arpalen); + } + +#ifdef PICO_SUPPORT_IPV6 + else if (arpa == PICO_DNS_ARPA6) { + pico_dns_ipv6_set_ptr(url, domain + PICO_DNS_LABEL_INITIAL); + memcpy(domain + PICO_DNS_LABEL_INITIAL + STRLEN_PTR_IP6, inaddr_arpa, arpalen); + } +#endif + else { + memcpy(domain + PICO_DNS_LABEL_INITIAL, url, strlen); + } + + /* assemble dns message */ + pico_dns_client_query_header(*header); + pico_dns_name_to_dns_notation(domain, strlen); + + return 0; +} + +static int pico_dns_client_addr_label_check_len(const char *url) +{ + const char *p, *label; + int count; + label = url; + p = label; + + while(*p != (char) 0) { + count = 0; + while((*p != (char)0)) { + if (*p == '.') { + label = ++p; + break; + } + + count++; + p++; + if (count > PICO_DNS_MAX_QUERY_LABEL_LEN) + return -1; + } + } + return 0; +} + +static int pico_dns_client_getaddr_check(const char *url, void (*callback)(char *, void *)) +{ + if (!url || !callback) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + if (strlen(url) > PICO_DNS_MAX_QUERY_LEN) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + if (pico_dns_client_addr_label_check_len(url) < 0) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + return 0; +} + +static int pico_dns_client_getaddr_init(const char *url, uint16_t proto, void (*callback)(char *, void *), void *arg) +{ + struct pico_dns_header *header = NULL; + struct pico_dns_question_suffix *qsuffix = NULL; + struct pico_dns_query *q = NULL; + uint16_t len = 0, lblen = 0; + (void)proto; + + if (pico_dns_client_getaddr_check(url, callback) < 0) + return -1; + + if(pico_dns_create_message(&header, &qsuffix, PICO_DNS_NO_ARPA, url, &lblen, &len) != 0) + return -1; + +#ifdef PICO_SUPPORT_IPV6 + if (proto == PICO_PROTO_IPV6) { + pico_dns_question_fill_suffix(qsuffix, PICO_DNS_TYPE_AAAA, PICO_DNS_CLASS_IN); + } else +#endif + pico_dns_question_fill_suffix(qsuffix, PICO_DNS_TYPE_A, PICO_DNS_CLASS_IN); + + q = pico_dns_client_add_query(header, len, qsuffix, callback, arg); + if (!q) { + PICO_FREE(header); + return -1; + } + + if (pico_dns_client_send(q) < 0) { + pico_dns_client_del_query(q->id); /* frees msg */ + return -1; + } + + return 0; +} + +int pico_dns_client_getaddr(const char *url, void (*callback)(char *, void *), void *arg) +{ + return pico_dns_client_getaddr_init(url, PICO_PROTO_IPV4, callback, arg); +} + +int pico_dns_client_getaddr6(const char *url, void (*callback)(char *, void *), void *arg) +{ + return pico_dns_client_getaddr_init(url, PICO_PROTO_IPV6, callback, arg); +} + +static int pico_dns_getname_univ(const char *ip, void (*callback)(char *, void *), void *arg, enum pico_dns_arpa arpa) +{ + struct pico_dns_header *header = NULL; + struct pico_dns_question_suffix *qsuffix = NULL; + struct pico_dns_query *q = NULL; + uint16_t len = 0, lblen = 0; + + if (!ip || !callback) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + if(pico_dns_create_message(&header, &qsuffix, arpa, ip, &lblen, &len) != 0) + return -1; + + pico_dns_question_fill_suffix(qsuffix, PICO_DNS_TYPE_PTR, PICO_DNS_CLASS_IN); + q = pico_dns_client_add_query(header, len, qsuffix, callback, arg); + if (!q) { + PICO_FREE(header); + return -1; + } + + if (pico_dns_client_send(q) < 0) { + pico_dns_client_del_query(q->id); /* frees header */ + return -1; + } + + return 0; +} + +int pico_dns_client_getname(const char *ip, void (*callback)(char *, void *), void *arg) +{ + return pico_dns_getname_univ(ip, callback, arg, PICO_DNS_ARPA4); +} + + +int pico_dns_client_getname6(const char *ip, void (*callback)(char *, void *), void *arg) +{ + return pico_dns_getname_univ(ip, callback, arg, PICO_DNS_ARPA6); +} + +int pico_dns_client_nameserver(struct pico_ip4 *ns, uint8_t flag) +{ + if (!ns) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + switch (flag) + { + case PICO_DNS_NS_ADD: + if (!pico_dns_client_add_ns(ns)) + return -1; + + break; + + case PICO_DNS_NS_DEL: + if (pico_dns_client_del_ns(ns) < 0) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + break; + + default: + pico_err = PICO_ERR_EINVAL; + return -1; + } + return 0; +} + +int pico_dns_client_init(void) +{ + struct pico_ip4 default_ns = { + 0 + }; + + if (pico_string_to_ipv4(PICO_DNS_NS_DEFAULT, (uint32_t *)&default_ns.addr) < 0) + return -1; + + return pico_dns_client_nameserver(&default_ns, PICO_DNS_NS_ADD); +} + +#else + +int pico_dns_client_init(void) +{ + dbg("ERROR Trying to initialize DNS module: IPv4 not supported in this build.\n"); + return -1; +} +#endif /* PICO_SUPPORT_IPV4 */ + + +#endif /* PICO_SUPPORT_DNS_CLIENT */ + diff --git a/ext/picotcp/modules/pico_dns_client.h b/ext/picotcp/modules/pico_dns_client.h new file mode 100644 index 0000000..910cc92 --- /dev/null +++ b/ext/picotcp/modules/pico_dns_client.h @@ -0,0 +1,46 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. + See LICENSE and COPYING for usage. + + . + + Authors: Kristof Roelants + *********************************************************************/ + +#ifndef INCLUDE_PICO_DNS_CLIENT +#define INCLUDE_PICO_DNS_CLIENT + +#define PICO_DNS_NS_DEL 0 +#define PICO_DNS_NS_ADD 1 +#include "pico_config.h" + +/* Compression values */ +#define PICO_DNS_LABEL 0 +#define PICO_DNS_POINTER 3 + +/* Label len */ +#define PICO_DNS_LABEL_INITIAL 1u +#define PICO_DNS_LABEL_ROOT 1 + +/* TTL values */ +#define PICO_DNS_MAX_TTL 604800 /* one week */ + +/* Len of an IPv4 address string */ +#define PICO_DNS_IPV4_ADDR_LEN 16 +#define PICO_DNS_IPV6_ADDR_LEN 54 + +/* Default nameservers + port */ +#define PICO_DNS_NS_DEFAULT "208.67.222.222" +#define PICO_DNS_NS_PORT 53 + +int pico_dns_client_init(void); +/* flag is PICO_DNS_NS_DEL or PICO_DNS_NS_ADD */ +int pico_dns_client_nameserver(struct pico_ip4 *ns, uint8_t flag); +int pico_dns_client_getaddr(const char *url, void (*callback)(char *ip, void *arg), void *arg); +int pico_dns_client_getname(const char *ip, void (*callback)(char *url, void *arg), void *arg); +#ifdef PICO_SUPPORT_IPV6 +int pico_dns_client_getaddr6(const char *url, void (*callback)(char *, void *), void *arg); +int pico_dns_client_getname6(const char *url, void (*callback)(char *, void *), void *arg); +#endif + +#endif /* _INCLUDE_PICO_DNS_CLIENT */ diff --git a/ext/picotcp/modules/pico_dns_common.c b/ext/picotcp/modules/pico_dns_common.c new file mode 100644 index 0000000..0c0250e --- /dev/null +++ b/ext/picotcp/modules/pico_dns_common.c @@ -0,0 +1,1773 @@ +/* **************************************************************************** + * PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. + * See LICENSE and COPYING for usage. + * . + * Authors: Toon Stegen, Jelle De Vleeschouwer + * ****************************************************************************/ +#include "pico_config.h" +#include "pico_protocol.h" +#include "pico_stack.h" +#include "pico_addressing.h" +#include "pico_ipv4.h" +#include "pico_ipv6.h" +#include "pico_dns_common.h" +#include "pico_tree.h" + +#define dns_dbg(...) do {} while(0) +//#define dns_dbg dbg + +/* MARK: v NAME & IP FUNCTIONS */ +#define dns_name_foreach_label_safe(label, name, next, maxlen) \ + for ((label) = (name), (next) = (char *)((name) + *(name) + 1); \ + (*(label) != '\0') && ((uint16_t)((label) - (name)) < (maxlen)); \ + (label) = (next), (next) = (char *)((next) + *(next) + 1)) + +/* **************************************************************************** + * Checks if the DNS name doesn't exceed 256 bytes including zero-byte. + * + * @param namelen Length of the DNS name-string including zero-byte + * @return 0 when the length is correct + * ****************************************************************************/ +int +pico_dns_check_namelen( uint16_t namelen ) +{ + return ((namelen > 2u) && (namelen < 256u)) ? (0) : (-1); +} + +/* **************************************************************************** + * Returns the length of a name in a DNS-packet as if DNS name compression + * would be applied to the packet. If there's no compression present + * + * @param name Compressed name you want the calculate the strlen from + * @return Returns strlen of a compressed name, takes the first byte of compr- + * ession pointer into account but not the second byte, which acts + * like a trailing zero-byte + * ****************************************************************************/ +uint16_t +pico_dns_namelen_comp( char *name ) +{ + uint16_t len = 0; + char *label = NULL, *next = NULL; + + /* Check params */ + if (!name) { + pico_err = PICO_ERR_EINVAL; + return 0; + } + + /* Just count until the zero-byte or a pointer */ + dns_name_foreach_label_safe(label, name, next, 255) { + if ((0xC0 & *label)) + break; + } + + /* Calculate the length */ + len = (uint16_t)(label - name); + if(*label != '\0') + len++; + + return len; +} + +/* **************************************************************************** + * Returns the uncompressed name in DNS name format when DNS name compression + * is applied to the packet-buffer. + * + * @param name Compressed name, should be in the bounds of the actual packet + * @param packet Packet that contains the compressed name + * @return Returns the decompressed name, NULL on failure. + * ****************************************************************************/ +char * +pico_dns_decompress_name( char *name, pico_dns_packet *packet ) +{ + char decompressed_name[PICO_DNS_NAMEBUF_SIZE] = { + 0 + }; + char *return_name = NULL; + uint8_t *dest_iterator = NULL; + uint8_t *iterator = NULL; + uint16_t ptr = 0, nslen = 0; + + /* Initialise iterators */ + iterator = (uint8_t *) name; + dest_iterator = (uint8_t *) decompressed_name; + while (*iterator != '\0') { + if ((*iterator) & 0xC0) { + /* We have a pointer */ + ptr = (uint16_t)((((uint16_t) *iterator) & 0x003F) << 8); + ptr = (uint16_t)(ptr | (uint16_t) *(iterator + 1)); + iterator = (uint8_t *)((uint8_t *)packet + ptr); + } else { + /* We want to keep the label lengths */ + *dest_iterator = (uint8_t) *iterator; + /* Copy the label */ + memcpy(dest_iterator + 1, iterator + 1, *iterator); + /* Move to next length label */ + dest_iterator += (*iterator) + 1; + iterator += (*iterator) + 1; + } + } + /* Append final zero-byte */ + *dest_iterator = (uint8_t) '\0'; + + /* Provide storage for the name to return */ + nslen = (uint16_t)(pico_dns_strlen(decompressed_name) + 1); + if(!(return_name = PICO_ZALLOC((size_t)nslen))) { + pico_err = PICO_ERR_ENOMEM; + return NULL; + } + + memcpy((void *)return_name, (void *)decompressed_name, (size_t)nslen); + + return return_name; +} + +/* **************************************************************************** + * Determines the length of a given url as if it where a DNS name in reverse + * resolution format. + * + * @param url URL wanted to create a reverse resolution name from. + * @param arpalen Will get filled with the length of the ARPA-suffix depending + * on the proto-parameter. + * @param proto The protocol to create a ARPA-suffix for. Can be either + * 'PICO_PROTO_IPV4' or 'PICO_PROTO_IPV6' + * @return Returns the length of the reverse name + * ****************************************************************************/ +static uint16_t +pico_dns_url_get_reverse_len( const char *url, + uint16_t *arpalen, + uint16_t proto ) +{ + uint16_t slen = (uint16_t)(pico_dns_strlen(url) + 2u); + + /* Check if pointers given are not NULL */ + if (pico_dns_check_namelen(slen) && !arpalen) { + pico_err = PICO_ERR_EINVAL; + return 0; + } + + /* Get the length of arpa-suffix if needed */ + if (proto == PICO_PROTO_IPV4) + *arpalen = (uint16_t) pico_dns_strlen(PICO_ARPA_IPV4_SUFFIX); + +#ifdef PICO_SUPPORT_IPV6 + else if (proto == PICO_PROTO_IPV6) + { + *arpalen = (uint16_t) pico_dns_strlen(PICO_ARPA_IPV6_SUFFIX); + slen = STRLEN_PTR_IP6 + 2u; + } +#endif + return slen; +} + +/* **************************************************************************** + * Converts a DNS name in URL format to a reverse name in DNS name format. + * Provides space for the DNS name as well. PICO_FREE() should be called on the + * returned string buffer that contains the reverse DNS name. + * + * @param url DNS name in URL format to convert to reverse name + * @param proto Depending on the protocol given the ARPA-suffix will be added. + * @return Returns a pointer to a string-buffer with the reverse DNS name. + * ****************************************************************************/ +static char * +pico_dns_url_to_reverse_qname( const char *url, uint8_t proto ) +{ + char *reverse_qname = NULL; + uint16_t arpalen = 0; + uint16_t slen = pico_dns_url_get_reverse_len(url, &arpalen, proto); + + /* Check namelen */ + if (pico_dns_check_namelen(slen)) { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + + /* Provide space for the reverse name */ + if (!(reverse_qname = PICO_ZALLOC((size_t)(slen + arpalen)))) { + pico_err = PICO_ERR_ENOMEM; + return NULL; + } + + /* If reverse IPv4 address resolving, convert to IPv4 arpa-format */ + if (PICO_PROTO_IPV4 == proto) { + memcpy(reverse_qname + 1u, url, slen - 1u); + pico_dns_mirror_addr(reverse_qname + 1u); + memcpy(reverse_qname + slen - 1, PICO_ARPA_IPV4_SUFFIX, arpalen); + } + + /* If reverse IPv6 address resolving, convert to IPv6 arpa-format */ +#ifdef PICO_SUPPORT_IPV6 + else if (proto == PICO_PROTO_IPV6) { + pico_dns_ipv6_set_ptr(url, reverse_qname + 1u); + memcpy(reverse_qname + 1u + STRLEN_PTR_IP6, + PICO_ARPA_IPV6_SUFFIX, arpalen); + } +#endif + else { /* This shouldn't happen */ + PICO_FREE(reverse_qname); + return NULL; + } + + pico_dns_name_to_dns_notation(reverse_qname, (unsigned int)(slen + arpalen)); + return reverse_qname; +} + +/* **************************************************************************** + * Converts a DNS name in DNS name format to a name in URL format. Provides + * space for the name in URL format as well. PICO_FREE() should be called on + * the returned string buffer that contains the name in URL format. + * + * @param qname DNS name in DNS name format to convert + * @return Returns a pointer to a string-buffer with the URL name on success. + * ****************************************************************************/ +char * +pico_dns_qname_to_url( const char *qname ) +{ + char *url = NULL; + char temp[256] = { + 0 + }; + uint16_t namelen = pico_dns_strlen(qname); + + /* Check if qname is not a NULL-pointer and if the length is OK */ + if (pico_dns_check_namelen(namelen)) { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + + /* Provide space for the URL */ + if (!(url = PICO_ZALLOC(namelen))) { + pico_err = PICO_ERR_ENOMEM; + return NULL; + } + + /* Convert qname to an URL */ + memcpy(temp, qname, namelen); + pico_dns_notation_to_name(temp, namelen); + memcpy((void *)url, (void *)(temp + 1), (size_t)(namelen - 1)); + + return url; +} + +/* **************************************************************************** + * Converts a DNS name in URL format to a name in DNS name format. Provides + * space for the DNS name as well. PICO_FREE() should be called on the returned + * string buffer that contains the DNS name. + * + * @param url DNS name in URL format to convert + * @return Returns a pointer to a string-buffer with the DNS name on success. + * ****************************************************************************/ +char * +pico_dns_url_to_qname( const char *url ) +{ + char *qname = NULL; + uint16_t namelen = (uint16_t)(pico_dns_strlen(url) + 2u); + + /* Check if url or qname_addr is not a NULL-pointer */ + if (pico_dns_check_namelen(namelen)) { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + + /* Provide space for the qname */ + if (!(qname = PICO_ZALLOC(namelen))) { + pico_err = PICO_ERR_ENOMEM; + return NULL; + } + + /* Copy in the URL (+1 to leave space for leading '.') */ + memcpy(qname + 1, url, (size_t)(namelen - 1)); + pico_dns_name_to_dns_notation(qname, namelen); + return qname; +} + +/* **************************************************************************** + * @param url String-buffer + * @return Length of string-buffer in an uint16_t + * ****************************************************************************/ +uint16_t +pico_dns_strlen( const char *url ) +{ + if (!url) + return 0; + + return (uint16_t) strlen(url); +} + +/* **************************************************************************** + * Replaces .'s in a DNS name in URL format by the label lengths. So it + * actually converts a name in URL format to a name in DNS name format. + * f.e. "*www.google.be" => "3www6google2be0" + * + * @param url Location to buffer with name in URL format. The URL needs to + * be +1 byte offset in the actual buffer. Size is should be + * pico_dns_strlen(url) + 2. + * @param maxlen Maximum length of buffer so it doesn't cause a buffer overflow + * @return 0 on success, something else on failure. + * ****************************************************************************/ +int pico_dns_name_to_dns_notation( char *url, unsigned int maxlen ) +{ + char c = '\0'; + char *lbl = url, *i = url; + + /* Check params */ + if (!url || pico_dns_check_namelen((uint16_t)maxlen)) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* Iterate over url */ + while ((c = *++i) != '\0') { + if ('.' == c) { + *lbl = (char)(i - lbl - 1); + lbl = i; + } + + if ((uint16_t)(i - url) > (uint16_t)maxlen) break; + } + *lbl = (char)(i - lbl - 1); + + return 0; +} + +/* **************************************************************************** + * Replaces the label lengths in a DNS-name by .'s. So it actually converts a + * name in DNS format to a name in URL format. + * f.e. 3www6google2be0 => .www.google.be + * + * @param ptr Location to buffer with name in DNS name format + * @param maxlen Maximum length of buffer so it doesn't cause a buffer overflow + * @return 0 on success, something else on failure. + * ****************************************************************************/ +int pico_dns_notation_to_name( char *ptr, unsigned int maxlen ) +{ + char *label = NULL, *next = NULL; + + /* Iterate safely over the labels and update each label */ + dns_name_foreach_label_safe(label, ptr, next, (uint16_t)maxlen) { + *label = '.'; + } + + return 0; +} + +/* **************************************************************************** + * Determines the length of the first label of a DNS name in URL-format + * + * @param url DNS name in URL-format + * @return Length of the first label of DNS name in URL-format + * ****************************************************************************/ +uint16_t +pico_dns_first_label_length( const char *url ) +{ + const char *i = NULL; + uint16_t len = 0; + + /* Check params */ + if (!url) return 0; + + /* Count */ + i = url; + while (*i != '.' && *i != '\0') { + ++i; + ++len; + } + return len; +} + +/* **************************************************************************** + * Mirrors a dotted IPv4-address string. + * f.e. 192.168.0.1 => 1.0.168.192 + * + * @param ptr + * @return 0 on success, something else on failure. + * ****************************************************************************/ +int +pico_dns_mirror_addr( char *ip ) +{ + uint32_t addr = 0; + + /* Convert IPv4-string to network-order 32-bit number */ + if (pico_string_to_ipv4(ip, &addr) < 0) + return -1; + + /* Mirror the 32-bit number */ + addr = (uint32_t)((uint32_t)((addr & (uint32_t)0xFF000000u) >> 24) | + (uint32_t)((addr & (uint32_t)0xFF0000u) >> 8) | + (uint32_t)((addr & (uint32_t)0xFF00u) << 8) | + (uint32_t)((addr & (uint32_t)0xFFu) << 24)); + + return pico_ipv4_to_string(ip, addr); +} + +#ifdef PICO_SUPPORT_IPV6 +/* **************************************************************************** + * Get the ASCII value of the Most Significant Nibble of a byte + * + * @param byte Byte you want to extract the MSN from. + * @return The ASCII value of the Most Significant Nibble of the byte + * ****************************************************************************/ +static inline char +dns_ptr_ip6_nibble_lo( uint8_t byte ) +{ + uint8_t nibble = byte & 0x0f; + if (nibble < 10) + return (char)(nibble + '0'); + else + return (char)(nibble - 0xa + 'a'); +} + +/* **************************************************************************** + * Get the ASCII value of the Least Significant Nibble of a byte + * + * @param byte Byte you want to extract the LSN from. + * @return The ASCII value of the Least Significant Nibble of the byte + * ****************************************************************************/ +static inline char +dns_ptr_ip6_nibble_hi( uint8_t byte ) +{ + uint8_t nibble = (byte & 0xf0u) >> 4u; + if (nibble < 10u) + return (char)(nibble + '0'); + else + return (char)(nibble - 0xa + 'a'); +} + +/* **************************************************************************** + * Convert an IPv6-address in string-format to a IPv6-address in nibble-format. + * Doesn't add a IPv6 ARPA-suffix though. + * + * @param ip IPv6-address stored as a string + * @param dst Destination to store IPv6-address in nibble-format + * ****************************************************************************/ +void +pico_dns_ipv6_set_ptr( const char *ip, char *dst ) +{ + int i = 0, j = 0; + struct pico_ip6 ip6; + memset(&ip6, 0, sizeof(struct pico_ip6)); + pico_string_to_ipv6(ip, ip6.addr); + for (i = 15; i >= 0; i--) { + if ((j + 3) > 64) return; /* Don't want j to go out of bounds */ + + dst[j++] = dns_ptr_ip6_nibble_lo(ip6.addr[i]); + dst[j++] = '.'; + dst[j++] = dns_ptr_ip6_nibble_hi(ip6.addr[i]); + dst[j++] = '.'; + } +} +#endif + +/* MARK: ^ NAME & IP FUNCTIONS */ +/* MARK: v QUESTION FUNCTIONS */ + +/* **************************************************************************** + * Calculates the size of a single DNS Question. Void-pointer allows this + * function to be used with pico_tree_size. + * + * @param question Void-point to DNS Question + * @return Size in bytes of single DNS Question if it was copied flat. + * ****************************************************************************/ +static uint16_t pico_dns_question_size( void *question ) +{ + uint16_t size = 0; + struct pico_dns_question *q = (struct pico_dns_question *)question; + if (!q) + return 0; + + size = q->qname_length; + size = (uint16_t)(size + sizeof(struct pico_dns_question_suffix)); + return size; +} + +/* **************************************************************************** + * Deletes a single DNS Question. + * + * @param question Void-pointer to DNS Question. Can be used with pico_tree_- + * destroy. + * @return Returns 0 on success, something else on failure. + * ****************************************************************************/ +int +pico_dns_question_delete( void **question ) +{ + struct pico_dns_question **q = (struct pico_dns_question **)question; + + /* Check params */ + if ((!q) || !(*q)) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + if ((*q)->qname) + PICO_FREE(((*q)->qname)); + + if ((*q)->qsuffix) + PICO_FREE((*q)->qsuffix); + + PICO_FREE((*q)); + *question = NULL; + + return 0; +} + +/* **************************************************************************** + * Fills in the DNS question suffix-fields with the correct values. + * + * todo: Update pico_dns_client to make the same mechanism possible like with + * filling DNS Resource Record-suffixes. + * + * @param suf Pointer to the suffix member of the DNS question. + * @param qtype DNS type of the DNS question to be. + * @param qclass DNS class of the DNS question to be. + * @return Returns 0 on success, something else on failure. + * ****************************************************************************/ +int +pico_dns_question_fill_suffix( struct pico_dns_question_suffix *suf, + uint16_t qtype, + uint16_t qclass ) +{ + if (!suf) + return -1; + + suf->qtype = short_be(qtype); + suf->qclass = short_be(qclass); + return 0; +} + +/* **************************************************************************** + * Fills in the name of the DNS question. + * + * @param qname Pointer-pointer to the name-member of the DNS-question + * @param url Name in URL format you want to convert to a name in DNS name + * format. When reverse resolving, only the IP, either IPV4 or + * IPV6, should be given in string format. + * f.e. => for IPv4: "192.168.2.1" + * => for IPv6: "2001:0db8:85a3:0042:1000:8a2e:0370:7334" + * @param qtype DNS type type of the DNS question to be. + * @param proto When reverse is true the reverse resolution name will be + * generated depending on the protocol. Can be either + * PICO_PROTO_IPV4 or PICO_PROTO_IPV6. + * @param reverse When this is true a reverse resolution name will be generated + * from the URL. + * @return The eventual length of the generated name, 0 on failure. + * ****************************************************************************/ +static uint16_t +pico_dns_question_fill_name( char **qname, + const char *url, + uint16_t qtype, + uint8_t proto, + uint8_t reverse ) +{ + uint16_t slen = 0; + + /* Try to convert the URL to an FQDN */ + if (reverse && qtype == PICO_DNS_TYPE_PTR) + *qname = pico_dns_url_to_reverse_qname(url, proto); + else { + (*qname) = pico_dns_url_to_qname(url); + } + + if (!(*qname)) { + return 0; + } + + slen = (uint16_t)(pico_dns_strlen(*qname) + 1u); + return (pico_dns_check_namelen(slen)) ? ((uint16_t)0) : (slen); +} + +/* **************************************************************************** + * Creates a standalone DNS Question with a given name and type. + * + * @param url DNS question name in URL format. Will be converted to DNS + * name notation format. + * @param len Will be filled with the total length of the DNS question. + * @param proto Protocol for which you want to create a question. Can be + * either PICO_PROTO_IPV4 or PICO_PROTO_IPV6. + * @param qtype DNS type of the question to be. + * @param qclass DNS class of the question to be. + * @param reverse When this is true, a reverse resolution name will be gene- + * from the URL + * @return Returns pointer to the created DNS Question on success, NULL on + * failure. + * ****************************************************************************/ +struct pico_dns_question * +pico_dns_question_create( const char *url, + uint16_t *len, + uint8_t proto, + uint16_t qtype, + uint16_t qclass, + uint8_t reverse ) +{ + struct pico_dns_question *question = NULL; + uint16_t slen = 0; + int ret = 0; + + /* Check if valid arguments are provided */ + if (!url || !len) { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + + /* Allocate space for the question and the subfields */ + if (!(question = PICO_ZALLOC(sizeof(struct pico_dns_question)))) { + pico_err = PICO_ERR_ENOMEM; + return NULL; + } + + /* Fill name field */ + slen = pico_dns_question_fill_name(&(question->qname), url, + qtype, proto, reverse); + question->qname_length = (uint8_t)(slen); + question->proto = proto; + + /* Provide space for the question suffix & try to fill in */ + question->qsuffix = PICO_ZALLOC(sizeof(struct pico_dns_question_suffix)); + ret = pico_dns_question_fill_suffix(question->qsuffix, qtype, qclass); + if (ret || pico_dns_check_namelen(slen)) { + pico_dns_question_delete((void **)&question); + return NULL; + } + + /* Determine the entire length of the question */ + *len = (uint16_t)(slen + (uint16_t)sizeof(struct pico_dns_question_suffix)); + + return question; +} + +/* **************************************************************************** + * Decompresses the name of a single DNS question. + * + * @param question Question you want to decompress the name of + * @param packet Packet in which the DNS question is contained. + * @return Pointer to original name of the DNS question before decompressing. + * ****************************************************************************/ +char * +pico_dns_question_decompress( struct pico_dns_question *question, + pico_dns_packet *packet ) +{ + char *qname_original = question->qname; + + /* Try to decompress the question name */ + if (!(question->qname = pico_dns_decompress_name(question->qname, packet))) { + question->qname = qname_original; + } + + return qname_original; +} + + +/* MARK: ^ QUESTION FUNCTIONS */ +/* MARK: v RESOURCE RECORD FUNCTIONS */ + +/* **************************************************************************** + * Copies the contents of DNS Resource Record to a single flat memory-buffer. + * + * @param record Pointer to DNS record you want to copy flat. + * @param destination Pointer-pointer to flat memory buffer to copy DNS record + * to. When function returns, this will point to location + * right after the flat copied DNS Resource Record. + * @return Returns 0 on success, something else on failure. + * ****************************************************************************/ +static int +pico_dns_record_copy_flat( struct pico_dns_record *record, + uint8_t **destination ) +{ + char *dest_rname = NULL; /* rname destination location */ + struct pico_dns_record_suffix *dest_rsuffix = NULL; /* rsuffix destin. */ + uint8_t *dest_rdata = NULL; /* rdata destination location */ + + /* Check if there are no NULL-pointers given */ + if (!record || !destination || !(*destination)) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* Initialise the destination pointers to the right locations */ + dest_rname = (char *) *destination; + dest_rsuffix = (struct pico_dns_record_suffix *) + (dest_rname + record->rname_length); + dest_rdata = ((uint8_t *)dest_rsuffix + + sizeof(struct pico_dns_record_suffix)); + + /* Copy the rname of the resource record into the flat location */ + strcpy(dest_rname, record->rname); + + /* Copy the question suffix fields */ + dest_rsuffix->rtype = record->rsuffix->rtype; + dest_rsuffix->rclass = record->rsuffix->rclass; + dest_rsuffix->rttl = record->rsuffix->rttl; + dest_rsuffix->rdlength = record->rsuffix->rdlength; + + /* Copy the rdata of the resource */ + memcpy(dest_rdata, record->rdata, short_be(dest_rsuffix->rdlength)); + + /* Point to location right after flat resource record */ + *destination = (uint8_t *)(dest_rdata + + short_be(record->rsuffix->rdlength)); + return 0; +} + +/* **************************************************************************** + * Calculates the size of a single DNS Resource Record. Void-pointer allows + * this function to be used with pico_tree_size. + * + * @param record void-pointer to DNS record you want to know the size of. + * @return Size of single DNS record if it was copied flat. + * ****************************************************************************/ +static uint16_t +pico_dns_record_size( void *record ) +{ + uint16_t size = 0; + struct pico_dns_record *rr = (struct pico_dns_record *)record; + + if (!rr || !(rr->rsuffix)) + return 0; + + size = rr->rname_length; + size = (uint16_t)(size + sizeof(struct pico_dns_record_suffix)); + size = (uint16_t)(size + short_be(rr->rsuffix->rdlength)); + return size; +} + +/* **************************************************************************** + * Deletes a single DNS resource record. Void-pointer-pointer allows this + * function to be used with pico_tree_destroy. + * + * @param record void-pointer-pointer to DNS record you want to delete. + * @return Returns 0 on success, something else on failure. + * ****************************************************************************/ +int +pico_dns_record_delete( void **record ) +{ + struct pico_dns_record **rr = (struct pico_dns_record **)record; + + if ((!rr) || !(*rr)) + return 0; + + if ((*rr)->rname) + PICO_FREE((*rr)->rname); + + if ((*rr)->rsuffix) + PICO_FREE((*rr)->rsuffix); + + if ((*rr)->rdata) + PICO_FREE((*rr)->rdata); + + PICO_FREE((*rr)); + *record = NULL; + + return 0; +} + +/* **************************************************************************** + * Just copies a resource record hard. + * + * @param record DNS record you want to copy + * @return Pointer to copy of DNS record. + * ****************************************************************************/ +struct pico_dns_record * +pico_dns_record_copy( struct pico_dns_record *record ) +{ + struct pico_dns_record *copy = NULL; + + /* Check params */ + if (!record || !(record->rname) || !(record->rdata) || !(record->rsuffix)) { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + + /* Provide space for the copy */ + if (!(copy = PICO_ZALLOC(sizeof(struct pico_dns_record)))) { + pico_err = PICO_ERR_ENOMEM; + return NULL; + } + + /* Provide space for the subfields */ + copy->rname = PICO_ZALLOC((size_t)record->rname_length); + copy->rsuffix = PICO_ZALLOC(sizeof(struct pico_dns_record_suffix)); + copy->rdata = PICO_ZALLOC((size_t)short_be(record->rsuffix->rdlength)); + if (!(copy->rname) || !(copy->rsuffix) || !(copy->rdata)) { + pico_dns_record_delete((void **)©); + pico_err = PICO_ERR_ENOMEM; + return NULL; + } + + /* Fill in the rname field */ + memcpy((void *)(copy->rname), (void *)(record->rname), + (size_t)(record->rname_length)); + copy->rname_length = record->rname_length; + + /* Fill in the rsuffix fields */ + copy->rsuffix->rtype = record->rsuffix->rtype; + copy->rsuffix->rclass = record->rsuffix->rclass; + copy->rsuffix->rttl = record->rsuffix->rttl; + copy->rsuffix->rdlength = record->rsuffix->rdlength; + + /* Fill in the rdata field */ + memcpy(copy->rdata, record->rdata, short_be(record->rsuffix->rdlength)); + + return copy; +} + +/* **************************************************************************** + * Fills in the DNS resource record suffix-fields with the correct values. + * + * @param suf Pointer-pointer to rsuffix-member of struct pico_dns_record. + * @param rtype DNS type of the resource record to be. + * @param rclass DNS class of the resource record to be. + * @param rttl DNS ttl of the resource record to be. + * @param rdlength DNS rdlength of the resource record to be. + * @return Returns 0 on success, something else on failure. + * ****************************************************************************/ +static int +pico_dns_record_fill_suffix( struct pico_dns_record_suffix **suf, + uint16_t rtype, + uint16_t rclass, + uint32_t rttl, + uint16_t rdlength ) +{ + /* Try to provide space for the rsuffix */ + if (!(*suf = PICO_ZALLOC(sizeof(struct pico_dns_record_suffix)))) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + /* Fill in the fields */ + (*suf)->rtype = short_be(rtype); + (*suf)->rclass = short_be(rclass); + (*suf)->rttl = long_be(rttl); + (*suf)->rdlength = short_be(rdlength); + + return 0; +} + +/* **************************************************************************** + * Fills the data-buffer of a DNS resource record. + * + * @param rdata Pointer-pointer to rdata-member of struct pico_dns_record. + * @param _rdata Memory buffer with data to insert in the resource record. If + * data should contain a DNS name, the name in the databuffer + * needs to be in URL-format. + * @param datalen The exact length in bytes of the _rdata-buffer. If data of + * record should contain a DNS name, datalen needs to be + * pico_dns_strlen(_rdata). + * @param rtype DNS type of the resource record to be + * @return Returns 0 on failure, length of filled in rdata-member on success. + * Can differ from datalen-param because of URL to DNS Name conversion. + * ****************************************************************************/ +static uint16_t +pico_dns_record_fill_rdata( uint8_t **rdata, + void *_rdata, + uint16_t datalen, + uint16_t rtype ) +{ + uint16_t _datalen = 0; + + /* If type is PTR, rdata will be a DNS name in URL format */ + if (rtype == PICO_DNS_TYPE_PTR) { + _datalen = (uint16_t)(datalen + 2u); + if (!(*rdata = (uint8_t *)pico_dns_url_to_qname(_rdata))) { + pico_err = PICO_ERR_ENOMEM; + return 0; + } + } else { + /* Otherwise just copy in the databuffer */ + if (datalen == 0) { + return datalen; + } + _datalen = datalen; + if (!(*rdata = (uint8_t *)PICO_ZALLOC((size_t)datalen))) { + pico_err = PICO_ERR_ENOMEM; + return 0; + } + + memcpy((void *)*rdata, (void *)_rdata, datalen); + } + + return _datalen; +} + +/* **************************************************************************** + * Create a standalone DNS Resource Record with a given name. + * + * @param url DNS rrecord name in URL format. Will be converted to DNS + * name notation format. + * @param _rdata Memory buffer with data to insert in the resource record. If + * data should contain a DNS name, the name in the databuffer + * needs to be in URL-format. + * @param datalen The exact length in bytes of the _rdata-buffer. If data of + * record should contain a DNS name, datalen needs to be + * pico_dns_strlen(_rdata). + * @param len Will be filled with the total length of the DNS rrecord. + * @param rtype DNS type of the resource record to be. + * @param rclass DNS class of the resource record to be. + * @param rttl DNS ttl of the resource record to be. + * @return Returns pointer to the created DNS Resource Record + * ****************************************************************************/ +struct pico_dns_record * +pico_dns_record_create( const char *url, + void *_rdata, + uint16_t datalen, + uint16_t *len, + uint16_t rtype, + uint16_t rclass, + uint32_t rttl ) +{ + struct pico_dns_record *record = NULL; + uint16_t slen = (uint16_t)(pico_dns_strlen(url) + 2u); + int ret = 0; + + /* Check params */ + if (pico_dns_check_namelen(slen) || !_rdata || !len) { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + + /* Allocate space for the record and subfields */ + if (!(record = PICO_ZALLOC(sizeof(struct pico_dns_record)))) { + pico_err = PICO_ERR_ENOMEM; + return NULL; + } + + /* Provide space and convert the URL to a DNS name */ + record->rname = pico_dns_url_to_qname(url); + record->rname_length = slen; + + /* Provide space & fill in the rdata field */ + datalen = pico_dns_record_fill_rdata(&(record->rdata), _rdata, + datalen, rtype); + + /* Provide space & fill in the rsuffix */ + ret = pico_dns_record_fill_suffix(&(record->rsuffix), rtype, rclass, rttl, + datalen); + + /* Check if everything succeeded */ + if (!(record->rname) || ret) { + pico_dns_record_delete((void **)&record); + return NULL; + } + + /* Determine the complete length of resource record */ + *len = (uint16_t)(slen + sizeof(struct pico_dns_record_suffix) + datalen); + return record; +} + +/* **************************************************************************** + * Decompresses the name of single DNS record. + * + * @param record DNS record to decompress the name of. + * @param packet Packet in which is DNS record is present + * @return Pointer to original name of the DNS record before decompressing. + * ****************************************************************************/ +char * +pico_dns_record_decompress( struct pico_dns_record *record, + pico_dns_packet *packet ) +{ + char *rname_original = record->rname; + + /* Try to decompress the record name */ + if (!(record->rname = pico_dns_decompress_name(record->rname, packet))) { + record->rname = rname_original; + } + + return rname_original; +} + +static int pico_tolower(int c) +{ + if ((c >= 'A') && (c <= 'Z')) + c += 'a' - 'A'; + return c; +} + +/* MARK: ^ RESOURCE RECORD FUNCTIONS */ +/* MARK: v COMPARING */ + +/* **************************************************************************** + * Compares two databuffers against each other. + * + * @param a 1st Memory buffer to compare + * @param b 2nd Memory buffer to compare + * @param rdlength_a Length of 1st memory buffer + * @param rdlength_b Length of 2nd memory buffer + * @param caseinsensitive Whether or not the bytes are compared + * case-insensitive + * @return 0 when the buffers are equal, returns difference when they're not. + * ****************************************************************************/ +int +pico_dns_rdata_cmp( uint8_t *a, uint8_t *b, + uint16_t rdlength_a, uint16_t rdlength_b, uint8_t caseinsensitive ) +{ + uint16_t i = 0; + uint16_t slen = 0; + int dif = 0; + + /* Check params */ + if (!a || !b) { + if (!a && !b) + return 0; + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* Determine the smallest length */ + slen = rdlength_a; + if (rdlength_b < slen) + slen = rdlength_b; + + /* loop over slen */ + if(caseinsensitive){ + for (i = 0; i < slen; i++) { + if ((dif = pico_tolower((int)a[i]) - pico_tolower((int)b[i]))) { + return dif; + } + } + }else{ + for (i = 0; i < slen; i++) { + if ((dif = (int)a[i] - (int)b[i])){ + return dif; + } + } + } + + /* Return difference of buffer lengths */ + return (int)((int)rdlength_a - (int)rdlength_b); +} + +/* **************************************************************************** + * Compares 2 DNS questions + * + * @param qa DNS question A as a void-pointer (for pico_tree) + * @param qb DNS question A as a void-pointer (for pico_tree) + * @return 0 when questions are equal, returns difference when they're not. + * ****************************************************************************/ +int +pico_dns_question_cmp( void *qa, + void *qb ) +{ + int dif = 0; + uint16_t at = 0, bt = 0; + struct pico_dns_question *a = (struct pico_dns_question *)qa; + struct pico_dns_question *b = (struct pico_dns_question *)qb; + + /* Check params */ + if (!a || !b) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* First, compare the qtypes */ + at = short_be(a->qsuffix->qtype); + bt = short_be(b->qsuffix->qtype); + if ((dif = (int)((int)at - (int)bt))) + return dif; + + /* Then compare qnames */ + return pico_dns_rdata_cmp((uint8_t *)a->qname, (uint8_t *)b->qname, + pico_dns_strlen(a->qname), + pico_dns_strlen(b->qname), 1); +} + +/* **************************************************************************** + * Compares 2 DNS records by type and name only + * + * @param ra DNS record A as a void-pointer (for pico_tree) + * @param rb DNS record B as a void-pointer (for pico_tree) + * @return 0 when name and type of records are equal, returns difference when + * they're not. + * ****************************************************************************/ +int +pico_dns_record_cmp_name_type( void *ra, + void *rb ) +{ + int dif; + uint16_t at = 0, bt = 0; + struct pico_dns_record *a = (struct pico_dns_record *)ra; + struct pico_dns_record *b = (struct pico_dns_record *)rb; + + /* Check params */ + if (!a || !b) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* First, compare the rrtypes */ + at = short_be(a->rsuffix->rtype); + bt = short_be(b->rsuffix->rtype); + if ((dif = (int)((int)at - (int)bt))) + return dif; + + /* Then compare names */ + return pico_dns_rdata_cmp((uint8_t *)(a->rname), (uint8_t *)(b->rname), + (uint16_t)strlen(a->rname), + (uint16_t)strlen(b->rname), 1); +} + +/* **************************************************************************** + * Compares 2 DNS records by type, name AND rdata for a truly unique result + * + * @param ra DNS record A as a void-pointer (for pico_tree) + * @param rb DNS record B as a void-pointer (for pico_tree) + * @return 0 when records are equal, returns difference when they're not + * ****************************************************************************/ +int +pico_dns_record_cmp( void *ra, + void *rb ) +{ + int dif = 0; + struct pico_dns_record *a = (struct pico_dns_record *)ra; + struct pico_dns_record *b = (struct pico_dns_record *)rb; + + /* Check params */ + if (!a || !b) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* Compare type and name */ + if ((dif = pico_dns_record_cmp_name_type(a, b))) + return dif; + + /* Then compare rdata */ + return pico_dns_rdata_cmp(a->rdata, b->rdata, + short_be(a->rsuffix->rdlength), + short_be(b->rsuffix->rdlength), 0); +} + +/* MARK: ^ COMPARING */ +/* MARK: v PICO_TREE */ + +/* **************************************************************************** + * Erases a pico_tree entirely. + * + * @param tree Pointer to a pico_tree-instance + * @param node_delete Helper-function for type-specific deleting. + * @return Returns 0 on success, something else on failure. + * ****************************************************************************/ +int +pico_tree_destroy( struct pico_tree *tree, int (*node_delete)(void **)) +{ + struct pico_tree_node *node = NULL, *next = NULL; + void *item = NULL; + + /* Check params */ + if (!tree) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + pico_tree_foreach_safe(node, tree, next) { + item = node->keyValue; + pico_tree_delete(tree, node->keyValue); + if (item && node_delete) { + node_delete((void **)&item); + } + } + + return 0; +} + +/* **************************************************************************** + * Calculates the size in bytes of all the nodes contained in the tree summed + * up. And gets the amount of items in the tree as well. + * + * @param tree Pointer to pico_tree-instance + * @param size Will get filled with the size of all the nodes summed up. + * Make sure you clear out (set to 0) this param before you + * call this function because it doesn't happen inside and + * each size will be added to the initial value. + * @param node_size Helper-function for type-specific size-determination + * @return Amount of items in the tree. + * ****************************************************************************/ +static uint16_t +pico_tree_size( struct pico_tree *tree, + uint16_t *size, + uint16_t (*node_size)(void *)) +{ + struct pico_tree_node *node = NULL; + void *node_item = NULL; + uint16_t count = 0; + + /* Check params */ + if (!tree || !size) { + pico_err = PICO_ERR_EINVAL; + return 0; + } + + /* Add up the node sizes */ + pico_tree_foreach(node, tree) { + if ((node_item = node->keyValue)) { + *size = (uint16_t)((*size) + node_size(node_item)); + count++; + } + } + + return count; +} + +/* **************************************************************************** + * Determines the amount of nodes in a pico_tere + * + * @param tree Pointer to pico_tree-instance + * @return Amount of items in the tree. + * ****************************************************************************/ +uint16_t +pico_tree_count( struct pico_tree *tree ) +{ + struct pico_tree_node *node = NULL; + uint16_t count = 0; + + pico_tree_foreach(node, tree) { + if (node->keyValue) + count++; + } + + return count; +} + +/* **************************************************************************** + * Deletes all the questions with given DNS name from a pico_tree + * + * @param qtree Pointer to pico_tree-instance which contains DNS questions + * @param name Name of the questions you want to delete + * @return Returns 0 on success, something else on failure. + * ****************************************************************************/ +int +pico_dns_qtree_del_name( struct pico_tree *qtree, + const char *name ) +{ + struct pico_tree_node *node = NULL, *next = NULL; + struct pico_dns_question *question = NULL; + + /* Check params */ + if (!qtree || !name) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* Iterate over tree and delete every node with given name */ + pico_tree_foreach_safe(node, qtree, next) { + question = (struct pico_dns_question *)node->keyValue; + if ((question) && (strcasecmp(question->qname, name) == 0)) { + question = pico_tree_delete(qtree, (void *)question); + pico_dns_question_delete((void **)&question); + } + } + + return 0; +} + +/* **************************************************************************** + * Checks whether a question with given name is in the tree or not. + * + * @param qtree Pointer to pico_tree-instance which contains DNS questions + * @param name Name you want to check for + * @return 1 when the name is present in the qtree, 0 when it's not. + * ****************************************************************************/ +int +pico_dns_qtree_find_name( struct pico_tree *qtree, + const char *name ) +{ + struct pico_tree_node *node = NULL; + struct pico_dns_question *question = NULL; + + /* Check params */ + if (!qtree || !name) { + pico_err = PICO_ERR_EINVAL; + return 0; + } + + /* Iterate over tree and compare names */ + pico_tree_foreach(node, qtree) { + question = (struct pico_dns_question *)node->keyValue; + if ((question) && (strcasecmp(question->qname, name) == 0)) + return 1; + } + + return 0; +} + +/* MARK: ^ PICO_TREE */ +/* MARK: v DNS PACKET FUNCTIONS */ + +/* **************************************************************************** + * Fills the header section of a DNS packet with the correct flags and section + * -counts. + * + * @param hdr Header to fill in. + * @param qdcount Amount of questions added to the packet + * @param ancount Amount of answer records added to the packet + * @param nscount Amount of authority records added to the packet + * @param arcount Amount of additional records added to the packet + * ****************************************************************************/ +void +pico_dns_fill_packet_header( struct pico_dns_header *hdr, + uint16_t qdcount, + uint16_t ancount, + uint16_t nscount, + uint16_t arcount ) +{ + /* ID should be filled by caller */ + + if(qdcount > 0) { /* Questions present? Make it a query */ + hdr->qr = PICO_DNS_QR_QUERY; + hdr->aa = PICO_DNS_AA_NO_AUTHORITY; + } else { /* No questions present? Make it an answer*/ + hdr->qr = PICO_DNS_QR_RESPONSE; + hdr->aa = PICO_DNS_AA_IS_AUTHORITY; + } + + /* Fill in the flags and the fields */ + hdr->opcode = PICO_DNS_OPCODE_QUERY; + hdr->tc = PICO_DNS_TC_NO_TRUNCATION; + hdr->rd = PICO_DNS_RD_IS_DESIRED; + hdr->ra = PICO_DNS_RA_NO_SUPPORT; + hdr->z = 0; /* Z, AD, CD are 0 */ + hdr->rcode = PICO_DNS_RCODE_NO_ERROR; + hdr->qdcount = short_be(qdcount); + hdr->ancount = short_be(ancount); + hdr->nscount = short_be(nscount); + hdr->arcount = short_be(arcount); +} + +/* **************************************************************************** + * Fills a single DNS resource record section of a DNS packet. + * + * @param rtree Tree that contains the DNS resource records. + * @param dest Pointer-pointer to location where you want to insert records. + * Will point to location after current section on return. + * @return 0 on success, something else on failure. + * ****************************************************************************/ +static int +pico_dns_fill_packet_rr_section( struct pico_tree *rtree, + uint8_t **dest ) +{ + struct pico_tree_node *node = NULL; + struct pico_dns_record *record = NULL; + + pico_tree_foreach(node, rtree) { + record = node->keyValue; + if ((record) && pico_dns_record_copy_flat(record, dest)) { + dns_dbg("Could not copy record into Answer Section!\n"); + return -1; + } + } + return 0; +} + +/* **************************************************************************** + * Fills the resource record sections of a DNS packet with provided record- + * trees. + * + * @param packet Packet you want to fill + * @param qtree Question tree to determine where the rrsections begin. + * @param antree DNS records to put in Answer section + * @param nstree DNS records to put in Authority section + * @param artree DNS records to put in Additional section + * @return 0 on success, something else on failure. + * ****************************************************************************/ +static int +pico_dns_fill_packet_rr_sections( pico_dns_packet *packet, + struct pico_tree *qtree, + struct pico_tree *antree, + struct pico_tree *nstree, + struct pico_tree *artree ) +{ + int anret = 0, nsret = 0, arret = 0; + uint16_t temp = 0; + uint8_t *destination = NULL; + + /* Check params */ + if (!packet || !qtree || !antree || !nstree || !artree) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* Initialise the destination pointers before iterating */ + destination = (uint8_t *)packet + sizeof(struct pico_dns_header); + pico_tree_size(qtree, &temp, &pico_dns_question_size); + destination = destination + temp; + + /* Iterate over ANSWERS */ + anret = pico_dns_fill_packet_rr_section(antree, &destination); + + /* Iterate over AUTHORITIES */ + nsret = pico_dns_fill_packet_rr_section(nstree, &destination); + + /* Iterate over ADDITIONALS */ + arret = pico_dns_fill_packet_rr_section(artree, &destination); + + if (anret || nsret || arret) + return -1; + + return 0; +} + +/* **************************************************************************** + * Fills the question section of a DNS packet with provided questions in the + * tree. + * + * @param packet Packet you want to fill + * @param qtree Question tree with question you want to insert + * @return 0 on success, something else on failure. + * ****************************************************************************/ +static int +pico_dns_fill_packet_question_section( pico_dns_packet *packet, + struct pico_tree *qtree ) +{ + struct pico_tree_node *node = NULL; + struct pico_dns_question *question = NULL; + struct pico_dns_question_suffix *dest_qsuffix = NULL; + char *dest_qname = NULL; + + /* Check params */ + if (!packet || !qtree) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* Initialise pointer */ + dest_qname = (char *)((char *)packet + sizeof(struct pico_dns_header)); + + pico_tree_foreach(node, qtree) { + question = node->keyValue; + if (question) { + /* Copy the name */ + memcpy(dest_qname, question->qname, question->qname_length); + + /* Copy the suffix */ + dest_qsuffix = (struct pico_dns_question_suffix *) + (dest_qname + question->qname_length); + dest_qsuffix->qtype = question->qsuffix->qtype; + dest_qsuffix->qclass = question->qsuffix->qclass; + + /* Move to next question */ + dest_qname = (char *)((char *)dest_qsuffix + + sizeof(struct pico_dns_question_suffix)); + } + } + return 0; +} + +/* **************************************************************************** + * Looks for a name somewhere else in packet, more specifically between the + * beginning of the data buffer and the name itself. + * ****************************************************************************/ +static uint8_t * +pico_dns_packet_compress_find_ptr( uint8_t *name, + uint8_t *data, + uint16_t len ) +{ + uint8_t *iterator = NULL; + + /* Check params */ + if (!name || !data || !len) + return NULL; + + if ((name < data) || (name > (data + len))) + return NULL; + + iterator = data; + + /* Iterate from the beginning of data up until the name-ptr */ + while (iterator < name) { + /* Compare in each iteration of current name is equal to a section of + the DNS packet and if so return the pointer to that section */ + if (memcmp((void *)iterator++, (void *)name, + pico_dns_strlen((char *)name) + 1u) == 0) + return (iterator - 1); + } + return NULL; +} + +/* **************************************************************************** + * Compresses a single name by looking for the same name somewhere else in the + * packet-buffer. + * ****************************************************************************/ +static int +pico_dns_packet_compress_name( uint8_t *name, + uint8_t *packet, + uint16_t *len) +{ + uint8_t *lbl_iterator = NULL; /* To iterate over labels */ + uint8_t *compression_ptr = NULL; /* PTR to somewhere else in the packet */ + uint8_t *offset = NULL; /* PTR after compression pointer */ + uint8_t *ptr_after_str = NULL; + uint8_t *last_byte = NULL; + uint8_t *i = NULL; + uint16_t ptr = 0; + uint16_t difference = 0; + + /* Check params */ + if (!name || !packet || !len) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + if ((name < packet) || (name > (packet + *len))) { + dns_dbg("Name ptr OOB. name: %p max: %p\n", name, packet + *len); + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* Try to compress name */ + lbl_iterator = name; + while (lbl_iterator != '\0') { + /* Try to find a compression pointer with current name */ + compression_ptr = pico_dns_packet_compress_find_ptr(lbl_iterator, + packet + 12, *len); + /* If name can be compressed */ + if (compression_ptr) { + /* Point to place after current string */ + ptr_after_str = lbl_iterator + strlen((char *)lbl_iterator) + 1u; + + /* Calculate the compression pointer value */ + ptr = (uint16_t)(compression_ptr - packet); + + /* Set the compression pointer in the packet */ + *lbl_iterator = (uint8_t)(0xC0 | (uint8_t)(ptr >> 8)); + *(lbl_iterator + 1) = (uint8_t)(ptr & 0xFF); + + /* Move up the rest of the packet data to right after the pointer */ + offset = lbl_iterator + 2; + + /* Move up left over data */ + difference = (uint16_t)(ptr_after_str - offset); + last_byte = packet + *len; + for (i = ptr_after_str; i < last_byte; i++) + *((uint8_t *)(i - difference)) = *i; + /* Update length */ + *len = (uint16_t)(*len - difference); + break; + } + + /* Move to next length label */ + lbl_iterator = lbl_iterator + *(lbl_iterator) + 1; + } + return 0; +} + +/* **************************************************************************** + * Utility function compress a record section + * ****************************************************************************/ +static int +pico_dns_compress_record_sections( uint16_t qdcount, uint16_t count, + uint8_t *buf, uint8_t **iterator, + uint16_t *len ) +{ + struct pico_dns_record_suffix *rsuffix = NULL; + uint8_t *_iterator = NULL; + uint16_t i = 0; + + /* Check params */ + if (!iterator || !(*iterator) || !buf || !len) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + _iterator = *iterator; + + for (i = 0; i < count; i++) { + if (qdcount || i) + pico_dns_packet_compress_name(_iterator, buf, len); + + /* To get rdlength */ + rsuffix = (struct pico_dns_record_suffix *) + (_iterator + pico_dns_namelen_comp((char *)_iterator) + 1u); + + /* Move to next res record */ + _iterator = ((uint8_t *)rsuffix + + sizeof(struct pico_dns_record_suffix) + + short_be(rsuffix->rdlength)); + } + *iterator = _iterator; + return 0; +} + +/* **************************************************************************** + * Applies DNS name compression to an entire DNS packet + * ****************************************************************************/ +static int +pico_dns_packet_compress( pico_dns_packet *packet, uint16_t *len ) +{ + uint8_t *packet_buf = NULL; + uint8_t *iterator = NULL; + uint16_t qdcount = 0, rcount = 0, i = 0; + + /* Check params */ + if (!packet || !len) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + packet_buf = (uint8_t *)packet; + + /* Temporarily store the question & record counts */ + qdcount = short_be(packet->qdcount); + rcount = (uint16_t)(rcount + short_be(packet->ancount)); + rcount = (uint16_t)(rcount + short_be(packet->nscount)); + rcount = (uint16_t)(rcount + short_be(packet->arcount)); + + /* Move past the DNS packet header */ + iterator = (uint8_t *)((uint8_t *) packet + 12u); + + /* Start with the questions */ + for (i = 0; i < qdcount; i++) { + if (i) /* First question can't be compressed */ + pico_dns_packet_compress_name(iterator, packet_buf, len); + + /* Move to next question */ + iterator = (uint8_t *)(iterator + + pico_dns_namelen_comp((char *)iterator) + + sizeof(struct pico_dns_question_suffix) + 1u); + } + /* Then onto the answers */ + pico_dns_compress_record_sections(qdcount, rcount, packet_buf, &iterator, + len); + return 0; +} + +/* **************************************************************************** + * Calculates how big a packet needs be in order to store all the question & + * records in the tree. Also determines the amount of questions and records. + * + * @param qtree Tree with Questions. + * @param antree Tree with Answer Records. + * @param nstree Tree with Authority Records. + * @param artree Tree with Additional Records. + * @param qdcount Pointer to var to store amount of questions + * @param ancount Pointer to var to store amount of answers. + * @param nscount Pointer to var to store amount of authorities. + * @param arcount Pointer to var to store amount of additionals. + * @return Returns the total length that the DNS packet needs to be. + * ****************************************************************************/ +static uint16_t +pico_dns_packet_len( struct pico_tree *qtree, + struct pico_tree *antree, + struct pico_tree *nstree, + struct pico_tree *artree, + uint8_t *qdcount, uint8_t *ancount, + uint8_t *nscount, uint8_t *arcount ) +{ + uint16_t len = (uint16_t) sizeof(pico_dns_packet); + + /* Check params */ + if (!qtree || !antree || !nstree || !artree) { + pico_err = PICO_ERR_EINVAL; + return 0; + } + + *qdcount = (uint8_t)pico_tree_size(qtree, &len, &pico_dns_question_size); + *ancount = (uint8_t)pico_tree_size(antree, &len, &pico_dns_record_size); + *nscount = (uint8_t)pico_tree_size(nstree, &len, &pico_dns_record_size); + *arcount = (uint8_t)pico_tree_size(artree, &len, &pico_dns_record_size); + return len; +} + +/* **************************************************************************** + * Generic packet creation utility that just creates a DNS packet with given + * questions and resource records to put in the Resource Record Sections. If a + * NULL-pointer is provided for a certain tree, no records will be added to + * that particular section of the packet. + * + * @param qtree DNS Questions to put in the Question Section. + * @param antree DNS Records to put in the Answer Section. + * @param nstree DNS Records to put in the Authority Section. + * @param artree DNS Records to put in the Additional Section. + * @param len Will get fill with the entire size of the packet + * @return Pointer to created DNS packet + * ****************************************************************************/ +static pico_dns_packet * +pico_dns_packet_create( struct pico_tree *qtree, + struct pico_tree *antree, + struct pico_tree *nstree, + struct pico_tree *artree, + uint16_t *len ) +{ + PICO_DNS_QTREE_DECLARE(_qtree); + PICO_DNS_RTREE_DECLARE(_antree); + PICO_DNS_RTREE_DECLARE(_nstree); + PICO_DNS_RTREE_DECLARE(_artree); + pico_dns_packet *packet = NULL; + uint8_t qdcount = 0, ancount = 0, nscount = 0, arcount = 0; + + /* Set default vector, if arguments are NULL-pointers */ + _qtree = (qtree) ? (*qtree) : (_qtree); + _antree = (antree) ? (*antree) : (_antree); + _nstree = (nstree) ? (*nstree) : (_nstree); + _artree = (artree) ? (*artree) : (_artree); + + /* Get the size of the entire packet and determine the header counters */ + *len = pico_dns_packet_len(&_qtree, &_antree, &_nstree, &_artree, + &qdcount, &ancount, &nscount, &arcount); + + /* Provide space for the entire packet */ + if (!(packet = PICO_ZALLOC(*len))) { + pico_err = PICO_ERR_ENOMEM; + return NULL; + } + + /* Fill the Question Section with questions */ + if (qtree && pico_tree_count(&_qtree) != 0) { + if (pico_dns_fill_packet_question_section(packet, &_qtree)) { + dns_dbg("Could not fill Question Section correctly!\n"); + return NULL; + } + } + + /* Fill the Resource Record Sections with resource records */ + if (pico_dns_fill_packet_rr_sections(packet, &_qtree, &_antree, + &_nstree, &_artree)) { + dns_dbg("Could not fill Resource Record Sections correctly!\n"); + return NULL; + } + + /* Fill the DNS packet header and try to compress */ + pico_dns_fill_packet_header(packet, qdcount, ancount, nscount, arcount); + pico_dns_packet_compress(packet, len); + + return packet; +} + +/* **************************************************************************** + * Creates a DNS Query packet with given question and resource records to put + * the Resource Record Sections. If a NULL-pointer is provided for a certain + * tree, no records will be added to that particular section of the packet. + * + * @param qtree DNS Questions to put in the Question Section + * @param antree DNS Records to put in the Answer Section + * @param nstree DNS Records to put in the Authority Section + * @param artree DNS Records to put in the Additional Section + * @param len Will get filled with the entire size of the packet + * @return Pointer to created DNS packet + * ****************************************************************************/ +pico_dns_packet * +pico_dns_query_create( struct pico_tree *qtree, + struct pico_tree *antree, + struct pico_tree *nstree, + struct pico_tree *artree, + uint16_t *len ) +{ + return pico_dns_packet_create(qtree, antree, nstree, artree, len); +} + +/* **************************************************************************** + * Creates a DNS Answer packet with given resource records to put in the + * Resource Record Sections. If a NULL-pointer is provided for a certain tree, + * no records will be added to that particular section of the packet. + * + * @param antree DNS Records to put in the Answer Section + * @param nstree DNS Records to put in the Authority Section + * @param artree DNS Records to put in the Additional Section + * @param len Will get filled with the entire size of the packet + * @return Pointer to created DNS packet. + * ****************************************************************************/ +pico_dns_packet * +pico_dns_answer_create( struct pico_tree *antree, + struct pico_tree *nstree, + struct pico_tree *artree, + uint16_t *len ) +{ + return pico_dns_packet_create(NULL, antree, nstree, artree, len); +} +/* MARK: ^ DNS PACKET FUNCTIONS */ diff --git a/ext/picotcp/modules/pico_dns_common.h b/ext/picotcp/modules/pico_dns_common.h new file mode 100644 index 0000000..32a1a1d --- /dev/null +++ b/ext/picotcp/modules/pico_dns_common.h @@ -0,0 +1,523 @@ + +/********************************************************************* + PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. + See LICENSE and COPYING for usage. + . + Authors: Toon Stegen, Jelle De Vleeschouwer + *********************************************************************/ + +#ifndef INCLUDE_PICO_DNS_COMMON +#define INCLUDE_PICO_DNS_COMMON + +#include "pico_config.h" +#include "pico_tree.h" + +/* TYPE values */ +#define PICO_DNS_TYPE_A 1 +#define PICO_DNS_TYPE_CNAME 5 +#define PICO_DNS_TYPE_PTR 12 +#define PICO_DNS_TYPE_TXT 16 +#define PICO_DNS_TYPE_AAAA 28 +#define PICO_DNS_TYPE_SRV 33 +#define PICO_DNS_TYPE_NSEC 47 +#define PICO_DNS_TYPE_ANY 255 + +/* CLASS values */ +#define PICO_DNS_CLASS_IN 1 + +/* FLAG values */ +#define PICO_DNS_QR_QUERY 0 +#define PICO_DNS_QR_RESPONSE 1 +#define PICO_DNS_OPCODE_QUERY 0 +#define PICO_DNS_OPCODE_IQUERY 1 +#define PICO_DNS_OPCODE_STATUS 2 +#define PICO_DNS_AA_NO_AUTHORITY 0 +#define PICO_DNS_AA_IS_AUTHORITY 1 +#define PICO_DNS_TC_NO_TRUNCATION 0 +#define PICO_DNS_TC_IS_TRUNCATED 1 +#define PICO_DNS_RD_NO_DESIRE 0 +#define PICO_DNS_RD_IS_DESIRED 1 +#define PICO_DNS_RA_NO_SUPPORT 0 +#define PICO_DNS_RA_IS_SUPPORTED 1 +#define PICO_DNS_RCODE_NO_ERROR 0 +#define PICO_DNS_RCODE_EFORMAT 1 +#define PICO_DNS_RCODE_ESERVER 2 +#define PICO_DNS_RCODE_ENAME 3 +#define PICO_DNS_RCODE_ENOIMP 4 +#define PICO_DNS_RCODE_EREFUSED 5 + +#define PICO_ARPA_IPV4_SUFFIX ".in-addr.arpa" + +#ifdef PICO_SUPPORT_IPV6 +#define STRLEN_PTR_IP6 63 +#define PICO_ARPA_IPV6_SUFFIX ".IP6.ARPA" +#endif + +#define PICO_DNS_NAMEBUF_SIZE (256) + +enum pico_dns_arpa +{ + PICO_DNS_ARPA4, + PICO_DNS_ARPA6, + PICO_DNS_NO_ARPA, +}; + +/* flags split in 2x uint8 due to endianness */ +PACKED_STRUCT_DEF pico_dns_header +{ + uint16_t id; /* Packet id */ + uint8_t rd : 1; /* Recursion Desired */ + uint8_t tc : 1; /* TrunCation */ + uint8_t aa : 1; /* Authoritative Answer */ + uint8_t opcode : 4; /* Opcode */ + uint8_t qr : 1; /* Query/Response */ + uint8_t rcode : 4; /* Response code */ + uint8_t z : 3; /* Zero */ + uint8_t ra : 1; /* Recursion Available */ + uint16_t qdcount; /* Question count */ + uint16_t ancount; /* Answer count */ + uint16_t nscount; /* Authority count */ + uint16_t arcount; /* Additional count */ +}; +typedef struct pico_dns_header pico_dns_packet; + +/* Question fixed-sized fields */ +PACKED_STRUCT_DEF pico_dns_question_suffix +{ + uint16_t qtype; + uint16_t qclass; +}; + +/* Resource record fixed-sized fields */ +PACKED_STRUCT_DEF pico_dns_record_suffix +{ + uint16_t rtype; + uint16_t rclass; + uint32_t rttl; + uint16_t rdlength; +}; + +/* DNS QUESTION */ +struct pico_dns_question +{ + char *qname; + struct pico_dns_question_suffix *qsuffix; + uint16_t qname_length; + uint8_t proto; +}; + +/* DNS RECORD */ +struct pico_dns_record +{ + char *rname; + struct pico_dns_record_suffix *rsuffix; + uint8_t *rdata; + uint16_t rname_length; +}; + +/* MARK: v NAME & IP FUNCTIONS */ + +/* **************************************************************************** + * Checks if the DNS name doesn't exceed 256 bytes including zero-byte. + * + * @param namelen Length of the DNS name-string including zero-byte + * @return 0 when the length is correct + * ****************************************************************************/ +int +pico_dns_check_namelen( uint16_t namelen ); + +/* **************************************************************************** + * Returns the length of a name in a DNS-packet as if DNS name compression + * would be applied to the packet. If there's no compression present this + * returns the strlen. If there's compression present this returns the length + * until the compression-pointer + 1. + * + * @param name Compressed name you want the calculate the strlen from + * @return Returns strlen of a compressed name, takes the first byte of compr- + * ession pointer into account but not the second byte, which acts + * like a trailing zero-byte. + * ****************************************************************************/ +uint16_t +pico_dns_namelen_comp( char *name ); + +/* **************************************************************************** + * Returns the uncompressed name in DNS name format when DNS name compression + * is applied to the packet-buffer. + * + * @param name Compressed name, should be in the bounds of the actual packet + * @param packet Packet that contains the compressed name + * @return Returns the decompressed name, NULL on failure. + * ****************************************************************************/ +char * +pico_dns_decompress_name( char *name, pico_dns_packet *packet ); + +/* **************************************************************************** + * Converts a DNS name in DNS name format to a name in URL format. Provides + * space for the name in URL format as well. PICO_FREE() should be called on + * the returned string buffer that contains the name in URL format. + * + * @param qname DNS name in DNS name format to convert + * @return Returns a pointer to a string-buffer with the URL name on success. + * ****************************************************************************/ +char * +pico_dns_qname_to_url( const char *qname ); + +/* **************************************************************************** + * Converts a DNS name in URL format to name in DNS name format. Provides + * space for the DNS name as well. PICO_FREE() should be called on the returned + * string buffer that contains the DNS name. + * + * @param url DNS name in URL format to convert + * @return Returns a pointer to a string-buffer with the DNS name on success. + * ****************************************************************************/ +char * +pico_dns_url_to_qname( const char *url ); + +/* **************************************************************************** + * @param url String-buffer + * @return Length of string-buffer in an uint16_t + * ****************************************************************************/ +uint16_t +pico_dns_strlen( const char *url ); + +/* **************************************************************************** + * Replaces .'s in a DNS name in URL format by the label lengths. So it + * actually converts a name in URL format to a name in DNS name format. + * f.e. "*www.google.be" => "3www6google2be0" + * + * @param url Location to buffer with name in URL format. The URL needs to + * be +1 byte offset in the actual buffer. Size is should be + * strlen(url) + 2. + * @param maxlen Maximum length of buffer so it doesn't cause a buffer overflow + * @return 0 on success, something else on failure. + * ****************************************************************************/ +int pico_dns_name_to_dns_notation( char *url, unsigned int maxlen ); + +/* **************************************************************************** + * Replaces the label lengths in a DNS-name by .'s. So it actually converts a + * name in DNS format to a name in URL format. + * f.e. 3www6google2be0 => .www.google.be + * + * @param ptr Location to buffer with name in DNS name format + * @param maxlen Maximum length of buffer so it doesn't cause a buffer overflow + * @return 0 on success, something else on failure. + * ****************************************************************************/ +int pico_dns_notation_to_name( char *ptr, unsigned int maxlen ); + +/* **************************************************************************** + * Determines the length of the first label of a DNS name in URL-format + * + * @param url DNS name in URL-format + * @return Length of the first label of DNS name in URL-format + * ****************************************************************************/ +uint16_t +pico_dns_first_label_length( const char *url ); + +/* **************************************************************************** + * Mirrors a dotted IPv4-address string. + * f.e. 192.168.0.1 => 1.0.168.192 + * + * @param ptr + * @return 0 on success, something else on failure. + * ****************************************************************************/ +int +pico_dns_mirror_addr( char *ptr ); + +/* **************************************************************************** + * Convert an IPv6-address in string-format to a IPv6-address in nibble-format. + * Doesn't add a IPv6 ARPA-suffix though. + * + * @param ip IPv6-address stored as a string + * @param dst Destination to store IPv6-address in nibble-format + * ****************************************************************************/ +void +pico_dns_ipv6_set_ptr( const char *ip, char *dst ); + +/* MARK: QUESTION FUNCTIONS */ + +/* **************************************************************************** + * Deletes a single DNS Question. + * + * @param question Void-pointer to DNS Question. Can be used with pico_tree_- + * destroy. + * @return Returns 0 on success, something else on failure. + * ****************************************************************************/ +int +pico_dns_question_delete( void **question); + +/* **************************************************************************** + * Fills in the DNS question suffix-fields with the correct values. + * + * todo: Update pico_dns_client to make the same mechanism possible as with + * filling DNS Resource Record-suffixes. This function shouldn't be an + * API-function. + * + * @param suf Pointer to the suffix member of the DNS question. + * @param qtype DNS type of the DNS question to be. + * @param qclass DNS class of the DNS question to be. + * @return Returns 0 on success, something else on failure. + * ****************************************************************************/ +int +pico_dns_question_fill_suffix( struct pico_dns_question_suffix *suf, + uint16_t qtype, + uint16_t qclass ); + +/* **************************************************************************** + * Creates a standalone DNS Question with a given name and type. + * + * @param url DNS question name in URL format. Will be converted to DNS + * name notation format. + * @param len Will be filled with the total length of the DNS question. + * @param proto Protocol for which you want to create a question. Can be + * either PICO_PROTO_IPV4 or PICO_PROTO_IPV6. + * @param qtype DNS type of the question to be. + * @param qclass DNS class of the question to be. + * @param reverse When this is true, a reverse resolution name will be gene- + * from the URL + * @return Returns pointer to the created DNS Question on success, NULL on + * failure. + * ****************************************************************************/ +struct pico_dns_question * +pico_dns_question_create( const char *url, + uint16_t *len, + uint8_t proto, + uint16_t qtype, + uint16_t qclass, + uint8_t reverse ); + +/* **************************************************************************** + * Decompresses the name of a single DNS question. + * + * @param question Question you want to decompress the name of + * @param packet Packet in which the DNS question is contained. + * @return Pointer to original name of the DNS question before decompressing. + * ****************************************************************************/ +char * +pico_dns_question_decompress( struct pico_dns_question *question, + pico_dns_packet *packet ); + +/* MARK: RESOURCE RECORD FUNCTIONS */ + +/* **************************************************************************** + * Deletes a single DNS resource record. + * + * @param record Void-pointer to DNS record. Can be used with pico_tree_destroy + * @return Returns 0 on success, something else on failure. + * ****************************************************************************/ +int +pico_dns_record_delete( void **record ); + +/* **************************************************************************** + * Just makes a hardcopy from a single DNS Resource Record + * + * @param record DNS record you want to copy + * @return Pointer to copy of DNS record. + * ****************************************************************************/ +struct pico_dns_record * +pico_dns_record_copy( struct pico_dns_record *record ); + +/* **************************************************************************** + * Create a standalone DNS Resource Record with given name, type and data. + * + * @param url DNS rrecord name in URL format. Will be converted to DNS + * name notation format. + * @param _rdata Memory buffer with data to insert in the resource record. If + * data of record should contain a DNS name, the name in the + * databuffer needs to be in URL-format. + * @param datalen The exact length in bytes of the _rdata-buffer. If data of + * record should contain a DNS name, datalen needs to be + * pico_dns_strlen(_rdata). + * @param len Will be filled with the total length of the DNS rrecord. + * @param rtype DNS type of the resource record to be. + * @param rclass DNS class of the resource record to be. + * @param rttl DNS ttl of the resource record to be. + * @return Returns pointer to the created DNS Resource Record + * ****************************************************************************/ +struct pico_dns_record * +pico_dns_record_create( const char *url, + void *_rdata, + uint16_t datalen, + uint16_t *len, + uint16_t rtype, + uint16_t rclass, + uint32_t rttl ); + +/* **************************************************************************** + * Decompresses the name of single DNS record. + * + * @param record DNS record to decompress the name of. + * @param packet Packet in which is DNS record is present + * @return Pointer to original name of the DNS record before decompressing. + * ****************************************************************************/ +char * +pico_dns_record_decompress( struct pico_dns_record *record, + pico_dns_packet *packet ); + +/* MARK: COMPARING */ + +/* **************************************************************************** + * Compares two databuffers against each other. + * + * @param a 1st Memory buffer to compare + * @param b 2nd Memory buffer to compare + * @param rdlength_a Length of 1st memory buffer + * @param rdlength_b Length of 2nd memory buffer + * @param caseinsensitive Whether or not the bytes are compared + * case-insensitive + * @return 0 when the buffers are equal, returns difference when they're not. + * ****************************************************************************/ +int +pico_dns_rdata_cmp( uint8_t *a, uint8_t *b, + uint16_t rdlength_a, uint16_t rdlength_b, uint8_t caseinsensitive ); + +/* **************************************************************************** + * Compares 2 DNS questions + * + * @param qa DNS question A as a void-pointer (for pico_tree) + * @param qb DNS question A as a void-pointer (for pico_tree) + * @return 0 when questions are equal, returns difference when they're not. + * ****************************************************************************/ +int +pico_dns_question_cmp( void *qa, + void *qb ); + +/* **************************************************************************** + * Compares 2 DNS records by type and name only + * + * @param ra DNS record A as a void-pointer (for pico_tree) + * @param rb DNS record B as a void-pointer (for pico_tree) + * @return 0 when name and type of records are equal, returns difference when + * they're not. + * ****************************************************************************/ +int +pico_dns_record_cmp_name_type( void *ra, + void *rb ); + +/* **************************************************************************** + * Compares 2 DNS records by type, name AND rdata for a truly unique result + * + * @param ra DNS record A as a void-pointer (for pico_tree) + * @param rb DNS record B as a void-pointer (for pico_tree) + * @return 0 when records are equal, returns difference when they're not + * ****************************************************************************/ +int +pico_dns_record_cmp( void *ra, + void *rb ); + +/* MARK: PICO_TREE */ + +/* **************************************************************************** + * Erases a pico_tree entirely. + * + * @param tree Pointer to a pico_tree-instance + * @param node_delete Helper-function for type-specific deleting. + * @return Returns 0 on success, something else on failure. + * ****************************************************************************/ +int +pico_tree_destroy( struct pico_tree *tree, int (*node_delete)(void **)); + +/* **************************************************************************** + * Determines the amount of nodes in a pico_tree + * + * @param tree Pointer to pico_tree-instance + * @return Amount of items in the tree. + * ****************************************************************************/ +uint16_t +pico_tree_count( struct pico_tree *tree ); + +/* **************************************************************************** + * Definition of DNS question tree + * ****************************************************************************/ +typedef struct pico_tree pico_dns_qtree; +#define PICO_DNS_QTREE_DECLARE(name) \ + pico_dns_qtree (name) = {&LEAF, pico_dns_question_cmp} +#define PICO_DNS_QTREE_DESTROY(qtree) \ + pico_tree_destroy(qtree, pico_dns_question_delete) + +/* **************************************************************************** + * Deletes all the questions with given DNS name from a pico_tree + * + * @param qtree Pointer to pico_tree-instance which contains DNS questions + * @param name Name of the questions you want to delete + * @return Returns 0 on success, something else on failure. + * ****************************************************************************/ +int +pico_dns_qtree_del_name( struct pico_tree *qtree, + const char *name ); + +/* **************************************************************************** + * Checks whether a question with given name is in the tree or not. + * + * @param qtree Pointer to pico_tree-instance which contains DNS questions + * @param name Name you want to check for + * @return 1 when the name is present in the qtree, 0 when it's not. + * ****************************************************************************/ +int +pico_dns_qtree_find_name( struct pico_tree *qtree, + const char *name ); + +/* **************************************************************************** + * Definition of DNS record tree + * ****************************************************************************/ +typedef struct pico_tree pico_dns_rtree; +#define PICO_DNS_RTREE_DECLARE(name) \ + pico_dns_rtree (name) = {&LEAF, pico_dns_record_cmp} +#define PICO_DNS_RTREE_DESTROY(rtree) \ + pico_tree_destroy((rtree), pico_dns_record_delete) + +/* MARK: DNS PACKET FUNCTIONS */ + +/* **************************************************************************** + * Fills the header section of a DNS packet with the correct flags and section + * -counts. + * + * @param hdr Header to fill in. + * @param qdcount Amount of questions added to the packet + * @param ancount Amount of answer records added to the packet + * @param nscount Amount of authority records added to the packet + * @param arcount Amount of additional records added to the packet + * ****************************************************************************/ +void +pico_dns_fill_packet_header( struct pico_dns_header *hdr, + uint16_t qdcount, + uint16_t ancount, + uint16_t authcount, + uint16_t addcount ); + +/* **************************************************************************** + * Creates a DNS Query packet with given question and resource records to put + * the Resource Record Sections. If a NULL-pointer is provided for a certain + * tree, no records will be added to that particular section of the packet. + * + * @param qtree DNS Questions to put in the Question Section + * @param antree DNS Records to put in the Answer Section + * @param nstree DNS Records to put in the Authority Section + * @param artree DNS Records to put in the Additional Section + * @param len Will get filled with the entire size of the packet + * @return Pointer to created DNS packet + * ****************************************************************************/ +pico_dns_packet * +pico_dns_query_create( struct pico_tree *qtree, + struct pico_tree *antree, + struct pico_tree *nstree, + struct pico_tree *artree, + uint16_t *len ); + +/* **************************************************************************** + * Creates a DNS Answer packet with given resource records to put in the + * Resource Record Sections. If a NULL-pointer is provided for a certain tree, + * no records will be added to that particular section of the packet. + * + * @param antree DNS Records to put in the Answer Section + * @param nstree DNS Records to put in the Authority Section + * @param artree DNS Records to put in the Additional Section + * @param len Will get filled with the entire size of the packet + * @return Pointer to created DNS packet. + * ****************************************************************************/ +pico_dns_packet * +pico_dns_answer_create( struct pico_tree *antree, + struct pico_tree *nstree, + struct pico_tree *artree, + uint16_t *len ); + +#endif /* _INCLUDE_PICO_DNS_COMMON */ diff --git a/ext/picotcp/modules/pico_dns_sd.c b/ext/picotcp/modules/pico_dns_sd.c new file mode 100644 index 0000000..1a60c2a --- /dev/null +++ b/ext/picotcp/modules/pico_dns_sd.c @@ -0,0 +1,549 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2014-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + . + Author: Jelle De Vleeschouwer + *********************************************************************/ + +#include "pico_dns_sd.h" + +#ifdef PICO_SUPPORT_DNS_SD + +/* --- Debugging --- */ +#define dns_sd_dbg(...) do {} while(0) +//#define dns_sd_dbg dbg + +/* --- PROTOTYPES --- */ +key_value_pair_t * +pico_dns_sd_kv_vector_get( kv_vector *vector, uint16_t index ); +int +pico_dns_sd_kv_vector_erase( kv_vector *vector ); +/* ------------------- */ + +typedef PACKED_STRUCT_DEF pico_dns_srv_record_prefix +{ + uint16_t priority; + uint16_t weight; + uint16_t port; +} pico_dns_srv_record; + +/* **************************************************************************** + * Determines the length of the resulting string when a string would be + * created from a key-value pair vector. + * + * @param vector Key-Value pair vector to determine the length of. + * @return The length of the key-value pair vector in bytes as if it would be + * converted to a string. + * ****************************************************************************/ +static uint16_t +pico_dns_sd_kv_vector_strlen( kv_vector *vector ) +{ + key_value_pair_t *iterator = NULL; + uint16_t i = 0, len = 0; + + /* Check params */ + if (!vector) { + pico_err = PICO_ERR_EINVAL; + return 0; + } + + /* Iterate over the key-value pairs */ + for (i = 0; i < vector->count; i++) { + iterator = pico_dns_sd_kv_vector_get(vector, i); + len = (uint16_t) (len + 1u + /* Length byte */ + strlen(iterator->key) /* Length of the key */); + if (iterator->value) + len = (uint16_t) (len + 1u /* '=' char */ + + strlen(iterator->value) /* Length of value */); + } + return len; +} + +/* **************************************************************************** + * Creates an mDNS record with the SRV record format. + * + * @param url Name of the SRV record in URL format. + * @param priority Priority, should be 0. + * @param weight Weight, should be 0. + * @param port Port to register the service on. + * @param target_url Hostname of the service-target, in URL-format + * @param ttl TTL of the SRV Record + * @param flags mDNS record flags to set specifications of the record. + * @return Pointer to newly created record on success, NULL on failure. + * ****************************************************************************/ +static struct pico_mdns_record * +pico_dns_sd_srv_record_create( const char *url, + uint16_t priority, + uint16_t weight, + uint16_t port, + const char *target_url, + uint32_t ttl, + uint8_t flags ) +{ + struct pico_mdns_record *record = NULL; + pico_dns_srv_record *srv_data = NULL; + char *target_rname = NULL; + uint16_t srv_length = 0; + + /* Check params */ + if (!url || !target_url) { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + + /* Determine the length the rdata buf needs to be */ + srv_length = (uint16_t) (6u + strlen(target_url) + 2u); + + /* Provide space for the data-buf */ + if (!(srv_data = (pico_dns_srv_record *) PICO_ZALLOC(srv_length))) { + pico_err = PICO_ERR_ENOMEM; + return NULL; + } + + /* Set the fields */ + srv_data->priority = short_be(priority); + srv_data->weight = short_be(weight); + srv_data->port = short_be(port); + + /* Copy in the URL and convert to DNS notation */ + if (!(target_rname = pico_dns_url_to_qname(target_url))) { + dns_sd_dbg("Could not convert URL to qname!\n"); + PICO_FREE(srv_data); + return NULL; + } + + strcpy((char *)srv_data + 6u, target_rname); + PICO_FREE(target_rname); + + /* Create and return new mDNS record */ + record = pico_mdns_record_create(url, srv_data, srv_length, + PICO_DNS_TYPE_SRV, + ttl, flags); + PICO_FREE(srv_data); + return record; +} + +/* **************************************************************************** + * Creates an mDNS record with the TXT record format. + * + * @param url Name of the TXT record in URL format. + * @param key_value_pairs Key-Value pair vector to generate the data from. + * @param ttl TTL of the TXT record. + * @param flags mDNS record flags to set specifications of the record + * @return Pointer to newly created record on success, NULL on failure. + * ****************************************************************************/ +static struct pico_mdns_record * +pico_dns_sd_txt_record_create( const char *url, + kv_vector key_value_pairs, + uint32_t ttl, + uint8_t flags ) +{ + struct pico_mdns_record *record = NULL; + key_value_pair_t *iterator = NULL; + char *txt = NULL; + uint16_t i = 0, txt_i = 0, pair_len = 0, key_len = 0, value_len = 0; + + /* Determine the length of the string to fit in all pairs */ + uint16_t len = (uint16_t)(pico_dns_sd_kv_vector_strlen(&key_value_pairs) + 1u); + + /* If kv-vector is empty don't bother to create a TXT record */ + if (len <= 1) + return NULL; + + /* Provide space for the txt buf */ + if (!(txt = (char *)PICO_ZALLOC(len))) { + pico_err = PICO_ERR_ENOMEM; + return NULL; + } + + /* Iterate over all the key-value pairs */ + for (i = 0; i < key_value_pairs.count; i++) { + iterator = pico_dns_sd_kv_vector_get(&key_value_pairs, i); + + /* Determine the length of the key */ + key_len = (uint16_t) strlen(iterator->key); + pair_len = key_len; + + /* If value is not a NULL-ptr */ + if (iterator->value) { + value_len = (uint16_t) strlen(iterator->value); + pair_len = (uint16_t) (pair_len + 1u + value_len); + } + + /* Set the pair length label */ + txt[txt_i] = (char)pair_len; + + /* Copy the key */ + strcpy(txt + txt_i + 1u, iterator->key); + + /* Copy the value if it is not a NULL-ptr */ + if (iterator->value) { + strcpy(txt + txt_i + 1u + key_len, "="); + strcpy(txt + txt_i + 2u + key_len, iterator->value); + txt_i = (uint16_t) (txt_i + 2u + key_len + value_len); + } else { + txt_i = (uint16_t) (txt_i + 1u + key_len); + } + } + record = pico_mdns_record_create(url, txt, (uint16_t)(len - 1u), PICO_DNS_TYPE_TXT, ttl, flags); + PICO_FREE(txt); + + return record; +} + +/* **************************************************************************** + * Deletes a single key-value pair instance + * + * @param kv_pair Pointer-pointer to to delete instance + * @return Returns 0 on success, something else on failure. + * ****************************************************************************/ +static int +pico_dns_sd_kv_delete( key_value_pair_t **kv_pair ) +{ + /* Check params */ + if (!kv_pair || !(*kv_pair)) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* Delete the fields */ + if ((*kv_pair)->key) + PICO_FREE((*kv_pair)->key); + + if ((*kv_pair)->value) + PICO_FREE((*kv_pair)->value); + + PICO_FREE(*kv_pair); + *kv_pair = NULL; + kv_pair = NULL; + + return 0; +} + +/* **************************************************************************** + * Creates a single key-value pair-instance + * + * @param key Key of the pair, cannot be NULL. + * @param value Value of the pair, can be NULL, empty ("") or filled ("qkejq") + * @return Pointer to newly created KV-instance on success, NULL on failure. + * ****************************************************************************/ +static key_value_pair_t * +pico_dns_sd_kv_create( const char *key, const char *value ) +{ + key_value_pair_t *kv_pair = NULL; + + /* Check params */ + if (!key || !(kv_pair = PICO_ZALLOC(sizeof(key_value_pair_t)))) { + pico_dns_sd_kv_delete(&kv_pair); + pico_err = PICO_ERR_EINVAL; + return NULL; + } + + /* Provide space to copy the values */ + if (!(kv_pair->key = PICO_ZALLOC((size_t)(strlen(key) + 1)))) { + pico_err = PICO_ERR_ENOMEM; + pico_dns_sd_kv_delete(&kv_pair); + return NULL; + } + + strcpy(kv_pair->key, key); + + if (value) { + if (!(kv_pair->value = PICO_ZALLOC((size_t)(strlen(value) + 1)))) { + pico_err = PICO_ERR_ENOMEM; + pico_dns_sd_kv_delete(&kv_pair); + return NULL; + } + + strcpy(kv_pair->value, value); + } else + kv_pair->value = NULL; + + return kv_pair; +} + +/* **************************************************************************** + * Checks whether the type is correctly formatted ant it's label length are + * between the allowed boundaries. + * + * @param type Servicetype to check the format of. + * @return Returns 0 when the type is correctly formatted, something else when + * it's not. + * ****************************************************************************/ +static int +pico_dns_sd_check_type_format( const char *type ) +{ + uint16_t first_lbl = 0; + int8_t subtype_present = 0; + + /* Check params */ + if (!(first_lbl = pico_dns_first_label_length(type))) + return -1; + + subtype_present = !memcmp(type + first_lbl + 1, "_sub", 4); + + /* Check if there is a subtype present */ + if (subtype_present && (first_lbl > 63)) + return -1; + else if (subtype_present) + /* Get the length of the service name */ + first_lbl = pico_dns_first_label_length(type + first_lbl + 6); + else { + /* Check if type is not greater then 21 bytes (22 - 1, since the length + byte of the service name isn't included yet) */ + if (strlen(type) > (size_t) 21) + return -1; + } + + /* Check if the service name is not greater then 16 bytes (17 - 1) */ + return (first_lbl > ((uint16_t) 16u)); +} + +/* **************************************************************************** + * Checks whether the service instance name is correctly formatted and it's + * label length falls between the allowed boundaries. + * + * @param name Instance name to check the format of. + * @return Returns 0 when the name is correctly formatted, something else when + * it's not. + * ****************************************************************************/ +static int +pico_dns_sd_check_instance_name_format( const char *name ) +{ + /* First of all check if the total length is larger than 63 bytes */ + if (pico_dns_strlen(name) > 63 || !pico_dns_strlen(name)) + return -1; + + return 0; +} + +/* **************************************************************************** + * Append the instance name adn service type to create a '.local' service SIN. + * + * @param name Instance Name of the service, f.e. "Printer 2nd Floor". + * @param type ServiceType of the service, f.e. "_http._tcp". + * @return Pointer to newly created SIN on success, NULL on failure. + * ****************************************************************************/ +static char * +pico_dns_sd_create_service_url( const char *name, + const char *type ) +{ + char *url = NULL; + uint16_t len = 0, namelen = 0, typelen = 0; + + if (pico_dns_sd_check_type_format(type)) { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + + if (pico_dns_sd_check_instance_name_format(name)) { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + + namelen = (uint16_t)strlen(name); + typelen = (uint16_t)strlen(type); + + /* Determine the length that the URL needs to be */ + len = (uint16_t)(namelen + 1u /* for '.'*/ + + typelen + 7u /* for '.local\0' */); + url = (char *)PICO_ZALLOC(len); + if (!url) { + pico_err = PICO_ERR_ENOMEM; + return NULL; + } + + /* Append the parts together */ + strcpy(url, name); + strcpy(url + namelen, "."); + strcpy(url + namelen + 1, type); + strcpy(url + namelen + 1 + typelen, ".local"); + + return url; +} + +/* **************************************************************************** + * This function actually does exactly the same as pico_mdns_init(); + * ****************************************************************************/ +int +pico_dns_sd_init( const char *_hostname, + struct pico_ip4 address, + void (*callback)(pico_mdns_rtree *, + char *, + void *), + void *arg ) +{ + return pico_mdns_init(_hostname, address, callback, arg); +} + +/* **************************************************************************** + * Just calls pico_mdns_init in it's turn to initialise the mDNS-module. + * See pico_mdns.h for description. + * ****************************************************************************/ +int +pico_dns_sd_register_service( const char *name, + const char *type, + uint16_t port, + kv_vector *txt_data, + uint16_t ttl, + void (*callback)(pico_mdns_rtree *, + char *, + void *), + void *arg) +{ + PICO_MDNS_RTREE_DECLARE(rtree); + struct pico_mdns_record *srv_record = NULL; + struct pico_mdns_record *txt_record = NULL; + const char *hostname = pico_mdns_get_hostname(); + char *url = NULL; + + /* Try to create a service URL to create records with */ + if (!(url = pico_dns_sd_create_service_url(name, type)) || !txt_data || !hostname) { + if (url) + PICO_FREE(url); + pico_err = PICO_ERR_EINVAL; + return -1; + } + + dns_sd_dbg("\n>>>>>>>>>> Target: %s <<<<<<<<<<\n\n", hostname); + + /* Create the SRV record */ + srv_record = pico_dns_sd_srv_record_create(url, 0, 0, port, hostname, ttl, PICO_MDNS_RECORD_UNIQUE); + if (!srv_record) { + PICO_FREE(url); + return -1; + } + + /* Create the TXT record */ + txt_record = pico_dns_sd_txt_record_create(url, *txt_data, ttl, PICO_MDNS_RECORD_UNIQUE); + PICO_FREE(url); + + /* Erase the key-value pair vector, it's no longer needed */ + pico_dns_sd_kv_vector_erase(txt_data); + + if (txt_record) + pico_tree_insert(&rtree, txt_record); + + pico_tree_insert(&rtree, srv_record); + + if (pico_mdns_claim(rtree, callback, arg)) { + PICO_MDNS_RTREE_DESTROY(&rtree); + return -1; + } + pico_tree_destroy(&rtree, NULL); + return 0; +} + +/* **************************************************************************** + * Does nothing for now. + * + * @param type Type to browse for. + * @param callback Callback to call when something particular happens. + * @return When the module successfully started browsing the servicetype. + * ****************************************************************************/ +int +pico_dns_sd_browse_service( const char *type, + void (*callback)(pico_mdns_rtree *, + char *, + void *), + void *arg ) +{ + IGNORE_PARAMETER(type); + IGNORE_PARAMETER(callback); + IGNORE_PARAMETER(arg); + return 0; +} + +/* **************************************************************************** + * Add a key-value pair the a key-value pair vector. + * + * @param vector Vector to add the pair to. + * @param key Key of the pair, cannot be NULL. + * @param value Value of the pair, can be NULL, empty ("") or filled ("qkejq") + * @return Returns 0 when the pair is added successfully, something else on + * failure. + * ****************************************************************************/ +int +pico_dns_sd_kv_vector_add( kv_vector *vector, char *key, char *value ) +{ + key_value_pair_t *kv_pair = NULL; + key_value_pair_t **new_pairs = NULL; + uint16_t i = 0; + + /* Check params */ + if (!vector || !key || !(kv_pair = pico_dns_sd_kv_create(key, value))) { + pico_err = PICO_ERR_EINVAL; + pico_dns_sd_kv_delete(&kv_pair); + return -1; + } + + /* Provide enough space for the new pair pointers */ + if (!(new_pairs = PICO_ZALLOC(sizeof(key_value_pair_t *) * + (vector->count + 1u)))) { + pico_err = PICO_ERR_ENOMEM; + pico_dns_sd_kv_delete(&kv_pair); + return -1; + } + + /* Copy previous pairs and add new one */ + for (i = 0; i < vector->count; i++) + new_pairs[i] = vector->pairs[i]; + new_pairs[i] = kv_pair; + + /* Free the previous array */ + if (vector->pairs) + PICO_FREE(vector->pairs); + + vector->pairs = new_pairs; + vector->count++; + + return 0; +} + +/* **************************************************************************** + * Gets a single key-value pair form a Key-Value pair vector @ certain index. + * + * @param vector Vector to get KV-pair from. + * @param index Index of the KV-pair. + * @return key_value_pair_t* on success, NULL on failure. + * ****************************************************************************/ +key_value_pair_t * +pico_dns_sd_kv_vector_get( kv_vector *vector, uint16_t index ) +{ + /* Check params */ + if (!vector) + return NULL; + + /* Return record with conditioned index */ + if (index < vector->count) + return vector->pairs[index]; + + return NULL; +} + +/* **************************************************************************** + * Erase all the contents of a key-value pair vector. + * + * @param vector Key-Value pair vector. + * @return 0 on success, something else on failure. + * ****************************************************************************/ +int +pico_dns_sd_kv_vector_erase( kv_vector *vector ) +{ + uint16_t i = 0; + + /* Iterate over each key-value pair */ + for (i = 0; i < vector->count; i++) { + if (pico_dns_sd_kv_delete(&(vector->pairs[i])) < 0) { + dns_sd_dbg("Could not delete key-value pairs from vector"); + return -1; + } + } + PICO_FREE(vector->pairs); + vector->pairs = NULL; + vector->count = 0; + + return 0; +} + +#endif diff --git a/ext/picotcp/modules/pico_dns_sd.h b/ext/picotcp/modules/pico_dns_sd.h new file mode 100644 index 0000000..b93d599 --- /dev/null +++ b/ext/picotcp/modules/pico_dns_sd.h @@ -0,0 +1,91 @@ +/* **************************************************************************** + * PicoTCP. Copyright (c) 2014 TASS Belgium NV. Some rights reserved. + * See LICENSE and COPYING for usage. + * . + * Author: Jelle De Vleeschouwer + * ****************************************************************************/ +#ifndef INCLUDE_PICO_DNS_SD +#define INCLUDE_PICO_DNS_SD + +#include "pico_mdns.h" + +typedef struct +{ + char *key; + char *value; +} key_value_pair_t; + +typedef struct +{ + key_value_pair_t **pairs; + uint16_t count; +} kv_vector; + +#define PICO_DNS_SD_KV_VECTOR_DECLARE(name) \ + kv_vector (name) = {0} + +/* **************************************************************************** + * Just calls pico_mdns_init in it's turn to initialise the mDNS-module. + * See pico_mdns.h for description. + * ****************************************************************************/ +int +pico_dns_sd_init( const char *_hostname, + struct pico_ip4 address, + void (*callback)(pico_mdns_rtree *, + char *, + void *), + void *arg ); + +/* **************************************************************************** + * Register a DNS-SD service via Multicast DNS on the local network. + * + * @param name Instance Name of the service, f.e. "Printer 2nd Floor". + * @param type ServiceType of the service, f.e. "_http._tcp". + * @param port Port number on which the service runs. + * @param txt_data TXT data to create TXT record with, need kv_vector-type, + * Declare such a type with PICO_DNS_SD_KV_VECTOR_DECLARE(*) & + * add key-value pairs with pico_dns_sd_kv_vector_add(). + * @param ttl TTL + * @param callback Callback-function to call when the service is registered. + * @return + * ****************************************************************************/ +int +pico_dns_sd_register_service( const char *name, + const char *type, + uint16_t port, + kv_vector *txt_data, + uint16_t ttl, + void (*callback)(pico_mdns_rtree *, + char *, + void *), + void *arg); + +/* **************************************************************************** + * Does nothing for now. + * + * @param type Type to browse for. + * @param callback Callback to call when something particular happens. + * @return When the module successfully started browsing the servicetype. + * ****************************************************************************/ +int +pico_dns_sd_browse_service( const char *type, + void (*callback)(pico_mdns_rtree *, + char *, + void *), + void *arg ); + +/* **************************************************************************** + * Add a key-value pair the a key-value pair vector. + * + * @param vector Vector to add the pair to. + * @param key Key of the pair, cannot be NULL. + * @param value Value of the pair, can be NULL, empty ("") or filled ("qkejq") + * @return Returns 0 when the pair is added successfully, something else on + * failure. + * ****************************************************************************/ +int +pico_dns_sd_kv_vector_add( kv_vector *vector, char *key, char *value ); + + +#endif /* _INCLUDE_PICO_DNS_SD */ + diff --git a/ext/picotcp/modules/pico_fragments.c b/ext/picotcp/modules/pico_fragments.c new file mode 100644 index 0000000..33e69ca --- /dev/null +++ b/ext/picotcp/modules/pico_fragments.c @@ -0,0 +1,391 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. + See LICENSE and COPYING for usage. + + Authors: Laurens Miers, Daniele Lacamera + *********************************************************************/ + + +#include "pico_config.h" +#ifdef PICO_SUPPORT_IPV6 +#include "pico_ipv6.h" +#include "pico_icmp6.h" +#endif +#ifdef PICO_SUPPORT_IPV4 +#include "pico_ipv4.h" +#include "pico_icmp4.h" +#endif +#include "pico_stack.h" +#include "pico_eth.h" +#include "pico_udp.h" +#include "pico_tcp.h" +#include "pico_socket.h" +#include "pico_device.h" +#include "pico_tree.h" +#include "pico_constants.h" +#include "pico_fragments.h" + +#define frag_dbg(...) do {} while(0) + +#if defined(PICO_SUPPORT_IPV6) && defined(PICO_SUPPORT_IPV6FRAG) +#define IP6_FRAG_OFF(x) ((x & 0xFFF8u)) +#define IP6_FRAG_MORE(x) ((x & 0x0001)) +#define IP6_FRAG_ID(x) ((uint32_t)((x->ext.frag.id[0] << 24) + (x->ext.frag.id[1] << 16) + \ + (x->ext.frag.id[2] << 8) + x->ext.frag.id[3])) +#else +#define IP6_FRAG_OFF(x) (0) +#define IP6_FRAG_MORE(x) (0) +#define IP6_FRAG_ID(x) (0) +#endif + +#if defined(PICO_SUPPORT_IPV4) && defined(PICO_SUPPORT_IPV4FRAG) +#define IP4_FRAG_OFF(frag) (((uint32_t)frag & PICO_IPV4_FRAG_MASK) << 3ul) +#define IP4_FRAG_MORE(frag) ((frag & PICO_IPV4_MOREFRAG) ? 1 : 0) +#define IP4_FRAG_ID(hdr) (hdr->id) +#else +#define IP4_FRAG_OFF(frag) (0) +#define IP4_FRAG_MORE(frag) (0) +#define IP4_FRAG_ID(hdr) (0) +#endif + +#define FRAG_OFF(net, frag) ((net == PICO_PROTO_IPV4) ? (IP4_FRAG_OFF(frag)) : (IP6_FRAG_OFF(frag))) +#define FRAG_MORE(net, frag) ((net == PICO_PROTO_IPV4) ? (IP4_FRAG_MORE(frag)) : (IP6_FRAG_MORE(frag))) + +#define PICO_IPV6_FRAG_TIMEOUT 60000 +#define PICO_IPV4_FRAG_TIMEOUT 15000 + +static void pico_frag_expire(pico_time now, void *arg); +static void pico_fragments_complete(unsigned int bookmark, uint8_t proto, uint8_t net); +static int pico_fragments_check_complete(uint8_t proto, uint8_t net); + +#if defined(PICO_SUPPORT_IPV6) && defined(PICO_SUPPORT_IPV6FRAG) +static uint32_t ipv6_cur_frag_id = 0u; +uint32_t ipv6_fragments_timer = 0u; + +static int pico_ipv6_frag_compare(void *ka, void *kb) +{ + struct pico_frame *a = ka, *b = kb; + if (IP6_FRAG_OFF(a->frag) > IP6_FRAG_OFF(b->frag)) + return 1; + + if (IP6_FRAG_OFF(a->frag) < IP6_FRAG_OFF(b->frag)) + return -1; + + return 0; +} +PICO_TREE_DECLARE(ipv6_fragments, pico_ipv6_frag_compare); + +static void pico_ipv6_fragments_complete(unsigned int len, uint8_t proto) +{ + struct pico_tree_node *index, *tmp; + struct pico_frame *f; + unsigned int bookmark = 0; + struct pico_frame *full = NULL; + struct pico_frame *first = pico_tree_first(&ipv6_fragments); + + full = pico_frame_alloc((uint16_t)(PICO_SIZE_IP6HDR + len)); + if (full) { + full->net_hdr = full->buffer; + full->net_len = PICO_SIZE_IP6HDR; + memcpy(full->net_hdr, first->net_hdr, full->net_len); + full->transport_hdr = full->net_hdr + full->net_len; + full->transport_len = (uint16_t)len; + full->dev = first->dev; + pico_tree_foreach_safe(index, &ipv6_fragments, tmp) { + f = index->keyValue; + memcpy(full->transport_hdr + bookmark, f->transport_hdr, f->transport_len); + bookmark += f->transport_len; + pico_tree_delete(&ipv6_fragments, f); + pico_frame_discard(f); + } + if (pico_transport_receive(full, proto) == -1) + { + pico_frame_discard(full); + } + pico_timer_cancel(ipv6_fragments_timer); + ipv6_fragments_timer = 0; + } +} + +static void pico_ipv6_frag_timer_on(void) +{ + ipv6_fragments_timer = pico_timer_add(PICO_IPV6_FRAG_TIMEOUT, pico_frag_expire, &ipv6_fragments); +} + +static int pico_ipv6_frag_match(struct pico_frame *a, struct pico_frame *b) +{ + struct pico_ipv6_hdr *ha, *hb; + if (!a || !b) + return 0; + + ha = (struct pico_ipv6_hdr *)a->net_hdr; + hb = (struct pico_ipv6_hdr *)b->net_hdr; + if (!ha || !hb) + return 0; + + if (memcmp(ha->src.addr, hb->src.addr, PICO_SIZE_IP6) != 0) + return 0; + + if (memcmp(ha->dst.addr, hb->dst.addr, PICO_SIZE_IP6) != 0) + return 0; + + return 1; +} +#endif + +#if defined(PICO_SUPPORT_IPV4) && defined(PICO_SUPPORT_IPV4FRAG) +static uint32_t ipv4_cur_frag_id = 0u; +uint32_t ipv4_fragments_timer = 0u; + +static int pico_ipv4_frag_compare(void *ka, void *kb) +{ + struct pico_frame *a = ka, *b = kb; + if (IP4_FRAG_OFF(a->frag) > IP4_FRAG_OFF(b->frag)) + return 1; + + if (IP4_FRAG_OFF(a->frag) < IP4_FRAG_OFF(b->frag)) + return -1; + + return 0; +} +PICO_TREE_DECLARE(ipv4_fragments, pico_ipv4_frag_compare); + +static void pico_ipv4_fragments_complete(unsigned int len, uint8_t proto) +{ + struct pico_tree_node *index, *tmp; + struct pico_frame *f; + unsigned int bookmark = 0; + struct pico_frame *full = NULL; + struct pico_frame *first = pico_tree_first(&ipv4_fragments); + + full = pico_frame_alloc((uint16_t)(PICO_SIZE_IP4HDR + len)); + if (full) { + full->net_hdr = full->buffer; + full->net_len = PICO_SIZE_IP4HDR; + memcpy(full->net_hdr, first->net_hdr, full->net_len); + full->transport_hdr = full->net_hdr + full->net_len; + full->transport_len = (uint16_t)len; + full->dev = first->dev; + pico_tree_foreach_safe(index, &ipv4_fragments, tmp) { + f = index->keyValue; + memcpy(full->transport_hdr + bookmark, f->transport_hdr, f->transport_len); + bookmark += f->transport_len; + pico_tree_delete(&ipv4_fragments, f); + pico_frame_discard(f); + } + ipv4_cur_frag_id = 0; + if (pico_transport_receive(full, proto) == -1) + { + pico_frame_discard(full); + } + pico_timer_cancel(ipv4_fragments_timer); + ipv4_fragments_timer = 0; + } +} + +static void pico_ipv4_frag_timer_on(void) +{ + ipv4_fragments_timer = pico_timer_add( PICO_IPV4_FRAG_TIMEOUT, pico_frag_expire, &ipv4_fragments); +} + +static int pico_ipv4_frag_match(struct pico_frame *a, struct pico_frame *b) +{ + struct pico_ipv4_hdr *ha, *hb; + if (!a || !b) + return 0; + + ha = (struct pico_ipv4_hdr *)a->net_hdr; + hb = (struct pico_ipv4_hdr *)b->net_hdr; + if (!ha || !hb) + return 0; + + if (memcmp(&(ha->src.addr), &(hb->src.addr), PICO_SIZE_IP4) != 0) + return 0; + + if (memcmp(&(ha->dst.addr), &(hb->dst.addr), PICO_SIZE_IP4) != 0) + return 0; + + return 1; +} +#endif + +static void pico_fragments_complete(unsigned int bookmark, uint8_t proto, uint8_t net) +{ + if (0) {} + +#if defined(PICO_SUPPORT_IPV4) && defined(PICO_SUPPORT_IPV4FRAG) + else if (net == PICO_PROTO_IPV4) + { + pico_ipv4_fragments_complete(bookmark, proto); + } +#endif +#if defined(PICO_SUPPORT_IPV6) && defined(PICO_SUPPORT_IPV6FRAG) + else if (net == PICO_PROTO_IPV6) + { + pico_ipv6_fragments_complete(bookmark, proto); + } +#endif +} + +static int pico_fragments_check_complete(uint8_t proto, uint8_t net) +{ + struct pico_tree_node *index, *temp; + struct pico_frame *cur; + unsigned int bookmark = 0; + struct pico_tree *tree = NULL; + +#if defined(PICO_SUPPORT_IPV4) && defined(PICO_SUPPORT_IPV4FRAG) + if (net == PICO_PROTO_IPV4) + { + tree = &ipv4_fragments; + } +#endif +#if defined(PICO_SUPPORT_IPV6) && defined(PICO_SUPPORT_IPV6FRAG) + if (net == PICO_PROTO_IPV6) + { + tree = &ipv6_fragments; + } +#endif + + pico_tree_foreach_safe(index, tree, temp) { + cur = index->keyValue; + if (FRAG_OFF(net, cur->frag) != bookmark) + return 0; + + bookmark += cur->transport_len; + if (!FRAG_MORE(net, cur->frag)) { + pico_fragments_complete(bookmark, proto, net); + return 1; + } + } + return 0; +} + +static void pico_frag_expire(pico_time now, void *arg) +{ + struct pico_tree_node *index, *tmp; + struct pico_frame *f = NULL; + struct pico_tree *tree = (struct pico_tree *) arg; + struct pico_frame *first = NULL; + uint8_t net = 0; + (void)now; + + if (!tree) + { + frag_dbg("Expired packet but no tree supplied!\n"); + return; + } + + first = pico_tree_first(tree); + + if (!first) { + frag_dbg("not first - not sending notify\n"); + return; + } + +#if defined(PICO_SUPPORT_IPV4) && defined(PICO_SUPPORT_IPV4FRAG) + if (IS_IPV4(first)) + { + net = PICO_PROTO_IPV4; + frag_dbg("Packet expired! ID:%hu\n", ipv4_cur_frag_id); + } +#endif +#if defined(PICO_SUPPORT_IPV6) && defined(PICO_SUPPORT_IPV6FRAG) + if (IS_IPV6(first)) + { + net = PICO_PROTO_IPV6; + frag_dbg("Packet expired! ID:%hu\n", ipv6_cur_frag_id); + } +#endif + + /* Empty the tree */ + pico_tree_foreach_safe(index, tree, tmp) { + f = index->keyValue; + pico_tree_delete(tree, f); + if (f != first) + pico_frame_discard(f); /* Later, after ICMP notification...*/ + + } + + if (((FRAG_OFF(net, first->frag) == 0) && (pico_frame_dst_is_unicast(first)))) + { + frag_dbg("sending notify\n"); + pico_notify_frag_expired(first); + } + + if (f) + pico_tree_delete(tree, f); + + pico_frame_discard(first); +} + +void pico_ipv6_process_frag(struct pico_ipv6_exthdr *frag, struct pico_frame *f, uint8_t proto) +{ +#if defined(PICO_SUPPORT_IPV6) && defined(PICO_SUPPORT_IPV6FRAG) + struct pico_frame *first = pico_tree_first(&ipv6_fragments); + + if (!first) { + if (ipv6_cur_frag_id && (IP6_FRAG_ID(frag) == ipv6_cur_frag_id)) { + /* Discard late arrivals, without firing the timer. */ + frag_dbg("discarded late arrival, exp:%hu found:%hu\n", ipv6_cur_frag_id, IP6_FRAG_ID(frag)); + return; + } + + pico_ipv6_frag_timer_on(); + ipv6_cur_frag_id = IP6_FRAG_ID(frag); + frag_dbg("Started new reassembly, ID:%hu\n", ipv6_cur_frag_id); + } + + if (!first || (pico_ipv6_frag_match(f, first) && (IP6_FRAG_ID(frag) == ipv6_cur_frag_id))) { + pico_tree_insert(&ipv6_fragments, pico_frame_copy(f)); + } + + pico_fragments_check_complete(proto, PICO_PROTO_IPV6); +#else + IGNORE_PARAMETER(frag); + IGNORE_PARAMETER(f); + IGNORE_PARAMETER(proto); +#endif +} + +void pico_ipv4_process_frag(struct pico_ipv4_hdr *hdr, struct pico_frame *f, uint8_t proto) +{ +#if defined(PICO_SUPPORT_IPV4) && defined(PICO_SUPPORT_IPV4FRAG) + struct pico_frame *first = pico_tree_first(&ipv4_fragments); + + /* fragments from old packets still in tree, and new first fragment ? */ + if (first && (IP4_FRAG_ID(hdr) != ipv4_cur_frag_id) && (IP4_FRAG_OFF(f->frag) == 0)) + { + /* Empty the tree */ + struct pico_tree_node *index, *tmp; + pico_tree_foreach_safe(index, &ipv4_fragments, tmp) { + struct pico_frame * old = index->keyValue; + pico_tree_delete(&ipv4_fragments, old); + pico_frame_discard(old); + } + first = NULL; + ipv4_cur_frag_id = 0; + } + + f->frag = short_be(hdr->frag); + if (!first) { + if (ipv4_cur_frag_id && (IP4_FRAG_ID(hdr) == ipv4_cur_frag_id)) { + /* Discard late arrivals, without firing the timer */ + return; + } + + pico_ipv4_frag_timer_on(); + ipv4_cur_frag_id = IP4_FRAG_ID(hdr); + } + + if (!first || (pico_ipv4_frag_match(f, first) && (IP4_FRAG_ID(hdr) == ipv4_cur_frag_id))) { + pico_tree_insert(&ipv4_fragments, pico_frame_copy(f)); + } + + pico_fragments_check_complete(proto, PICO_PROTO_IPV4); +#else + IGNORE_PARAMETER(hdr); + IGNORE_PARAMETER(f); + IGNORE_PARAMETER(proto); +#endif +} diff --git a/ext/picotcp/modules/pico_fragments.h b/ext/picotcp/modules/pico_fragments.h new file mode 100644 index 0000000..e51ec44 --- /dev/null +++ b/ext/picotcp/modules/pico_fragments.h @@ -0,0 +1,11 @@ +#ifndef PICO_FRAGMENTS_H +#define PICO_FRAGMENTS_H +#include "pico_ipv4.h" +#include "pico_ipv6.h" +#include "pico_addressing.h" +#include "pico_frame.h" + +void pico_ipv6_process_frag(struct pico_ipv6_exthdr *frag, struct pico_frame *f, uint8_t proto); +void pico_ipv4_process_frag(struct pico_ipv4_hdr *hdr, struct pico_frame *f, uint8_t proto); + +#endif diff --git a/ext/picotcp/modules/pico_hotplug_detection.c b/ext/picotcp/modules/pico_hotplug_detection.c new file mode 100644 index 0000000..732b4d7 --- /dev/null +++ b/ext/picotcp/modules/pico_hotplug_detection.c @@ -0,0 +1,134 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + Authors: Frederik Van Slycken + *********************************************************************/ +#include "pico_protocol.h" +#include "pico_hotplug_detection.h" +#include "pico_tree.h" +#include "pico_device.h" + +struct pico_hotplug_device{ + struct pico_device *dev; + int prev_state; + struct pico_tree callbacks; +}; + +uint32_t timer_id = 0; + +static int pico_hotplug_dev_cmp(void *ka, void *kb) +{ + struct pico_hotplug_device *a = ka, *b = kb; + if (a->dev->hash < b->dev->hash) + return -1; + + if (a->dev->hash > b->dev->hash) + return 1; + + return 0; +} + +static int callback_compare(void *ka, void *kb) +{ + if (ka < kb) + return -1; + if (ka > kb) + return 1; + return 0; +} + +PICO_TREE_DECLARE(Hotplug_device_tree, pico_hotplug_dev_cmp); + +static void timer_cb(__attribute__((unused)) pico_time t, __attribute__((unused)) void* v) +{ + struct pico_tree_node *node = NULL, *safe = NULL, *cb_node = NULL, *cb_safe = NULL; + int new_state, event; + struct pico_hotplug_device *hpdev = NULL; + void (*cb)(struct pico_device *dev, int event); + + //we don't know if one of the callbacks might deregister, so be safe + pico_tree_foreach_safe(node, &Hotplug_device_tree, safe) + { + hpdev = node->keyValue; + new_state = hpdev->dev->link_state(hpdev->dev); + if (new_state != hpdev->prev_state) + { + if (new_state == 1){ + event = PICO_HOTPLUG_EVENT_UP; + } else { + event = PICO_HOTPLUG_EVENT_DOWN; + } + //we don't know if one of the callbacks might deregister, so be safe + pico_tree_foreach_safe(cb_node, &(hpdev->callbacks), cb_safe) + { + cb = cb_node->keyValue; + cb(hpdev->dev, event); + } + hpdev->prev_state = new_state; + } + } + + timer_id = pico_timer_add(PICO_HOTPLUG_INTERVAL, &timer_cb, NULL); +} + + +int pico_hotplug_register(struct pico_device *dev, void (*cb)(struct pico_device *dev, int event)) +{ + struct pico_hotplug_device *hotplug_dev; + struct pico_hotplug_device search = {.dev = dev}; + + if (dev->link_state == NULL){ + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; + } + + hotplug_dev = (struct pico_hotplug_device*)pico_tree_findKey(&Hotplug_device_tree, &search); + if (! hotplug_dev ) + { + hotplug_dev = PICO_ZALLOC(sizeof(struct pico_hotplug_device)); + if (!hotplug_dev) + { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + hotplug_dev->dev = dev; + hotplug_dev->prev_state = dev->link_state(hotplug_dev->dev); + hotplug_dev->callbacks.root = &LEAF; + hotplug_dev->callbacks.compare = &callback_compare; + pico_tree_insert(&Hotplug_device_tree, hotplug_dev); + } + pico_tree_insert(&(hotplug_dev->callbacks), cb); + + if (timer_id == 0) + { + timer_id = pico_timer_add(PICO_HOTPLUG_INTERVAL, &timer_cb, NULL); + } + + return 0; +} + +int pico_hotplug_deregister(struct pico_device *dev, void (*cb)(struct pico_device *dev, int event)) +{ + struct pico_hotplug_device* hotplug_dev; + struct pico_hotplug_device search = {.dev = dev}; + + hotplug_dev = (struct pico_hotplug_device*)pico_tree_findKey(&Hotplug_device_tree, &search); + if (!hotplug_dev) + //wasn't registered + return 0; + pico_tree_delete(&hotplug_dev->callbacks, cb); + if (pico_tree_empty(&hotplug_dev->callbacks)) + { + pico_tree_delete(&Hotplug_device_tree, hotplug_dev); + PICO_FREE(hotplug_dev); + } + + if (pico_tree_empty(&Hotplug_device_tree) && timer_id != 0) + { + pico_timer_cancel(timer_id); + timer_id = 0; + } + return 0; +} + diff --git a/ext/picotcp/modules/pico_hotplug_detection.h b/ext/picotcp/modules/pico_hotplug_detection.h new file mode 100644 index 0000000..19319b4 --- /dev/null +++ b/ext/picotcp/modules/pico_hotplug_detection.h @@ -0,0 +1,20 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + Authors: Frederik Van Slycken + *********************************************************************/ +#ifndef INCLUDE_PICO_SUPPORT_HOTPLUG +#define INCLUDE_PICO_SUPPORT_HOTPLUG +#include "pico_stack.h" + +#define PICO_HOTPLUG_EVENT_UP 1 /* link went up */ +#define PICO_HOTPLUG_EVENT_DOWN 2 /* link went down */ + +#define PICO_HOTPLUG_INTERVAL 100 + +int pico_hotplug_register(struct pico_device *dev, void (*cb)(struct pico_device *dev, int event)); +int pico_hotplug_deregister(struct pico_device *dev, void (*cb)(struct pico_device *dev, int event)); + +#endif /* _INCLUDE_PICO_SUPPORT_HOTPLUG */ + diff --git a/ext/picotcp/modules/pico_icmp4.c b/ext/picotcp/modules/pico_icmp4.c new file mode 100644 index 0000000..da3d89c --- /dev/null +++ b/ext/picotcp/modules/pico_icmp4.c @@ -0,0 +1,395 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + . + + Authors: Daniele Lacamera + *********************************************************************/ + + +#include "pico_icmp4.h" +#include "pico_config.h" +#include "pico_ipv4.h" +#include "pico_eth.h" +#include "pico_device.h" +#include "pico_stack.h" +#include "pico_tree.h" + +/* Queues */ +static struct pico_queue icmp_in = { + 0 +}; +static struct pico_queue icmp_out = { + 0 +}; + + +/* Functions */ + +static int pico_icmp4_checksum(struct pico_frame *f) +{ + struct pico_icmp4_hdr *hdr = (struct pico_icmp4_hdr *) f->transport_hdr; + if (!hdr) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + hdr->crc = 0; + hdr->crc = short_be(pico_checksum(hdr, f->transport_len)); + return 0; +} + +#ifdef PICO_SUPPORT_PING +static void ping_recv_reply(struct pico_frame *f); +#endif + +static int pico_icmp4_process_in(struct pico_protocol *self, struct pico_frame *f) +{ + struct pico_icmp4_hdr *hdr = (struct pico_icmp4_hdr *) f->transport_hdr; + static int firstpkt = 1; + static uint16_t last_id = 0; + static uint16_t last_seq = 0; + IGNORE_PARAMETER(self); + + if (hdr->type == PICO_ICMP_ECHO) { + hdr->type = PICO_ICMP_ECHOREPLY; + /* outgoing frames require a f->len without the ethernet header len */ + if (f->dev && f->dev->eth) + f->len -= PICO_SIZE_ETHHDR; + + if (!firstpkt && (hdr->hun.ih_idseq.idseq_id == last_id) && (last_seq == hdr->hun.ih_idseq.idseq_seq)) { + /* The network duplicated the echo. Do not reply. */ + pico_frame_discard(f); + return 0; + } + + firstpkt = 0; + last_id = hdr->hun.ih_idseq.idseq_id; + last_seq = hdr->hun.ih_idseq.idseq_seq; + pico_icmp4_checksum(f); + pico_ipv4_rebound(f); + } else if (hdr->type == PICO_ICMP_UNREACH) { + f->net_hdr = f->transport_hdr + PICO_ICMPHDR_UN_SIZE; + pico_ipv4_unreachable(f, hdr->code); + } else if (hdr->type == PICO_ICMP_ECHOREPLY) { +#ifdef PICO_SUPPORT_PING + ping_recv_reply(f); +#endif + pico_frame_discard(f); + } else { + pico_frame_discard(f); + } + + return 0; +} + +static int pico_icmp4_process_out(struct pico_protocol *self, struct pico_frame *f) +{ + IGNORE_PARAMETER(self); + IGNORE_PARAMETER(f); + dbg("Called %s\n", __FUNCTION__); + return 0; +} + +/* Interface: protocol definition */ +struct pico_protocol pico_proto_icmp4 = { + .name = "icmp4", + .proto_number = PICO_PROTO_ICMP4, + .layer = PICO_LAYER_TRANSPORT, + .process_in = pico_icmp4_process_in, + .process_out = pico_icmp4_process_out, + .q_in = &icmp_in, + .q_out = &icmp_out, +}; + +static int pico_icmp4_notify(struct pico_frame *f, uint8_t type, uint8_t code) +{ + struct pico_frame *reply; + struct pico_icmp4_hdr *hdr; + struct pico_ipv4_hdr *info; + uint16_t f_tot_len; + + f_tot_len = short_be(((struct pico_ipv4_hdr *)f->net_hdr)->len); + + if (f_tot_len < (sizeof(struct pico_ipv4_hdr))) + return -1; + + /* Truncate tot len to be at most 8 bytes + iphdr */ + if (f_tot_len > (sizeof(struct pico_ipv4_hdr) + 8u)) { + f_tot_len = (sizeof(struct pico_ipv4_hdr) + 8u); + } + + if (f == NULL) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + reply = pico_proto_ipv4.alloc(&pico_proto_ipv4, (uint16_t) (f_tot_len + PICO_ICMPHDR_UN_SIZE)); + info = (struct pico_ipv4_hdr*)(f->net_hdr); + hdr = (struct pico_icmp4_hdr *) reply->transport_hdr; + hdr->type = type; + hdr->code = code; + hdr->hun.ih_pmtu.ipm_nmtu = short_be(1500); + hdr->hun.ih_pmtu.ipm_void = 0; + reply->transport_len = (uint16_t)(f_tot_len + PICO_ICMPHDR_UN_SIZE); + reply->payload = reply->transport_hdr + PICO_ICMPHDR_UN_SIZE; + memcpy(reply->payload, f->net_hdr, f_tot_len); + pico_icmp4_checksum(reply); + pico_ipv4_frame_push(reply, &info->src, PICO_PROTO_ICMP4); + return 0; +} + +int pico_icmp4_port_unreachable(struct pico_frame *f) +{ + /*Parameter check executed in pico_icmp4_notify*/ + return pico_icmp4_notify(f, PICO_ICMP_UNREACH, PICO_ICMP_UNREACH_PORT); +} + +int pico_icmp4_proto_unreachable(struct pico_frame *f) +{ + /*Parameter check executed in pico_icmp4_notify*/ + return pico_icmp4_notify(f, PICO_ICMP_UNREACH, PICO_ICMP_UNREACH_PROTOCOL); +} + +int pico_icmp4_dest_unreachable(struct pico_frame *f) +{ + /*Parameter check executed in pico_icmp4_notify*/ + return pico_icmp4_notify(f, PICO_ICMP_UNREACH, PICO_ICMP_UNREACH_HOST); +} + +int pico_icmp4_ttl_expired(struct pico_frame *f) +{ + /*Parameter check executed in pico_icmp4_notify*/ + return pico_icmp4_notify(f, PICO_ICMP_TIME_EXCEEDED, PICO_ICMP_TIMXCEED_INTRANS); +} + +MOCKABLE int pico_icmp4_frag_expired(struct pico_frame *f) +{ + /*Parameter check executed in pico_icmp4_notify*/ + return pico_icmp4_notify(f, PICO_ICMP_TIME_EXCEEDED, PICO_ICMP_TIMXCEED_REASS); +} + +int pico_icmp4_mtu_exceeded(struct pico_frame *f) +{ + /*Parameter check executed in pico_icmp4_notify*/ + return pico_icmp4_notify(f, PICO_ICMP_UNREACH, PICO_ICMP_UNREACH_NEEDFRAG); +} + +int pico_icmp4_packet_filtered(struct pico_frame *f) +{ + /*Parameter check executed in pico_icmp4_notify*/ + /*Packet Filtered: type 3, code 13 (Communication Administratively Prohibited)*/ + return pico_icmp4_notify(f, PICO_ICMP_UNREACH, PICO_ICMP_UNREACH_FILTER_PROHIB); +} + +int pico_icmp4_param_problem(struct pico_frame *f, uint8_t code) +{ + return pico_icmp4_notify(f, PICO_ICMP_PARAMPROB, code); +} + +/***********************/ +/* Ping implementation */ +/***********************/ +/***********************/ +/***********************/ +/***********************/ + + +#ifdef PICO_SUPPORT_PING + + +struct pico_icmp4_ping_cookie +{ + struct pico_ip4 dst; + uint16_t err; + uint16_t id; + uint16_t seq; + uint16_t size; + int count; + pico_time timestamp; + int interval; + int timeout; + void (*cb)(struct pico_icmp4_stats*); +}; + +static int cookie_compare(void *ka, void *kb) +{ + struct pico_icmp4_ping_cookie *a = ka, *b = kb; + if (a->id < b->id) + return -1; + + if (a->id > b->id) + return 1; + + return (a->seq - b->seq); +} + +PICO_TREE_DECLARE(Pings, cookie_compare); + +static int8_t pico_icmp4_send_echo(struct pico_icmp4_ping_cookie *cookie) +{ + struct pico_frame *echo = pico_proto_ipv4.alloc(&pico_proto_ipv4, (uint16_t)(PICO_ICMPHDR_UN_SIZE + cookie->size)); + struct pico_icmp4_hdr *hdr; + if (!echo) { + return -1; + } + + hdr = (struct pico_icmp4_hdr *) echo->transport_hdr; + + hdr->type = PICO_ICMP_ECHO; + hdr->code = 0; + hdr->hun.ih_idseq.idseq_id = short_be(cookie->id); + hdr->hun.ih_idseq.idseq_seq = short_be(cookie->seq); + echo->transport_len = (uint16_t)(PICO_ICMPHDR_UN_SIZE + cookie->size); + echo->payload = echo->transport_hdr + PICO_ICMPHDR_UN_SIZE; + echo->payload_len = cookie->size; + /* XXX: Fill payload */ + pico_icmp4_checksum(echo); + pico_ipv4_frame_push(echo, &cookie->dst, PICO_PROTO_ICMP4); + return 0; +} + + +static void ping_timeout(pico_time now, void *arg) +{ + struct pico_icmp4_ping_cookie *cookie = (struct pico_icmp4_ping_cookie *)arg; + IGNORE_PARAMETER(now); + + if(pico_tree_findKey(&Pings, cookie)) { + if (cookie->err == PICO_PING_ERR_PENDING) { + struct pico_icmp4_stats stats; + stats.dst = cookie->dst; + stats.seq = cookie->seq; + stats.time = 0; + stats.size = cookie->size; + stats.err = PICO_PING_ERR_TIMEOUT; + dbg(" ---- Ping timeout!!!\n"); + cookie->cb(&stats); + } + + pico_tree_delete(&Pings, cookie); + PICO_FREE(cookie); + } +} + +static void next_ping(pico_time now, void *arg); +static inline void send_ping(struct pico_icmp4_ping_cookie *cookie) +{ + pico_icmp4_send_echo(cookie); + cookie->timestamp = pico_tick; + pico_timer_add((uint32_t)cookie->timeout, ping_timeout, cookie); + if (cookie->seq < (uint16_t)cookie->count) + pico_timer_add((uint32_t)cookie->interval, next_ping, cookie); +} + +static void next_ping(pico_time now, void *arg) +{ + struct pico_icmp4_ping_cookie *newcookie, *cookie = (struct pico_icmp4_ping_cookie *)arg; + IGNORE_PARAMETER(now); + + if(pico_tree_findKey(&Pings, cookie)) { + if (cookie->err == PICO_PING_ERR_ABORTED) + return; + + if (cookie->seq < (uint16_t)cookie->count) { + newcookie = PICO_ZALLOC(sizeof(struct pico_icmp4_ping_cookie)); + if (!newcookie) + return; + + memcpy(newcookie, cookie, sizeof(struct pico_icmp4_ping_cookie)); + newcookie->seq++; + + pico_tree_insert(&Pings, newcookie); + send_ping(newcookie); + } + } +} + + +static void ping_recv_reply(struct pico_frame *f) +{ + struct pico_icmp4_ping_cookie test, *cookie; + struct pico_icmp4_hdr *hdr = (struct pico_icmp4_hdr *) f->transport_hdr; + test.id = short_be(hdr->hun.ih_idseq.idseq_id ); + test.seq = short_be(hdr->hun.ih_idseq.idseq_seq); + + cookie = pico_tree_findKey(&Pings, &test); + if (cookie) { + struct pico_icmp4_stats stats; + if (cookie->err == PICO_PING_ERR_ABORTED) + return; + + cookie->err = PICO_PING_ERR_REPLIED; + stats.dst = ((struct pico_ipv4_hdr *)f->net_hdr)->src; + stats.seq = cookie->seq; + stats.size = cookie->size; + stats.time = pico_tick - cookie->timestamp; + stats.err = cookie->err; + stats.ttl = ((struct pico_ipv4_hdr *)f->net_hdr)->ttl; + if(cookie->cb != NULL) + cookie->cb(&stats); + } else { + dbg("Reply for seq=%d, not found.\n", test.seq); + } +} + +int pico_icmp4_ping(char *dst, int count, int interval, int timeout, int size, void (*cb)(struct pico_icmp4_stats *)) +{ + static uint16_t next_id = 0x91c0; + struct pico_icmp4_ping_cookie *cookie; + + if((dst == NULL) || (interval == 0) || (timeout == 0) || (count == 0)) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + cookie = PICO_ZALLOC(sizeof(struct pico_icmp4_ping_cookie)); + if (!cookie) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + if (pico_string_to_ipv4(dst, (uint32_t *)&cookie->dst.addr) < 0) { + pico_err = PICO_ERR_EINVAL; + PICO_FREE(cookie); + return -1; + } + + cookie->seq = 1; + cookie->id = next_id++; + cookie->err = PICO_PING_ERR_PENDING; + cookie->size = (uint16_t)size; + cookie->interval = interval; + cookie->timeout = timeout; + cookie->cb = cb; + cookie->count = count; + + pico_tree_insert(&Pings, cookie); + send_ping(cookie); + + return cookie->id; + +} + +int pico_icmp4_ping_abort(int id) +{ + struct pico_tree_node *node; + int found = 0; + pico_tree_foreach(node, &Pings) + { + struct pico_icmp4_ping_cookie *ck = + (struct pico_icmp4_ping_cookie *) node->keyValue; + if (ck->id == (uint16_t)id) { + ck->err = PICO_PING_ERR_ABORTED; + found++; + } + } + if (found > 0) + return 0; /* OK if at least one pending ping has been canceled */ + + pico_err = PICO_ERR_ENOENT; + return -1; +} + +#endif diff --git a/ext/picotcp/modules/pico_icmp4.h b/ext/picotcp/modules/pico_icmp4.h new file mode 100644 index 0000000..45f0a62 --- /dev/null +++ b/ext/picotcp/modules/pico_icmp4.h @@ -0,0 +1,162 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + . + + *********************************************************************/ +#ifndef INCLUDE_PICO_ICMP4 +#define INCLUDE_PICO_ICMP4 +#include "pico_defines.h" +#include "pico_addressing.h" +#include "pico_protocol.h" + + +extern struct pico_protocol pico_proto_icmp4; + +PACKED_STRUCT_DEF pico_icmp4_hdr { + uint8_t type; + uint8_t code; + uint16_t crc; + + /* hun */ + PACKED_UNION_DEF hun_u { + uint8_t ih_pptr; + struct pico_ip4 ih_gwaddr; + PEDANTIC_STRUCT_DEF ih_idseq_s { + uint16_t idseq_id; + uint16_t idseq_seq; + } ih_idseq; + uint32_t ih_void; + PEDANTIC_STRUCT_DEF ih_pmtu_s { + uint16_t ipm_void; + uint16_t ipm_nmtu; + } ih_pmtu; + PEDANTIC_STRUCT_DEF ih_rta_s { + uint8_t rta_numgw; + uint8_t rta_wpa; + uint16_t rta_lifetime; + } ih_rta; + } hun; + + /* dun */ + PACKED_UNION_DEF dun_u { + PEDANTIC_STRUCT_DEF id_ts_s { + uint32_t ts_otime; + uint32_t ts_rtime; + uint32_t ts_ttime; + } id_ts; + PEDANTIC_STRUCT_DEF id_ip_s { + uint32_t ip_options; + uint32_t ip_data_hi; + uint32_t ip_data_lo; + } id_ip; + PEDANTIC_STRUCT_DEF id_ra_s { + uint32_t ira_addr; + uint32_t ira_pref; + } id_ra; + uint32_t id_mask; + uint8_t id_data[1]; + } dun; +}; + +#define PICO_ICMPHDR_DRY_SIZE 4 +#define PICO_ICMPHDR_UN_SIZE 8u + +#define PICO_ICMP_ECHOREPLY 0 +#define PICO_ICMP_DEST_UNREACH 3 +#define PICO_ICMP_SOURCE_QUENCH 4 +#define PICO_ICMP_REDIRECT 5 +#define PICO_ICMP_ECHO 8 +#define PICO_ICMP_TIME_EXCEEDED 11 +#define PICO_ICMP_PARAMETERPROB 12 +#define PICO_ICMP_TIMESTAMP 13 +#define PICO_ICMP_TIMESTAMPREPLY 14 +#define PICO_ICMP_INFO_REQUEST 15 +#define PICO_ICMP_INFO_REPLY 16 +#define PICO_ICMP_ADDRESS 17 +#define PICO_ICMP_ADDRESSREPLY 18 + + +#define PICO_ICMP_UNREACH 3 +#define PICO_ICMP_SOURCEQUENCH 4 +#define PICO_ICMP_ROUTERADVERT 9 +#define PICO_ICMP_ROUTERSOLICIT 10 +#define PICO_ICMP_TIMXCEED 11 +#define PICO_ICMP_PARAMPROB 12 +#define PICO_ICMP_TSTAMP 13 +#define PICO_ICMP_TSTAMPREPLY 14 +#define PICO_ICMP_IREQ 15 +#define PICO_ICMP_IREQREPLY 16 +#define PICO_ICMP_MASKREQ 17 +#define PICO_ICMP_MASKREPLY 18 + +#define PICO_ICMP_MAXTYPE 18 + + +#define PICO_ICMP_UNREACH_NET 0 +#define PICO_ICMP_UNREACH_HOST 1 +#define PICO_ICMP_UNREACH_PROTOCOL 2 +#define PICO_ICMP_UNREACH_PORT 3 +#define PICO_ICMP_UNREACH_NEEDFRAG 4 +#define PICO_ICMP_UNREACH_SRCFAIL 5 +#define PICO_ICMP_UNREACH_NET_UNKNOWN 6 +#define PICO_ICMP_UNREACH_HOST_UNKNOWN 7 +#define PICO_ICMP_UNREACH_ISOLATED 8 +#define PICO_ICMP_UNREACH_NET_PROHIB 9 +#define PICO_ICMP_UNREACH_HOST_PROHIB 10 +#define PICO_ICMP_UNREACH_TOSNET 11 +#define PICO_ICMP_UNREACH_TOSHOST 12 +#define PICO_ICMP_UNREACH_FILTER_PROHIB 13 +#define PICO_ICMP_UNREACH_HOST_PRECEDENCE 14 +#define PICO_ICMP_UNREACH_PRECEDENCE_CUTOFF 15 + + +#define PICO_ICMP_REDIRECT_NET 0 +#define PICO_ICMP_REDIRECT_HOST 1 +#define PICO_ICMP_REDIRECT_TOSNET 2 +#define PICO_ICMP_REDIRECT_TOSHOST 3 + + +#define PICO_ICMP_TIMXCEED_INTRANS 0 +#define PICO_ICMP_TIMXCEED_REASS 1 + + +#define PICO_ICMP_PARAMPROB_OPTABSENT 1 + +#define PICO_SIZE_ICMP4HDR ((sizeof(struct pico_icmp4_hdr))) + +struct pico_icmp4_stats +{ + struct pico_ip4 dst; + unsigned long size; + unsigned long seq; + pico_time time; + unsigned long ttl; + int err; +}; + +int pico_icmp4_port_unreachable(struct pico_frame *f); +int pico_icmp4_proto_unreachable(struct pico_frame *f); +int pico_icmp4_dest_unreachable(struct pico_frame *f); +int pico_icmp4_mtu_exceeded(struct pico_frame *f); +int pico_icmp4_ttl_expired(struct pico_frame *f); +int pico_icmp4_frag_expired(struct pico_frame *f); +int pico_icmp4_ping(char *dst, int count, int interval, int timeout, int size, void (*cb)(struct pico_icmp4_stats *)); +int pico_icmp4_ping_abort(int id); + +#ifdef PICO_SUPPORT_ICMP4 +int pico_icmp4_packet_filtered(struct pico_frame *f); +int pico_icmp4_param_problem(struct pico_frame *f, uint8_t code); +#else +# define pico_icmp4_packet_filtered(f) (-1) +# define pico_icmp4_param_problem(f, c) (-1) +#endif /* PICO_SUPPORT_ICMP4 */ + +#define PICO_PING_ERR_REPLIED 0 +#define PICO_PING_ERR_TIMEOUT 1 +#define PICO_PING_ERR_UNREACH 2 +#define PICO_PING_ERR_ABORTED 3 +#define PICO_PING_ERR_PENDING 0xFFFF + +#endif diff --git a/ext/picotcp/modules/pico_icmp6.c b/ext/picotcp/modules/pico_icmp6.c new file mode 100644 index 0000000..3c46e7e --- /dev/null +++ b/ext/picotcp/modules/pico_icmp6.c @@ -0,0 +1,698 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + . + + Authors: Kristof Roelants, Daniele Lacamera + *********************************************************************/ + +#include "pico_config.h" +#include "pico_icmp6.h" +#include "pico_ipv6_nd.h" +#include "pico_eth.h" +#include "pico_device.h" +#include "pico_stack.h" +#include "pico_tree.h" +#include "pico_socket.h" +#include "pico_mld.h" +#define icmp6_dbg(...) do { }while(0); + +static struct pico_queue icmp6_in; +static struct pico_queue icmp6_out; + +uint16_t pico_icmp6_checksum(struct pico_frame *f) +{ + struct pico_ipv6_hdr *ipv6_hdr = (struct pico_ipv6_hdr *)f->net_hdr; + + struct pico_icmp6_hdr *icmp6_hdr = (struct pico_icmp6_hdr *)f->transport_hdr; + struct pico_ipv6_pseudo_hdr pseudo; + + pseudo.src = ipv6_hdr->src; + pseudo.dst = ipv6_hdr->dst; + pseudo.len = long_be(f->transport_len); + pseudo.nxthdr = PICO_PROTO_ICMP6; + + pseudo.zero[0] = 0; + pseudo.zero[1] = 0; + pseudo.zero[2] = 0; + + return pico_dualbuffer_checksum(&pseudo, sizeof(struct pico_ipv6_pseudo_hdr), icmp6_hdr, f->transport_len); +} + +#ifdef PICO_SUPPORT_PING +static void pico_icmp6_ping_recv_reply(struct pico_frame *f); +#endif + +static int pico_icmp6_send_echoreply(struct pico_frame *echo) +{ + struct pico_frame *reply = NULL; + struct pico_icmp6_hdr *ehdr = NULL, *rhdr = NULL; + struct pico_ip6 src; + struct pico_ip6 dst; + + reply = pico_proto_ipv6.alloc(&pico_proto_ipv6, (uint16_t)(echo->transport_len)); + if (!reply) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + echo->payload = echo->transport_hdr + PICO_ICMP6HDR_ECHO_REQUEST_SIZE; + reply->payload = reply->transport_hdr + PICO_ICMP6HDR_ECHO_REQUEST_SIZE; + reply->payload_len = echo->transport_len; + reply->dev = echo->dev; + + ehdr = (struct pico_icmp6_hdr *)echo->transport_hdr; + rhdr = (struct pico_icmp6_hdr *)reply->transport_hdr; + rhdr->type = PICO_ICMP6_ECHO_REPLY; + rhdr->code = 0; + rhdr->msg.info.echo_reply.id = ehdr->msg.info.echo_reply.id; + rhdr->msg.info.echo_reply.seq = ehdr->msg.info.echo_request.seq; + memcpy(reply->payload, echo->payload, (uint32_t)(echo->transport_len - PICO_ICMP6HDR_ECHO_REQUEST_SIZE)); + rhdr->crc = 0; + rhdr->crc = short_be(pico_icmp6_checksum(reply)); + /* Get destination and source swapped */ + memcpy(dst.addr, ((struct pico_ipv6_hdr *)echo->net_hdr)->src.addr, PICO_SIZE_IP6); + memcpy(src.addr, ((struct pico_ipv6_hdr *)echo->net_hdr)->dst.addr, PICO_SIZE_IP6); + pico_ipv6_frame_push(reply, &src, &dst, PICO_PROTO_ICMP6, 0); + return 0; +} + +static int pico_icmp6_process_in(struct pico_protocol *self, struct pico_frame *f) +{ + struct pico_icmp6_hdr *hdr = (struct pico_icmp6_hdr *)f->transport_hdr; + + IGNORE_PARAMETER(self); + + icmp6_dbg("Process IN, type = %d\n", hdr->type); + + switch (hdr->type) + { + case PICO_ICMP6_DEST_UNREACH: + pico_ipv6_unreachable(f, hdr->code); + break; + + case PICO_ICMP6_ECHO_REQUEST: + icmp6_dbg("ICMP6: Received ECHO REQ\n"); + f->transport_len = (uint16_t)(f->len - f->net_len - (uint16_t)(f->net_hdr - f->buffer)); + pico_icmp6_send_echoreply(f); + pico_frame_discard(f); + break; + + case PICO_ICMP6_ECHO_REPLY: +#ifdef PICO_SUPPORT_PING + pico_icmp6_ping_recv_reply(f); +#endif + pico_frame_discard(f); + break; +#ifdef PICO_SUPPORT_MCAST + case PICO_MLD_QUERY: + case PICO_MLD_REPORT: + case PICO_MLD_DONE: + case PICO_MLD_REPORTV2: + pico_mld_process_in(f); + break; +#endif + default: + return pico_ipv6_nd_recv(f); /* CAUTION -- Implies: pico_frame_discard in any case, keep in the default! */ + } + return -1; +} + +static int pico_icmp6_process_out(struct pico_protocol *self, struct pico_frame *f) +{ + IGNORE_PARAMETER(self); + IGNORE_PARAMETER(f); + return 0; +} + +/* Interface: protocol definition */ +struct pico_protocol pico_proto_icmp6 = { + .name = "icmp6", + .proto_number = PICO_PROTO_ICMP6, + .layer = PICO_LAYER_TRANSPORT, + .process_in = pico_icmp6_process_in, + .process_out = pico_icmp6_process_out, + .q_in = &icmp6_in, + .q_out = &icmp6_out, +}; + +static int pico_icmp6_notify(struct pico_frame *f, uint8_t type, uint8_t code, uint32_t ptr) +{ + struct pico_frame *notice = NULL; + struct pico_ipv6_hdr *ipv6_hdr = NULL; + struct pico_icmp6_hdr *icmp6_hdr = NULL; + uint16_t len = 0; + + if (!f) + return -1; + + ipv6_hdr = (struct pico_ipv6_hdr *)(f->net_hdr); + len = (uint16_t)(short_be(ipv6_hdr->len) + PICO_SIZE_IP6HDR); + switch (type) + { + case PICO_ICMP6_DEST_UNREACH: + /* as much of invoking packet as possible without exceeding the minimum IPv6 MTU */ + if (PICO_SIZE_IP6HDR + PICO_ICMP6HDR_DEST_UNREACH_SIZE + len > PICO_IPV6_MIN_MTU) + len = PICO_IPV6_MIN_MTU - (PICO_SIZE_IP6HDR + PICO_ICMP6HDR_DEST_UNREACH_SIZE); + + notice = pico_proto_ipv6.alloc(&pico_proto_ipv6, (uint16_t)(PICO_ICMP6HDR_DEST_UNREACH_SIZE + len)); + if (!notice) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + notice->payload = notice->transport_hdr + PICO_ICMP6HDR_DEST_UNREACH_SIZE; + notice->payload_len = len; + icmp6_hdr = (struct pico_icmp6_hdr *)notice->transport_hdr; + icmp6_hdr->msg.err.dest_unreach.unused = 0; + break; + + case PICO_ICMP6_TIME_EXCEEDED: + /* as much of invoking packet as possible without exceeding the minimum IPv6 MTU */ + if (PICO_SIZE_IP6HDR + PICO_ICMP6HDR_TIME_XCEEDED_SIZE + len > PICO_IPV6_MIN_MTU) + len = PICO_IPV6_MIN_MTU - (PICO_SIZE_IP6HDR + PICO_ICMP6HDR_TIME_XCEEDED_SIZE); + + notice = pico_proto_ipv6.alloc(&pico_proto_ipv6, (uint16_t)(PICO_ICMP6HDR_TIME_XCEEDED_SIZE + len)); + if (!notice) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + notice->payload = notice->transport_hdr + PICO_ICMP6HDR_TIME_XCEEDED_SIZE; + notice->payload_len = len; + icmp6_hdr = (struct pico_icmp6_hdr *)notice->transport_hdr; + icmp6_hdr->msg.err.time_exceeded.unused = 0; + break; + + case PICO_ICMP6_PARAM_PROBLEM: + if (PICO_SIZE_IP6HDR + PICO_ICMP6HDR_PARAM_PROBLEM_SIZE + len > PICO_IPV6_MIN_MTU) + len = PICO_IPV6_MIN_MTU - (PICO_SIZE_IP6HDR + PICO_ICMP6HDR_PARAM_PROBLEM_SIZE); + + notice = pico_proto_ipv6.alloc(&pico_proto_ipv6, (uint16_t)(PICO_ICMP6HDR_PARAM_PROBLEM_SIZE + len)); + if (!notice) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + notice->payload = notice->transport_hdr + PICO_ICMP6HDR_PARAM_PROBLEM_SIZE; + notice->payload_len = len; + icmp6_hdr = (struct pico_icmp6_hdr *)notice->transport_hdr; + icmp6_hdr->msg.err.param_problem.ptr = long_be(ptr); + break; + + default: + return -1; + } + + icmp6_hdr->type = type; + icmp6_hdr->code = code; + memcpy(notice->payload, f->net_hdr, notice->payload_len); + notice->dev = f->dev; + /* f->src is set in frame_push, checksum calculated there */ + pico_ipv6_frame_push(notice, NULL, &ipv6_hdr->src, PICO_PROTO_ICMP6, 0); + return 0; +} + +int pico_icmp6_port_unreachable(struct pico_frame *f) +{ + struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)f->net_hdr; + if (pico_ipv6_is_multicast(hdr->dst.addr)) + return 0; + + return pico_icmp6_notify(f, PICO_ICMP6_DEST_UNREACH, PICO_ICMP6_UNREACH_PORT, 0); +} + +int pico_icmp6_proto_unreachable(struct pico_frame *f) +{ + struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)f->net_hdr; + if (pico_ipv6_is_multicast(hdr->dst.addr)) + return 0; + + return pico_icmp6_notify(f, PICO_ICMP6_DEST_UNREACH, PICO_ICMP6_UNREACH_ADDR, 0); +} + +int pico_icmp6_dest_unreachable(struct pico_frame *f) +{ + struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)f->net_hdr; + if (pico_ipv6_is_multicast(hdr->dst.addr)) + return 0; + + return pico_icmp6_notify(f, PICO_ICMP6_DEST_UNREACH, PICO_ICMP6_UNREACH_ADDR, 0); +} + +int pico_icmp6_ttl_expired(struct pico_frame *f) +{ + struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)f->net_hdr; + if (pico_ipv6_is_multicast(hdr->dst.addr)) + return 0; + + return pico_icmp6_notify(f, PICO_ICMP6_TIME_EXCEEDED, PICO_ICMP6_TIMXCEED_INTRANS, 0); +} + +int pico_icmp6_pkt_too_big(struct pico_frame *f) +{ + struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)f->net_hdr; + if (pico_ipv6_is_multicast(hdr->dst.addr)) + return 0; + + return pico_icmp6_notify(f, PICO_ICMP6_PKT_TOO_BIG, 0, 0); +} + +#ifdef PICO_SUPPORT_IPFILTER +int pico_icmp6_packet_filtered(struct pico_frame *f) +{ + return pico_icmp6_notify(f, PICO_ICMP6_DEST_UNREACH, PICO_ICMP6_UNREACH_ADMIN, 0); +} +#endif + +int pico_icmp6_parameter_problem(struct pico_frame *f, uint8_t problem, uint32_t ptr) +{ + return pico_icmp6_notify(f, PICO_ICMP6_PARAM_PROBLEM, problem, ptr); +} + +MOCKABLE int pico_icmp6_frag_expired(struct pico_frame *f) +{ + struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)f->net_hdr; + if (pico_ipv6_is_multicast(hdr->dst.addr)) + return 0; + + return pico_icmp6_notify(f, PICO_ICMP6_TIME_EXCEEDED, PICO_ICMP6_TIMXCEED_REASS, 0); +} + +/* RFC 4861 $7.2.2: sending neighbor solicitations */ +int pico_icmp6_neighbor_solicitation(struct pico_device *dev, struct pico_ip6 *dst, uint8_t type) +{ + struct pico_frame *sol = NULL; + struct pico_icmp6_hdr *icmp6_hdr = NULL; + struct pico_icmp6_opt_lladdr *opt = NULL; + struct pico_ip6 daddr = {{ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x00, 0x00, 0x00 }}; + uint8_t i = 0; + uint16_t len = 0; + + if (pico_ipv6_is_multicast(dst->addr)) + return -1; + + len = PICO_ICMP6HDR_NEIGH_SOL_SIZE; + if (type != PICO_ICMP6_ND_DAD) + len = (uint16_t)(len + 8); + + sol = pico_proto_ipv6.alloc(&pico_proto_ipv6, len); + if (!sol) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + sol->payload = sol->transport_hdr + len; + sol->payload_len = 0; + + icmp6_hdr = (struct pico_icmp6_hdr *)sol->transport_hdr; + icmp6_hdr->type = PICO_ICMP6_NEIGH_SOL; + icmp6_hdr->code = 0; + icmp6_hdr->msg.info.neigh_sol.unused = 0; + icmp6_hdr->msg.info.neigh_sol.target = *dst; + + if (type != PICO_ICMP6_ND_DAD) { + opt = (struct pico_icmp6_opt_lladdr *)(((uint8_t *)&icmp6_hdr->msg.info.neigh_sol) + sizeof(struct neigh_sol_s)); + opt->type = PICO_ND_OPT_LLADDR_SRC; + opt->len = 1; + memcpy(opt->addr.mac.addr, dev->eth->mac.addr, PICO_SIZE_ETH); + } + + if (type == PICO_ICMP6_ND_SOLICITED || type == PICO_ICMP6_ND_DAD) { + for (i = 1; i <= 3; ++i) { + daddr.addr[PICO_SIZE_IP6 - i] = dst->addr[PICO_SIZE_IP6 - i]; + } + } else { + daddr = *dst; + } + + sol->dev = dev; + + /* f->src is set in frame_push, checksum calculated there */ + pico_ipv6_frame_push(sol, NULL, &daddr, PICO_PROTO_ICMP6, (type == PICO_ICMP6_ND_DAD)); + return 0; +} + +/* RFC 4861 $7.2.4: sending solicited neighbor advertisements */ +int pico_icmp6_neighbor_advertisement(struct pico_frame *f, struct pico_ip6 *target) +{ + struct pico_frame *adv = NULL; + struct pico_ipv6_hdr *ipv6_hdr = NULL; + struct pico_icmp6_hdr *icmp6_hdr = NULL; + struct pico_icmp6_opt_lladdr *opt = NULL; + struct pico_ip6 dst = {{0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}}; + + ipv6_hdr = (struct pico_ipv6_hdr *)f->net_hdr; + adv = pico_proto_ipv6.alloc(&pico_proto_ipv6, PICO_ICMP6HDR_NEIGH_ADV_SIZE + 8); + if (!adv) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + adv->payload = adv->transport_hdr + PICO_ICMP6HDR_NEIGH_ADV_SIZE + 8; + adv->payload_len = 0; + + icmp6_hdr = (struct pico_icmp6_hdr *)adv->transport_hdr; + icmp6_hdr->type = PICO_ICMP6_NEIGH_ADV; + icmp6_hdr->code = 0; + icmp6_hdr->msg.info.neigh_adv.target = *target; + icmp6_hdr->msg.info.neigh_adv.rsor = long_be(0x60000000); /* !router && solicited && override */ + if (pico_ipv6_is_unspecified(ipv6_hdr->src.addr)) { + /* solicited = clear && dst = all-nodes address (scope link-local) */ + icmp6_hdr->msg.info.neigh_adv.rsor ^= long_be(0x40000000); + } else { + /* solicited = set && dst = source of solicitation */ + dst = ipv6_hdr->src; + } + + /* XXX if the target address is either an anycast address or a unicast + * address for which the node is providing proxy service, or the target + * link-layer Address option is not included, the Override flag SHOULD + * be set to zero. + */ + + /* XXX if the target address is an anycast address, the sender SHOULD delay + * sending a response for a random time between 0 and MAX_ANYCAST_DELAY_TIME seconds. + */ + + opt = (struct pico_icmp6_opt_lladdr *)(((uint8_t *)&icmp6_hdr->msg.info.neigh_adv) + sizeof(struct neigh_adv_s)); + opt->type = PICO_ND_OPT_LLADDR_TGT; + opt->len = 1; + memcpy(opt->addr.mac.addr, f->dev->eth->mac.addr, PICO_SIZE_ETH); + adv->dev = f->dev; + + /* f->src is set in frame_push, checksum calculated there */ + pico_ipv6_frame_push(adv, NULL, &dst, PICO_PROTO_ICMP6, 0); + return 0; +} + +/* RFC 4861 $6.3.7: sending router solicitations */ +int pico_icmp6_router_solicitation(struct pico_device *dev, struct pico_ip6 *src) +{ + struct pico_frame *sol = NULL; + struct pico_icmp6_hdr *icmp6_hdr = NULL; + struct pico_icmp6_opt_lladdr *lladdr = NULL; + uint16_t len = 0; + struct pico_ip6 daddr = {{ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 }}; + + len = PICO_ICMP6HDR_ROUTER_SOL_SIZE; + if (!pico_ipv6_is_unspecified(src->addr)) + len = (uint16_t)(len + 8); + + sol = pico_proto_ipv6.alloc(&pico_proto_ipv6, len); + if (!sol) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + sol->payload = sol->transport_hdr + len; + sol->payload_len = 0; + + icmp6_hdr = (struct pico_icmp6_hdr *)sol->transport_hdr; + icmp6_hdr->type = PICO_ICMP6_ROUTER_SOL; + icmp6_hdr->code = 0; + + if (!pico_ipv6_is_unspecified(src->addr)) { + lladdr = (struct pico_icmp6_opt_lladdr *)(((uint8_t *)&icmp6_hdr->msg.info.router_sol) + sizeof(struct router_sol_s)); + lladdr->type = PICO_ND_OPT_LLADDR_SRC; + lladdr->len = 1; + memcpy(lladdr->addr.mac.addr, dev->eth->mac.addr, PICO_SIZE_ETH); + } + + sol->dev = dev; + + /* f->src is set in frame_push, checksum calculated there */ + pico_ipv6_frame_push(sol, NULL, &daddr, PICO_PROTO_ICMP6, 0); + return 0; +} + +#define PICO_RADV_VAL_LIFETIME (long_be(86400)) +#define PICO_RADV_PREF_LIFETIME (long_be(14400)) + +/* RFC 4861: sending router advertisements */ +int pico_icmp6_router_advertisement(struct pico_device *dev, struct pico_ip6 *dst) +{ + struct pico_frame *adv = NULL; + struct pico_icmp6_hdr *icmp6_hdr = NULL; + struct pico_icmp6_opt_lladdr *lladdr; + struct pico_icmp6_opt_prefix *prefix; + uint16_t len = 0; + struct pico_ip6 dst_mcast = {{ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }}; + uint8_t *nxt_opt; + + len = PICO_ICMP6HDR_ROUTER_ADV_SIZE + PICO_ICMP6_OPT_LLADDR_SIZE + sizeof(struct pico_icmp6_opt_prefix); + + adv = pico_proto_ipv6.alloc(&pico_proto_ipv6, len); + if (!adv) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + adv->payload = adv->transport_hdr + len; + adv->payload_len = 0; + adv->dev = dev; + + icmp6_hdr = (struct pico_icmp6_hdr *)adv->transport_hdr; + icmp6_hdr->type = PICO_ICMP6_ROUTER_ADV; + icmp6_hdr->code = 0; + icmp6_hdr->msg.info.router_adv.life_time = short_be(45); + icmp6_hdr->msg.info.router_adv.hop = 64; + nxt_opt = (uint8_t *)&icmp6_hdr->msg.info.router_adv + sizeof(struct router_adv_s); + + prefix = (struct pico_icmp6_opt_prefix *)nxt_opt; + prefix->type = PICO_ND_OPT_PREFIX; + prefix->len = sizeof(struct pico_icmp6_opt_prefix) >> 3; + prefix->prefix_len = 64; /* Only /64 are forwarded */ + prefix->aac = 1; + prefix->onlink = 1; + prefix->val_lifetime = PICO_RADV_VAL_LIFETIME; + prefix->pref_lifetime = PICO_RADV_PREF_LIFETIME; + memcpy(&prefix->prefix, dst, sizeof(struct pico_ip6)); + + nxt_opt += (sizeof (struct pico_icmp6_opt_prefix)); + lladdr = (struct pico_icmp6_opt_lladdr *)nxt_opt; + lladdr->type = PICO_ND_OPT_LLADDR_SRC; + lladdr->len = 1; + memcpy(lladdr->addr.mac.addr, dev->eth->mac.addr, PICO_SIZE_ETH); + icmp6_hdr->crc = 0; + icmp6_hdr->crc = short_be(pico_icmp6_checksum(adv)); + /* f->src is set in frame_push, checksum calculated there */ + pico_ipv6_frame_push(adv, NULL, &dst_mcast, PICO_PROTO_ICMP6, 0); + return 0; +} + +/***********************/ +/* Ping implementation */ +/***********************/ + +#ifdef PICO_SUPPORT_PING +struct pico_icmp6_ping_cookie +{ + uint16_t id; + uint16_t seq; + uint16_t size; + uint16_t err; + int count; + int interval; + int timeout; + pico_time timestamp; + struct pico_ip6 dst; + struct pico_device *dev; + void (*cb)(struct pico_icmp6_stats*); +}; + +static int icmp6_cookie_compare(void *ka, void *kb) +{ + struct pico_icmp6_ping_cookie *a = ka, *b = kb; + if (a->id < b->id) + return -1; + + if (a->id > b->id) + return 1; + + return (a->seq - b->seq); +} +PICO_TREE_DECLARE(IPV6Pings, icmp6_cookie_compare); + +static int pico_icmp6_send_echo(struct pico_icmp6_ping_cookie *cookie) +{ + struct pico_frame *echo = NULL; + struct pico_icmp6_hdr *hdr = NULL; + + echo = pico_proto_ipv6.alloc(&pico_proto_ipv6, (uint16_t)(PICO_ICMP6HDR_ECHO_REQUEST_SIZE + cookie->size)); + if (!echo) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + echo->payload = echo->transport_hdr + PICO_ICMP6HDR_ECHO_REQUEST_SIZE; + echo->payload_len = cookie->size; + + hdr = (struct pico_icmp6_hdr *)echo->transport_hdr; + hdr->type = PICO_ICMP6_ECHO_REQUEST; + hdr->code = 0; + hdr->msg.info.echo_request.id = short_be(cookie->id); + hdr->msg.info.echo_request.seq = short_be(cookie->seq); + /* XXX: Fill payload */ + hdr->crc = 0; + hdr->crc = short_be(pico_icmp6_checksum(echo)); + echo->dev = cookie->dev; + pico_ipv6_frame_push(echo, NULL, &cookie->dst, PICO_PROTO_ICMP6, 0); + return 0; +} + + +static void pico_icmp6_ping_timeout(pico_time now, void *arg) +{ + struct pico_icmp6_ping_cookie *cookie = NULL; + + IGNORE_PARAMETER(now); + + cookie = (struct pico_icmp6_ping_cookie *)arg; + if (pico_tree_findKey(&IPV6Pings, cookie)) { + if (cookie->err == PICO_PING6_ERR_PENDING) { + struct pico_icmp6_stats stats = { + 0 + }; + stats.dst = cookie->dst; + stats.seq = cookie->seq; + stats.time = 0; + stats.size = cookie->size; + stats.err = PICO_PING6_ERR_TIMEOUT; + dbg(" ---- Ping6 timeout!!!\n"); + if (cookie->cb) + cookie->cb(&stats); + } + + pico_tree_delete(&IPV6Pings, cookie); + PICO_FREE(cookie); + } +} + +static void pico_icmp6_next_ping(pico_time now, void *arg); +static inline void pico_icmp6_send_ping(struct pico_icmp6_ping_cookie *cookie) +{ + pico_icmp6_send_echo(cookie); + cookie->timestamp = pico_tick; + pico_timer_add((pico_time)(cookie->interval), pico_icmp6_next_ping, cookie); + pico_timer_add((pico_time)(cookie->timeout), pico_icmp6_ping_timeout, cookie); +} + +static void pico_icmp6_next_ping(pico_time now, void *arg) +{ + struct pico_icmp6_ping_cookie *cookie = NULL, *new = NULL; + + IGNORE_PARAMETER(now); + + cookie = (struct pico_icmp6_ping_cookie *)arg; + if (pico_tree_findKey(&IPV6Pings, cookie)) { + if (cookie->err == PICO_PING6_ERR_ABORTED) + return; + + if (cookie->seq < (uint16_t)cookie->count) { + new = PICO_ZALLOC(sizeof(struct pico_icmp6_ping_cookie)); + if (!new) { + pico_err = PICO_ERR_ENOMEM; + return; + } + + memcpy(new, cookie, sizeof(struct pico_icmp6_ping_cookie)); + new->seq++; + + pico_tree_insert(&IPV6Pings, new); + pico_icmp6_send_ping(new); + } + } +} + +static void pico_icmp6_ping_recv_reply(struct pico_frame *f) +{ + struct pico_icmp6_ping_cookie *cookie = NULL, test = { + 0 + }; + struct pico_icmp6_hdr *hdr = NULL; + + hdr = (struct pico_icmp6_hdr *)f->transport_hdr; + test.id = short_be(hdr->msg.info.echo_reply.id); + test.seq = short_be(hdr->msg.info.echo_reply.seq); + cookie = pico_tree_findKey(&IPV6Pings, &test); + if (cookie) { + struct pico_icmp6_stats stats = { + 0 + }; + if (cookie->err == PICO_PING6_ERR_ABORTED) + return; + + cookie->err = PICO_PING6_ERR_REPLIED; + stats.dst = cookie->dst; + stats.seq = cookie->seq; + stats.size = cookie->size; + stats.time = pico_tick - cookie->timestamp; + stats.err = cookie->err; + stats.ttl = ((struct pico_ipv6_hdr *)f->net_hdr)->hop; + if(cookie->cb) + cookie->cb(&stats); + } else { + dbg("Reply for seq=%d, not found.\n", test.seq); + } +} + +int pico_icmp6_ping(char *dst, int count, int interval, int timeout, int size, void (*cb)(struct pico_icmp6_stats *), struct pico_device *dev) +{ + static uint16_t next_id = 0x91c0; + struct pico_icmp6_ping_cookie *cookie = NULL; + + if(!dst || !count || !interval || !timeout) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + cookie = PICO_ZALLOC(sizeof(struct pico_icmp6_ping_cookie)); + if (!cookie) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + if (pico_string_to_ipv6(dst, cookie->dst.addr) < 0) { + pico_err = PICO_ERR_EINVAL; + PICO_FREE(cookie); + return -1; + } + + cookie->seq = 1; + cookie->id = next_id++; + cookie->err = PICO_PING6_ERR_PENDING; + cookie->size = (uint16_t)size; + cookie->interval = interval; + cookie->timeout = timeout; + cookie->cb = cb; + cookie->count = count; + cookie->dev = dev; + + pico_tree_insert(&IPV6Pings, cookie); + pico_icmp6_send_ping(cookie); + return (int)cookie->id; +} + +int pico_icmp6_ping_abort(int id) +{ + struct pico_tree_node *node; + int found = 0; + pico_tree_foreach(node, &IPV6Pings) + { + struct pico_icmp6_ping_cookie *ck = + (struct pico_icmp6_ping_cookie *) node->keyValue; + if (ck->id == (uint16_t)id) { + ck->err = PICO_PING6_ERR_ABORTED; + found++; + } + } + if (found > 0) + return 0; /* OK if at least one pending ping has been canceled */ + + pico_err = PICO_ERR_ENOENT; + return -1; +} + +#endif diff --git a/ext/picotcp/modules/pico_icmp6.h b/ext/picotcp/modules/pico_icmp6.h new file mode 100644 index 0000000..7734060 --- /dev/null +++ b/ext/picotcp/modules/pico_icmp6.h @@ -0,0 +1,256 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + . + + *********************************************************************/ +#ifndef _INCLUDE_PICO_ICMP6 +#define _INCLUDE_PICO_ICMP6 +#include "pico_addressing.h" +#include "pico_protocol.h" +#include "pico_mld.h" +/* ICMP header sizes */ +#define PICO_ICMP6HDR_DRY_SIZE 4 +#define PICO_ICMP6HDR_ECHO_REQUEST_SIZE 8 +#define PICO_ICMP6HDR_DEST_UNREACH_SIZE 8 +#define PICO_ICMP6HDR_TIME_XCEEDED_SIZE 8 +#define PICO_ICMP6HDR_PARAM_PROBLEM_SIZE 8 +#define PICO_ICMP6HDR_NEIGH_SOL_SIZE 24 +#define PICO_ICMP6HDR_NEIGH_ADV_SIZE 24 +#define PICO_ICMP6HDR_ROUTER_SOL_SIZE 8 +#define PICO_ICMP6HDR_ROUTER_ADV_SIZE 16 +#define PICO_ICMP6HDR_REDIRECT_SIZE 40 + +/* ICMP types */ +#define PICO_ICMP6_DEST_UNREACH 1 +#define PICO_ICMP6_PKT_TOO_BIG 2 +#define PICO_ICMP6_TIME_EXCEEDED 3 +#define PICO_ICMP6_PARAM_PROBLEM 4 +#define PICO_ICMP6_ECHO_REQUEST 128 +#define PICO_ICMP6_ECHO_REPLY 129 +#define PICO_ICMP6_ROUTER_SOL 133 +#define PICO_ICMP6_ROUTER_ADV 134 +#define PICO_ICMP6_NEIGH_SOL 135 +#define PICO_ICMP6_NEIGH_ADV 136 +#define PICO_ICMP6_REDIRECT 137 + +/* destination unreachable codes */ +#define PICO_ICMP6_UNREACH_NOROUTE 0 +#define PICO_ICMP6_UNREACH_ADMIN 1 +#define PICO_ICMP6_UNREACH_SRCSCOPE 2 +#define PICO_ICMP6_UNREACH_ADDR 3 +#define PICO_ICMP6_UNREACH_PORT 4 +#define PICO_ICMP6_UNREACH_SRCFILTER 5 +#define PICO_ICMP6_UNREACH_REJROUTE 6 + +/* time exceeded codes */ +#define PICO_ICMP6_TIMXCEED_INTRANS 0 +#define PICO_ICMP6_TIMXCEED_REASS 1 + +/* parameter problem codes */ +#define PICO_ICMP6_PARAMPROB_HDRFIELD 0 +#define PICO_ICMP6_PARAMPROB_NXTHDR 1 +#define PICO_ICMP6_PARAMPROB_IPV6OPT 2 + +/* ping error codes */ +#define PICO_PING6_ERR_REPLIED 0 +#define PICO_PING6_ERR_TIMEOUT 1 +#define PICO_PING6_ERR_UNREACH 2 +#define PICO_PING6_ERR_ABORTED 3 +#define PICO_PING6_ERR_PENDING 0xFFFF + +/* ND configuration */ +#define PICO_ND_MAX_FRAMES_QUEUED 4 /* max frames queued while awaiting address resolution */ + +/* ND RFC constants */ +#define PICO_ND_MAX_SOLICIT 3 +#define PICO_ND_MAX_NEIGHBOR_ADVERT 3 +#define PICO_ND_DELAY_INCOMPLETE 1000 /* msec */ +#define PICO_ND_DELAY_FIRST_PROBE_TIME 5000 /* msec */ + +/* neighbor discovery options */ +#define PICO_ND_OPT_LLADDR_SRC 1 +#define PICO_ND_OPT_LLADDR_TGT 2 +#define PICO_ND_OPT_PREFIX 3 +#define PICO_ND_OPT_REDIRECT 4 +#define PICO_ND_OPT_MTU 5 +#define PICO_ND_OPT_RDNSS 25 /* RFC 5006 */ + +/* ND advertisement flags */ +#define PICO_ND_ROUTER 0x80000000 +#define PICO_ND_SOLICITED 0x40000000 +#define PICO_ND_OVERRIDE 0x20000000 +#define IS_ROUTER(x) (long_be(x->msg.info.neigh_adv.rsor) & (PICO_ND_ROUTER)) /* router flag set? */ +#define IS_SOLICITED(x) (long_be(x->msg.info.neigh_adv.rsor) & (PICO_ND_SOLICITED)) /* solicited flag set? */ +#define IS_OVERRIDE(x) (long_be(x->msg.info.neigh_adv.rsor) & (PICO_ND_OVERRIDE)) /* override flag set? */ + +#define PICO_ND_PREFIX_LIFETIME_INF 0xFFFFFFFFu +/* #define PICO_ND_DESTINATION_LRU_TIME 600000u / * msecs (10min) * / */ + +/* custom defines */ +#define PICO_ICMP6_ND_UNICAST 0 +#define PICO_ICMP6_ND_ANYCAST 1 +#define PICO_ICMP6_ND_SOLICITED 2 +#define PICO_ICMP6_ND_DAD 3 + +#define PICO_ICMP6_MAX_RTR_SOL_DELAY 1000 + +#define PICO_SIZE_ICMP6HDR ((sizeof(struct pico_icmp6_hdr))) +#define PICO_ICMP6_OPT_LLADDR_SIZE (8) + +extern struct pico_protocol pico_proto_icmp6; + +PACKED_STRUCT_DEF pico_icmp6_hdr { + uint8_t type; + uint8_t code; + uint16_t crc; + + PACKED_UNION_DEF icmp6_msg_u { + /* error messages */ + PACKED_UNION_DEF icmp6_err_u { + PEDANTIC_STRUCT_DEF dest_unreach_s { + uint32_t unused; + } dest_unreach; + PEDANTIC_STRUCT_DEF pkt_too_big_s { + uint32_t mtu; + } pkt_too_big; + PEDANTIC_STRUCT_DEF time_exceeded_s { + uint32_t unused; + } time_exceeded; + PEDANTIC_STRUCT_DEF param_problem_s { + uint32_t ptr; + } param_problem; + } err; + + /* informational messages */ + PACKED_UNION_DEF icmp6_info_u { + PEDANTIC_STRUCT_DEF echo_request_s { + uint16_t id; + uint16_t seq; + } echo_request; + PEDANTIC_STRUCT_DEF echo_reply_s { + uint16_t id; + uint16_t seq; + } echo_reply; + PEDANTIC_STRUCT_DEF router_sol_s { + uint32_t unused; + } router_sol; + PEDANTIC_STRUCT_DEF router_adv_s { + uint8_t hop; + uint8_t mor; + uint16_t life_time; + uint32_t reachable_time; + uint32_t retrans_time; + } router_adv; + PEDANTIC_STRUCT_DEF neigh_sol_s { + uint32_t unused; + struct pico_ip6 target; + } neigh_sol; + PEDANTIC_STRUCT_DEF neigh_adv_s { + uint32_t rsor; + struct pico_ip6 target; + } neigh_adv; + PEDANTIC_STRUCT_DEF redirect_s { + uint32_t reserved; + struct pico_ip6 target; + struct pico_ip6 dest; + } redirect; + PEDANTIC_STRUCT_DEF mld_s { + uint16_t max_resp_time; + uint16_t reserved; + struct pico_ip6 mmcast_group; + /*MLDv2*/ + uint8_t reserverd; // With S and QRV + uint8_t QQIC; + uint16_t nbr_src; + struct pico_ip6 src[0]; + } mld; + } info; + } msg; +}; + +PACKED_STRUCT_DEF pico_icmp6_opt_lladdr +{ + uint8_t type; + uint8_t len; + PACKED_UNION_DEF icmp6_opt_hw_addr_u { + struct pico_eth mac; + } addr; +}; + +PACKED_STRUCT_DEF pico_icmp6_opt_prefix +{ + uint8_t type; + uint8_t len; + uint8_t prefix_len; + uint8_t res : 6; + uint8_t aac : 1; + uint8_t onlink : 1; + uint32_t val_lifetime; + uint32_t pref_lifetime; + uint32_t reserved; + struct pico_ip6 prefix; +}; + +PACKED_STRUCT_DEF pico_icmp6_opt_mtu +{ + uint8_t type; + uint8_t len; + uint16_t res; + uint32_t mtu; +}; + +PACKED_STRUCT_DEF pico_icmp6_opt_redirect +{ + uint8_t type; + uint8_t len; + uint16_t res0; + uint32_t res1; +}; + +PACKED_STRUCT_DEF pico_icmp6_opt_rdnss +{ + uint8_t type; + uint8_t len; + uint16_t res0; + uint32_t lifetime; + struct pico_ip6 *addr; +}; + +PACKED_STRUCT_DEF pico_icmp6_opt_na +{ + uint8_t type; + uint8_t len; +}; + +struct pico_icmp6_stats +{ + unsigned long size; + unsigned long seq; + pico_time time; + unsigned long ttl; + int err; + struct pico_ip6 dst; +}; + +int pico_icmp6_ping(char *dst, int count, int interval, int timeout, int size, void (*cb)(struct pico_icmp6_stats *), struct pico_device *dev); +int pico_icmp6_ping_abort(int id); + +int pico_icmp6_neighbor_solicitation(struct pico_device *dev, struct pico_ip6 *dst, uint8_t type); +int pico_icmp6_neighbor_advertisement(struct pico_frame *f, struct pico_ip6 *target); +int pico_icmp6_router_solicitation(struct pico_device *dev, struct pico_ip6 *src); + +int pico_icmp6_port_unreachable(struct pico_frame *f); +int pico_icmp6_proto_unreachable(struct pico_frame *f); +int pico_icmp6_dest_unreachable(struct pico_frame *f); +int pico_icmp6_ttl_expired(struct pico_frame *f); +int pico_icmp6_packet_filtered(struct pico_frame *f); +int pico_icmp6_parameter_problem(struct pico_frame *f, uint8_t problem, uint32_t ptr); +int pico_icmp6_pkt_too_big(struct pico_frame *f); +int pico_icmp6_frag_expired(struct pico_frame *f); + +uint16_t pico_icmp6_checksum(struct pico_frame *f); +int pico_icmp6_router_advertisement(struct pico_device *dev, struct pico_ip6 *dst); + +#endif diff --git a/ext/picotcp/modules/pico_igmp.c b/ext/picotcp/modules/pico_igmp.c new file mode 100644 index 0000000..2d2fc08 --- /dev/null +++ b/ext/picotcp/modules/pico_igmp.c @@ -0,0 +1,1276 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + RFC 1112, 2236, 3376, 3569, 3678, 4607 + + Authors: Kristof Roelants (IGMPv3), Simon Maes, Brecht Van Cauwenberghe + *********************************************************************/ + +#include "pico_stack.h" +#include "pico_ipv4.h" +#include "pico_igmp.h" +#include "pico_config.h" +#include "pico_eth.h" +#include "pico_addressing.h" +#include "pico_frame.h" +#include "pico_tree.h" +#include "pico_device.h" +#include "pico_socket.h" + +#if defined(PICO_SUPPORT_IGMP) && defined(PICO_SUPPORT_MCAST) + +#define igmp_dbg(...) do {} while(0) +/* #define igmp_dbg dbg */ + +/* membership states */ +#define IGMP_STATE_NON_MEMBER (0x0) +#define IGMP_STATE_DELAYING_MEMBER (0x1) +#define IGMP_STATE_IDLE_MEMBER (0x2) + +/* events */ +#define IGMP_EVENT_DELETE_GROUP (0x0) +#define IGMP_EVENT_CREATE_GROUP (0x1) +#define IGMP_EVENT_UPDATE_GROUP (0x2) +#define IGMP_EVENT_QUERY_RECV (0x3) +#define IGMP_EVENT_REPORT_RECV (0x4) +#define IGMP_EVENT_TIMER_EXPIRED (0x5) + +/* message types */ +#define IGMP_TYPE_MEM_QUERY (0x11) +#define IGMP_TYPE_MEM_REPORT_V1 (0x12) +#define IGMP_TYPE_MEM_REPORT_V2 (0x16) +#define IGMP_TYPE_LEAVE_GROUP (0x17) +#define IGMP_TYPE_MEM_REPORT_V3 (0x22) + +/* group record types */ +#define IGMP_MODE_IS_INCLUDE (1) +#define IGMP_MODE_IS_EXCLUDE (2) +#define IGMP_CHANGE_TO_INCLUDE_MODE (3) +#define IGMP_CHANGE_TO_EXCLUDE_MODE (4) +#define IGMP_ALLOW_NEW_SOURCES (5) +#define IGMP_BLOCK_OLD_SOURCES (6) + +/* host flag */ +#define IGMP_HOST_LAST (0x1) +#define IGMP_HOST_NOT_LAST (0x0) + +/* list of timers, counters and their default values */ +#define IGMP_ROBUSTNESS (2u) +#define IGMP_QUERY_INTERVAL (125) /* secs */ +#define IGMP_QUERY_RESPONSE_INTERVAL (10u) /* secs */ +#define IGMP_STARTUP_QUERY_INTERVAL (IGMPV3_QUERY_INTERVAL / 4) +#define IGMP_STARTUP_QUERY_COUNT (IGMPV3_ROBUSTNESS) +#define IGMP_LAST_MEMBER_QUERY_INTERVAL (1) /* secs */ +#define IGMP_LAST_MEMBER_QUERY_COUNT (IGMPV3_ROBUSTNESS) +#define IGMP_UNSOLICITED_REPORT_INTERVAL (1) /* secs */ +#define IGMP_DEFAULT_MAX_RESPONSE_TIME (100) + +/* custom timers types */ +#define IGMP_TIMER_GROUP_REPORT (1) +#define IGMP_TIMER_V1_QUERIER (2) +#define IGMP_TIMER_V2_QUERIER (3) + +/* IGMP groups */ +#define IGMP_ALL_HOST_GROUP long_be(0xE0000001) /* 224.0.0.1 */ +#define IGMP_ALL_ROUTER_GROUP long_be(0xE0000002) /* 224.0.0.2 */ +#define IGMPV3_ALL_ROUTER_GROUP long_be(0xE0000016) /* 224.0.0.22 */ + +/* misc */ +#define IGMP_TIMER_STOPPED (1) +#define IP_OPTION_ROUTER_ALERT_LEN (4u) +#define IGMP_MAX_GROUPS (32) /* max 255 */ + +PACKED_STRUCT_DEF igmp_message { + uint8_t type; + uint8_t max_resp_time; + uint16_t crc; + uint32_t mcast_group; +}; + +PACKED_STRUCT_DEF igmpv3_query { + uint8_t type; + uint8_t max_resp_time; + uint16_t crc; + uint32_t mcast_group; + uint8_t rsq; + uint8_t qqic; + uint16_t sources; +}; + +PACKED_STRUCT_DEF igmpv3_group_record { + uint8_t type; + uint8_t aux; + uint16_t sources; + uint32_t mcast_group; +}; + +PACKED_STRUCT_DEF igmpv3_report { + uint8_t type; + uint8_t res0; + uint16_t crc; + uint16_t res1; + uint16_t groups; +}; + +struct igmp_parameters { + uint8_t event; + uint8_t state; + uint8_t last_host; + uint8_t filter_mode; + uint8_t max_resp_time; + struct pico_ip4 mcast_link; + struct pico_ip4 mcast_group; + struct pico_tree *MCASTFilter; + struct pico_frame *f; +}; + +struct igmp_timer { + uint8_t type; + uint8_t stopped; + pico_time start; + pico_time delay; + struct pico_ip4 mcast_link; + struct pico_ip4 mcast_group; + struct pico_frame *f; + void (*callback)(struct igmp_timer *t); +}; + +/* queues */ +static struct pico_queue igmp_in = { + 0 +}; +static struct pico_queue igmp_out = { + 0 +}; + +/* finite state machine caller */ +static int pico_igmp_process_event(struct igmp_parameters *p); + +/* state callback prototype */ +typedef int (*callback)(struct igmp_parameters *); + +static inline int igmpt_type_compare(struct igmp_timer *a, struct igmp_timer *b) +{ + if (a->type < b->type) + return -1; + + if (a->type > b->type) + return 1; + + return 0; +} + + +static inline int igmpt_group_compare(struct igmp_timer *a, struct igmp_timer *b) +{ + return pico_ipv4_compare(&a->mcast_group, &b->mcast_group); +} + +static inline int igmpt_link_compare(struct igmp_timer *a, struct igmp_timer *b) +{ + return pico_ipv4_compare(&a->mcast_link, &b->mcast_link); +} + +/* redblack trees */ +static int igmp_timer_cmp(void *ka, void *kb) +{ + struct igmp_timer *a = ka, *b = kb; + int cmp = igmpt_type_compare(a, b); + if (cmp) + return cmp; + + cmp = igmpt_group_compare(a, b); + if (cmp) + return cmp; + + return igmpt_link_compare(a, b); + +} +PICO_TREE_DECLARE(IGMPTimers, igmp_timer_cmp); + +static inline int igmpparm_group_compare(struct igmp_parameters *a, struct igmp_parameters *b) +{ + return pico_ipv4_compare(&a->mcast_group, &b->mcast_group); +} + +static inline int igmpparm_link_compare(struct igmp_parameters *a, struct igmp_parameters *b) +{ + return pico_ipv4_compare(&a->mcast_link, &b->mcast_link); +} + +static int igmp_parameters_cmp(void *ka, void *kb) +{ + struct igmp_parameters *a = ka, *b = kb; + int cmp = igmpparm_group_compare(a, b); + if (cmp) + return cmp; + + return igmpparm_link_compare(a, b); +} +PICO_TREE_DECLARE(IGMPParameters, igmp_parameters_cmp); + +static int igmp_sources_cmp(void *ka, void *kb) +{ + struct pico_ip4 *a = ka, *b = kb; + return pico_ipv4_compare(a, b); +} +PICO_TREE_DECLARE(IGMPAllow, igmp_sources_cmp); +PICO_TREE_DECLARE(IGMPBlock, igmp_sources_cmp); + +static struct igmp_parameters *pico_igmp_find_parameter(struct pico_ip4 *mcast_link, struct pico_ip4 *mcast_group) +{ + struct igmp_parameters test = { + 0 + }; + if (!mcast_link || !mcast_group) + return NULL; + + test.mcast_link.addr = mcast_link->addr; + test.mcast_group.addr = mcast_group->addr; + return pico_tree_findKey(&IGMPParameters, &test); +} + +static int pico_igmp_delete_parameter(struct igmp_parameters *p) +{ + if (pico_tree_delete(&IGMPParameters, p)) + PICO_FREE(p); + else + return -1; + + return 0; +} + +static void pico_igmp_timer_expired(pico_time now, void *arg) +{ + struct igmp_timer *t = NULL, *timer = NULL, test = { + 0 + }; + + IGNORE_PARAMETER(now); + t = (struct igmp_timer *)arg; + test.type = t->type; + test.mcast_link = t->mcast_link; + test.mcast_group = t->mcast_group; + igmp_dbg("IGMP: timer expired for %08X link %08X type %u, delay %lu\n", t->mcast_group.addr, t->mcast_link.addr, t->type, t->delay); + timer = pico_tree_findKey(&IGMPTimers, &test); + if (!timer) { + return; + } + + if (timer->stopped == IGMP_TIMER_STOPPED) { + pico_tree_delete(&IGMPTimers, timer); + PICO_FREE(t); + return; + } + + if (timer->start + timer->delay < PICO_TIME_MS()) { + pico_tree_delete(&IGMPTimers, timer); + if (timer->callback) + timer->callback(timer); + + PICO_FREE(timer); + } else { + igmp_dbg("IGMP: restart timer for %08X, delay %lu, new delay %lu\n", t->mcast_group.addr, t->delay, (timer->start + timer->delay) - PICO_TIME_MS()); + pico_timer_add((timer->start + timer->delay) - PICO_TIME_MS(), &pico_igmp_timer_expired, timer); + } + + return; +} + +static int pico_igmp_timer_reset(struct igmp_timer *t) +{ + struct igmp_timer *timer = NULL, test = { + 0 + }; + + igmp_dbg("IGMP: reset timer for %08X, delay %lu\n", t->mcast_group.addr, t->delay); + test.type = t->type; + test.mcast_link = t->mcast_link; + test.mcast_group = t->mcast_group; + timer = pico_tree_findKey(&IGMPTimers, &test); + if (!timer) + return -1; + + *timer = *t; + timer->start = PICO_TIME_MS(); + return 0; +} + +static int pico_igmp_timer_start(struct igmp_timer *t) +{ + struct igmp_timer *timer = NULL, test = { + 0 + }; + + igmp_dbg("IGMP: start timer for %08X link %08X type %u, delay %lu\n", t->mcast_group.addr, t->mcast_link.addr, t->type, t->delay); + test.type = t->type; + test.mcast_link = t->mcast_link; + test.mcast_group = t->mcast_group; + timer = pico_tree_findKey(&IGMPTimers, &test); + if (timer) + return pico_igmp_timer_reset(t); + + timer = PICO_ZALLOC(sizeof(struct igmp_timer)); + if (!timer) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + *timer = *t; + timer->start = PICO_TIME_MS(); + + pico_tree_insert(&IGMPTimers, timer); + pico_timer_add(timer->delay, &pico_igmp_timer_expired, timer); + return 0; +} + +static int pico_igmp_timer_stop(struct igmp_timer *t) +{ + struct igmp_timer *timer = NULL, test = { + 0 + }; + + test.type = t->type; + test.mcast_link = t->mcast_link; + test.mcast_group = t->mcast_group; + timer = pico_tree_findKey(&IGMPTimers, &test); + if (!timer) + return -1; + + igmp_dbg("IGMP: stop timer for %08X, delay %lu\n", timer->mcast_group.addr, timer->delay); + timer->stopped = IGMP_TIMER_STOPPED; + return 0; +} + +static int pico_igmp_timer_is_running(struct igmp_timer *t) +{ + struct igmp_timer *timer = NULL, test = { + 0 + }; + + test.type = t->type; + test.mcast_link = t->mcast_link; + test.mcast_group = t->mcast_group; + timer = pico_tree_findKey(&IGMPTimers, &test); + if (timer) + return 1; + + return 0; +} + +static struct igmp_timer *pico_igmp_find_timer(uint8_t type, struct pico_ip4 *mcast_link, struct pico_ip4 *mcast_group) +{ + struct igmp_timer test = { + 0 + }; + + test.type = type; + test.mcast_link = *mcast_link; + test.mcast_group = *mcast_group; + return pico_tree_findKey(&IGMPTimers, &test); +} + +static void pico_igmp_report_expired(struct igmp_timer *t) +{ + struct igmp_parameters *p = NULL; + + p = pico_igmp_find_parameter(&t->mcast_link, &t->mcast_group); + if (!p) + return; + + p->event = IGMP_EVENT_TIMER_EXPIRED; + pico_igmp_process_event(p); +} + +static void pico_igmp_v2querier_expired(struct igmp_timer *t) +{ + struct pico_ipv4_link *link = NULL; + struct pico_tree_node *index = NULL, *_tmp = NULL; + + link = pico_ipv4_link_by_dev(t->f->dev); + if (!link) + return; + + /* When changing compatibility mode, cancel all pending response + * and retransmission timers. + */ + pico_tree_foreach_safe(index, &IGMPTimers, _tmp) + { + ((struct igmp_timer *)index->keyValue)->stopped = IGMP_TIMER_STOPPED; + pico_tree_delete(&IGMPTimers, index->keyValue); + } + igmp_dbg("IGMP: switch to compatibility mode IGMPv3\n"); + link->mcast_compatibility = PICO_IGMPV3; + return; +} + +static int pico_igmp_is_checksum_valid(struct pico_frame *f) +{ + struct pico_ipv4_hdr *hdr = NULL; + uint8_t ihl = 24, datalen = 0; + + hdr = (struct pico_ipv4_hdr *)f->net_hdr; + ihl = (uint8_t)((hdr->vhl & 0x0F) * 4); /* IHL is in 32bit words */ + datalen = (uint8_t)(short_be(hdr->len) - ihl); + + if (short_be(pico_checksum(f->transport_hdr, datalen)) == 0) + return 1; + + igmp_dbg("IGMP: invalid checksum\n"); + return 0; +} + +/* RFC 3376 $7.1 */ +static int pico_igmp_compatibility_mode(struct pico_frame *f) +{ + struct pico_ipv4_hdr *hdr = NULL; + struct pico_ipv4_link *link = NULL; + struct pico_tree_node *index = NULL, *_tmp = NULL; + struct igmp_timer t = { + 0 + }; + uint8_t ihl = 24, datalen = 0; + + link = pico_ipv4_link_by_dev(f->dev); + if (!link) + return -1; + + hdr = (struct pico_ipv4_hdr *) f->net_hdr; + ihl = (uint8_t)((hdr->vhl & 0x0F) * 4); /* IHL is in 32bit words */ + datalen = (uint8_t)(short_be(hdr->len) - ihl); + igmp_dbg("IGMP: IHL = %u, LEN = %u, OCTETS = %u\n", ihl, short_be(hdr->len), datalen); + + if (datalen >= 12) { + /* IGMPv3 query */ + t.type = IGMP_TIMER_V2_QUERIER; + if (pico_igmp_timer_is_running(&t)) { /* IGMPv2 querier present timer still running */ + igmp_dbg("Timer is already running\n"); + return -1; + } else { + link->mcast_compatibility = PICO_IGMPV3; + igmp_dbg("IGMP Compatibility: v3\n"); + return 0; + } + } else if (datalen == 8) { + struct igmp_message *query = (struct igmp_message *)f->transport_hdr; + if (query->max_resp_time != 0) { + /* IGMPv2 query */ + /* When changing compatibility mode, cancel all pending response + * and retransmission timers. + */ + pico_tree_foreach_safe(index, &IGMPTimers, _tmp) + { + ((struct igmp_timer *)index->keyValue)->stopped = IGMP_TIMER_STOPPED; + pico_tree_delete(&IGMPTimers, index->keyValue); + } + igmp_dbg("IGMP: switch to compatibility mode IGMPv2\n"); + link->mcast_compatibility = PICO_IGMPV2; + t.type = IGMP_TIMER_V2_QUERIER; + t.delay = ((IGMP_ROBUSTNESS * link->mcast_last_query_interval) + IGMP_QUERY_RESPONSE_INTERVAL) * 1000; + t.f = f; + t.callback = pico_igmp_v2querier_expired; + /* only one of this type of timer may exist! */ + pico_igmp_timer_start(&t); + } else { + /* IGMPv1 query, not supported */ + return -1; + } + } else { + /* invalid query, silently ignored */ + return -1; + } + + return 0; +} + +static struct igmp_parameters *pico_igmp_analyse_packet(struct pico_frame *f) +{ + struct igmp_message *message = NULL; + struct igmp_parameters *p = NULL; + struct pico_ipv4_link *link = NULL; + struct pico_ip4 mcast_group = { + 0 + }; + + link = pico_ipv4_link_by_dev(f->dev); + if (!link) + return NULL; + + /* IGMPv2 and IGMPv3 have a similar structure for the first 8 bytes */ + message = (struct igmp_message *)f->transport_hdr; + mcast_group.addr = message->mcast_group; + p = pico_igmp_find_parameter(&link->address, &mcast_group); + if (!p && mcast_group.addr == 0) { /* general query */ + p = PICO_ZALLOC(sizeof(struct igmp_parameters)); + if (!p) + return NULL; + + p->state = IGMP_STATE_NON_MEMBER; + p->mcast_link.addr = link->address.addr; + p->mcast_group.addr = mcast_group.addr; + pico_tree_insert(&IGMPParameters, p); + } else if (!p) { + return NULL; + } + + switch (message->type) { + case IGMP_TYPE_MEM_QUERY: + p->event = IGMP_EVENT_QUERY_RECV; + break; + case IGMP_TYPE_MEM_REPORT_V1: + p->event = IGMP_EVENT_REPORT_RECV; + break; + case IGMP_TYPE_MEM_REPORT_V2: + p->event = IGMP_EVENT_REPORT_RECV; + break; + case IGMP_TYPE_MEM_REPORT_V3: + p->event = IGMP_EVENT_REPORT_RECV; + break; + default: + return NULL; + } + p->max_resp_time = message->max_resp_time; /* if IGMPv3 report this will be 0 (res0 field) */ + p->f = f; + + return p; +} + +static int pico_igmp_process_in(struct pico_protocol *self, struct pico_frame *f) +{ + struct igmp_parameters *p = NULL; + IGNORE_PARAMETER(self); + + if (!pico_igmp_is_checksum_valid(f)) + goto out; + + if (pico_igmp_compatibility_mode(f) < 0) + goto out; + + p = pico_igmp_analyse_packet(f); + if (!p) + goto out; + + return pico_igmp_process_event(p); + +out: + pico_frame_discard(f); + return 0; +} + +static int pico_igmp_process_out(struct pico_protocol *self, struct pico_frame *f) +{ + /* packets are directly transferred to the IP layer by calling pico_ipv4_frame_push */ + IGNORE_PARAMETER(self); + IGNORE_PARAMETER(f); + return 0; +} + +/* Interface: protocol definition */ +struct pico_protocol pico_proto_igmp = { + .name = "igmp", + .proto_number = PICO_PROTO_IGMP, + .layer = PICO_LAYER_TRANSPORT, + .process_in = pico_igmp_process_in, + .process_out = pico_igmp_process_out, + .q_in = &igmp_in, + .q_out = &igmp_out, +}; + +int pico_igmp_state_change(struct pico_ip4 *mcast_link, struct pico_ip4 *mcast_group, uint8_t filter_mode, struct pico_tree *_MCASTFilter, uint8_t state) +{ + struct igmp_parameters *p = NULL; + + if (mcast_group->addr == IGMP_ALL_HOST_GROUP) + return 0; + + p = pico_igmp_find_parameter(mcast_link, mcast_group); + if (!p && state == PICO_IGMP_STATE_CREATE) { + p = PICO_ZALLOC(sizeof(struct igmp_parameters)); + if (!p) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + if (!mcast_link || !mcast_group) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + p->state = IGMP_STATE_NON_MEMBER; + p->mcast_link = *mcast_link; + p->mcast_group = *mcast_group; + pico_tree_insert(&IGMPParameters, p); + } else if (!p) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + switch (state) { + case PICO_IGMP_STATE_CREATE: + p->event = IGMP_EVENT_CREATE_GROUP; + break; + + case PICO_IGMP_STATE_UPDATE: + p->event = IGMP_EVENT_UPDATE_GROUP; + break; + + case PICO_IGMP_STATE_DELETE: + p->event = IGMP_EVENT_DELETE_GROUP; + break; + + default: + return -1; + } + p->filter_mode = filter_mode; + p->MCASTFilter = _MCASTFilter; + + return pico_igmp_process_event(p); +} + +static int pico_igmp_send_report(struct igmp_parameters *p, struct pico_frame *f) +{ + struct pico_ip4 dst = { + 0 + }; + struct pico_ip4 mcast_group = { + 0 + }; + struct pico_ipv4_link *link = NULL; + + link = pico_ipv4_link_get(&p->mcast_link); + if (!link) + return -1; + + mcast_group.addr = p->mcast_group.addr; + switch (link->mcast_compatibility) { + case PICO_IGMPV2: + if (p->event == IGMP_EVENT_DELETE_GROUP) + dst.addr = IGMP_ALL_ROUTER_GROUP; + else + dst.addr = mcast_group.addr; + + break; + + case PICO_IGMPV3: + dst.addr = IGMPV3_ALL_ROUTER_GROUP; + break; + + default: + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; + } + + igmp_dbg("IGMP: send membership report on group %08X to %08X\n", mcast_group.addr, dst.addr); + pico_ipv4_frame_push(f, &dst, PICO_PROTO_IGMP); + return 0; +} + +static int8_t pico_igmp_generate_report(struct igmp_parameters *p) +{ + struct pico_ipv4_link *link = NULL; + int i = 0; + + link = pico_ipv4_link_get(&p->mcast_link); + if (!link) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + switch (link->mcast_compatibility) { + case PICO_IGMPV1: + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; + + case PICO_IGMPV2: + { + struct igmp_message *report = NULL; + uint8_t report_type = IGMP_TYPE_MEM_REPORT_V2; + if (p->event == IGMP_EVENT_DELETE_GROUP) + report_type = IGMP_TYPE_LEAVE_GROUP; + + p->f = pico_proto_ipv4.alloc(&pico_proto_ipv4, IP_OPTION_ROUTER_ALERT_LEN + sizeof(struct igmp_message)); + p->f->net_len = (uint16_t)(p->f->net_len + IP_OPTION_ROUTER_ALERT_LEN); + p->f->transport_hdr += IP_OPTION_ROUTER_ALERT_LEN; + p->f->transport_len = (uint16_t)(p->f->transport_len - IP_OPTION_ROUTER_ALERT_LEN); + p->f->dev = pico_ipv4_link_find(&p->mcast_link); + /* p->f->len is correctly set by alloc */ + + report = (struct igmp_message *)p->f->transport_hdr; + report->type = report_type; + report->max_resp_time = IGMP_DEFAULT_MAX_RESPONSE_TIME; + report->mcast_group = p->mcast_group.addr; + + report->crc = 0; + report->crc = short_be(pico_checksum(report, sizeof(struct igmp_message))); + break; + } + case PICO_IGMPV3: + { + struct igmpv3_report *report = NULL; + struct igmpv3_group_record *record = NULL; + struct pico_mcast_group *g = NULL, test = { + 0 + }; + struct pico_tree_node *index = NULL, *_tmp = NULL; + struct pico_tree *IGMPFilter = NULL; + struct pico_ip4 *source = NULL; + uint8_t record_type = 0; + uint8_t sources = 0; + uint16_t len = 0; + + test.mcast_addr = p->mcast_group; + g = pico_tree_findKey(link->MCASTGroups, &test); + if (!g) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + if (p->event == IGMP_EVENT_DELETE_GROUP) { /* "non-existent" state of filter mode INCLUDE and empty source list */ + p->filter_mode = PICO_IP_MULTICAST_INCLUDE; + p->MCASTFilter = NULL; + } + + if (p->event == IGMP_EVENT_QUERY_RECV) { + goto igmp3_report; + } + + + /* cleanup filters */ + pico_tree_foreach_safe(index, &IGMPAllow, _tmp) + { + pico_tree_delete(&IGMPAllow, index->keyValue); + } + pico_tree_foreach_safe(index, &IGMPBlock, _tmp) + { + pico_tree_delete(&IGMPBlock, index->keyValue); + } + + switch (g->filter_mode) { + + case PICO_IP_MULTICAST_INCLUDE: + switch (p->filter_mode) { + case PICO_IP_MULTICAST_INCLUDE: + if (p->event == IGMP_EVENT_DELETE_GROUP) { /* all ADD_SOURCE_MEMBERSHIP had an equivalent DROP_SOURCE_MEMBERSHIP */ + /* TO_IN (B) */ + record_type = IGMP_CHANGE_TO_INCLUDE_MODE; + IGMPFilter = &IGMPAllow; + if (p->MCASTFilter) { + pico_tree_foreach(index, p->MCASTFilter) /* B */ + { + pico_tree_insert(&IGMPAllow, index->keyValue); + sources++; + } + } /* else { IGMPAllow stays empty } */ + + break; + } + + /* ALLOW (B-A) */ + /* if event is CREATE A will be empty, thus only ALLOW (B-A) has sense */ + if (p->event == IGMP_EVENT_CREATE_GROUP) /* first ADD_SOURCE_MEMBERSHIP */ + record_type = IGMP_CHANGE_TO_INCLUDE_MODE; + else + record_type = IGMP_ALLOW_NEW_SOURCES; + + IGMPFilter = &IGMPAllow; + pico_tree_foreach(index, p->MCASTFilter) /* B */ + { + pico_tree_insert(&IGMPAllow, index->keyValue); + sources++; + } + pico_tree_foreach(index, &g->MCASTSources) /* A */ + { + source = pico_tree_findKey(&IGMPAllow, index->keyValue); + if (source) { + pico_tree_delete(&IGMPAllow, source); + sources--; + } + } + if (!pico_tree_empty(&IGMPAllow)) /* record type is ALLOW */ + break; + + /* BLOCK (A-B) */ + record_type = IGMP_BLOCK_OLD_SOURCES; + IGMPFilter = &IGMPBlock; + pico_tree_foreach(index, &g->MCASTSources) /* A */ + { + pico_tree_insert(&IGMPBlock, index->keyValue); + sources++; + } + pico_tree_foreach(index, p->MCASTFilter) /* B */ + { + source = pico_tree_findKey(&IGMPBlock, index->keyValue); + if (source) { + pico_tree_delete(&IGMPBlock, source); + sources--; + } + } + if (!pico_tree_empty(&IGMPBlock)) /* record type is BLOCK */ + break; + + /* ALLOW (B-A) and BLOCK (A-B) are empty: do not send report (RFC 3376 $5.1) */ + p->f = NULL; + return 0; + + case PICO_IP_MULTICAST_EXCLUDE: + /* TO_EX (B) */ + record_type = IGMP_CHANGE_TO_EXCLUDE_MODE; + IGMPFilter = &IGMPBlock; + pico_tree_foreach(index, p->MCASTFilter) /* B */ + { + pico_tree_insert(&IGMPBlock, index->keyValue); + sources++; + } + break; + + default: + pico_err = PICO_ERR_EINVAL; + return -1; + } + break; + + case PICO_IP_MULTICAST_EXCLUDE: + switch (p->filter_mode) { + case PICO_IP_MULTICAST_INCLUDE: + /* TO_IN (B) */ + record_type = IGMP_CHANGE_TO_INCLUDE_MODE; + IGMPFilter = &IGMPAllow; + if (p->MCASTFilter) { + pico_tree_foreach(index, p->MCASTFilter) /* B */ + { + pico_tree_insert(&IGMPAllow, index->keyValue); + sources++; + } + } /* else { IGMPAllow stays empty } */ + + break; + + case PICO_IP_MULTICAST_EXCLUDE: + /* BLOCK (B-A) */ + record_type = IGMP_BLOCK_OLD_SOURCES; + IGMPFilter = &IGMPBlock; + pico_tree_foreach(index, p->MCASTFilter) + { + pico_tree_insert(&IGMPBlock, index->keyValue); + sources++; + } + pico_tree_foreach(index, &g->MCASTSources) /* A */ + { + source = pico_tree_findKey(&IGMPBlock, index->keyValue); /* B */ + if (source) { + pico_tree_delete(&IGMPBlock, source); + sources--; + } + } + if (!pico_tree_empty(&IGMPBlock)) /* record type is BLOCK */ + break; + + /* ALLOW (A-B) */ + record_type = IGMP_ALLOW_NEW_SOURCES; + IGMPFilter = &IGMPAllow; + pico_tree_foreach(index, &g->MCASTSources) + { + pico_tree_insert(&IGMPAllow, index->keyValue); + sources++; + } + pico_tree_foreach(index, p->MCASTFilter) /* B */ + { + source = pico_tree_findKey(&IGMPAllow, index->keyValue); /* A */ + if (source) { + pico_tree_delete(&IGMPAllow, source); + sources--; + } + } + if (!pico_tree_empty(&IGMPAllow)) /* record type is ALLOW */ + break; + + /* BLOCK (B-A) and ALLOW (A-B) are empty: do not send report (RFC 3376 $5.1) */ + p->f = NULL; + return 0; + + default: + pico_err = PICO_ERR_EINVAL; + return -1; + } + break; + + default: + pico_err = PICO_ERR_EINVAL; + return -1; + } + +igmp3_report: + len = (uint16_t)(sizeof(struct igmpv3_report) + sizeof(struct igmpv3_group_record) + (sources * sizeof(struct pico_ip4))); + p->f = pico_proto_ipv4.alloc(&pico_proto_ipv4, (uint16_t)(IP_OPTION_ROUTER_ALERT_LEN + len)); + p->f->net_len = (uint16_t)(p->f->net_len + IP_OPTION_ROUTER_ALERT_LEN); + p->f->transport_hdr += IP_OPTION_ROUTER_ALERT_LEN; + p->f->transport_len = (uint16_t)(p->f->transport_len - IP_OPTION_ROUTER_ALERT_LEN); + p->f->dev = pico_ipv4_link_find(&p->mcast_link); + /* p->f->len is correctly set by alloc */ + + report = (struct igmpv3_report *)p->f->transport_hdr; + report->type = IGMP_TYPE_MEM_REPORT_V3; + report->res0 = 0; + report->crc = 0; + report->res1 = 0; + report->groups = short_be(1); + + record = (struct igmpv3_group_record *)(((uint8_t *)report) + sizeof(struct igmpv3_report)); + record->type = record_type; + record->aux = 0; + record->sources = short_be(sources); + record->mcast_group = p->mcast_group.addr; + if (IGMPFilter && !pico_tree_empty(IGMPFilter)) { + uint32_t *source_addr = (uint32_t *)((void *)((uint8_t *)record + sizeof(struct igmpv3_group_record))); + i = 0; + pico_tree_foreach(index, IGMPFilter) + { + source_addr[i] = ((struct pico_ip4 *)index->keyValue)->addr; + i++; + } + } + + report->crc = short_be(pico_checksum(report, len)); + break; + } + + default: + pico_err = PICO_ERR_EINVAL; + return -1; + } + return 0; +} + +/* stop timer, send leave if flag set */ +static int stslifs(struct igmp_parameters *p) +{ + struct igmp_timer t = { + 0 + }; + + igmp_dbg("IGMP: event = leave group | action = stop timer, send leave if flag set\n"); + + t.type = IGMP_TIMER_GROUP_REPORT; + t.mcast_link = p->mcast_link; + t.mcast_group = p->mcast_group; + if (pico_igmp_timer_stop(&t) < 0) + return -1; + + /* always send leave, even if not last host */ + if (pico_igmp_send_report(p, p->f) < 0) + return -1; + + pico_igmp_delete_parameter(p); + igmp_dbg("IGMP: new state = non-member\n"); + return 0; +} + +/* send report, set flag, start timer */ +static int srsfst(struct igmp_parameters *p) +{ + struct igmp_timer t = { + 0 + }; + struct pico_frame *copy_frame = NULL; + + igmp_dbg("IGMP: event = join group | action = send report, set flag, start timer\n"); + + p->last_host = IGMP_HOST_LAST; + + if (pico_igmp_generate_report(p) < 0) + return -1; + + if (!p->f) + return 0; + + copy_frame = pico_frame_copy(p->f); + if (!copy_frame) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + if (pico_igmp_send_report(p, copy_frame) < 0) + return -1; + + t.type = IGMP_TIMER_GROUP_REPORT; + t.mcast_link = p->mcast_link; + t.mcast_group = p->mcast_group; + t.delay = (pico_rand() % (IGMP_UNSOLICITED_REPORT_INTERVAL * 10000)); + t.f = p->f; + t.callback = pico_igmp_report_expired; + pico_igmp_timer_start(&t); + + p->state = IGMP_STATE_DELAYING_MEMBER; + igmp_dbg("IGMP: new state = delaying member\n"); + return 0; +} + +/* merge report, send report, reset timer (IGMPv3 only) */ +static int mrsrrt(struct igmp_parameters *p) +{ + struct igmp_timer *t = NULL; + struct pico_frame *copy_frame = NULL; + struct pico_ipv4_link *link = NULL; + + igmp_dbg("IGMP: event = update group | action = merge report, send report, reset timer (IGMPv3 only)\n"); + + link = pico_ipv4_link_get(&p->mcast_link); + if (!link) + return -1; + + if (link->mcast_compatibility != PICO_IGMPV3) { + igmp_dbg("IGMP: no IGMPv3 compatible router on network\n"); + return -1; + } + + /* XXX: merge with pending report rfc 3376 $5.1 */ + + copy_frame = pico_frame_copy(p->f); + if (!copy_frame) + return -1; + + if (pico_igmp_send_report(p, copy_frame) < 0) + return -1; + + t = pico_igmp_find_timer(IGMP_TIMER_GROUP_REPORT, &p->mcast_link, &p->mcast_group); + if (!t) + return -1; + + t->delay = (pico_rand() % (IGMP_UNSOLICITED_REPORT_INTERVAL * 10000)); + pico_igmp_timer_reset(t); + + p->state = IGMP_STATE_DELAYING_MEMBER; + igmp_dbg("IGMP: new state = delaying member\n"); + return 0; +} + +/* send report, start timer (IGMPv3 only) */ +static int srst(struct igmp_parameters *p) +{ + struct igmp_timer t = { + 0 + }; + struct pico_frame *copy_frame = NULL; + struct pico_ipv4_link *link = NULL; + + igmp_dbg("IGMP: event = update group | action = send report, start timer (IGMPv3 only)\n"); + + link = pico_ipv4_link_get(&p->mcast_link); + if (!link) + return -1; + + if (link->mcast_compatibility != PICO_IGMPV3) { + igmp_dbg("IGMP: no IGMPv3 compatible router on network\n"); + return -1; + } + + if (pico_igmp_generate_report(p) < 0) + return -1; + + if (!p->f) + return 0; + + copy_frame = pico_frame_copy(p->f); + if (!copy_frame) + return -1; + + if (pico_igmp_send_report(p, copy_frame) < 0) + return -1; + + t.type = IGMP_TIMER_GROUP_REPORT; + t.mcast_link = p->mcast_link; + t.mcast_group = p->mcast_group; + t.delay = (pico_rand() % (IGMP_UNSOLICITED_REPORT_INTERVAL * 10000)); + t.f = p->f; + t.callback = pico_igmp_report_expired; + pico_igmp_timer_start(&t); + + p->state = IGMP_STATE_DELAYING_MEMBER; + igmp_dbg("IGMP: new state = delaying member\n"); + return 0; +} + +/* send leave if flag set */ +static int slifs(struct igmp_parameters *p) +{ + igmp_dbg("IGMP: event = leave group | action = send leave if flag set\n"); + + /* always send leave, even if not last host */ + if (pico_igmp_send_report(p, p->f) < 0) + return -1; + + pico_igmp_delete_parameter(p); + igmp_dbg("IGMP: new state = non-member\n"); + return 0; +} + +/* start timer */ +static int st(struct igmp_parameters *p) +{ + struct igmp_timer t = { + 0 + }; + + igmp_dbg("IGMP: event = query received | action = start timer\n"); + + if (pico_igmp_generate_report(p) < 0) { + igmp_dbg("Failed to generate report\n"); + return -1; + } + + if (!p->f) { + igmp_dbg("No pending frame\n"); + return -1; + } + + t.type = IGMP_TIMER_GROUP_REPORT; + t.mcast_link = p->mcast_link; + t.mcast_group = p->mcast_group; + t.delay = (pico_rand() % ((1u + p->max_resp_time) * 100u)); + t.f = p->f; + t.callback = pico_igmp_report_expired; + pico_igmp_timer_start(&t); + + p->state = IGMP_STATE_DELAYING_MEMBER; + igmp_dbg("IGMP: new state = delaying member\n"); + return 0; +} + +/* stop timer, clear flag */ +static int stcl(struct igmp_parameters *p) +{ + struct igmp_timer t = { + 0 + }; + + igmp_dbg("IGMP: event = report received | action = stop timer, clear flag\n"); + + t.type = IGMP_TIMER_GROUP_REPORT; + t.mcast_link = p->mcast_link; + t.mcast_group = p->mcast_group; + if (pico_igmp_timer_stop(&t) < 0) + return -1; + + p->last_host = IGMP_HOST_NOT_LAST; + p->state = IGMP_STATE_IDLE_MEMBER; + igmp_dbg("IGMP: new state = idle member\n"); + return 0; +} + +/* send report, set flag */ +static int srsf(struct igmp_parameters *p) +{ + igmp_dbg("IGMP: event = timer expired | action = send report, set flag\n"); + + if (pico_igmp_send_report(p, p->f) < 0) + return -1; + + p->state = IGMP_STATE_IDLE_MEMBER; + igmp_dbg("IGMP: new state = idle member\n"); + return 0; +} + +/* reset timer if max response time < current timer */ +static int rtimrtct(struct igmp_parameters *p) +{ + struct igmp_timer *t = NULL; + uint32_t time_to_run = 0; + + igmp_dbg("IGMP: event = query received | action = reset timer if max response time < current timer\n"); + + t = pico_igmp_find_timer(IGMP_TIMER_GROUP_REPORT, &p->mcast_link, &p->mcast_group); + if (!t) + return -1; + + time_to_run = (uint32_t)(t->start + t->delay - PICO_TIME_MS()); + if ((p->max_resp_time * 100u) < time_to_run) { /* max_resp_time in units of 1/10 seconds */ + t->delay = pico_rand() % ((1u + p->max_resp_time) * 100u); + pico_igmp_timer_reset(t); + } + + p->state = IGMP_STATE_DELAYING_MEMBER; + igmp_dbg("IGMP: new state = delaying member\n"); + return 0; +} + +static int discard(struct igmp_parameters *p) +{ + igmp_dbg("IGMP: ignore and discard frame\n"); + pico_frame_discard(p->f); + return 0; +} + +/* finite state machine table */ +const callback host_membership_diagram_table[3][6] = +{ /* event |Delete Group |Create Group |Update Group |Query Received |Report Received |Timer Expired */ +/* state Non-Member */ + { discard, srsfst, srsfst, discard, discard, discard }, +/* state Delaying Member */ { stslifs, mrsrrt, mrsrrt, rtimrtct, stcl, srsf }, +/* state Idle Member */ { slifs, srst, srst, st, discard, discard } +}; + +static int pico_igmp_process_event(struct igmp_parameters *p) +{ + struct pico_tree_node *index = NULL; + struct igmp_parameters *_p = NULL; + + igmp_dbg("IGMP: process event on group address %08X\n", p->mcast_group.addr); + if (p->event == IGMP_EVENT_QUERY_RECV && p->mcast_group.addr == 0) { /* general query */ + pico_tree_foreach(index, &IGMPParameters) { + _p = index->keyValue; + _p->max_resp_time = p->max_resp_time; + _p->event = IGMP_EVENT_QUERY_RECV; + igmp_dbg("IGMP: for each mcast_group = %08X | state = %u\n", _p->mcast_group.addr, _p->state); + host_membership_diagram_table[_p->state][_p->event](_p); + } + } else { + igmp_dbg("IGMP: state = %u (0: non-member - 1: delaying member - 2: idle member)\n", p->state); + host_membership_diagram_table[p->state][p->event](p); + } + + return 0; +} + +#else +static struct pico_queue igmp_in = { + 0 +}; +static struct pico_queue igmp_out = { + 0 +}; + +static int pico_igmp_process_in(struct pico_protocol *self, struct pico_frame *f) { + IGNORE_PARAMETER(self); + IGNORE_PARAMETER(f); + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} + +static int pico_igmp_process_out(struct pico_protocol *self, struct pico_frame *f) { + IGNORE_PARAMETER(self); + IGNORE_PARAMETER(f); + return -1; +} + +/* Interface: protocol definition */ +struct pico_protocol pico_proto_igmp = { + .name = "igmp", + .proto_number = PICO_PROTO_IGMP, + .layer = PICO_LAYER_TRANSPORT, + .process_in = pico_igmp_process_in, + .process_out = pico_igmp_process_out, + .q_in = &igmp_in, + .q_out = &igmp_out, +}; + +int pico_igmp_state_change(struct pico_ip4 *mcast_link, struct pico_ip4 *mcast_group, uint8_t filter_mode, struct pico_tree *_MCASTFilter, uint8_t state) { + IGNORE_PARAMETER(mcast_link); + IGNORE_PARAMETER(mcast_group); + IGNORE_PARAMETER(filter_mode); + IGNORE_PARAMETER(_MCASTFilter); + IGNORE_PARAMETER(state); + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} +#endif diff --git a/ext/picotcp/modules/pico_igmp.h b/ext/picotcp/modules/pico_igmp.h new file mode 100644 index 0000000..6f8d74e --- /dev/null +++ b/ext/picotcp/modules/pico_igmp.h @@ -0,0 +1,26 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + . + + Authors: Kristof Roelants, Simon Maes, Brecht Van Cauwenberghe + *********************************************************************/ + +#ifndef INCLUDE_PICO_IGMP +#define INCLUDE_PICO_IGMP + +#define PICO_IGMPV1 1 +#define PICO_IGMPV2 2 +#define PICO_IGMPV3 3 + +#define PICO_IGMP_STATE_CREATE 1 +#define PICO_IGMP_STATE_UPDATE 2 +#define PICO_IGMP_STATE_DELETE 3 + +#define PICO_IGMP_QUERY_INTERVAL 125 + +extern struct pico_protocol pico_proto_igmp; + +int pico_igmp_state_change(struct pico_ip4 *mcast_link, struct pico_ip4 *mcast_group, uint8_t filter_mode, struct pico_tree *_MCASTFilter, uint8_t state); +#endif /* _INCLUDE_PICO_IGMP */ diff --git a/ext/picotcp/modules/pico_ipfilter.c b/ext/picotcp/modules/pico_ipfilter.c new file mode 100644 index 0000000..64ee349 --- /dev/null +++ b/ext/picotcp/modules/pico_ipfilter.c @@ -0,0 +1,458 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + Authors: Andrei Carp + Simon Maes + *********************************************************************/ + +#include "pico_ipv4.h" +#include "pico_config.h" +#include "pico_icmp4.h" +#include "pico_stack.h" +#include "pico_eth.h" +#include "pico_socket.h" +#include "pico_device.h" +#include "pico_ipfilter.h" +#include "pico_tcp.h" +#include "pico_udp.h" +#include "pico_tree.h" + +/**************** LOCAL MACROS ****************/ +#define MAX_PRIORITY (10) +#define MIN_PRIORITY (-10) + +#define ipf_dbg(...) do {} while(0) + +/**************** LOCAL DECLARATIONS ****************/ +struct filter_node; +static int filter_compare(void *filterA, void *filterB); + +/**************** FILTER TREE ****************/ + +struct filter_node { + struct pico_device *fdev; + /* output address */ + uint32_t out_addr; + uint32_t out_addr_netmask; + /* input address */ + uint32_t in_addr; + uint32_t in_addr_netmask; + /* transport */ + uint16_t out_port; + uint16_t in_port; + /* filter details */ + uint8_t proto; + int8_t priority; + uint8_t tos; + uint32_t filter_id; + int (*function_ptr)(struct filter_node *filter, struct pico_frame *f); +}; + +PICO_TREE_DECLARE(filter_tree, &filter_compare); + +static inline int ipfilter_uint32_cmp(uint32_t a, uint32_t b) +{ + if (a < b) + return -1; + + if (b < a) + return 1; + + return 0; +} + +static inline int ipfilter_uint16_cmp(uint16_t a, uint16_t b) +{ + if (a < b) + return -1; + + if (b < a) + return 1; + + return 0; +} + +static inline int ipfilter_uint8_cmp(uint8_t a, uint8_t b) +{ + if (a < b) + return -1; + + if (b < a) + return 1; + + return 0; +} + +static inline int ipfilter_ptr_cmp(void *a, void *b) +{ + if (a < b) + return -1; + + if (b < a) + return 1; + + return 0; +} + + + +static inline int filter_compare_ports(struct filter_node *a, struct filter_node *b) +{ + int cmp; + cmp = ipfilter_uint16_cmp(a->in_port, b->in_port); + if (cmp) + return cmp; + + cmp = ipfilter_uint16_cmp(a->out_port, b->out_port); + return cmp; +} + +static inline int filter_compare_addresses(struct filter_node *a, struct filter_node *b) +{ + int cmp; + /* Compare source address */ + cmp = ipfilter_uint32_cmp((a->in_addr & a->in_addr_netmask), (b->in_addr & b->in_addr_netmask)); + if (cmp) + return cmp; + + /* Compare destination address */ + cmp = ipfilter_uint32_cmp((a->out_addr & a->out_addr_netmask), (b->out_addr & b->out_addr_netmask)); + return cmp; +} + +static inline int filter_compare_proto(struct filter_node *a, struct filter_node *b) +{ + return ipfilter_uint8_cmp(a->proto, b->proto); +} + +static inline int filter_compare_address_port(struct filter_node *a, struct filter_node *b) +{ + int cmp; + cmp = filter_compare_addresses(a, b); + if (cmp) + return cmp; + + return filter_compare_ports(a, b); +} + +static inline int filter_match_packet_dev(struct filter_node *a, struct filter_node *b, struct filter_node *rule) +{ + int cmp; + /* 1. Compare devices */ + if (rule->fdev) { + cmp = ipfilter_ptr_cmp(a->fdev, b->fdev); + if (cmp) + return cmp; + } + + return 0; + +} + +static inline int filter_match_packet_proto(struct filter_node *a, struct filter_node *b, struct filter_node *rule) +{ + int cmp; + /* 2. Compare protocol */ + if (rule->proto) { + cmp = filter_compare_proto(a, b); + if (cmp) + return cmp; + } + + return 0; + +} +static inline int filter_match_packet_addr_in(struct filter_node *a, struct filter_node *b, struct filter_node *rule) +{ + int cmp; + /* 3. Compare addresses order: in, out */ + if (rule->in_addr_netmask) { + cmp = ipfilter_uint32_cmp(a->in_addr & rule->in_addr_netmask, b->in_addr & rule->in_addr_netmask); + if (cmp) + return cmp; + } + + return 0; +} +static inline int filter_match_packet_addr_out(struct filter_node *a, struct filter_node *b, struct filter_node *rule) +{ + int cmp; + if (rule->out_addr_netmask) { + cmp = ipfilter_uint32_cmp(a->out_addr & rule->out_addr_netmask, b->out_addr & rule->out_addr_netmask); + if (cmp) { + return cmp; + } + } + + return 0; +} +static inline int filter_match_packet_port_in(struct filter_node *a, struct filter_node *b, struct filter_node *rule) +{ + int cmp; + /* 4. Compare ports order: in, out */ + if (rule->in_port) { + cmp = ipfilter_uint16_cmp(a->in_port, b->in_port); + if (cmp) + return cmp; + } + + return 0; +} +static inline int filter_match_packet_port_out(struct filter_node *a, struct filter_node *b, struct filter_node *rule) +{ + int cmp; + if (rule->out_port) { + cmp = ipfilter_uint16_cmp(a->out_port, b->out_port); + if (cmp) + return cmp; + } + + return 0; +} + +static inline int filter_match_packet_dev_and_proto(struct filter_node *a, struct filter_node *b, struct filter_node *rule) +{ + int cmp = filter_match_packet_dev(a, b, rule); + if (cmp) + return cmp; + + return filter_match_packet_proto(a, b, rule); +} + +static inline int filter_match_packet_addr(struct filter_node *a, struct filter_node *b, struct filter_node *rule) +{ + int cmp = filter_match_packet_addr_in(a, b, rule); + if (cmp) + return cmp; + + return filter_match_packet_addr_out(a, b, rule); + +} + +static inline int filter_match_packet_port(struct filter_node *a, struct filter_node *b, struct filter_node *rule) +{ + int cmp = filter_match_packet_port_in(a, b, rule); + if (cmp) + return cmp; + + return filter_match_packet_port_out(a, b, rule); +} + +static inline struct filter_node *filter_match_packet_find_rule(struct filter_node *a, struct filter_node *b) +{ + if (!a->filter_id) + return b; + + return a; +} + +static inline int filter_match_packet(struct filter_node *a, struct filter_node *b) +{ + struct filter_node *rule; + int cmp = 0; + rule = filter_match_packet_find_rule(a, b); + + cmp = filter_match_packet_dev_and_proto(a, b, rule); + if (cmp) + return cmp; + + cmp = filter_match_packet_addr(a, b, rule); + if (cmp) + return cmp; + + cmp = filter_match_packet_port(a, b, rule); + if (cmp) + return cmp; + + return 0; +} + + +int filter_compare(void *filterA, void *filterB) +{ + + struct filter_node *a = (struct filter_node *)filterA; + struct filter_node *b = (struct filter_node *)filterB; + int cmp = 0; + if (a->filter_id == 0 || b->filter_id == 0) { + return filter_match_packet(a, b); + } + + /* improve the search */ + if(a->filter_id == b->filter_id) + return 0; + + /* 1. Compare devices */ + cmp = ipfilter_ptr_cmp(a->fdev, a->fdev); + if (cmp) + return cmp; + + /* 2. Compare protocol */ + cmp = filter_compare_proto(a, b); + if(cmp) + return cmp; + + /* 3. Compare addresses order: in, out */ + /* 4. Compare ports order: in, out */ + cmp = filter_compare_address_port(a, b); + + return cmp; +} + +/**************** FILTER CALLBACKS ****************/ + +static int fp_priority(struct filter_node *filter, struct pico_frame *f) +{ + /* TODO do priority-stuff */ + IGNORE_PARAMETER(filter); + IGNORE_PARAMETER(f); + return 0; +} + +static int fp_reject(struct filter_node *filter, struct pico_frame *f) +{ +/* TODO check first if sender is pico itself or not */ + IGNORE_PARAMETER(filter); + ipf_dbg("ipfilter> reject\n"); + (void)pico_icmp4_packet_filtered(f); + pico_frame_discard(f); + return 1; +} + +static int fp_drop(struct filter_node *filter, struct pico_frame *f) +{ + IGNORE_PARAMETER(filter); + ipf_dbg("ipfilter> drop\n"); + pico_frame_discard(f); + return 1; +} + +struct fp_function { + int (*fn)(struct filter_node *filter, struct pico_frame *f); +}; + + +static const struct fp_function fp_function[FILTER_COUNT] = +{ + {&fp_priority}, + {&fp_reject}, + {&fp_drop} +}; + +static int pico_ipv4_filter_add_validate(int8_t priority, enum filter_action action) +{ + if ( priority > MAX_PRIORITY || priority < MIN_PRIORITY) { + return -1; + } + + if (action >= FILTER_COUNT) { + return -1; + } + + return 0; +} + + +/**************** FILTER API's ****************/ +uint32_t pico_ipv4_filter_add(struct pico_device *dev, uint8_t proto, + struct pico_ip4 *out_addr, struct pico_ip4 *out_addr_netmask, + struct pico_ip4 *in_addr, struct pico_ip4 *in_addr_netmask, + uint16_t out_port, uint16_t in_port, int8_t priority, + uint8_t tos, enum filter_action action) +{ + static uint32_t filter_id = 1u; /* 0 is a special value used in the binary-tree search for packets being processed */ + struct filter_node *new_filter; + + if (pico_ipv4_filter_add_validate(priority, action) < 0) { + pico_err = PICO_ERR_EINVAL; + return 0; + } + + new_filter = PICO_ZALLOC(sizeof(struct filter_node)); + if (!new_filter) { + pico_err = PICO_ERR_ENOMEM; + return 0; + } + + new_filter->fdev = dev; + new_filter->proto = proto; + new_filter->out_addr = (!out_addr) ? (0U) : (out_addr->addr); + new_filter->out_addr_netmask = (!out_addr_netmask) ? (0U) : (out_addr_netmask->addr); + new_filter->in_addr = (!in_addr) ? (0U) : (in_addr->addr); + new_filter->in_addr_netmask = (!in_addr_netmask) ? (0U) : (in_addr_netmask->addr); + new_filter->out_port = out_port; + new_filter->in_port = in_port; + new_filter->priority = priority; + new_filter->tos = tos; + new_filter->filter_id = filter_id++; + new_filter->function_ptr = fp_function[action].fn; + + if(pico_tree_insert(&filter_tree, new_filter)) + { + PICO_FREE(new_filter); + filter_id--; + return 0; + } + + return new_filter->filter_id; +} + +int pico_ipv4_filter_del(uint32_t filter_id) +{ + struct filter_node *node = NULL; + struct filter_node dummy = { 0 }; + + dummy.filter_id = filter_id; + if((node = pico_tree_delete(&filter_tree, &dummy)) == NULL) + { + ipf_dbg("ipfilter> failed to delete filter :%d\n", filter_id); + return -1; + } + + PICO_FREE(node); + return 0; +} + +static int ipfilter_apply_filter(struct pico_frame *f, struct filter_node *pkt) +{ + struct filter_node *filter_frame = NULL; + filter_frame = pico_tree_findKey(&filter_tree, pkt); + if(filter_frame) + { + filter_frame->function_ptr(filter_frame, f); + return 1; + } + + return 0; +} + +int ipfilter(struct pico_frame *f) +{ + struct filter_node temp; + struct pico_ipv4_hdr *ipv4_hdr = (struct pico_ipv4_hdr *) f->net_hdr; + struct pico_trans *trans; + struct pico_icmp4_hdr *icmp_hdr; + + memset(&temp, 0u, sizeof(struct filter_node)); + + temp.fdev = f->dev; + temp.out_addr = ipv4_hdr->dst.addr; + temp.in_addr = ipv4_hdr->src.addr; + if ((ipv4_hdr->proto == PICO_PROTO_TCP) || (ipv4_hdr->proto == PICO_PROTO_UDP)) { + trans = (struct pico_trans *) f->transport_hdr; + temp.out_port = short_be(trans->dport); + temp.in_port = short_be(trans->sport); + } + else if(ipv4_hdr->proto == PICO_PROTO_ICMP4) { + icmp_hdr = (struct pico_icmp4_hdr *) f->transport_hdr; + if(icmp_hdr->type == PICO_ICMP_UNREACH && icmp_hdr->type == PICO_ICMP_UNREACH_FILTER_PROHIB) + return 0; + } + + temp.proto = ipv4_hdr->proto; + temp.priority = f->priority; + temp.tos = ipv4_hdr->tos; + return ipfilter_apply_filter(f, &temp); +} + diff --git a/ext/picotcp/modules/pico_ipfilter.h b/ext/picotcp/modules/pico_ipfilter.h new file mode 100644 index 0000000..fb92e67 --- /dev/null +++ b/ext/picotcp/modules/pico_ipfilter.h @@ -0,0 +1,29 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + Authors: Simon Maes + *********************************************************************/ +#ifndef INCLUDE_PICO_IPFILTER +#define INCLUDE_PICO_IPFILTER + +#include "pico_device.h" + +enum filter_action { + FILTER_PRIORITY = 0, + FILTER_REJECT, + FILTER_DROP, + FILTER_COUNT +}; + +uint32_t pico_ipv4_filter_add(struct pico_device *dev, uint8_t proto, + struct pico_ip4 *out_addr, struct pico_ip4 *out_addr_netmask, struct pico_ip4 *in_addr, + struct pico_ip4 *in_addr_netmask, uint16_t out_port, uint16_t in_port, + int8_t priority, uint8_t tos, enum filter_action action); + +int pico_ipv4_filter_del(uint32_t filter_id); + +int ipfilter(struct pico_frame *f); + +#endif /* _INCLUDE_PICO_IPFILTER */ + diff --git a/ext/picotcp/modules/pico_ipv4.c b/ext/picotcp/modules/pico_ipv4.c new file mode 100644 index 0000000..11317e9 --- /dev/null +++ b/ext/picotcp/modules/pico_ipv4.c @@ -0,0 +1,1585 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + Authors: Daniele Lacamera, Markian Yskout + *********************************************************************/ + + +#include "pico_config.h" +#include "pico_ipfilter.h" +#include "pico_ipv4.h" +#include "pico_icmp4.h" +#include "pico_stack.h" +#include "pico_eth.h" +#include "pico_udp.h" +#include "pico_tcp.h" +#include "pico_socket.h" +#include "pico_device.h" +#include "pico_nat.h" +#include "pico_igmp.h" +#include "pico_tree.h" +#include "pico_aodv.h" +#include "pico_socket_multicast.h" +#include "pico_fragments.h" + +#ifdef PICO_SUPPORT_IPV4 + +#ifdef PICO_SUPPORT_MCAST +# define ip_mcast_dbg(...) do {} while(0) /* so_mcast_dbg in pico_socket.c */ +/* #define ip_mcast_dbg dbg */ +# define PICO_MCAST_ALL_HOSTS long_be(0xE0000001) /* 224.0.0.1 */ +/* Default network interface for multicast transmission */ +static struct pico_ipv4_link *mcast_default_link = NULL; +#endif +#ifdef PICO_SUPPORT_IPV4FRAG +/* # define reassembly_dbg dbg */ +# define reassembly_dbg(...) do {} while(0) +#endif + +/* Queues */ +static struct pico_queue in = { + 0 +}; +static struct pico_queue out = { + 0 +}; + +/* Functions */ +static int ipv4_route_compare(void *ka, void *kb); +static struct pico_frame *pico_ipv4_alloc(struct pico_protocol *self, uint16_t size); + + +int pico_ipv4_compare(struct pico_ip4 *a, struct pico_ip4 *b) +{ + if (a->addr < b->addr) + return -1; + + if (a->addr > b->addr) + return 1; + + return 0; +} + +int pico_ipv4_to_string(char *ipbuf, const uint32_t ip) +{ + const unsigned char *addr = (const unsigned char *) &ip; + int i; + + if (!ipbuf) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + for(i = 0; i < 4; i++) + { + if (addr[i] > 99) { + *ipbuf++ = (char)('0' + (addr[i] / 100)); + *ipbuf++ = (char)('0' + ((addr[i] % 100) / 10)); + *ipbuf++ = (char)('0' + ((addr[i] % 100) % 10)); + } else if (addr[i] > 9) { + *ipbuf++ = (char)('0' + (addr[i] / 10)); + *ipbuf++ = (char)('0' + (addr[i] % 10)); + } else { + *ipbuf++ = (char)('0' + addr[i]); + } + + if (i < 3) + *ipbuf++ = '.'; + } + *ipbuf = '\0'; + + return 0; +} + +static int pico_string_check_null_args(const char *ipstr, uint32_t *ip) +{ + + if (!ipstr || !ip) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + return 0; + +} + +extern int pico_string_to_ipv4(const char *ipstr, uint32_t *ip) +{ + unsigned char buf[PICO_SIZE_IP4] = { + 0 + }; + int cnt = 0; + char p; + + if (pico_string_check_null_args(ipstr, ip) < 0) + return -1; + + while((p = *ipstr++) != 0 && cnt < PICO_SIZE_IP4) + { + if (pico_is_digit(p)) { + buf[cnt] = (uint8_t)((10 * buf[cnt]) + (p - '0')); + } else if (p == '.') { + cnt++; + } else { + return -1; + } + } + /* Handle short notation */ + if (cnt == 1) { + buf[3] = buf[1]; + buf[1] = 0; + buf[2] = 0; + } else if (cnt == 2) { + buf[3] = buf[2]; + buf[2] = 0; + } else if (cnt != 3) { + /* String could not be parsed, return error */ + return -1; + } + + *ip = long_from(buf); + + return 0; +} + +int pico_ipv4_valid_netmask(uint32_t mask) +{ + int cnt = 0; + int end = 0; + int i; + uint32_t mask_swap = long_be(mask); + + /* + * Swap bytes for convenient parsing + * e.g. 0x..f8ff will become 0xfff8.. + * Then, we count the consecutive bits + * + * */ + + for(i = 0; i < 32; i++) { + if ((mask_swap << i) & 0x80000000) { + if (end) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + cnt++; + } else { + end = 1; + } + } + return cnt; +} + +int pico_ipv4_is_unicast(uint32_t address) +{ + const unsigned char *addr = (unsigned char *) &address; + if ((addr[0] & 0xe0) == 0xe0) + return 0; /* multicast */ + + return 1; +} + +int pico_ipv4_is_multicast(uint32_t address) +{ + const unsigned char *addr = (unsigned char *) &address; + if ((addr[0] != 0xff) && ((addr[0] & 0xe0) == 0xe0)) + return 1; /* multicast */ + + return 0; +} + +int pico_ipv4_is_loopback(uint32_t address) +{ + const unsigned char *addr = (unsigned char *) &address; + if (addr[0] == 0x7f) + return 1; + + return 0; +} + +static int pico_ipv4_is_invalid_loopback(uint32_t address, struct pico_device *dev) +{ + return pico_ipv4_is_loopback(address) && ((!dev) || strcmp(dev->name, "loop")); +} + +int pico_ipv4_is_valid_src(uint32_t address, struct pico_device *dev) +{ + if (pico_ipv4_is_broadcast(address)) { + dbg("Source is a broadcast address, discard packet\n"); + return 0; + } else if ( pico_ipv4_is_multicast(address)) { + dbg("Source is a multicast address, discard packet\n"); + return 0; + } else if (pico_ipv4_is_invalid_loopback(address, dev)) { + dbg("Source is a loopback address, discard packet\n"); + return 0; + } else { +#ifdef PICO_SUPPORT_AODV + union pico_address src; + src.ip4.addr = address; + pico_aodv_refresh(&src); +#endif + return 1; + } +} + +static int pico_ipv4_checksum(struct pico_frame *f) +{ + struct pico_ipv4_hdr *hdr = (struct pico_ipv4_hdr *) f->net_hdr; + if (!hdr) + return -1; + + hdr->crc = 0; + hdr->crc = short_be(pico_checksum(hdr, f->net_len)); + return 0; +} + + +#ifdef PICO_SUPPORT_CRC +static inline int pico_ipv4_crc_check(struct pico_frame *f) +{ + uint16_t checksum_invalid = 1; + struct pico_ipv4_hdr *hdr = (struct pico_ipv4_hdr *) f->net_hdr; + + checksum_invalid = short_be(pico_checksum(hdr, f->net_len)); + if (checksum_invalid) { + dbg("IP: checksum failed!\n"); + pico_frame_discard(f); + return 0; + } + + return 1; +} +#else +static inline int pico_ipv4_crc_check(struct pico_frame *f) +{ + IGNORE_PARAMETER(f); + return 1; +} +#endif /* PICO_SUPPORT_CRC */ + +static int pico_ipv4_forward(struct pico_frame *f); +#ifdef PICO_SUPPORT_MCAST +static int pico_ipv4_mcast_filter(struct pico_frame *f); +#endif + +static int ipv4_link_compare(void *ka, void *kb) +{ + struct pico_ipv4_link *a = ka, *b = kb; + int cmp = pico_ipv4_compare(&a->address, &b->address); + if (cmp) + return cmp; + + /* zero can be assigned multiple times (e.g. for DHCP) */ + if (a->dev != NULL && b->dev != NULL && a->address.addr == PICO_IP4_ANY && b->address.addr == PICO_IP4_ANY) { + if (a->dev < b->dev) + return -1; + + if (a->dev > b->dev) + return 1; + } + + return 0; +} + +PICO_TREE_DECLARE(Tree_dev_link, ipv4_link_compare); + +static int pico_ipv4_process_bcast_in(struct pico_frame *f) +{ + struct pico_ipv4_hdr *hdr = (struct pico_ipv4_hdr *) f->net_hdr; +#ifdef PICO_SUPPORT_UDP + if (pico_ipv4_is_broadcast(hdr->dst.addr) && (hdr->proto == PICO_PROTO_UDP)) { + /* Receiving UDP broadcast datagram */ + f->flags |= PICO_FRAME_FLAG_BCAST; + pico_enqueue(pico_proto_udp.q_in, f); + return 1; + } + +#endif + +#ifdef PICO_SUPPORT_ICMP4 + if (pico_ipv4_is_broadcast(hdr->dst.addr) && (hdr->proto == PICO_PROTO_ICMP4)) { + /* Receiving ICMP4 bcast packet */ + f->flags |= PICO_FRAME_FLAG_BCAST; + pico_enqueue(pico_proto_icmp4.q_in, f); + return 1; + } + +#endif + return 0; +} + +static int pico_ipv4_process_mcast_in(struct pico_frame *f) +{ + struct pico_ipv4_hdr *hdr = (struct pico_ipv4_hdr *) f->net_hdr; + if (pico_ipv4_is_multicast(hdr->dst.addr)) { +#ifdef PICO_SUPPORT_IGMP + /* Receiving UDP multicast datagram TODO set f->flags? */ + if (hdr->proto == PICO_PROTO_IGMP) { + ip_mcast_dbg("MCAST: received IGMP message\n"); + pico_transport_receive(f, PICO_PROTO_IGMP); + return 1; + } else if ((pico_ipv4_mcast_filter(f) == 0) && (hdr->proto == PICO_PROTO_UDP)) { + pico_enqueue(pico_proto_udp.q_in, f); + return 1; + } + +#endif + pico_frame_discard(f); + return 1; + } + + return 0; +} + +static int pico_ipv4_process_local_unicast_in(struct pico_frame *f) +{ + struct pico_ipv4_hdr *hdr = (struct pico_ipv4_hdr *) f->net_hdr; + struct pico_ipv4_link test = { + .address = {.addr = PICO_IP4_ANY}, .dev = NULL + }; + if (pico_ipv4_link_find(&hdr->dst)) { + if (pico_ipv4_nat_inbound(f, &hdr->dst) == 0) + pico_enqueue(pico_proto_ipv4.q_in, f); /* dst changed, reprocess */ + else + pico_transport_receive(f, hdr->proto); + + return 1; + } else if (pico_tree_findKey(&Tree_dev_link, &test)) { +#ifdef PICO_SUPPORT_UDP + /* address of this device is apparently 0.0.0.0; might be a DHCP packet */ + /* XXX KRO: is obsolete. Broadcast flag is set on outgoing DHCP messages. + * incomming DHCP messages are to be broadcasted. Our current DHCP server + * implementation does not take this flag into account yet though ... */ + pico_enqueue(pico_proto_udp.q_in, f); + return 1; +#endif + } + + return 0; +} + +static void pico_ipv4_process_finally_try_forward(struct pico_frame *f) +{ + struct pico_ipv4_hdr *hdr = (struct pico_ipv4_hdr *) f->net_hdr; + if ((pico_ipv4_is_broadcast(hdr->dst.addr)) || ((f->flags & PICO_FRAME_FLAG_BCAST)!= 0)) { + /* don't forward broadcast frame, discard! */ + pico_frame_discard(f); + } else if (pico_ipv4_forward(f) != 0) { + pico_frame_discard(f); + /* dbg("Forward failed.\n"); */ + } +} + + + +static int pico_ipv4_process_in(struct pico_protocol *self, struct pico_frame *f) +{ + uint8_t option_len = 0; + int ret = 0; + struct pico_ipv4_hdr *hdr = (struct pico_ipv4_hdr *) f->net_hdr; + uint16_t max_allowed = (uint16_t) ((int)f->buffer_len - (f->net_hdr - f->buffer) - (int)PICO_SIZE_IP4HDR); + uint16_t flag = short_be(hdr->frag); + + (void)self; + + if (!hdr) + return -1; + /* NAT needs transport header information */ + if (((hdr->vhl) & 0x0F) > 5) { + option_len = (uint8_t)(4 * (((hdr->vhl) & 0x0F) - 5)); + } + + f->transport_hdr = ((uint8_t *)f->net_hdr) + PICO_SIZE_IP4HDR + option_len; + f->transport_len = (uint16_t)(short_be(hdr->len) - PICO_SIZE_IP4HDR - option_len); + f->net_len = (uint16_t)(PICO_SIZE_IP4HDR + option_len); + + if (f->transport_len > max_allowed) { + pico_frame_discard(f); + return 0; /* Packet is discarded due to unfeasible length */ + } + +#ifdef PICO_SUPPORT_IPFILTER + if (ipfilter(f)) { + /*pico_frame is discarded as result of the filtering*/ + return 0; + } + +#endif + + + /* ret == 1 indicates to continue the function */ + ret = pico_ipv4_crc_check(f); + if (ret < 1) + return ret; + + /* Validate source IP address. Discard quietly if invalid */ + if (!pico_ipv4_is_valid_src(hdr->src.addr, f->dev)) { + pico_frame_discard(f); + return 0; + } + + if (hdr->frag & short_be(PICO_IPV4_EVIL)) { + (void)pico_icmp4_param_problem(f, 0); + pico_frame_discard(f); /* RFC 3514 */ + return 0; + } + + if ((hdr->vhl & 0x0f) < 5) { + /* RFC 791: IHL minimum value is 5 */ + (void)pico_icmp4_param_problem(f, 0); + pico_frame_discard(f); + return 0; + } + + if (flag & (PICO_IPV4_MOREFRAG | PICO_IPV4_FRAG_MASK)) + { +#ifdef PICO_SUPPORT_IPV4FRAG + pico_ipv4_process_frag(hdr, f, hdr ? hdr->proto : 0 ); + /* Frame can be discarded, frag will handle its own copy */ +#endif + /* We do not support fragmentation, discard quietly */ + pico_frame_discard(f); + return 0; + } + + if (pico_ipv4_process_bcast_in(f) > 0) + return 0; + + if (pico_ipv4_process_mcast_in(f) > 0) + return 0; + + if (pico_ipv4_process_local_unicast_in(f) > 0) + return 0; + + pico_ipv4_process_finally_try_forward(f); + + return 0; +} + +PICO_TREE_DECLARE(Routes, ipv4_route_compare); + + +static int pico_ipv4_process_out(struct pico_protocol *self, struct pico_frame *f) +{ + IGNORE_PARAMETER(self); + f->start = (uint8_t*) f->net_hdr; +#ifdef PICO_SUPPORT_IPFILTER + if (ipfilter(f)) { + /*pico_frame is discarded as result of the filtering*/ + return 0; + } + +#endif + return pico_sendto_dev(f); +} + + +static struct pico_frame *pico_ipv4_alloc(struct pico_protocol *self, uint16_t size) +{ + struct pico_frame *f = pico_frame_alloc(size + PICO_SIZE_IP4HDR + PICO_SIZE_ETHHDR); + IGNORE_PARAMETER(self); + + if (!f) + return NULL; + + f->datalink_hdr = f->buffer; + f->net_hdr = f->buffer + PICO_SIZE_ETHHDR; + f->net_len = PICO_SIZE_IP4HDR; + f->transport_hdr = f->net_hdr + PICO_SIZE_IP4HDR; + f->transport_len = size; + f->len = size + PICO_SIZE_IP4HDR; + return f; +} + +static int pico_ipv4_frame_sock_push(struct pico_protocol *self, struct pico_frame *f); + +/* Interface: protocol definition */ +struct pico_protocol pico_proto_ipv4 = { + .name = "ipv4", + .proto_number = PICO_PROTO_IPV4, + .layer = PICO_LAYER_NETWORK, + .alloc = pico_ipv4_alloc, + .process_in = pico_ipv4_process_in, + .process_out = pico_ipv4_process_out, + .push = pico_ipv4_frame_sock_push, + .q_in = &in, + .q_out = &out, +}; + + +static int ipv4_route_compare(void *ka, void *kb) +{ + struct pico_ipv4_route *a = ka, *b = kb; + uint32_t a_nm, b_nm; + int cmp; + + a_nm = long_be(a->netmask.addr); + b_nm = long_be(b->netmask.addr); + + /* Routes are sorted by (host side) netmask len, then by addr, then by metric. */ + if (a_nm < b_nm) + return -1; + + if (b_nm < a_nm) + return 1; + + cmp = pico_ipv4_compare(&a->dest, &b->dest); + if (cmp) + return cmp; + + if (a->metric < b->metric) + return -1; + + if (a->metric > b->metric) + return 1; + + return 0; +} + + +static struct pico_ipv4_route default_bcast_route = { + .dest = {PICO_IP4_BCAST}, + .netmask = {PICO_IP4_BCAST}, + .gateway = { 0 }, + .link = NULL, + .metric = 1000 +}; + +static struct pico_ipv4_route *route_find_default_bcast(void) +{ + return &default_bcast_route; +} + + +static struct pico_ipv4_route *route_find(const struct pico_ip4 *addr) +{ + struct pico_ipv4_route *r; + struct pico_tree_node *index; + + if (addr->addr != PICO_IP4_BCAST) { + pico_tree_foreach_reverse(index, &Routes) { + r = index->keyValue; + if ((addr->addr & (r->netmask.addr)) == (r->dest.addr)) { + return r; + } + } + return NULL; + } + + return route_find_default_bcast(); +} + +struct pico_ip4 pico_ipv4_route_get_gateway(struct pico_ip4 *addr) +{ + struct pico_ip4 nullip; + struct pico_ipv4_route *route; + nullip.addr = 0U; + + if (!addr) { + pico_err = PICO_ERR_EINVAL; + return nullip; + } + + route = route_find(addr); + if (!route) { + pico_err = PICO_ERR_EHOSTUNREACH; + return nullip; + } + else + return route->gateway; +} + +struct pico_ip4 *pico_ipv4_source_find(const struct pico_ip4 *dst) +{ + struct pico_ip4 *myself = NULL; + struct pico_ipv4_route *rt; +#ifdef PICO_SUPPORT_AODV + union pico_address node_address; + node_address.ip4.addr = dst->addr; + if (dst->addr && pico_ipv4_is_unicast(dst->addr)) + pico_aodv_lookup(&node_address); + +#endif + + if (!dst) { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + + rt = route_find(dst); + if (rt && rt->link) { + myself = &rt->link->address; + } else { + pico_err = PICO_ERR_EHOSTUNREACH; + } + + return myself; +} + +struct pico_device *pico_ipv4_source_dev_find(const struct pico_ip4 *dst) +{ + struct pico_device *dev = NULL; + struct pico_ipv4_route *rt; + + if (!dst) { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + + rt = route_find(dst); + if (rt && rt->link) { + dev = rt->link->dev; + } else { + pico_err = PICO_ERR_EHOSTUNREACH; + } + + return dev; +} + + +#ifdef PICO_SUPPORT_MCAST +/* link + * | + * MCASTGroups + * | | | + * ------------ | ------------ + * | | | + * MCASTSources MCASTSources MCASTSources + * | | | | | | | | | | | | + * S S S S S S S S S S S S + * + * MCASTGroups: RBTree(mcast_group) + * MCASTSources: RBTree(source) + */ +static int ipv4_mcast_groups_cmp(void *ka, void *kb) +{ + struct pico_mcast_group *a = ka, *b = kb; + return pico_ipv4_compare(&a->mcast_addr, &b->mcast_addr); +} + +static int ipv4_mcast_sources_cmp(void *ka, void *kb) +{ + struct pico_ip4 *a = ka, *b = kb; + return pico_ipv4_compare(a, b); +} + +static void pico_ipv4_mcast_print_groups(struct pico_ipv4_link *mcast_link) +{ + uint16_t i = 0; + struct pico_mcast_group *g = NULL; + struct pico_ip4 *source = NULL; + struct pico_tree_node *index = NULL, *index2 = NULL; + (void) source; + + ip_mcast_dbg("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"); + ip_mcast_dbg("+ MULTICAST list interface %-16s +\n", mcast_link->dev->name); + ip_mcast_dbg("+---------------------------------------------------------------------------------+\n"); + ip_mcast_dbg("+ nr | interface | host group | reference count | filter mode | source +\n"); + ip_mcast_dbg("+---------------------------------------------------------------------------------+\n"); + + pico_tree_foreach(index, mcast_link->MCASTGroups) { + g = index->keyValue; + ip_mcast_dbg("+ %04d | %16s | %08X | %05u | %u | %8s +\n", i, mcast_link->dev->name, g->mcast_addr.addr, g->reference_count, g->filter_mode, ""); + pico_tree_foreach(index2, &g->MCASTSources) { + source = index2->keyValue; + ip_mcast_dbg("+ %4s | %16s | %8s | %5s | %s | %08X +\n", "", "", "", "", "", source->addr); + } + i++; + } + ip_mcast_dbg("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"); +} + +static int mcast_group_update(struct pico_mcast_group *g, struct pico_tree *MCASTFilter, uint8_t filter_mode) +{ + struct pico_tree_node *index = NULL, *_tmp = NULL; + struct pico_ip4 *source = NULL; + /* cleanup filter */ + pico_tree_foreach_safe(index, &g->MCASTSources, _tmp) { + source = index->keyValue; + pico_tree_delete(&g->MCASTSources, source); + PICO_FREE(source); + } + /* insert new filter */ + if (MCASTFilter) { + pico_tree_foreach(index, MCASTFilter) { + if (index->keyValue) { + source = PICO_ZALLOC(sizeof(struct pico_ip4)); + if (!source) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + source->addr = ((struct pico_ip4 *)index->keyValue)->addr; + pico_tree_insert(&g->MCASTSources, source); + } + } + } + + g->filter_mode = filter_mode; + return 0; +} + +int pico_ipv4_mcast_join(struct pico_ip4 *mcast_link, struct pico_ip4 *mcast_group, uint8_t reference_count, uint8_t filter_mode, struct pico_tree *MCASTFilter) +{ + struct pico_mcast_group *g = NULL, test = { + 0 + }; + struct pico_ipv4_link *link = NULL; + + if (mcast_link) + link = pico_ipv4_link_get(mcast_link); + + if (!link) + link = mcast_default_link; + + test.mcast_addr = *mcast_group; + g = pico_tree_findKey(link->MCASTGroups, &test); + if (g) { + if (reference_count) + g->reference_count++; + + pico_igmp_state_change(mcast_link, mcast_group, filter_mode, MCASTFilter, PICO_IGMP_STATE_UPDATE); + } else { + g = PICO_ZALLOC(sizeof(struct pico_mcast_group)); + if (!g) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + /* "non-existent" state of filter mode INCLUDE and empty source list */ + g->filter_mode = PICO_IP_MULTICAST_INCLUDE; + g->reference_count = 1; + g->mcast_addr = *mcast_group; + g->MCASTSources.root = &LEAF; + g->MCASTSources.compare = ipv4_mcast_sources_cmp; + pico_tree_insert(link->MCASTGroups, g); + pico_igmp_state_change(mcast_link, mcast_group, filter_mode, MCASTFilter, PICO_IGMP_STATE_CREATE); + } + + if (mcast_group_update(g, MCASTFilter, filter_mode) < 0) { + dbg("Error in mcast_group update\n"); + return -1; + } + + pico_ipv4_mcast_print_groups(link); + return 0; +} + +int pico_ipv4_mcast_leave(struct pico_ip4 *mcast_link, struct pico_ip4 *mcast_group, uint8_t reference_count, uint8_t filter_mode, struct pico_tree *MCASTFilter) +{ + + struct pico_mcast_group *g = NULL, test = { + 0 + }; + struct pico_ipv4_link *link = NULL; + struct pico_tree_node *index = NULL, *_tmp = NULL; + struct pico_ip4 *source = NULL; + + if (mcast_link) + link = pico_ipv4_link_get(mcast_link); + + if (!link) + link = mcast_default_link; + + if (!link) + return -1; + + test.mcast_addr = *mcast_group; + g = pico_tree_findKey(link->MCASTGroups, &test); + if (!g) { + pico_err = PICO_ERR_EINVAL; + return -1; + } else { + if (reference_count && (--(g->reference_count) < 1)) { + pico_igmp_state_change(mcast_link, mcast_group, filter_mode, MCASTFilter, PICO_IGMP_STATE_DELETE); + /* cleanup filter */ + pico_tree_foreach_safe(index, &g->MCASTSources, _tmp) { + source = index->keyValue; + pico_tree_delete(&g->MCASTSources, source); + PICO_FREE(source); + } + pico_tree_delete(link->MCASTGroups, g); + PICO_FREE(g); + } else { + pico_igmp_state_change(mcast_link, mcast_group, filter_mode, MCASTFilter, PICO_IGMP_STATE_UPDATE); + if (mcast_group_update(g, MCASTFilter, filter_mode) < 0) + return -1; + } + } + + pico_ipv4_mcast_print_groups(link); + return 0; +} + +struct pico_ipv4_link *pico_ipv4_get_default_mcastlink(void) +{ + return mcast_default_link; +} + +static int pico_ipv4_mcast_filter(struct pico_frame *f) +{ + struct pico_ipv4_link *link = NULL; + struct pico_tree_node *index = NULL, *index2 = NULL; + struct pico_mcast_group *g = NULL, test = { + 0 + }; + struct pico_ipv4_hdr *hdr = (struct pico_ipv4_hdr *) f->net_hdr; + + test.mcast_addr = hdr->dst; + + pico_tree_foreach(index, &Tree_dev_link) { + link = index->keyValue; + g = pico_tree_findKey(link->MCASTGroups, &test); + if (g) { + if (f->dev == link->dev) { + ip_mcast_dbg("MCAST: IP %08X is group member of current link %s\n", hdr->dst.addr, f->dev->name); + /* perform source filtering */ + switch (g->filter_mode) { + case PICO_IP_MULTICAST_INCLUDE: + pico_tree_foreach(index2, &g->MCASTSources) { + if (hdr->src.addr == ((struct pico_ip4 *)index2->keyValue)->addr) { + ip_mcast_dbg("MCAST: IP %08X in included interface source list\n", hdr->src.addr); + return 0; + } + } + ip_mcast_dbg("MCAST: IP %08X NOT in included interface source list\n", hdr->src.addr); + return -1; + + case PICO_IP_MULTICAST_EXCLUDE: + pico_tree_foreach(index2, &g->MCASTSources) { + if (hdr->src.addr == ((struct pico_ip4 *)index2->keyValue)->addr) { + ip_mcast_dbg("MCAST: IP %08X in excluded interface source list\n", hdr->src.addr); + return -1; + } + } + ip_mcast_dbg("MCAST: IP %08X NOT in excluded interface source list\n", hdr->src.addr); + return 0; + + default: + return -1; + } + } else { + ip_mcast_dbg("MCAST: IP %08X is group member of different link %s\n", hdr->dst.addr, link->dev->name); + } + } else { + ip_mcast_dbg("MCAST: IP %08X is not a group member of link %s\n", hdr->dst.addr, f->dev->name); + } + } + return -1; +} + +#else + +int pico_ipv4_mcast_join(struct pico_ip4 *mcast_link, struct pico_ip4 *mcast_group, uint8_t reference_count, uint8_t filter_mode, struct pico_tree *MCASTFilter) +{ + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} + +int pico_ipv4_mcast_leave(struct pico_ip4 *mcast_link, struct pico_ip4 *mcast_group, uint8_t reference_count, uint8_t filter_mode, struct pico_tree *MCASTFilter) +{ + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} + +struct pico_ipv4_link *pico_ipv4_get_default_mcastlink(void) +{ + pico_err = PICO_ERR_EPROTONOSUPPORT; + return NULL; +} +#endif /* PICO_SUPPORT_MCAST */ + +/* #define DEBUG_ROUTE */ +#ifdef DEBUG_ROUTE +void dbg_route(void) +{ + struct pico_ipv4_route *r; + struct pico_tree_node *index; + int count_hosts = 0; + dbg("==== ROUTING TABLE =====\n"); + pico_tree_foreach(index, &Routes) { + r = index->keyValue; + dbg("Route to %08x/%08x, gw %08x, dev: %s, metric: %d\n", r->dest.addr, r->netmask.addr, r->gateway.addr, r->link->dev->name, r->metric); + if (r->netmask.addr == 0xFFFFFFFF) + count_hosts++; + } + dbg("================ total HOST nodes: %d ======\n\n\n", count_hosts); +} +#else +#define dbg_route() do { } while(0) +#endif + +int pico_ipv4_frame_push(struct pico_frame *f, struct pico_ip4 *dst, uint8_t proto) +{ + + struct pico_ipv4_route *route; + struct pico_ipv4_link *link; + struct pico_ipv4_hdr *hdr; + uint8_t ttl = PICO_IPV4_DEFAULT_TTL; + uint8_t vhl = 0x45; /* version 4, header length 20 */ + static uint16_t ipv4_progressive_id = 0x91c0; +#ifdef PICO_SUPPORT_MCAST + struct pico_tree_node *index; +#endif + + if (!f || !dst) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + + hdr = (struct pico_ipv4_hdr *) f->net_hdr; + if (!hdr) { + dbg("IP header error\n"); + pico_err = PICO_ERR_EINVAL; + goto drop; + } + + if (dst->addr == 0) { + dbg("IP destination addr error\n"); + pico_err = PICO_ERR_EINVAL; + goto drop; + } + + route = route_find(dst); + if (!route) { + /* dbg("Route to %08x not found.\n", long_be(dst->addr)); */ + + + pico_err = PICO_ERR_EHOSTUNREACH; + goto drop; + } else { + link = route->link; +#ifdef PICO_SUPPORT_MCAST + if (pico_ipv4_is_multicast(dst->addr)) { /* if multicast */ + switch (proto) { + case PICO_PROTO_UDP: + if (pico_udp_get_mc_ttl(f->sock, &ttl) < 0) + ttl = PICO_IP_DEFAULT_MULTICAST_TTL; + + break; +#ifdef PICO_SUPPORT_IGMP + case PICO_PROTO_IGMP: + vhl = 0x46; /* header length 24 */ + ttl = 1; + /* router alert (RFC 2113) */ + hdr->options[0] = 0x94; + hdr->options[1] = 0x04; + hdr->options[2] = 0x00; + hdr->options[3] = 0x00; + if (f->dev && link->dev != f->dev) { /* default link is not requested link */ + pico_tree_foreach(index, &Tree_dev_link) { + link = index->keyValue; + if (link->dev == f->dev) + break; + } + } + + break; +#endif + default: + ttl = PICO_IPV4_DEFAULT_TTL; + } + } + +#endif + } + + hdr->vhl = vhl; + hdr->len = short_be((uint16_t)(f->transport_len + f->net_len)); + hdr->id = short_be(ipv4_progressive_id); + + if ( +#ifdef PICO_SUPPORT_IPV4FRAG + (0 == (f->frag & PICO_IPV4_MOREFRAG)) && +#endif + 1 ) + ipv4_progressive_id++; + + if (f->send_ttl > 0) { + ttl = f->send_ttl; + } + + hdr->dst.addr = dst->addr; + hdr->src.addr = link->address.addr; + hdr->ttl = ttl; + hdr->tos = f->send_tos; + hdr->proto = proto; + hdr->frag = short_be(PICO_IPV4_DONTFRAG); + +#ifdef PICO_SUPPORT_IPV4FRAG +# ifdef PICO_SUPPORT_UDP + if (proto == PICO_PROTO_UDP) { + /* first fragment, can not use transport_len to calculate IP length */ + if (f->transport_hdr != f->payload) + hdr->len = short_be((uint16_t)(f->payload_len + sizeof(struct pico_udp_hdr) + f->net_len)); + + /* set fragmentation flags and offset calculated in socket layer */ + hdr->frag = short_be(f->frag); + } + + if (proto == PICO_PROTO_ICMP4) + { + hdr->frag = short_be(f->frag); + } + +# endif +#endif /* PICO_SUPPORT_IPV4FRAG */ + pico_ipv4_checksum(f); + + if (f->sock && f->sock->dev) { + /* if the socket has its device set, use that (currently used for DHCP) */ + f->dev = f->sock->dev; + } else { + f->dev = link->dev; + if (f->sock) + f->sock->dev = f->dev; + } + +#ifdef PICO_SUPPORT_MCAST + if (pico_ipv4_is_multicast(hdr->dst.addr)) { + struct pico_frame *cpy; + /* Sending UDP multicast datagram, am I member? If so, loopback copy */ + if ((proto != PICO_PROTO_IGMP) && (pico_ipv4_mcast_filter(f) == 0)) { + ip_mcast_dbg("MCAST: sender is member of group, loopback copy\n"); + cpy = pico_frame_copy(f); + pico_enqueue(&in, cpy); + } + } + +#endif + +/* #ifdef PICO_SUPPORT_AODV */ +#if 0 + { + union pico_address node_address; + node_address.ip4.addr = hdr->dst.addr; + if(hdr->dst.addr && pico_ipv4_is_unicast(hdr->dst.addr)) + pico_aodv_lookup(&node_address); + } +#endif + + if (pico_ipv4_link_get(&hdr->dst)) { + /* it's our own IP */ + return pico_enqueue(&in, f); + } else{ + /* TODO: Check if there are members subscribed here */ + return pico_enqueue(&out, f); + } + +drop: + pico_frame_discard(f); + return -1; +} + + +static int pico_ipv4_frame_sock_push(struct pico_protocol *self, struct pico_frame *f) +{ + struct pico_ip4 *dst; + struct pico_remote_endpoint *remote_endpoint = (struct pico_remote_endpoint *) f->info; + IGNORE_PARAMETER(self); + + if (!f->sock) { + pico_frame_discard(f); + return -1; + } + + if (remote_endpoint) { + dst = &remote_endpoint->remote_addr.ip4; + } else { + dst = &f->sock->remote_addr.ip4; + } + + return pico_ipv4_frame_push(f, dst, (uint8_t)f->sock->proto->proto_number); +} + + +int MOCKABLE pico_ipv4_route_add(struct pico_ip4 address, struct pico_ip4 netmask, struct pico_ip4 gateway, int metric, struct pico_ipv4_link *link) +{ + struct pico_ipv4_route test, *new; + test.dest.addr = address.addr; + test.netmask.addr = netmask.addr; + test.metric = (uint32_t)metric; + + if (pico_tree_findKey(&Routes, &test)) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + new = PICO_ZALLOC(sizeof(struct pico_ipv4_route)); + if (!new) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + new->dest.addr = address.addr; + new->netmask.addr = netmask.addr; + new->gateway.addr = gateway.addr; + new->metric = (uint32_t)metric; + if (gateway.addr == 0) { + /* No gateway provided, use the link */ + new->link = link; + } else { + struct pico_ipv4_route *r = route_find(&gateway); + if (!r ) { /* Specified Gateway is unreachable */ + pico_err = PICO_ERR_EHOSTUNREACH; + PICO_FREE(new); + return -1; + } + + if (r->gateway.addr) { /* Specified Gateway is not a neighbor */ + pico_err = PICO_ERR_ENETUNREACH; + PICO_FREE(new); + return -1; + } + + new->link = r->link; + } + + if (!new->link) { + pico_err = PICO_ERR_EINVAL; + PICO_FREE(new); + return -1; + } + + pico_tree_insert(&Routes, new); + dbg_route(); + return 0; +} + +int pico_ipv4_route_del(struct pico_ip4 address, struct pico_ip4 netmask, int metric) +{ + struct pico_ipv4_route test, *found; + + test.dest.addr = address.addr; + test.netmask.addr = netmask.addr; + test.metric = (uint32_t)metric; + + found = pico_tree_findKey(&Routes, &test); + if (found) { + + pico_tree_delete(&Routes, found); + PICO_FREE(found); + + dbg_route(); + return 0; + } + + pico_err = PICO_ERR_EINVAL; + return -1; +} + + +extern int pico_ipv4_link_add(struct pico_device *dev, struct pico_ip4 address, struct pico_ip4 netmask) +{ + struct pico_ipv4_link test, *new; + struct pico_ip4 network, gateway; + char ipstr[30]; + + if (!dev) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + test.address.addr = address.addr; + test.netmask.addr = netmask.addr; + test.dev = dev; + /** XXX: Valid netmask / unicast address test **/ + + if (pico_tree_findKey(&Tree_dev_link, &test)) { + pico_err = PICO_ERR_EADDRINUSE; + return -1; + } + + /** XXX: Check for network already in use (e.g. trying to assign 10.0.0.1/24 where 10.1.0.1/8 is in use) **/ + new = PICO_ZALLOC(sizeof(struct pico_ipv4_link)); + if (!new) { + dbg("IPv4: Out of memory!\n"); + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + new->address.addr = address.addr; + new->netmask.addr = netmask.addr; + new->dev = dev; +#ifdef PICO_SUPPORT_MCAST + new->MCASTGroups = PICO_ZALLOC(sizeof(struct pico_tree)); + if (!new->MCASTGroups) { + PICO_FREE(new); + dbg("IPv4: Out of memory!\n"); + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + new->MCASTGroups->root = &LEAF; + new->MCASTGroups->compare = ipv4_mcast_groups_cmp; +#ifdef PICO_SUPPORT_IGMP + new->mcast_compatibility = PICO_IGMPV3; /* default RFC 3376 $7.2.1 */ + new->mcast_last_query_interval = PICO_IGMP_QUERY_INTERVAL; +#endif +#endif + + pico_tree_insert(&Tree_dev_link, new); +#ifdef PICO_SUPPORT_MCAST + do { + struct pico_ip4 mcast_all_hosts, mcast_addr, mcast_nm, mcast_gw; + if (!mcast_default_link) { + mcast_addr.addr = long_be(0xE0000000); /* 224.0.0.0 */ + mcast_nm.addr = long_be(0xF0000000); /* 15.0.0.0 */ + mcast_gw.addr = long_be(0x00000000); + mcast_default_link = new; + pico_ipv4_route_add(mcast_addr, mcast_nm, mcast_gw, 1, new); + } + + mcast_all_hosts.addr = PICO_MCAST_ALL_HOSTS; + pico_ipv4_mcast_join(&address, &mcast_all_hosts, 1, PICO_IP_MULTICAST_EXCLUDE, NULL); + } while(0); +#endif + + network.addr = address.addr & netmask.addr; + gateway.addr = 0U; + pico_ipv4_route_add(network, netmask, gateway, 1, new); + pico_ipv4_to_string(ipstr, new->address.addr); + dbg("Assigned ipv4 %s to device %s\n", ipstr, new->dev->name); + if (default_bcast_route.link == NULL) + default_bcast_route.link = new; + + return 0; +} + +static int pico_ipv4_cleanup_routes(struct pico_ipv4_link *link) +{ + struct pico_tree_node *index = NULL, *tmp = NULL; + struct pico_ipv4_route *route = NULL; + + pico_tree_foreach_safe(index, &Routes, tmp) { + route = index->keyValue; + if (link == route->link) + pico_ipv4_route_del(route->dest, route->netmask, (int)route->metric); + } + return 0; +} + +void MOCKABLE pico_ipv4_route_set_bcast_link(struct pico_ipv4_link *link) +{ + if (link) + default_bcast_route.link = link; +} + +int pico_ipv4_link_del(struct pico_device *dev, struct pico_ip4 address) +{ + struct pico_ipv4_link test, *found; + + if (!dev) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + test.address.addr = address.addr; + test.dev = dev; + found = pico_tree_findKey(&Tree_dev_link, &test); + if (!found) { + pico_err = PICO_ERR_ENXIO; + return -1; + } + +#ifdef PICO_SUPPORT_MCAST + do { + struct pico_ip4 mcast_all_hosts, mcast_addr, mcast_nm; + struct pico_mcast_group *g = NULL; + struct pico_tree_node *index, *_tmp; + if (found == mcast_default_link) { + mcast_addr.addr = long_be(0xE0000000); /* 224.0.0.0 */ + mcast_nm.addr = long_be(0xF0000000); /* 15.0.0.0 */ + mcast_default_link = NULL; + pico_ipv4_route_del(mcast_addr, mcast_nm, 1); + } + + mcast_all_hosts.addr = PICO_MCAST_ALL_HOSTS; + pico_ipv4_mcast_leave(&address, &mcast_all_hosts, 1, PICO_IP_MULTICAST_EXCLUDE, NULL); + pico_tree_foreach_safe(index, found->MCASTGroups, _tmp) { + g = index->keyValue; + pico_tree_delete(found->MCASTGroups, g); + PICO_FREE(g); + } + } while(0); + PICO_FREE(found->MCASTGroups); +#endif + + pico_ipv4_cleanup_routes(found); + pico_tree_delete(&Tree_dev_link, found); + if (default_bcast_route.link == found) + default_bcast_route.link = NULL; + + PICO_FREE(found); + + return 0; +} + + +struct pico_ipv4_link *pico_ipv4_link_get(struct pico_ip4 *address) +{ + struct pico_ipv4_link test = { + 0 + }, *found = NULL; + test.address.addr = address->addr; + + found = pico_tree_findKey(&Tree_dev_link, &test); + if (!found) + return NULL; + else + return found; +} + +struct pico_ipv4_link *MOCKABLE pico_ipv4_link_by_dev(struct pico_device *dev) +{ + struct pico_tree_node *index = NULL; + struct pico_ipv4_link *link = NULL; + + pico_tree_foreach(index, &Tree_dev_link) { + link = index->keyValue; + if (link->dev == dev) + return link; + } + return NULL; +} + +struct pico_ipv4_link *pico_ipv4_link_by_dev_next(struct pico_device *dev, struct pico_ipv4_link *last) +{ + struct pico_tree_node *index = NULL; + struct pico_ipv4_link *link = NULL; + int valid = 0; + + if (last == NULL) + valid = 1; + + pico_tree_foreach(index, &Tree_dev_link) { + link = index->keyValue; + if (link->dev == dev) { + if (last == link) + valid = 1; + else if (valid > 0) + return link; + } + } + return NULL; +} + +struct pico_device *MOCKABLE pico_ipv4_link_find(struct pico_ip4 *address) +{ + struct pico_ipv4_link test, *found; + if (!address) { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + + test.dev = NULL; + test.address.addr = address->addr; + found = pico_tree_findKey(&Tree_dev_link, &test); + if (!found) { + pico_err = PICO_ERR_ENXIO; + return NULL; + } + + return found->dev; +} + + +static int pico_ipv4_rebound_large(struct pico_frame *f) +{ +#ifdef PICO_SUPPORT_IPV4FRAG + uint16_t total_payload_written = 0; + uint32_t len = f->transport_len; + struct pico_frame *fr; + struct pico_ip4 dst; + struct pico_ipv4_hdr *hdr; + hdr = (struct pico_ipv4_hdr *) f->net_hdr; + dst.addr = hdr->src.addr; + + while(total_payload_written < len) { + uint32_t space = (uint32_t)len - total_payload_written; + if (space > PICO_IPV4_MAXPAYLOAD) + space = PICO_IPV4_MAXPAYLOAD; + + fr = pico_ipv4_alloc(&pico_proto_ipv4, (uint16_t)space); + if (!fr) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + if (space + total_payload_written < len) + { + fr->frag |= PICO_IPV4_MOREFRAG; + } + else + { + fr->frag &= PICO_IPV4_FRAG_MASK; + } + + fr->frag = (((total_payload_written) >> 3u) & 0xffffu) | fr->frag; + + memcpy(fr->transport_hdr, f->transport_hdr + total_payload_written, fr->transport_len); + if (pico_ipv4_frame_push(fr, &dst, hdr->proto) > 0) { + total_payload_written = (uint16_t)((uint16_t)fr->transport_len + total_payload_written); + } else { + pico_frame_discard(fr); + break; + } + } /* while() */ + return (int)total_payload_written; +#else + (void)f; + return -1; +#endif +} + +int pico_ipv4_rebound(struct pico_frame *f) +{ + struct pico_ip4 dst; + struct pico_ipv4_hdr *hdr; + if (!f) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + hdr = (struct pico_ipv4_hdr *) f->net_hdr; + if (!hdr) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + dst.addr = hdr->src.addr; + if (f->transport_len > PICO_IPV4_MAXPAYLOAD) { + return pico_ipv4_rebound_large(f); + } + + return pico_ipv4_frame_push(f, &dst, hdr->proto); +} + +static int pico_ipv4_pre_forward_checks(struct pico_frame *f) +{ + static uint16_t last_id = 0; + static uint16_t last_proto = 0; + static struct pico_ip4 last_src = { + 0 + }; + static struct pico_ip4 last_dst = { + 0 + }; + struct pico_ipv4_hdr *hdr = (struct pico_ipv4_hdr *)f->net_hdr; + + /* Decrease TTL, check if expired */ + hdr->ttl = (uint8_t)(hdr->ttl - 1); + if (hdr->ttl < 1) { + pico_notify_ttl_expired(f); + dbg(" ------------------- TTL EXPIRED\n"); + return -1; + } + + /* HACK: increase crc to compensate decreased TTL */ + hdr->crc++; + + /* If source is local, discard anyway (packets bouncing back and forth) */ + if (pico_ipv4_link_get(&hdr->src)) + return -1; + + /* If this was the last forwarded packet, silently discard to prevent duplications */ + if ((last_src.addr == hdr->src.addr) && (last_id == hdr->id) + && (last_dst.addr == hdr->dst.addr) && (last_proto == hdr->proto)) { + return -1; + } else { + last_src.addr = hdr->src.addr; + last_dst.addr = hdr->dst.addr; + last_id = hdr->id; + last_proto = hdr->proto; + } + + return 0; +} + +static int pico_ipv4_forward_check_dev(struct pico_frame *f) +{ + if (f->dev->eth != NULL) + f->len -= PICO_SIZE_ETHHDR; + + if (f->len > f->dev->mtu) { + pico_notify_pkt_too_big(f); + return -1; + } + + return 0; +} + +static int pico_ipv4_forward(struct pico_frame *f) +{ + struct pico_ipv4_hdr *hdr = (struct pico_ipv4_hdr *)f->net_hdr; + struct pico_ipv4_route *rt; + if (!hdr) { + return -1; + } + + rt = route_find(&hdr->dst); + if (!rt) { + pico_notify_dest_unreachable(f); + return -1; + } + + f->dev = rt->link->dev; + + if (pico_ipv4_pre_forward_checks(f) < 0) + return -1; + + pico_ipv4_nat_outbound(f, &rt->link->address); + + f->start = f->net_hdr; + + if (pico_ipv4_forward_check_dev(f) < 0) + return -1; + + pico_sendto_dev(f); + return 0; + +} + +int pico_ipv4_is_broadcast(uint32_t addr) +{ + struct pico_ipv4_link *link; + struct pico_tree_node *index; + if (addr == PICO_IP4_BCAST) + return 1; + + pico_tree_foreach(index, &Tree_dev_link) { + link = index->keyValue; + if ((link->address.addr | (~link->netmask.addr)) == addr) + return 1; + } + return 0; +} + +void pico_ipv4_unreachable(struct pico_frame *f, int err) +{ + struct pico_ipv4_hdr *hdr = (struct pico_ipv4_hdr *) f->net_hdr; +#if defined PICO_SUPPORT_TCP || defined PICO_SUPPORT_UDP + f->transport_hdr = ((uint8_t *)f->net_hdr) + PICO_SIZE_IP4HDR; + pico_transport_error(f, hdr->proto, err); +#endif +} + +int pico_ipv4_cleanup_links(struct pico_device *dev) +{ + struct pico_tree_node *index = NULL, *_tmp = NULL; + struct pico_ipv4_link *link = NULL; + + pico_tree_foreach_safe(index, &Tree_dev_link, _tmp) { + link = index->keyValue; + if (dev == link->dev) + pico_ipv4_link_del(dev, link->address); + } + return 0; +} + + +#endif diff --git a/ext/picotcp/modules/pico_ipv4.h b/ext/picotcp/modules/pico_ipv4.h new file mode 100644 index 0000000..095423c --- /dev/null +++ b/ext/picotcp/modules/pico_ipv4.h @@ -0,0 +1,129 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + . + + *********************************************************************/ +#ifndef INCLUDE_PICO_IPV4 +#define INCLUDE_PICO_IPV4 +#include "pico_addressing.h" +#include "pico_protocol.h" +#include "pico_tree.h" +#include "pico_device.h" + +#define PICO_IPV4_INADDR_ANY 0x00000000U + +#define PICO_IPV4_MTU (1500u) +#define PICO_SIZE_IP4HDR (uint32_t)((sizeof(struct pico_ipv4_hdr))) +#define PICO_IPV4_MAXPAYLOAD (PICO_IPV4_MTU - PICO_SIZE_IP4HDR) +#define PICO_IPV4_DONTFRAG 0x4000U +#define PICO_IPV4_MOREFRAG 0x2000U +#define PICO_IPV4_EVIL 0x8000U +#define PICO_IPV4_FRAG_MASK 0x1FFFU +#define PICO_IPV4_DEFAULT_TTL 64 +#ifndef MBED + #define PICO_IPV4_FRAG_MAX_SIZE (uint32_t)(63 * 1024) +#else + #define PICO_IPV4_FRAG_MAX_SIZE PICO_DEFAULT_SOCKETQ +#endif + +extern struct pico_protocol pico_proto_ipv4; + +PACKED_STRUCT_DEF pico_ipv4_hdr { + uint8_t vhl; + uint8_t tos; + uint16_t len; + uint16_t id; + uint16_t frag; + uint8_t ttl; + uint8_t proto; + uint16_t crc; + struct pico_ip4 src; + struct pico_ip4 dst; + uint8_t options[]; +}; + +PACKED_STRUCT_DEF pico_ipv4_pseudo_hdr +{ + struct pico_ip4 src; + struct pico_ip4 dst; + uint8_t zeros; + uint8_t proto; + uint16_t len; +}; + +/* Interface: link to device */ +struct pico_mcast_list; + +struct pico_ipv4_link +{ + struct pico_device *dev; + struct pico_ip4 address; + struct pico_ip4 netmask; +#ifdef PICO_SUPPORT_MCAST + struct pico_tree *MCASTGroups; + uint8_t mcast_compatibility; + uint8_t mcast_last_query_interval; +#endif +}; + +#ifdef PICO_SUPPORT_MCAST +struct pico_mcast_group { + uint8_t filter_mode; + uint16_t reference_count; + struct pico_ip4 mcast_addr; + struct pico_tree MCASTSources; +}; +#endif + +struct pico_ipv4_route +{ + struct pico_ip4 dest; + struct pico_ip4 netmask; + struct pico_ip4 gateway; + struct pico_ipv4_link *link; + uint32_t metric; +}; + +extern struct pico_tree Routes; + +int pico_ipv4_compare(struct pico_ip4 *a, struct pico_ip4 *b); +int pico_ipv4_to_string(char *ipbuf, const uint32_t ip); +int pico_ipv4_valid_netmask(uint32_t mask); +int pico_ipv4_is_unicast(uint32_t address); +int pico_ipv4_is_multicast(uint32_t address); +int pico_ipv4_is_broadcast(uint32_t addr); +int pico_ipv4_is_loopback(uint32_t addr); +int pico_ipv4_is_valid_src(uint32_t addr, struct pico_device *dev); + +#ifdef __cplusplus +extern "C" { +#endif +int pico_string_to_ipv4(const char *ipstr, uint32_t *ip); +int pico_ipv4_link_add(struct pico_device *dev, struct pico_ip4 address, struct pico_ip4 netmask); +#ifdef __cplusplus +} +#endif +int pico_ipv4_link_del(struct pico_device *dev, struct pico_ip4 address); +int pico_ipv4_rebound(struct pico_frame *f); + +int pico_ipv4_frame_push(struct pico_frame *f, struct pico_ip4 *dst, uint8_t proto); +struct pico_ipv4_link *pico_ipv4_link_get(struct pico_ip4 *address); +struct pico_ipv4_link *pico_ipv4_link_by_dev(struct pico_device *dev); +struct pico_ipv4_link *pico_ipv4_link_by_dev_next(struct pico_device *dev, struct pico_ipv4_link *last); +struct pico_device *pico_ipv4_link_find(struct pico_ip4 *address); +struct pico_ip4 *pico_ipv4_source_find(const struct pico_ip4 *dst); +struct pico_device *pico_ipv4_source_dev_find(const struct pico_ip4 *dst); +int pico_ipv4_route_add(struct pico_ip4 address, struct pico_ip4 netmask, struct pico_ip4 gateway, int metric, struct pico_ipv4_link *link); +int pico_ipv4_route_del(struct pico_ip4 address, struct pico_ip4 netmask, int metric); +struct pico_ip4 pico_ipv4_route_get_gateway(struct pico_ip4 *addr); +void pico_ipv4_route_set_bcast_link(struct pico_ipv4_link *link); +void pico_ipv4_unreachable(struct pico_frame *f, int err); + +int pico_ipv4_mcast_join(struct pico_ip4 *mcast_link, struct pico_ip4 *mcast_group, uint8_t reference_count, uint8_t filter_mode, struct pico_tree *MCASTFilter); +int pico_ipv4_mcast_leave(struct pico_ip4 *mcast_link, struct pico_ip4 *mcast_group, uint8_t reference_count, uint8_t filter_mode, struct pico_tree *MCASTFilter); +struct pico_ipv4_link *pico_ipv4_get_default_mcastlink(void); +int pico_ipv4_cleanup_links(struct pico_device *dev); + +#endif /* _INCLUDE_PICO_IPV4 */ diff --git a/ext/picotcp/modules/pico_ipv6.c b/ext/picotcp/modules/pico_ipv6.c new file mode 100644 index 0000000..7cc25e3 --- /dev/null +++ b/ext/picotcp/modules/pico_ipv6.c @@ -0,0 +1,1921 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + Authors: Daniele Lacamera, Kristof Roelants + *********************************************************************/ + + +#include "pico_ipv6.h" +#include "pico_icmp6.h" +#include "pico_config.h" +#include "pico_stack.h" +#include "pico_eth.h" +#include "pico_udp.h" +#include "pico_tcp.h" +#include "pico_socket.h" +#include "pico_device.h" +#include "pico_tree.h" +#include "pico_fragments.h" +#include "pico_mld.h" + +#ifdef PICO_SUPPORT_IPV6 + + +#define PICO_IPV6_EXTHDR_OPT_PAD1 0 +#define PICO_IPV6_EXTHDR_OPT_PADN 1 +#define PICO_IPV6_EXTHDR_OPT_SRCADDR 201 + +#define PICO_IPV6_EXTHDR_OPT_ACTION_MASK 0xC0 /* highest-order two bits */ +#define PICO_IPV6_EXTHDR_OPT_ACTION_SKIP 0x00 /* skip and continue processing */ +#define PICO_IPV6_EXTHDR_OPT_ACTION_DISCARD 0x40 /* discard packet */ +#define PICO_IPV6_EXTHDR_OPT_ACTION_DISCARD_SI 0x80 /* discard and send ICMP parameter problem */ +#define PICO_IPV6_EXTHDR_OPT_ACTION_DISCARD_SINM 0xC0 /* discard and send ICMP parameter problem if not multicast */ + +#define PICO_IPV6_MAX_RTR_SOLICITATION_DELAY 1000 +#define PICO_IPV6_DEFAULT_DAD_RETRANS 1 + +#define ipv6_dbg(...) do { }while(0); +#define ipv6_mcast_dbg do{ }while(0); +static struct pico_ipv6_link *mcast_default_link_ipv6 = NULL; + +/* queues */ +static struct pico_queue ipv6_in; +static struct pico_queue ipv6_out; + +const uint8_t PICO_IP6_ANY[PICO_SIZE_IP6] = { + 0 +}; +#ifdef PICO_SUPPORT_MCAST +static int pico_ipv6_mcast_filter(struct pico_frame *f); +#endif + + +int pico_ipv6_compare(struct pico_ip6 *a, struct pico_ip6 *b) +{ + uint32_t i; + for (i = 0; i < sizeof(struct pico_ip6); i++) { + if (a->addr[i] < b->addr[i]) + return -1; + + if (a->addr[i] > b->addr[i]) + return 1; + } + return 0; +} + +static int ipv6_link_compare(void *ka, void *kb) +{ + struct pico_ipv6_link *a = ka, *b = kb; + struct pico_ip6 *a_addr, *b_addr; + int ret; + a_addr = &a->address; + b_addr = &b->address; + + ret = pico_ipv6_compare(a_addr, b_addr); + if (ret) + return ret; + + /* zero can be assigned multiple times (e.g. for DHCP) */ + if (a->dev != NULL && b->dev != NULL && !memcmp(a->address.addr, PICO_IP6_ANY, PICO_SIZE_IP6) && !memcmp(b->address.addr, PICO_IP6_ANY, PICO_SIZE_IP6)) { + /* XXX change PICO_IP6_ANY */ + if (a->dev < b->dev) + return -1; + + if (a->dev > b->dev) + return 1; + } + + return 0; +} + +static inline int ipv6_compare_metric(struct pico_ipv6_route *a, struct pico_ipv6_route *b) +{ + if (a->metric < b->metric) + return -1; + + if (a->metric > b->metric) + return 1; + + return 0; +} + +static int ipv6_route_compare(void *ka, void *kb) +{ + struct pico_ipv6_route *a = ka, *b = kb; + int ret; + + /* Routes are sorted by (host side) netmask len, then by addr, then by metric. */ + ret = pico_ipv6_compare(&a->netmask, &b->netmask); + if (ret) + return ret; + + ret = pico_ipv6_compare(&a->dest, &b->dest); + if (ret) + return ret; + + return ipv6_compare_metric(a, b); + +} + +PICO_TREE_DECLARE(Tree_dev_ip6_link, ipv6_link_compare); +PICO_TREE_DECLARE(IPV6Routes, ipv6_route_compare); +PICO_TREE_DECLARE(IPV6Links, ipv6_link_compare); + +static char pico_ipv6_dec_to_char(uint8_t u) +{ + if (u < 10) + return (char)('0' + u); + else if (u < 16) + return (char)('a' + (u - 10)); + else + return '0'; +} + +static int pico_ipv6_hex_to_dec(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + + if (c >= 'a' && c <= 'f') + return 10 + (c - 'a'); + + if (c >= 'A' && c <= 'F') + return 10 + (c - 'A'); + + return 0; +} + +int pico_ipv6_to_string(char *ipbuf, const uint8_t ip[PICO_SIZE_IP6]) +{ + uint8_t dec = 0, i = 0; + + if (!ipbuf || !ip) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* every nibble is one char */ + for (i = 0; i < ((uint8_t)PICO_SIZE_IP6) * 2u; ++i) { + if (i % 4 == 0 && i != 0) + *ipbuf++ = ':'; + + if (i % 2 == 0) { /* upper nibble */ + dec = ip[i / 2] >> 4; + } else { /* lower nibble */ + dec = ip[i / 2] & 0x0F; + } + + *ipbuf++ = pico_ipv6_dec_to_char(dec); + } + *ipbuf = '\0'; + + return 0; +} + +extern int pico_string_to_ipv6(const char *ipstr, uint8_t *ip) +{ + uint8_t buf[PICO_SIZE_IP6] = { + 0 + }; + uint8_t doublecolon = 0, byte = 0; + char p = 0; + int i = 0, diff = 0, nibble = 0, hex = 0, colons = 0; + int zeros = 0, shift = 0; + + pico_err = PICO_ERR_EINVAL; + if (!ipstr || !ip) + return -1; + + memset(ip, 0, PICO_SIZE_IP6); + + while((p = *ipstr++) != 0) + { + if (pico_is_hex(p) || (p == ':') || *ipstr == '\0') { /* valid signs */ + if (pico_is_hex(p)) { + buf[byte] = (uint8_t)((buf[byte] << 4) + pico_ipv6_hex_to_dec(p)); + if (++nibble % 2 == 0) + ++byte; + } + + if (p == ':' || *ipstr == '\0') { /* account for leftout leading zeros */ + ++hex; + if (p == ':') + ++colons; + + diff = (hex * 4) - nibble; + nibble += diff; + switch (diff) { + case 0: + /* 16-bit hex block ok f.e. 1db8 */ + break; + case 1: + /* one zero f.e. db8: byte = 1, buf[byte-1] = 0xdb, buf[byte] = 0x08 */ + buf[byte] |= (uint8_t)(buf[byte - 1] << 4); + buf[byte - 1] >>= 4; + byte++; + break; + case 2: + /* two zeros f.e. b8: byte = 1, buf[byte] = 0x00, buf[byte-1] = 0xb8 */ + buf[byte] = buf[byte - 1]; + buf[byte - 1] = 0x00; + byte++; + break; + case 3: + /* three zeros f.e. 8: byte = 0, buf[byte] = 0x08, buf[byte+1] = 0x00 */ + buf[byte + 1] = buf[byte]; + buf[byte] = 0x00; + byte = (uint8_t)(byte + 2); + break; + case 4: + /* case of :: */ + if (doublecolon && colons != 2) /* catch case x::x::x but not ::x */ + return -1; + else + doublecolon = byte; + + break; + default: + /* case of missing colons f.e. 20011db8 instead of 2001:1db8 */ + return -1; + } + } + } else { + return -1; + } + } + if (colons < 2) /* valid IPv6 has atleast two colons */ + return -1; + + /* account for leftout :: zeros */ + zeros = PICO_SIZE_IP6 - byte; + if (zeros) { + shift = PICO_SIZE_IP6 - zeros - doublecolon; + for (i = shift; i >= 0; --i) { + /* (i-1) as arrays are indexed from 0 onwards */ + if ((doublecolon + (i - 1)) >= 0) + buf[doublecolon + zeros + (i - 1)] = buf[doublecolon + (i - 1)]; + } + memset(&buf[doublecolon], 0, (size_t)zeros); + } + + memcpy(ip, buf, 16); + pico_err = PICO_ERR_NOERR; + return 0; +} + +int pico_ipv6_is_linklocal(const uint8_t addr[PICO_SIZE_IP6]) +{ + /* prefix: fe80::/10 */ + if ((addr[0] == 0xfe) && ((addr[1] >> 6) == 0x02)) + return 1; + + return 0; +} + +int pico_ipv6_is_sitelocal(const uint8_t addr[PICO_SIZE_IP6]) +{ + /* prefix: fec0::/10 */ + if ((addr[0] == 0xfe) && ((addr[1] >> 6) == 0x03)) + return 1; + + return 0; +} + +int pico_ipv6_is_uniquelocal(const uint8_t addr[PICO_SIZE_IP6]) +{ + /* prefix: fc00::/7 */ + if (((addr[0] >> 1) == 0x7e)) + return 1; + + return 0; +} + +int pico_ipv6_is_global(const uint8_t addr[PICO_SIZE_IP6]) +{ + /* prefix: 2000::/3 */ + if (((addr[0] >> 5) == 0x01)) + return 1; + + return 0; +} + +int pico_ipv6_is_localhost(const uint8_t addr[PICO_SIZE_IP6]) +{ + const uint8_t localhost[PICO_SIZE_IP6] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 + }; + if (memcmp(addr, localhost, PICO_SIZE_IP6) == 0) + return 1; + + return 0; + +} + +int pico_ipv6_is_unicast(struct pico_ip6 *a) +{ + if (pico_ipv6_is_global(a->addr)) + return 1; + else if (pico_ipv6_is_uniquelocal(a->addr)) + return 1; + else if (pico_ipv6_is_sitelocal(a->addr)) + return 1; + else if (pico_ipv6_is_linklocal(a->addr)) + return 1; + else if (pico_ipv6_is_localhost(a->addr)) + return 1; + else if(pico_ipv6_link_get(a)) + return 1; + else + return 0; + +} + +int pico_ipv6_is_multicast(const uint8_t addr[PICO_SIZE_IP6]) +{ + /* prefix: ff00::/8 */ + if ((addr[0] == 0xff)) + return 1; + + return 0; +} + +int pico_ipv6_is_allhosts_multicast(const uint8_t addr[PICO_SIZE_IP6]) +{ + struct pico_ip6 allhosts = {{ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }}; + return !memcmp(allhosts.addr, addr, PICO_SIZE_IP6); +} + +int pico_ipv6_is_solicited(const uint8_t addr[PICO_SIZE_IP6]) +{ + struct pico_ip6 solicited_node = {{ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x00, 0x00, 0x00 }}; + return !memcmp(solicited_node.addr, addr, 13); +} + +int pico_ipv6_is_solnode_multicast(const uint8_t addr[PICO_SIZE_IP6], struct pico_device *dev) +{ + struct pico_ipv6_link *link; + if (pico_ipv6_is_multicast(addr) == 0) + return 0; + + link = pico_ipv6_link_by_dev(dev); + while(link) { + if (pico_ipv6_is_linklocal(link->address.addr)) { + int i, match = 0; + for(i = 13; i < 16; i++) { + if (addr[i] == link->address.addr[i]) + ++match; + } + /* Solicitation: last 3 bytes match a local address. */ + if (match == 3) + return 1; + } + + link = pico_ipv6_link_by_dev_next(dev, link); + } + return 0; +} + +int pico_ipv6_is_unspecified(const uint8_t addr[PICO_SIZE_IP6]) +{ + return !memcmp(PICO_IP6_ANY, addr, PICO_SIZE_IP6); +} + +static struct pico_ipv6_route *pico_ipv6_route_find(const struct pico_ip6 *addr) +{ + struct pico_ipv6_route *r = NULL; + struct pico_tree_node *index = NULL; + int i = 0; + if (!pico_ipv6_is_localhost(addr->addr) && (pico_ipv6_is_linklocal(addr->addr) || pico_ipv6_is_sitelocal(addr->addr))) { + return NULL; + } + pico_tree_foreach_reverse(index, &IPV6Routes) + { + r = index->keyValue; + for (i = 0; i < PICO_SIZE_IP6; ++i) { + if ((addr->addr[i] & (r->netmask.addr[i])) != ((r->dest.addr[i]) & (r->netmask.addr[i]))) { + break; + } + + if (i + 1 == PICO_SIZE_IP6) { + return r; + } + } + } + return NULL; +} + +struct pico_ip6 *pico_ipv6_source_find(const struct pico_ip6 *dst) +{ + struct pico_ip6 *myself = NULL; + struct pico_ipv6_route *rt; + + if(!dst) { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + + rt = pico_ipv6_route_find(dst); + if (rt) { + myself = &rt->link->address; + } else + pico_err = PICO_ERR_EHOSTUNREACH; + + return myself; +} + +struct pico_device *pico_ipv6_source_dev_find(const struct pico_ip6 *dst) +{ + struct pico_device *dev = NULL; + struct pico_ipv6_route *rt; + + if(!dst) { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + + rt = pico_ipv6_route_find(dst); + if (rt && rt->link) { + dev = rt->link->dev; + } else + pico_err = PICO_ERR_EHOSTUNREACH; + + return dev; +} + +static int pico_ipv6_forward_check_dev(struct pico_frame *f) +{ + if(f->dev->eth != NULL) + f->len -= PICO_SIZE_ETHHDR; + + if(f->len > f->dev->mtu) { + pico_notify_pkt_too_big(f); + return -1; + } + + return 0; +} + +static int pico_ipv6_pre_forward_checks(struct pico_frame *f) +{ + struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)f->net_hdr; + + /* Decrease HOP count, check if expired */ + hdr->hop = (uint8_t)(hdr->hop - 1); + if (hdr->hop < 1) { + pico_notify_ttl_expired(f); + dbg(" ------------------- HOP COUNT EXPIRED\n"); + return -1; + } + /* If source is local, discard anyway (packets bouncing back and forth) */ + if (pico_ipv6_link_get(&hdr->src)) + return -1; + + if (pico_ipv6_forward_check_dev(f) < 0) + return -1; + + return 0; +} + +static int pico_ipv6_forward(struct pico_frame *f) +{ + struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)f->net_hdr; + struct pico_ipv6_route *rt; + if (!hdr) { + pico_frame_discard(f); + return -1; + } + + rt = pico_ipv6_route_find(&hdr->dst); + if (!rt) { + pico_notify_dest_unreachable(f); + pico_frame_discard(f); + return -1; + } + + f->dev = rt->link->dev; + + if (pico_ipv6_pre_forward_checks(f) < 0) + { + pico_frame_discard(f); + return -1; + } + + f->start = f->net_hdr; + + return pico_sendto_dev(f); +} + + +static int pico_ipv6_process_hopbyhop(struct pico_ipv6_exthdr *hbh, struct pico_frame *f) +{ + uint8_t *option = NULL; + uint8_t len = 0, optlen = 0; + uint32_t ptr = sizeof(struct pico_ipv6_hdr); + uint8_t *extensions_start = (uint8_t *)hbh; + uint8_t must_align = 1; + IGNORE_PARAMETER(f); + + option = ((uint8_t *)&hbh->ext.hopbyhop) + sizeof(struct hopbyhop_s); + len = (uint8_t)HBH_LEN(hbh); + ipv6_dbg("IPv6: hop by hop extension header length %u\n", len + 2); + while (len) { + switch (*option) + { + case PICO_IPV6_EXTHDR_OPT_PAD1: + ++option; + --len; + break; + + case PICO_IPV6_EXTHDR_OPT_PADN: + optlen = (uint8_t)((*(option + 1)) + 2); /* plus type and len byte */ + option += optlen; + len = (uint8_t)(len - optlen); + break; + case PICO_IPV6_EXTHDR_OPT_ROUTER_ALERT: + optlen = (uint8_t)((*(option + 1)) + 2); /* plus type and len byte */ + // MLD package + if(*(option+1) == 2) + must_align = 0; + option += optlen; + len = (uint8_t)(len - optlen); + break; + default: + /* unknown option */ + optlen = (uint8_t)(*(option + 1) + 2); /* plus type and len byte */ + switch ((*option) & PICO_IPV6_EXTHDR_OPT_ACTION_MASK) { + case PICO_IPV6_EXTHDR_OPT_ACTION_SKIP: + break; + case PICO_IPV6_EXTHDR_OPT_ACTION_DISCARD: + return -1; + case PICO_IPV6_EXTHDR_OPT_ACTION_DISCARD_SI: + pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_IPV6OPT, ptr + (uint32_t)(option - extensions_start)); + return -1; + case PICO_IPV6_EXTHDR_OPT_ACTION_DISCARD_SINM: + if (!pico_ipv6_is_multicast(((struct pico_ipv6_hdr *)(f->net_hdr))->dst.addr)) + pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_IPV6OPT, ptr + (uint32_t)(option - extensions_start)); + + return -1; + } + ipv6_dbg("IPv6: option with type %u and length %u\n", *option, optlen); + option += optlen; + len = (uint8_t)(len - optlen); + } + } + return must_align; +} + + +static int pico_ipv6_process_routing(struct pico_ipv6_exthdr *routing, struct pico_frame *f, uint32_t ptr) +{ + IGNORE_PARAMETER(f); + + if (routing->ext.routing.segleft == 0) + return 0; + + ipv6_dbg("IPv6: routing extension header with len %u\n", routing->ext.routing.len + 2); + switch (routing->ext.routing.routtype) { + case 0x00: + /* deprecated */ + pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_HDRFIELD, ptr + 2); + return -1; + case 0x02: + /* routing type for MIPv6: not supported yet */ + break; + default: + pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_HDRFIELD, ptr + 2); + return -1; + } + return 0; +} + +#define IP6FRAG_MORE(x) ((x & 0x0001)) + +static int pico_ipv6_process_destopt(struct pico_ipv6_exthdr *destopt, struct pico_frame *f, uint32_t opt_ptr) +{ + uint8_t *option = NULL; + uint8_t len = 0, optlen = 0; + opt_ptr += (uint32_t)(2u); /* Skip Dest_opts header */ + IGNORE_PARAMETER(f); + option = ((uint8_t *)&destopt->ext.destopt) + sizeof(struct destopt_s); + len = (uint8_t)(((destopt->ext.destopt.len + 1) << 3) - 2); /* len in bytes, minus nxthdr and len byte */ + ipv6_dbg("IPv6: destination option extension header length %u\n", len + 2); + while (len) { + optlen = (uint8_t)(*(option + 1) + 2); + switch (*option) + { + case PICO_IPV6_EXTHDR_OPT_PAD1: + break; + + case PICO_IPV6_EXTHDR_OPT_PADN: + break; + + case PICO_IPV6_EXTHDR_OPT_SRCADDR: + ipv6_dbg("IPv6: home address option with length %u\n", optlen); + break; + + default: + ipv6_dbg("IPv6: option with type %u and length %u\n", *option, optlen); + switch (*option & PICO_IPV6_EXTHDR_OPT_ACTION_MASK) { + case PICO_IPV6_EXTHDR_OPT_ACTION_SKIP: + break; + case PICO_IPV6_EXTHDR_OPT_ACTION_DISCARD: + return -1; + case PICO_IPV6_EXTHDR_OPT_ACTION_DISCARD_SI: + pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_IPV6OPT, opt_ptr); + return -1; + case PICO_IPV6_EXTHDR_OPT_ACTION_DISCARD_SINM: + if (!pico_ipv6_is_multicast(((struct pico_ipv6_hdr *)(f->net_hdr))->dst.addr)){ + pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_IPV6OPT, opt_ptr); + } + return -1; + } + break; + } + opt_ptr += optlen; + option += optlen; + len = (uint8_t)(len - optlen); + } + return 0; +} + +#define IPV6_OPTLEN(x) ((uint16_t)(((x + 1) << 3))) + +static int pico_ipv6_check_headers_sequence(struct pico_frame *f) +{ + struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)f->net_hdr; + int ptr = sizeof(struct pico_ipv6_hdr); + int cur_nexthdr = 6; /* Starts with nexthdr field in ipv6 pkt */ + uint8_t nxthdr = hdr->nxthdr; + for (;; ) { + uint8_t optlen = *(f->net_hdr + ptr + 1); + switch (nxthdr) { + case PICO_IPV6_EXTHDR_DESTOPT: + case PICO_IPV6_EXTHDR_ROUTING: + case PICO_IPV6_EXTHDR_HOPBYHOP: + case PICO_IPV6_EXTHDR_ESP: + case PICO_IPV6_EXTHDR_AUTH: + optlen = (uint8_t)IPV6_OPTLEN(optlen); + break; + case PICO_IPV6_EXTHDR_FRAG: + optlen = 8; + break; + case PICO_IPV6_EXTHDR_NONE: + return 0; + + case PICO_PROTO_TCP: + case PICO_PROTO_UDP: + case PICO_PROTO_ICMP6: + return 0; + default: + /* Invalid next header */ + pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_NXTHDR, (uint32_t)cur_nexthdr); + return -1; + } + cur_nexthdr = ptr; + nxthdr = *(f->net_hdr + ptr); + ptr += optlen; + } +} + +static int pico_ipv6_check_aligned(struct pico_frame *f) +{ + struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)f->net_hdr; + if ((short_be(hdr->len) % 8) != 0) { + pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_HDRFIELD, 4); + return -1; + } + + return 0; +} + +static int pico_ipv6_extension_headers(struct pico_frame *f) +{ + struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)f->net_hdr; + uint8_t nxthdr = hdr->nxthdr; + struct pico_ipv6_exthdr *exthdr = NULL, *frag_hdr = NULL; + uint32_t ptr = sizeof(struct pico_ipv6_hdr); + uint16_t cur_optlen; + uint32_t cur_nexthdr = 6; + int must_align = 0; + + f->net_len = sizeof(struct pico_ipv6_hdr); + + if (pico_ipv6_check_headers_sequence(f) < 0) + return -1; + for (;; ) { + exthdr = (struct pico_ipv6_exthdr *)(f->net_hdr + f->net_len); + cur_optlen = 0; + + switch (nxthdr) { + case PICO_IPV6_EXTHDR_HOPBYHOP: + if (cur_nexthdr != 6) { + /* The Hop-by-Hop Options header, + * when present, must immediately follow the IPv6 header. + */ + pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_NXTHDR, cur_nexthdr); + return -1; + } + + cur_optlen = IPV6_OPTLEN(exthdr->ext.hopbyhop.len); + f->net_len = (uint16_t) (f->net_len + cur_optlen); + must_align = pico_ipv6_process_hopbyhop(exthdr, f); + if(must_align < 0) + return -1; + + break; + case PICO_IPV6_EXTHDR_ROUTING: + cur_optlen = IPV6_OPTLEN(exthdr->ext.routing.len); + f->net_len = (uint16_t) (f->net_len + cur_optlen); + if (pico_ipv6_process_routing(exthdr, f, ptr) < 0) + return -1; + + break; + case PICO_IPV6_EXTHDR_FRAG: + cur_optlen = 8u; + f->net_len = (uint16_t) (f->net_len + cur_optlen); + frag_hdr = exthdr; + f->frag = (uint16_t)((frag_hdr->ext.frag.om[0] << 8) + frag_hdr->ext.frag.om[1]); + /* If M-Flag is set, and packet is not 8B aligned, discard and alert */ + if (IP6FRAG_MORE(f->frag) && ((short_be(hdr->len) % 8) != 0)) { + pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_HDRFIELD, 4); + return -1; + } + + break; + case PICO_IPV6_EXTHDR_DESTOPT: + cur_optlen = IPV6_OPTLEN(exthdr->ext.destopt.len); + f->net_len = (uint16_t) (f->net_len + cur_optlen); + must_align = 1; + if (pico_ipv6_process_destopt(exthdr, f, ptr) < 0) + return -1; + + break; + case PICO_IPV6_EXTHDR_ESP: + /* not supported, ignored. */ + return 0; + case PICO_IPV6_EXTHDR_AUTH: + /* not supported, ignored */ + return 0; + case PICO_IPV6_EXTHDR_NONE: + /* no next header */ + if (must_align && (pico_ipv6_check_aligned(f) < 0)) + return -1; + + return 0; + + case PICO_PROTO_TCP: + case PICO_PROTO_UDP: + case PICO_PROTO_ICMP6: + if (must_align && (pico_ipv6_check_aligned(f) < 0)) + return -1; + + f->transport_hdr = f->net_hdr + f->net_len; + f->transport_len = (uint16_t)(short_be(hdr->len) - (f->net_len - sizeof(struct pico_ipv6_hdr))); + if (frag_hdr) { +#ifdef PICO_SUPPORT_IPV6FRAG + pico_ipv6_process_frag(frag_hdr, f, nxthdr); +#endif + return -1; + } else { + return nxthdr; + } + + break; + default: + /* Invalid next header */ + pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_NXTHDR, cur_nexthdr); + return -1; + } + nxthdr = exthdr->nxthdr; + cur_nexthdr = ptr; + ptr += cur_optlen; + } +} +static int pico_ipv6_process_mcast_in(struct pico_frame *f) +{ + struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *) f->net_hdr; + struct pico_ipv6_exthdr *hbh = NULL; + if (pico_ipv6_is_multicast(hdr->dst.addr)) { +#ifdef PICO_SUPPORT_MCAST + /* Receiving UDP multicast datagram TODO set f->flags? */ + if(hdr->nxthdr == 0) { + hbh = (struct pico_ipv6_exthdr *) (f->transport_hdr); + } + if (hdr->nxthdr == PICO_PROTO_ICMP6 || (hbh != NULL && hbh->nxthdr == PICO_PROTO_ICMP6)) { + pico_transport_receive(f, PICO_PROTO_ICMP6); + return 1; + } else if ((pico_ipv6_mcast_filter(f) == 0) && (hdr->nxthdr == PICO_PROTO_UDP)) { + pico_enqueue(pico_proto_udp.q_in, f); + return 1; + } + +#endif + pico_frame_discard(f); + return 1; + } + + return 0; +} +static int pico_ipv6_process_in(struct pico_protocol *self, struct pico_frame *f) +{ + int proto = 0; + struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)f->net_hdr; + struct pico_ipv6_exthdr *hbh; + IGNORE_PARAMETER(self); + /* forward if not local, except if router alert is set */ + if (pico_ipv6_is_unicast(&hdr->dst) && !pico_ipv6_link_get(&hdr->dst)) { + if(hdr->nxthdr == 0) { + hbh = (struct pico_ipv6_exthdr *) f->transport_hdr; + if(hbh->ext.routing.routtype == 0) + return pico_ipv6_forward(f); + } else + /* not local, try to forward. */ + return pico_ipv6_forward(f); + } + + proto = pico_ipv6_extension_headers(f); + if (proto <= 0) { + pico_frame_discard(f); + return 0; + } + + f->proto = (uint8_t)proto; + ipv6_dbg("IPv6: payload %u net_len %u nxthdr %u\n", short_be(hdr->len), f->net_len, proto); + + if (pico_ipv6_is_unicast(&hdr->dst)) { + pico_transport_receive(f, f->proto); + } else if (pico_ipv6_is_multicast(hdr->dst.addr)) { + /* XXX perform multicast filtering: solicited-node multicast address MUST BE allowed! */ + if (pico_ipv6_process_mcast_in(f) > 0) + return 0; + pico_transport_receive(f, f->proto); + } + + return 0; +} + +static int pico_ipv6_process_out(struct pico_protocol *self, struct pico_frame *f) +{ + IGNORE_PARAMETER(self); + + f->start = (uint8_t*)f->net_hdr; + return pico_sendto_dev(f); +} + +/* allocates an IPv6 packet without extension headers. If extension headers are needed, + * include the len of the extension headers in the size parameter. Once a frame acquired + * increment net_len and transport_hdr with the len of the extension headers, decrement + * transport_len with this value. + */ +static struct pico_frame *pico_ipv6_alloc(struct pico_protocol *self, uint16_t size) +{ + struct pico_frame *f = pico_frame_alloc((uint32_t)(size + PICO_SIZE_IP6HDR + PICO_SIZE_ETHHDR)); + + IGNORE_PARAMETER(self); + + if (!f) + return NULL; + + f->datalink_hdr = f->buffer; + f->net_hdr = f->buffer + PICO_SIZE_ETHHDR; + f->net_len = PICO_SIZE_IP6HDR; + f->transport_hdr = f->net_hdr + PICO_SIZE_IP6HDR; + f->transport_len = (uint16_t)size; + /* PICO_SIZE_ETHHDR is accounted for in pico_ethernet_send */ + f->len = (uint32_t)(size + PICO_SIZE_IP6HDR); + return f; +} + +static inline int ipv6_pushed_frame_valid(struct pico_frame *f, struct pico_ip6 *dst) +{ + struct pico_ipv6_hdr *hdr = NULL; + if(!f || !dst) + return -1; + + hdr = (struct pico_ipv6_hdr *)f->net_hdr; + if (!hdr) { + dbg("IPv6: IP header error\n"); + return -1; + } + + return 0; +} +int pico_ipv6_is_null_address(struct pico_ip6 * ip6) { + struct pico_ip6 null_addr = {{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }}; + return !memcmp(ip6, &null_addr, sizeof(struct pico_ip6)); +} +#ifdef PICO_SUPPORT_MCAST +/* link + * | + * MCASTGroups + * | | | + * ------------ | ------------ + * | | | + * MCASTSources MCASTSources MCASTSources + * | | | | | | | | | | | | + * S S S S S S S S S S S S + * + * MCASTGroups: RBTree(mcast_group) + * MCASTSources: RBTree(source) + */ +static int ipv6_mcast_groups_cmp(void *ka, void *kb) +{ + struct pico_ipv6_mcast_group *a = ka, *b = kb; + return pico_ipv6_compare(&a->mcast_addr, &b->mcast_addr); +} +static int ipv6_mcast_sources_cmp(void *ka, void *kb) +{ + struct pico_ip6 *a = ka, *b = kb; + return pico_ipv6_compare(a, b); +} + +static void pico_ipv6_mcast_print_groups(struct pico_ipv6_link *mcast_link) +{ +#ifdef PICO_DEBUG_MULTICAST + uint16_t i = 0; + struct pico_ipv6_mcast_group *g = NULL; + struct pico_ip6 *source = NULL; + struct pico_tree_node *index = NULL, *index2 = NULL; + char *ipv6_addr; + (void) source; + ipv6_mcast_dbg("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"); + ipv6_mcast_dbg("+ MULTICAST list interface %-16s +\n", mcast_link->dev->name); + ipv6_mcast_dbg("+------------------------------------------------------------------------------------------+\n"); + ipv6_mcast_dbg("+ nr | interface | host group | reference count | filter mode | source +\n"); + ipv6_mcast_dbg("+------------------------------------------------------------------------------------------+\n"); + ipv6_addr = PICO_ZALLOC(PICO_IPV6_STRING); + pico_tree_foreach(index, mcast_link->MCASTGroups) { + g = index->keyValue; + pico_ipv6_to_string(ipv6_addr, &g->mcast_addr.addr[0]); + ipv6_mcast_dbg("+ %04d | %16s | %s | %05u | %u | %8s +\n", i, mcast_link->dev->name, ipv6_addr, g->reference_count, g->filter_mode, ""); + pico_tree_foreach(index2, &g->MCASTSources) { + source = index2->keyValue; + pico_ipv6_to_string(ipv6_addr, source->addr); + ipv6_mcast_dbg("+ %4s | %16s | %8s | %5s | %s | %s +\n", "", "", "", "", "", ipv6_addr); + } + i++; + } + PICO_FREE(ipv6_addr); + ipv6_mcast_dbg("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"); +#else + IGNORE_PARAMETER(mcast_link); +#endif + +} + +static int mcast_group_update_ipv6(struct pico_ipv6_mcast_group *g, struct pico_tree *_MCASTFilter, uint8_t filter_mode) +{ + struct pico_tree_node *index = NULL, *_tmp = NULL; + struct pico_ip6 *source = NULL; + /* cleanup filter */ + pico_tree_foreach_safe(index, &g->MCASTSources, _tmp) { + source = index->keyValue; + pico_tree_delete(&g->MCASTSources, source); + PICO_FREE(source); + } + /* insert new filter */ + if (_MCASTFilter) { + pico_tree_foreach(index, _MCASTFilter) { + if (index->keyValue) { + source = PICO_ZALLOC(sizeof(struct pico_ip6)); + if (!source) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + *source = *((struct pico_ip6 *)index->keyValue); + pico_tree_insert(&g->MCASTSources, source); + } + } + } + + g->filter_mode = filter_mode; + return 0; +} + +int pico_ipv6_mcast_join(struct pico_ip6 *mcast_link, struct pico_ip6 *mcast_group, uint8_t reference_count, uint8_t filter_mode, struct pico_tree *_MCASTFilter) +{ + struct pico_ipv6_mcast_group *g = NULL, test = { + 0 + }; + struct pico_ipv6_link *link = NULL; + int res = -1; + if (mcast_link) { + link = pico_ipv6_link_get(mcast_link); + } + if (!link) { + link = mcast_default_link_ipv6; + } + test.mcast_addr = *mcast_group; + g = pico_tree_findKey(link->MCASTGroups, &test); + if (g) { + if (reference_count) + g->reference_count++; + res = pico_mld_state_change(mcast_link, mcast_group, filter_mode, _MCASTFilter, PICO_MLD_STATE_UPDATE); + } else { + g = PICO_ZALLOC(sizeof(struct pico_ipv6_mcast_group)); + if (!g) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + /* "non-existent" state of filter mode INCLUDE and empty source list */ + g->filter_mode = PICO_IP_MULTICAST_INCLUDE; + g->reference_count = 1; + g->mcast_addr = *mcast_group; + g->MCASTSources.root = &LEAF; + g->MCASTSources.compare = ipv6_mcast_sources_cmp; + pico_tree_insert(link->MCASTGroups, g); + res = pico_mld_state_change(mcast_link, mcast_group, filter_mode, _MCASTFilter, PICO_MLD_STATE_CREATE); + } + + if (mcast_group_update_ipv6(g, _MCASTFilter, filter_mode) < 0) { + dbg("Error in mcast_group update\n"); + return -1; + } + pico_ipv6_mcast_print_groups(link); + return res; +} + +int pico_ipv6_mcast_leave(struct pico_ip6 *mcast_link, struct pico_ip6 *mcast_group, uint8_t reference_count, uint8_t filter_mode, struct pico_tree *_MCASTFilter) +{ + struct pico_ipv6_mcast_group *g = NULL, test = { + 0 + }; + struct pico_ipv6_link *link = NULL; + struct pico_tree_node *index = NULL, *_tmp = NULL; + struct pico_ip6 *source = NULL; + int res = -1; + if (mcast_link) + link = pico_ipv6_link_get(mcast_link); + + if (!link) + link = mcast_default_link_ipv6; + + test.mcast_addr = *mcast_group; + g = pico_tree_findKey(link->MCASTGroups, &test); + if (!g) { + pico_err = PICO_ERR_EINVAL; + return -1; + } else { + if (reference_count && (--(g->reference_count) < 1)) { + res = pico_mld_state_change(mcast_link, mcast_group, filter_mode, _MCASTFilter, PICO_MLD_STATE_DELETE); + /* cleanup filter */ + pico_tree_foreach_safe(index, &g->MCASTSources, _tmp) { + source = index->keyValue; + pico_tree_delete(&g->MCASTSources, source); + PICO_FREE(source); + } + pico_tree_delete(link->MCASTGroups, g); + PICO_FREE(g); + } else { + res = pico_mld_state_change(mcast_link, mcast_group, filter_mode, _MCASTFilter, PICO_MLD_STATE_UPDATE); + if (mcast_group_update_ipv6(g, _MCASTFilter, filter_mode) < 0) + return -1; + } + } + + pico_ipv6_mcast_print_groups(link); + return res; +} + +struct pico_ipv6_link *pico_ipv6_get_default_mcastlink(void) +{ + return mcast_default_link_ipv6; +} + +static int pico_ipv6_mcast_filter(struct pico_frame *f) +{ + struct pico_ipv6_link *link = NULL; + struct pico_tree_node *index = NULL, *index2 = NULL; + struct pico_ipv6_mcast_group *g = NULL, test = { + 0 + }; + struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *) f->net_hdr; +#ifdef PICO_DEBUG_MULTICAST + char ipv6_addr[PICO_IPV6_STRING]; +#endif + test.mcast_addr = hdr->dst; + + pico_tree_foreach(index, &Tree_dev_ip6_link) { + link = index->keyValue; + g = pico_tree_findKey(link->MCASTGroups, &test); + if (g) { + if (f->dev == link->dev) { +#ifdef PICO_DEBUG_MULTICAST + pico_ipv6_to_string( ipv6_addr, &hdr->dst.addr[0]); + ipv6_mcast_dbg("MCAST: IP %s is group member of current link %s\n", ipv6_addr, f->dev->name); +#endif + /* perform source filtering */ + switch (g->filter_mode) { + case PICO_IP_MULTICAST_INCLUDE: + pico_tree_foreach(index2, &g->MCASTSources) { + if (hdr->src.addr == ((struct pico_ip6 *)index2->keyValue)->addr) { +#ifdef PICO_DEBUG_MULTICAST + pico_ipv6_to_string(ipv6_addr,&hdr->src.addr[0]); + ipv6_mcast_dbg("MCAST: IP %s in included interface source list\n", ipv6_addr); +#endif + return 0; + } + } +#ifdef PICO_DEBUG_MULTICAST + pico_ipv6_to_string(ipv6_addr,&hdr->src.addr[0]); + ipv6_mcast_dbg("MCAST: IP %s NOT in included interface source list\n", ipv6_addr); +#endif + return -1; + + case PICO_IP_MULTICAST_EXCLUDE: + pico_tree_foreach(index2, &g->MCASTSources) { + if (memcmp(hdr->src.addr , (((struct pico_ip6 *)index2->keyValue)->addr) , sizeof(struct pico_ip6))== 0){ +#ifdef PICO_DEBUG_MULTICAST + pico_ipv6_to_string(ipv6_addr,&hdr->src.addr[0]); + ipv6_mcast_dbg("MCAST: IP %s in excluded interface source list\n", ipv6_addr); +#endif + return -1; + } + } +#ifdef PICO_DEBUG_MULTICAST + pico_ipv6_to_string(ipv6_addr,&hdr->src.addr[0]); + ipv6_mcast_dbg("MCAST: IP %s NOT in excluded interface source list\n", ipv6_addr); +#endif + return 0; + + default: + return -1; + } + } else { +#ifdef PICO_DEBUG_MULTICAST + pico_ipv6_to_string(ipv6_addr,&hdr->dst.addr[0]); + ipv6_mcast_dbg("MCAST: IP %s is group member of different link %s\n", ipv6_addr, link->dev->name); +#endif + } + } else { +#ifdef PICO_DEBUG_MULTICAST + pico_ipv6_to_string(ipv6_addr,&hdr->dst.addr[0]); + ipv6_mcast_dbg("MCAST: IP %s is not a group member of link %s\n", ipv6_addr, f->dev->name); +#endif + } + } + return -1; +} + +#else + +int pico_ipv6_mcast_join(struct pico_ip6 *mcast_link, struct pico_ip6 *mcast_group, uint8_t reference_count, uint8_t filter_mode, struct pico_tree *_MCASTFilter) +{ + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} + +int pico_ipv6_mcast_leave(struct pico_ip6 *mcast_link, struct pico_ip6 *mcast_group, uint8_t reference_count, uint8_t filter_mode, struct pico_tree *_MCASTFilter) +{ + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} + +struct pico_ipv6_link *pico_ipv6_get_default_mcastlink(void) +{ + pico_err = PICO_ERR_EPROTONOSUPPORT; + return NULL; +} +#endif /* PICO_SUPPORT_MCAST */ +static inline struct pico_ipv6_route *ipv6_pushed_frame_checks(struct pico_frame *f, struct pico_ip6 *dst) +{ + struct pico_ipv6_route *route = NULL; + + if (ipv6_pushed_frame_valid(f, dst) < 0) + return NULL; + + if (memcmp(dst->addr, PICO_IP6_ANY, PICO_SIZE_IP6) == 0) { + dbg("IPv6: IP destination address error\n"); + return NULL; + } + + route = pico_ipv6_route_find(dst); + if (!route && !f->dev) { + dbg("IPv6: route not found.\n"); + pico_err = PICO_ERR_EHOSTUNREACH; + return NULL; + } + + return route; +} + +static inline void ipv6_push_hdr_adjust(struct pico_frame *f, struct pico_ipv6_link *link, struct pico_ip6 *src, struct pico_ip6 *dst, uint8_t proto, int is_dad) +{ + struct pico_icmp6_hdr *icmp6_hdr = NULL; + struct pico_ipv6_hdr *hdr = NULL; + struct pico_ipv6_exthdr *hbh = NULL; + const uint8_t vtf = (uint8_t)long_be(0x60000000); /* version 6, traffic class 0, flow label 0 */ + + hdr = (struct pico_ipv6_hdr *)f->net_hdr; + hdr->vtf = vtf; + hdr->len = short_be((uint16_t)(f->transport_len + f->net_len - (uint16_t)sizeof(struct pico_ipv6_hdr))); + hdr->nxthdr = proto; + hdr->hop = f->dev->hostvars.hoplimit; + hdr->dst = *dst; + + if (!src || !pico_ipv6_is_unicast(src)) + /* Address defaults to the link information: src address selection is done via link */ + hdr->src = link->address; + else { + /* Sender protocol is forcing an IPv6 address */ + hdr->src = *src; + } + + if (f->send_ttl) { + hdr->hop = f->send_ttl; + } + + if (f->send_tos) { + hdr->vtf |= ((uint32_t)f->send_tos << 20u); + } + + /* make adjustments to defaults according to proto */ + switch (proto) + { + case 0: + { + hbh = (struct pico_ipv6_exthdr *) f->transport_hdr; + switch(hbh->nxthdr) { + case PICO_PROTO_ICMP6: + { + icmp6_hdr = (struct pico_icmp6_hdr *)(f->transport_hdr+sizeof(struct pico_ipv6_exthdr)); + if((icmp6_hdr->type >= PICO_MLD_QUERY && icmp6_hdr->type <= PICO_MLD_DONE) || icmp6_hdr->type == PICO_MLD_REPORTV2) { + hdr->hop = 1; + } + icmp6_hdr->crc = 0; + icmp6_hdr->crc = short_be(pico_mld_checksum(f)); + break; + } + } + break; + } + case PICO_PROTO_ICMP6: + { + icmp6_hdr = (struct pico_icmp6_hdr *)f->transport_hdr; + if (icmp6_hdr->type == PICO_ICMP6_NEIGH_SOL || icmp6_hdr->type == PICO_ICMP6_NEIGH_ADV || icmp6_hdr->type == PICO_ICMP6_ROUTER_SOL || icmp6_hdr->type == PICO_ICMP6_ROUTER_ADV) + hdr->hop = 255; + + if ((is_dad || link->istentative) && icmp6_hdr->type == PICO_ICMP6_NEIGH_SOL){ + memcpy(hdr->src.addr, PICO_IP6_ANY, PICO_SIZE_IP6); + + } + + icmp6_hdr->crc = 0; + icmp6_hdr->crc = short_be(pico_icmp6_checksum(f)); + break; + } +#ifdef PICO_SUPPORT_UDP + case PICO_PROTO_UDP: + { + struct pico_udp_hdr *udp_hdr = (struct pico_udp_hdr *) f->transport_hdr; + udp_hdr->crc = short_be(pico_udp_checksum_ipv6(f)); + break; + } +#endif + + default: + break; + } + +} + +static int ipv6_frame_push_final(struct pico_frame *f) +{ + struct pico_ipv6_hdr *hdr = NULL; + hdr = (struct pico_ipv6_hdr *)f->net_hdr; + if(pico_ipv6_link_get(&hdr->dst)) { + return pico_enqueue(&ipv6_in, f); + } + else { + return pico_enqueue(&ipv6_out, f); + } +} + +struct pico_ipv6_link *pico_ipv6_linklocal_get(struct pico_device *dev); + +int pico_ipv6_frame_push(struct pico_frame *f, struct pico_ip6 *src, struct pico_ip6 *dst, uint8_t proto, int is_dad) +{ + struct pico_ipv6_route *route = NULL; + struct pico_ipv6_link *link = NULL; + + if (pico_ipv6_is_linklocal(dst->addr) || pico_ipv6_is_multicast(dst->addr) || pico_ipv6_is_sitelocal(dst->addr)) { + if (!f->dev) { + pico_frame_discard(f); + return -1; + } + if (pico_ipv6_is_sitelocal(dst->addr)) + link = pico_ipv6_sitelocal_get(f->dev); + else + link = pico_ipv6_linklocal_get(f->dev); + + if (link) + goto push_final; + } + + if (pico_ipv6_is_localhost(dst->addr)) { + f->dev = pico_get_device("loop"); + } + + route = ipv6_pushed_frame_checks(f, dst); + if (!route) { + pico_frame_discard(f); + return -1; + } + + link = route->link; + + if (f->sock && f->sock->dev) + f->dev = f->sock->dev; + else { + f->dev = link->dev; + if (f->sock) + f->sock->dev = f->dev; + } + + + #if 0 + if (pico_ipv6_is_multicast(hdr->dst.addr)) { + /* XXX: reimplement loopback */ + } + + #endif + +push_final: + ipv6_push_hdr_adjust(f, link, src, dst, proto, is_dad); + return ipv6_frame_push_final(f); +} + +static int pico_ipv6_frame_sock_push(struct pico_protocol *self, struct pico_frame *f) +{ + struct pico_ip6 *dst = NULL; + struct pico_remote_endpoint *remote_endpoint = NULL; + + IGNORE_PARAMETER(self); + + if (!f->sock) { + pico_frame_discard(f); + return -1; + } + + remote_endpoint = (struct pico_remote_endpoint *)f->info; + if (remote_endpoint) { + dst = &remote_endpoint->remote_addr.ip6; + } else { + dst = &f->sock->remote_addr.ip6; + } + + return pico_ipv6_frame_push(f, NULL, dst, (uint8_t)f->sock->proto->proto_number, 0); +} + +/* interface: protocol definition */ +struct pico_protocol pico_proto_ipv6 = { + .name = "ipv6", + .proto_number = PICO_PROTO_IPV6, + .layer = PICO_LAYER_NETWORK, + .alloc = pico_ipv6_alloc, + .process_in = pico_ipv6_process_in, + .process_out = pico_ipv6_process_out, + .push = pico_ipv6_frame_sock_push, + .q_in = &ipv6_in, + .q_out = &ipv6_out, +}; + +#ifdef DEBUG_ROUTE +static void pico_ipv6_dbg_route(void) +{ + struct pico_ipv6_route *r; + struct pico_tree_node *index; + pico_tree_foreach(index, &Routes){ + r = index->keyValue; + dbg("Route to %08x/%08x, gw %08x, dev: %s, metric: %d\n", r->dest.addr, r->netmask.addr, r->gateway.addr, r->link->dev->name, r->metric); + } +} +#else +#define pico_ipv6_dbg_route() do { } while(0) +#endif + +static inline struct pico_ipv6_route *ipv6_route_add_link(struct pico_ip6 gateway) +{ + struct pico_ip6 zerogateway = {{0}}; + struct pico_ipv6_route *r = pico_ipv6_route_find(&gateway); + if (!r ) { /* Specified Gateway is unreachable */ + pico_err = PICO_ERR_EHOSTUNREACH; + return NULL; + } + + if (memcmp(r->gateway.addr, zerogateway.addr, PICO_SIZE_IP6) != 0) { /* Specified Gateway is not a neighbor */ + pico_err = PICO_ERR_ENETUNREACH; + return NULL; + } + + + return r; +} + +int pico_ipv6_route_add(struct pico_ip6 address, struct pico_ip6 netmask, struct pico_ip6 gateway, int metric, struct pico_ipv6_link *link) +{ + struct pico_ip6 zerogateway = {{0}}; + struct pico_ipv6_route test, *new = NULL; + test.dest = address; + test.netmask = netmask; + test.metric = (uint32_t)metric; + if (pico_tree_findKey(&IPV6Routes, &test)) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + new = PICO_ZALLOC(sizeof(struct pico_ipv6_route)); + if (!new) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + ipv6_dbg("Adding IPV6 static route\n"); + new->dest = address; + new->netmask = netmask; + new->gateway = gateway; + new->metric = (uint32_t)metric; + if (memcmp(gateway.addr, zerogateway.addr, PICO_SIZE_IP6) == 0) { + /* No gateway provided, use the link */ + new->link = link; + } else { + struct pico_ipv6_route *r = ipv6_route_add_link(gateway); + if (!r) { + if (link) + new->link = link; + else { + PICO_FREE(new); + return -1; + } + } else { + new->link = r->link; + } + } + + if (new->link && (pico_ipv6_is_global(address.addr)) && (!pico_ipv6_is_global(new->link->address.addr))) { + new->link = pico_ipv6_global_get(new->link->dev); + } + + if (!new->link) { + pico_err = PICO_ERR_EINVAL; + PICO_FREE(new); + return -1; + } + + + pico_tree_insert(&IPV6Routes, new); + pico_ipv6_dbg_route(); + return 0; +} + +int pico_ipv6_route_del(struct pico_ip6 address, struct pico_ip6 netmask, struct pico_ip6 gateway, int metric, struct pico_ipv6_link *link) +{ + struct pico_ipv6_route test, *found = NULL; + + IGNORE_PARAMETER(gateway); + + if (!link) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + test.dest = address; + test.netmask = netmask; + test.metric = (uint32_t)metric; + + found = pico_tree_findKey(&IPV6Routes, &test); + if (found) { + pico_tree_delete(&IPV6Routes, found); + PICO_FREE(found); + pico_ipv6_dbg_route(); + return 0; + } + + pico_err = PICO_ERR_EINVAL; + return -1; +} + +void pico_ipv6_router_down(struct pico_ip6 *address) +{ + struct pico_tree_node *index = NULL, *_tmp = NULL; + struct pico_ipv6_route *route = NULL; + if (!address) + return; + + pico_tree_foreach_safe(index, &IPV6Routes, _tmp) + { + route = index->keyValue; + if (pico_ipv6_compare(address, &route->gateway) == 0) + pico_ipv6_route_del(route->dest, route->netmask, route->gateway, (int)route->metric, route->link); + } +} + +#ifndef UNIT_TEST +static void pico_ipv6_nd_dad(pico_time now, void *arg) +{ + struct pico_ip6 *address = (struct pico_ip6 *)arg; + struct pico_ipv6_link *l = NULL; + struct pico_ip6 old_address; + if (!arg) + return; + + IGNORE_PARAMETER(now); + + l = pico_ipv6_link_istentative(address); + if (!l) + return; + + if (pico_device_link_state(l->dev) == 0) { + l->dad_timer = pico_timer_add(100, pico_ipv6_nd_dad, &l->address); + return; + } + + if (l->isduplicate) { + dbg("IPv6: duplicate address.\n"); + old_address = *address; + if (pico_ipv6_is_linklocal(address->addr)) { + address->addr[8] = (uint8_t)((uint8_t)(pico_rand() & 0xff) & (uint8_t)(~0x03)); + address->addr[9] = pico_rand() & 0xff; + address->addr[10] = pico_rand() & 0xff; + address->addr[11] = pico_rand() & 0xff; + address->addr[12] = pico_rand() & 0xff; + address->addr[13] = pico_rand() & 0xff; + address->addr[14] = pico_rand() & 0xff; + address->addr[15] = pico_rand() & 0xff; + pico_ipv6_link_add(l->dev, *address, l->netmask); + } + + pico_ipv6_link_del(l->dev, old_address); + } + else { + if (l->dup_detect_retrans-- == 0) { + dbg("IPv6: DAD verified valid address.\n"); + l->istentative = 0; + } else { + /* Duplicate Address Detection */ + pico_icmp6_neighbor_solicitation(l->dev, &l->address, PICO_ICMP6_ND_DAD); + l->dad_timer = pico_timer_add(PICO_ICMP6_MAX_RTR_SOL_DELAY, pico_ipv6_nd_dad, &l->address); + } + } +} +#endif + + +extern struct pico_ipv6_link *pico_ipv6_link_add(struct pico_device *dev, struct pico_ip6 address, struct pico_ip6 netmask) +{ + struct pico_ipv6_link test = { + 0 + }, *new = NULL; + struct pico_ip6 network = {{0}}, gateway = {{0}}; + struct pico_ip6 mcast_addr = {{ 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }}; + struct pico_ip6 mcast_nm = {{ 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }}; + struct pico_ip6 mcast_gw = {{0}}; + struct pico_ip6 all_hosts = {{ 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }}; +#ifdef PICO_DEBUG_IPV6 + char ipstr[40] = { + 0 + }; +#endif + int i = 0; + if (!dev) { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + + test.address = address; + test.dev = dev; + /** XXX: Valid netmask / unicast address test **/ + + if (pico_tree_findKey(&IPV6Links, &test)) { + dbg("IPv6: trying to assign an invalid address (in use)\n"); + pico_err = PICO_ERR_EADDRINUSE; + return NULL; + } + + /** XXX: Check for network already in use (e.g. trying to assign 10.0.0.1/24 where 10.1.0.1/8 is in use) **/ + new = PICO_ZALLOC(sizeof(struct pico_ipv6_link)); + if (!new) { + dbg("IPv6: out of memory!\n"); + pico_err = PICO_ERR_ENOMEM; + return NULL; + } + + new->address = address; + new->netmask = netmask; + new->dev = dev; + new->istentative = 1; + new->isduplicate = 0; +#ifdef PICO_SUPPORT_MCAST + new->MCASTGroups = PICO_ZALLOC(sizeof(struct pico_tree)); + if (!new->MCASTGroups) { + PICO_FREE(new); + dbg("IPv6: Out of memory!\n"); + pico_err = PICO_ERR_ENOMEM; + return NULL; + } + + new->MCASTGroups->root = &LEAF; + new->MCASTGroups->compare = ipv6_mcast_groups_cmp; + new->mcast_compatibility = PICO_MLDV2; + new->mcast_last_query_interval = MLD_QUERY_INTERVAL; +#endif + pico_tree_insert(&IPV6Links, new); + for (i = 0; i < PICO_SIZE_IP6; ++i) { + network.addr[i] = address.addr[i] & netmask.addr[i]; + } +#ifdef PICO_SUPPORT_MCAST + do { + if (!mcast_default_link_ipv6) { + mcast_default_link_ipv6 = new; + pico_ipv6_route_add(mcast_addr, mcast_nm, mcast_gw, 1, new); + } + pico_ipv6_mcast_join(&address, &all_hosts, 1, PICO_IP_MULTICAST_EXCLUDE, NULL); + } while(0); +#endif + pico_ipv6_route_add(network, netmask, gateway, 1, new); + pico_ipv6_route_add(mcast_addr, mcast_nm, mcast_gw, 1, new); + new->dup_detect_retrans = PICO_IPV6_DEFAULT_DAD_RETRANS; +#ifndef UNIT_TEST + /* Duplicate Address Detection */ + new->dad_timer = pico_timer_add(100, pico_ipv6_nd_dad, &new->address); +#else + new->istentative = 0; +#endif + +#ifdef PICO_DEBUG_IPV6 + pico_ipv6_to_string(ipstr, new->address.addr); + dbg("Assigned ipv6 %s to device %s\n", ipstr, new->dev->name); +#endif + return new; +} + +static int pico_ipv6_cleanup_routes(struct pico_ipv6_link *link) +{ + struct pico_tree_node *index = NULL, *_tmp = NULL; + struct pico_ipv6_route *route = NULL; + + pico_tree_foreach_safe(index, &IPV6Routes, _tmp) + { + route = index->keyValue; + if (link == route->link) + pico_ipv6_route_del(route->dest, route->netmask, route->gateway, (int)route->metric, route->link); + } + return 0; +} + +int pico_ipv6_cleanup_links(struct pico_device *dev) +{ + struct pico_tree_node *index = NULL, *_tmp = NULL; + struct pico_ipv6_link *link = NULL; + + pico_tree_foreach_safe(index, &IPV6Links, _tmp) + { + link = index->keyValue; + if (dev == link->dev) + pico_ipv6_link_del(dev, link->address); + } + return 0; +} + +int pico_ipv6_link_del(struct pico_device *dev, struct pico_ip6 address) +{ + struct pico_ipv6_link test = { + 0 + }, *found = NULL; + + if (!dev) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + test.address = address; + test.dev = dev; + found = pico_tree_findKey(&IPV6Links, &test); + if (!found) { + pico_err = PICO_ERR_ENXIO; + return -1; + } + + pico_ipv6_cleanup_routes(found); + if (found->dad_timer) + pico_timer_cancel(found->dad_timer); + + pico_tree_delete(&IPV6Links, found); + /* XXX MUST leave the solicited-node multicast address corresponding to the address (RFC 4861 $7.2.1) */ + PICO_FREE(found); + return 0; +} + +struct pico_ipv6_link *pico_ipv6_link_istentative(struct pico_ip6 *address) +{ + struct pico_ipv6_link test = { + 0 + }, *found = NULL; + test.address = *address; + + found = pico_tree_findKey(&IPV6Links, &test); + if (!found) + return NULL; + + if (found->istentative) + return found; + + return NULL; +} + +struct pico_ipv6_link *pico_ipv6_link_get(struct pico_ip6 *address) +{ + struct pico_ipv6_link test = { + 0 + }, *found = NULL; + test.address = *address; + found = pico_tree_findKey(&IPV6Links, &test); + if (!found) { + return NULL; + } + if (found->istentative) { + return NULL; + } + return found; +} + +struct pico_device *pico_ipv6_link_find(struct pico_ip6 *address) +{ + struct pico_ipv6_link test = { + 0 + }, *found = NULL; + if(!address) { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + test.dev = NULL; + test.address = *address; + found = pico_tree_findKey(&IPV6Links, &test); + if (!found) { + pico_err = PICO_ERR_ENXIO; + return NULL; + } + + if (found->istentative) { + return NULL; + } + + return found->dev; +} + +struct pico_ip6 pico_ipv6_route_get_gateway(struct pico_ip6 *addr) +{ + struct pico_ip6 nullip = {{0}}; + struct pico_ipv6_route *route = NULL; + + if (!addr) { + pico_err = PICO_ERR_EINVAL; + return nullip; + } + + route = pico_ipv6_route_find(addr); + if (!route) { + pico_err = PICO_ERR_EHOSTUNREACH; + return nullip; + } + else + return route->gateway; +} + + +struct pico_ipv6_link *pico_ipv6_link_by_dev(struct pico_device *dev) +{ + struct pico_tree_node *index = NULL; + struct pico_ipv6_link *link = NULL; + + pico_tree_foreach(index, &IPV6Links) + { + link = index->keyValue; + if (dev == link->dev) + return link; + } + return NULL; +} + +struct pico_ipv6_link *pico_ipv6_link_by_dev_next(struct pico_device *dev, struct pico_ipv6_link *last) +{ + struct pico_tree_node *index = NULL; + struct pico_ipv6_link *link = NULL; + int valid = 0; + + if (last == NULL) + valid = 1; + + pico_tree_foreach(index, &IPV6Links) + { + link = index->keyValue; + if (link->dev == dev) { + if (last == link) + valid = 1; + else if (valid > 0) + return link; + } + } + return NULL; +} + +struct pico_ipv6_link *pico_ipv6_prefix_configured(struct pico_ip6 *prefix) +{ + unsigned int nm64_len = 8; + struct pico_tree_node *index = NULL; + struct pico_ipv6_link *link = NULL; + pico_tree_foreach(index, &IPV6Links) { + link = index->keyValue; + if (memcmp(link->address.addr, prefix->addr, nm64_len) == 0) + return link; + } + return NULL; +} + +struct pico_ipv6_link *pico_ipv6_linklocal_get(struct pico_device *dev) +{ + struct pico_ipv6_link *link = pico_ipv6_link_by_dev(dev); + while (link && !pico_ipv6_is_linklocal(link->address.addr)) { + link = pico_ipv6_link_by_dev_next(dev, link); + } + return link; +} + +struct pico_ipv6_link *pico_ipv6_sitelocal_get(struct pico_device *dev) +{ + struct pico_ipv6_link *link = pico_ipv6_link_by_dev(dev); + while (link && !pico_ipv6_is_sitelocal(link->address.addr)) { + link = pico_ipv6_link_by_dev_next(dev, link); + } + return link; +} + +struct pico_ipv6_link *pico_ipv6_global_get(struct pico_device *dev) +{ + struct pico_ipv6_link *link = pico_ipv6_link_by_dev(dev); + while (link && !pico_ipv6_is_global(link->address.addr)) { + link = pico_ipv6_link_by_dev_next(dev, link); + } + return link; +} + +#define TWO_HOURS ((pico_time)(1000 * 60 * 60 * 2)) + +void pico_ipv6_check_lifetime_expired(pico_time now, void *arg) +{ + struct pico_tree_node *index = NULL, *temp; + struct pico_ipv6_link *link = NULL; + (void)arg; + pico_tree_foreach_safe(index, &IPV6Links, temp) { + link = index->keyValue; + if ((link->expire_time > 0) && (link->expire_time < now)) { + dbg("Warning: IPv6 address has expired.\n"); + pico_ipv6_link_del(link->dev, link->address); + } + } + pico_timer_add(1000, pico_ipv6_check_lifetime_expired, NULL); +} + +int pico_ipv6_lifetime_set(struct pico_ipv6_link *l, pico_time expire) +{ + pico_time now = PICO_TIME_MS(); + if (expire <= now) { + return -1; + } + + if (expire > 0xFFFFFFFE) { + l->expire_time = 0u; + }else if ((expire > (now + TWO_HOURS)) || (expire > l->expire_time)) { + l->expire_time = expire; + } else { + l->expire_time = now + TWO_HOURS; + } + + return 0; +} + +int pico_ipv6_dev_routing_enable(struct pico_device *dev) +{ + dev->hostvars.routing = 1; + return 0; +} + +int pico_ipv6_dev_routing_disable(struct pico_device *dev) +{ + dev->hostvars.routing = 0; + return 0; +} + +void pico_ipv6_unreachable(struct pico_frame *f, uint8_t code) +{ + struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)f->net_hdr; +#if defined PICO_SUPPORT_TCP || defined PICO_SUPPORT_UDP + pico_transport_error(f, hdr->nxthdr, code); +#endif +} + + + +#endif diff --git a/ext/picotcp/modules/pico_ipv6.h b/ext/picotcp/modules/pico_ipv6.h new file mode 100644 index 0000000..72064eb --- /dev/null +++ b/ext/picotcp/modules/pico_ipv6.h @@ -0,0 +1,182 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + . + + *********************************************************************/ +#ifndef _INCLUDE_PICO_IPV6 +#define _INCLUDE_PICO_IPV6 +#include "pico_addressing.h" +#include "pico_protocol.h" +#include "pico_ipv4.h" +#define PICO_SIZE_IP6HDR ((uint32_t)(sizeof(struct pico_ipv6_hdr))) +#define PICO_IPV6_DEFAULT_HOP 64 +#define PICO_IPV6_MIN_MTU 1280 +#define PICO_IPV6_STRING 46 + +#define PICO_IPV6_EXTHDR_HOPBYHOP 0 +#define PICO_IPV6_EXTHDR_ROUTING 43 +#define PICO_IPV6_EXTHDR_FRAG 44 +#define PICO_IPV6_EXTHDR_ESP 50 +#define PICO_IPV6_EXTHDR_AUTH 51 +#define PICO_IPV6_EXTHDR_NONE 59 +#define PICO_IPV6_EXTHDR_DESTOPT 60 + + +#define PICO_IPV6_EXTHDR_OPT_ROUTER_ALERT 5 +#define PICO_IPV6_EXTHDR_OPT_ROUTER_ALERT_DATALEN 2 + +#define HBH_LEN(hbh) ((((hbh->ext.hopbyhop.len + 1) << 3) - 2)) /* len in bytes, minus nxthdr and len byte */ + +extern const uint8_t PICO_IP6_ANY[PICO_SIZE_IP6]; +extern struct pico_protocol pico_proto_ipv6; +extern struct pico_tree IPV6Routes; + +PACKED_STRUCT_DEF pico_ipv6_hdr { + uint32_t vtf; + uint16_t len; + uint8_t nxthdr; + uint8_t hop; + struct pico_ip6 src; + struct pico_ip6 dst; +}; + +PACKED_STRUCT_DEF pico_ipv6_pseudo_hdr +{ + struct pico_ip6 src; + struct pico_ip6 dst; + uint32_t len; + uint8_t zero[3]; + uint8_t nxthdr; +}; + +struct pico_ipv6_link +{ + struct pico_device *dev; + struct pico_ip6 address; + struct pico_ip6 netmask; + uint8_t istentative : 1; + uint8_t isduplicate : 1; + uint32_t dad_timer; + uint16_t dup_detect_retrans; + pico_time expire_time; +#ifdef PICO_SUPPORT_MCAST + struct pico_tree *MCASTGroups; + uint8_t mcast_compatibility; + uint8_t mcast_last_query_interval; +#endif + +}; +union pico_link { + struct pico_ipv4_link ipv4; + struct pico_ipv6_link ipv6; +}; + +struct pico_ipv6_hbhoption { + uint8_t type; + uint8_t len; +}; +#ifdef PICO_SUPPORT_MCAST +struct pico_ipv6_mcast_group { + uint8_t filter_mode; + uint16_t reference_count; + struct pico_ip6 mcast_addr; + struct pico_tree MCASTSources; +}; +#endif +struct pico_ipv6_destoption { + uint8_t type; + uint8_t len; +}; + +struct pico_ipv6_route +{ + struct pico_ip6 dest; + struct pico_ip6 netmask; + struct pico_ip6 gateway; + struct pico_ipv6_link *link; + uint32_t metric; +}; + +PACKED_STRUCT_DEF pico_ipv6_exthdr { + uint8_t nxthdr; + + PACKED_UNION_DEF ipv6_ext_u { + PEDANTIC_STRUCT_DEF hopbyhop_s { + uint8_t len; + } hopbyhop; + + PEDANTIC_STRUCT_DEF destopt_s { + uint8_t len; + } destopt; + + PEDANTIC_STRUCT_DEF routing_s { + uint8_t len; + uint8_t routtype; + uint8_t segleft; + } routing; + + PEDANTIC_STRUCT_DEF fragmentation_s { + uint8_t res; + uint8_t om[2]; + uint8_t id[4]; + } frag; + } ext; +}; + +#ifdef __cplusplus +extern "C" { +#endif +struct pico_ipv6_link *pico_ipv6_link_add(struct pico_device *dev, struct pico_ip6 address, struct pico_ip6 netmask); +int pico_string_to_ipv6(const char *ipstr, uint8_t *ip); +#ifdef __cplusplus +} +#endif + +int pico_ipv6_compare(struct pico_ip6 *a, struct pico_ip6 *b); +int pico_ipv6_to_string(char *ipbuf, const uint8_t ip[PICO_SIZE_IP6]); +int pico_ipv6_is_unicast(struct pico_ip6 *a); +int pico_ipv6_is_multicast(const uint8_t addr[PICO_SIZE_IP6]); +int pico_ipv6_is_allhosts_multicast(const uint8_t addr[PICO_SIZE_IP6]); +int pico_ipv6_is_solnode_multicast(const uint8_t addr[PICO_SIZE_IP6], struct pico_device *dev); +int pico_ipv6_is_global(const uint8_t addr[PICO_SIZE_IP6]); +int pico_ipv6_is_uniquelocal(const uint8_t addr[PICO_SIZE_IP6]); +int pico_ipv6_is_sitelocal(const uint8_t addr[PICO_SIZE_IP6]); +int pico_ipv6_is_linklocal(const uint8_t addr[PICO_SIZE_IP6]); +int pico_ipv6_is_solicited(const uint8_t addr[PICO_SIZE_IP6]); +int pico_ipv6_is_unspecified(const uint8_t addr[PICO_SIZE_IP6]); +int pico_ipv6_is_localhost(const uint8_t addr[PICO_SIZE_IP6]); + +int pico_ipv6_frame_push(struct pico_frame *f, struct pico_ip6 *src, struct pico_ip6 *dst, uint8_t proto, int is_dad); +int pico_ipv6_route_add(struct pico_ip6 address, struct pico_ip6 netmask, struct pico_ip6 gateway, int metric, struct pico_ipv6_link *link); +int pico_ipv6_route_del(struct pico_ip6 address, struct pico_ip6 netmask, struct pico_ip6 gateway, int metric, struct pico_ipv6_link *link); +void pico_ipv6_unreachable(struct pico_frame *f, uint8_t code); + +int pico_ipv6_link_del(struct pico_device *dev, struct pico_ip6 address); +int pico_ipv6_cleanup_links(struct pico_device *dev); +struct pico_ipv6_link *pico_ipv6_link_istentative(struct pico_ip6 *address); +struct pico_ipv6_link *pico_ipv6_link_get(struct pico_ip6 *address); +struct pico_device *pico_ipv6_link_find(struct pico_ip6 *address); +struct pico_ip6 pico_ipv6_route_get_gateway(struct pico_ip6 *addr); +struct pico_ip6 *pico_ipv6_source_find(const struct pico_ip6 *dst); +struct pico_device *pico_ipv6_source_dev_find(const struct pico_ip6 *dst); +struct pico_ipv6_link *pico_ipv6_link_by_dev(struct pico_device *dev); +struct pico_ipv6_link *pico_ipv6_link_by_dev_next(struct pico_device *dev, struct pico_ipv6_link *last); +struct pico_ipv6_link *pico_ipv6_global_get(struct pico_device *dev); +struct pico_ipv6_link *pico_ipv6_linklocal_get(struct pico_device *dev); +struct pico_ipv6_link *pico_ipv6_sitelocal_get(struct pico_device *dev); +struct pico_ipv6_link *pico_ipv6_prefix_configured(struct pico_ip6 *prefix); +int pico_ipv6_lifetime_set(struct pico_ipv6_link *l, pico_time expire); +void pico_ipv6_check_lifetime_expired(pico_time now, void *arg); +int pico_ipv6_dev_routing_enable(struct pico_device *dev); +int pico_ipv6_dev_routing_disable(struct pico_device *dev); +void pico_ipv6_router_down(struct pico_ip6 *address); + +int pico_ipv6_mcast_join(struct pico_ip6 *mcast_link, struct pico_ip6 *mcast_group, uint8_t reference_count, uint8_t filter_mode, struct pico_tree *_MCASTFilter); +int pico_ipv6_mcast_leave(struct pico_ip6 *mcast_link, struct pico_ip6 *mcast_group, uint8_t reference_count, uint8_t filter_mode, struct pico_tree *_MCASTFilter); + +struct pico_ipv6_link *pico_ipv6_get_default_mcastlink(void); + +int pico_ipv6_is_null_address(struct pico_ip6 * ip6); +#endif diff --git a/ext/picotcp/modules/pico_ipv6_nd.c b/ext/picotcp/modules/pico_ipv6_nd.c new file mode 100644 index 0000000..c1048b3 --- /dev/null +++ b/ext/picotcp/modules/pico_ipv6_nd.c @@ -0,0 +1,1009 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + . + + Authors: Daniele Lacamera + *********************************************************************/ + +#include "pico_config.h" +#include "pico_tree.h" +#include "pico_icmp6.h" +#include "pico_ipv6.h" +#include "pico_stack.h" +#include "pico_device.h" +#include "pico_eth.h" +#include "pico_addressing.h" +#include "pico_ipv6_nd.h" + +#ifdef PICO_SUPPORT_IPV6 + + +#define nd_dbg(...) do {} while(0) + +static struct pico_frame *frames_queued_v6[PICO_ND_MAX_FRAMES_QUEUED] = { 0 }; + + +enum pico_ipv6_neighbor_state { + PICO_ND_STATE_INCOMPLETE = 0, + PICO_ND_STATE_REACHABLE, + PICO_ND_STATE_STALE, + PICO_ND_STATE_DELAY, + PICO_ND_STATE_PROBE +}; + +struct pico_ipv6_neighbor { + enum pico_ipv6_neighbor_state state; + struct pico_ip6 address; + struct pico_eth mac; + struct pico_device *dev; + uint16_t is_router; + uint16_t failure_count; + pico_time expire; +}; + +static int pico_ipv6_neighbor_compare(void *ka, void *kb) +{ + struct pico_ipv6_neighbor *a = ka, *b = kb; + return pico_ipv6_compare(&a->address, &b->address); +} + + +PICO_TREE_DECLARE(NCache, pico_ipv6_neighbor_compare); + +static struct pico_ipv6_neighbor *pico_nd_find_neighbor(struct pico_ip6 *dst) +{ + struct pico_ipv6_neighbor test = { + 0 + }; + + test.address = *dst; + return pico_tree_findKey(&NCache, &test); +} + +static void pico_ipv6_nd_queued_trigger(void) +{ + int i; + struct pico_frame *f; + for (i = 0; i < PICO_ND_MAX_FRAMES_QUEUED; i++) + { + f = frames_queued_v6[i]; + if (f) { + (void)pico_ethernet_send(f); + if(frames_queued_v6[i]) + pico_frame_discard(frames_queued_v6[i]); + frames_queued_v6[i] = NULL; + } + } +} + +static void ipv6_duplicate_detected(struct pico_ipv6_link *l) +{ + struct pico_device *dev; + int is_ll = pico_ipv6_is_linklocal(l->address.addr); + dev = l->dev; + dbg("IPV6: Duplicate address detected. Removing link.\n"); + pico_ipv6_link_del(l->dev, l->address); + if (is_ll) + pico_device_ipv6_random_ll(dev); +} + +static struct pico_ipv6_neighbor *pico_nd_add(struct pico_ip6 *addr, struct pico_device *dev) +{ + struct pico_ipv6_neighbor *n = PICO_ZALLOC(sizeof(struct pico_ipv6_neighbor)); + char address[120]; + if (!n) + return NULL; + + pico_ipv6_to_string(address, addr->addr); + nd_dbg("Adding address %s to cache...\n", address); + memcpy(&n->address, addr, sizeof(struct pico_ip6)); + n->dev = dev; + pico_tree_insert(&NCache, n); + return n; +} + +static void pico_ipv6_nd_unreachable(struct pico_ip6 *a) +{ + int i; + struct pico_frame *f; + struct pico_ipv6_hdr *hdr; + struct pico_ip6 dst; + for (i = 0; i < PICO_ND_MAX_FRAMES_QUEUED; i++) + { + f = frames_queued_v6[i]; + if (f) { + hdr = (struct pico_ipv6_hdr *) f->net_hdr; + dst = pico_ipv6_route_get_gateway(&hdr->dst); + if (pico_ipv6_is_unspecified(dst.addr)) + dst = hdr->dst; + + if (memcmp(dst.addr, a->addr, PICO_SIZE_IP6) == 0) { + if (!pico_source_is_local(f)) { + pico_notify_dest_unreachable(f); + } + + pico_frame_discard(f); + frames_queued_v6[i] = NULL; + } + } + } +} + +static void pico_nd_new_expire_time(struct pico_ipv6_neighbor *n) +{ + if (n->state == PICO_ND_STATE_REACHABLE) + n->expire = PICO_TIME_MS() + PICO_ND_REACHABLE_TIME; + else if ((n->state == PICO_ND_STATE_DELAY) || (n->state == PICO_ND_STATE_STALE)) + n->expire = PICO_TIME_MS() + PICO_ND_DELAY_FIRST_PROBE_TIME; + else { + n->expire = n->dev->hostvars.retranstime + PICO_TIME_MS(); + } +} + +static void pico_nd_discover(struct pico_ipv6_neighbor *n) +{ + char IPADDR[64]; + if (n->expire != (pico_time)0) + return; + + pico_ipv6_to_string(IPADDR, n->address.addr); + /* dbg("Sending NS for %s\n", IPADDR); */ + if (++n->failure_count > PICO_ND_MAX_SOLICIT) + return; + + if (n->state == PICO_ND_STATE_INCOMPLETE) { + pico_icmp6_neighbor_solicitation(n->dev, &n->address, PICO_ICMP6_ND_SOLICITED); + } else { + pico_icmp6_neighbor_solicitation(n->dev, &n->address, PICO_ICMP6_ND_UNICAST); + } + + pico_nd_new_expire_time(n); +} + +static struct pico_eth *pico_nd_get_neighbor(struct pico_ip6 *addr, struct pico_ipv6_neighbor *n, struct pico_device *dev) +{ + /* dbg("Finding neighbor %02x:...:%02x, state = %d\n", addr->addr[0], addr->addr[15], n?n->state:-1); */ + + if (!n) { + n = pico_nd_add(addr, dev); + pico_nd_discover(n); + return NULL; + } + + if (n->state == PICO_ND_STATE_INCOMPLETE) { + return NULL; + } + + if (n->state == PICO_ND_STATE_STALE) { + n->state = PICO_ND_STATE_DELAY; + pico_nd_new_expire_time(n); + } + + if (n->state != PICO_ND_STATE_REACHABLE) + pico_nd_discover(n); + + return &n->mac; + +} + +static struct pico_eth *pico_nd_get(struct pico_ip6 *address, struct pico_device *dev) +{ + struct pico_ip6 gateway = {{0}}, addr = {{0}}; + + /* should we use gateway, or is dst local (gateway == 0)? */ + gateway = pico_ipv6_route_get_gateway(address); + if (memcmp(gateway.addr, PICO_IP6_ANY, PICO_SIZE_IP6) == 0) + addr = *address; + else + addr = gateway; + + return pico_nd_get_neighbor(&addr, pico_nd_find_neighbor(&addr), dev); +} + +static int neigh_options(struct pico_frame *f, struct pico_icmp6_opt_lladdr *opt, uint8_t expected_opt) +{ + /* RFC 4861 $7.1.2 + $7.2.5. + * * The contents of any defined options that are not specified to be used + * * with Neighbor Advertisement messages MUST be ignored and the packet + * * processed as normal. The only defined option that may appear is the + * * Target Link-Layer Address option. + * */ + int optlen = 0; + uint8_t *option = NULL; + struct pico_icmp6_hdr *icmp6_hdr = NULL; + int len; + uint8_t type; + int found = 0; + + icmp6_hdr = (struct pico_icmp6_hdr *)f->transport_hdr; + optlen = f->transport_len - PICO_ICMP6HDR_NEIGH_ADV_SIZE; + if (optlen) + option = ((uint8_t *)&icmp6_hdr->msg.info.neigh_adv) + sizeof(struct neigh_adv_s); + + while (optlen > 0) { + type = ((struct pico_icmp6_opt_lladdr *)option)->type; + len = ((struct pico_icmp6_opt_lladdr *)option)->len; + optlen -= len << 3; /* len in units of 8 octets */ + if (len <= 0) + return -1; /* malformed option. */ + + if (type == expected_opt) { + if (found > 0) + return -1; /* malformed option: option is there twice. */ + + memcpy(opt, (struct pico_icmp6_opt_lladdr *)option, (size_t)(len << 3)); + found++; + } + + if (optlen > 0) { + option += len << 3; + } else { /* parsing options: terminated. */ + return found; + } + } + return found; +} + +static void pico_ipv6_neighbor_update(struct pico_ipv6_neighbor *n, struct pico_icmp6_opt_lladdr *opt) +{ + memcpy(n->mac.addr, opt->addr.mac.addr, PICO_SIZE_ETH); +} + +static int pico_ipv6_neighbor_compare_stored(struct pico_ipv6_neighbor *n, struct pico_icmp6_opt_lladdr *opt) +{ + return memcmp(n->mac.addr, opt->addr.mac.addr, PICO_SIZE_ETH); +} + +static void neigh_adv_reconfirm_router_option(struct pico_ipv6_neighbor *n, unsigned int isRouter) +{ + if (!isRouter && n->is_router) { + pico_ipv6_router_down(&n->address); + } + + if (isRouter) + n->is_router = 1; + else + n->is_router = 0; +} + + +static int neigh_adv_reconfirm_no_tlla(struct pico_ipv6_neighbor *n, struct pico_icmp6_hdr *hdr) +{ + if (IS_SOLICITED(hdr)) { + n->state = PICO_ND_STATE_REACHABLE; + n->failure_count = 0; + pico_ipv6_nd_queued_trigger(); + pico_nd_new_expire_time(n); + return 0; + } + + return -1; +} + + +static int neigh_adv_reconfirm(struct pico_ipv6_neighbor *n, struct pico_icmp6_opt_lladdr *opt, struct pico_icmp6_hdr *hdr) +{ + + if (IS_SOLICITED(hdr) && !IS_OVERRIDE(hdr) && (pico_ipv6_neighbor_compare_stored(n, opt) == 0)) { + n->state = PICO_ND_STATE_REACHABLE; + n->failure_count = 0; + pico_ipv6_nd_queued_trigger(); + pico_nd_new_expire_time(n); + return 0; + } + + if ((n->state == PICO_ND_STATE_REACHABLE) && IS_SOLICITED(hdr) && !IS_OVERRIDE(hdr)) { + n->state = PICO_ND_STATE_STALE; + return 0; + } + + if (IS_SOLICITED(hdr) && IS_OVERRIDE(hdr)) { + pico_ipv6_neighbor_update(n, opt); + n->state = PICO_ND_STATE_REACHABLE; + n->failure_count = 0; + pico_ipv6_nd_queued_trigger(); + pico_nd_new_expire_time(n); + return 0; + } + + if (!IS_SOLICITED(hdr) && IS_OVERRIDE(hdr) && (pico_ipv6_neighbor_compare_stored(n, opt) != 0)) { + pico_ipv6_neighbor_update(n, opt); + n->state = PICO_ND_STATE_STALE; + pico_ipv6_nd_queued_trigger(); + pico_nd_new_expire_time(n); + return 0; + } + + if ((n->state == PICO_ND_STATE_REACHABLE) && (!IS_SOLICITED(hdr)) && (!IS_OVERRIDE(hdr)) && + (pico_ipv6_neighbor_compare_stored(n, opt) != 0)) { + + /* I. If the Override flag is clear and the supplied link-layer address + * differs from that in the cache, then one of two actions takes + * place: + * a. If the state of the entry is REACHABLE, set it to STALE, but + * do not update the entry in any other way. + * b. Otherwise, the received advertisement should be ignored and + * MUST NOT update the cache. + */ + n->state = PICO_ND_STATE_STALE; + pico_nd_new_expire_time(n); + return 0; + } + + return -1; +} + +static void neigh_adv_process_incomplete(struct pico_ipv6_neighbor *n, struct pico_frame *f, struct pico_icmp6_opt_lladdr *opt) +{ + struct pico_icmp6_hdr *icmp6_hdr = NULL; + if (!n || !f) + return; + + icmp6_hdr = (struct pico_icmp6_hdr *)f->transport_hdr; + + if (!icmp6_hdr) + return; + + if (IS_SOLICITED(icmp6_hdr)) { + n->state = PICO_ND_STATE_REACHABLE; + n->failure_count = 0; + pico_nd_new_expire_time(n); + } else { + n->state = PICO_ND_STATE_STALE; + } + + if (opt) + pico_ipv6_neighbor_update(n, opt); + + pico_ipv6_nd_queued_trigger(); +} + + +static int neigh_adv_process(struct pico_frame *f) +{ + struct pico_icmp6_hdr *icmp6_hdr = NULL; + struct pico_ipv6_neighbor *n = NULL; + struct pico_icmp6_opt_lladdr opt = { + 0 + }; + int optres = neigh_options(f, &opt, PICO_ND_OPT_LLADDR_TGT); + icmp6_hdr = (struct pico_icmp6_hdr *)f->transport_hdr; + + if (optres < 0) { /* Malformed packet: option field cannot be processed. */ + return -1; + } + + n = pico_nd_find_neighbor(&icmp6_hdr->msg.info.neigh_adv.target); + if (!n) { + return 0; + } + + if ((optres == 0) || IS_OVERRIDE(icmp6_hdr) || (pico_ipv6_neighbor_compare_stored(n, &opt) == 0)) { + neigh_adv_reconfirm_router_option(n, IS_ROUTER(icmp6_hdr)); + } + + if ((optres > 0) && (n->state == PICO_ND_STATE_INCOMPLETE)) { + neigh_adv_process_incomplete(n, f, &opt); + return 0; + } + + if (optres > 0) + return neigh_adv_reconfirm(n, &opt, icmp6_hdr); + else + return neigh_adv_reconfirm_no_tlla(n, icmp6_hdr); + +} + + + +static struct pico_ipv6_neighbor *pico_ipv6_neighbor_from_sol_new(struct pico_ip6 *ip, struct pico_icmp6_opt_lladdr *opt, struct pico_device *dev) +{ + struct pico_ipv6_neighbor *n = NULL; + n = pico_nd_add(ip, dev); + if (!n) + return NULL; + + memcpy(n->mac.addr, opt->addr.mac.addr, PICO_SIZE_ETH); + n->state = PICO_ND_STATE_STALE; + pico_ipv6_nd_queued_trigger(); + return n; +} + +static void pico_ipv6_neighbor_from_unsolicited(struct pico_frame *f) +{ + struct pico_ipv6_neighbor *n = NULL; + struct pico_icmp6_opt_lladdr opt = { + 0 + }; + struct pico_ipv6_hdr *ip = (struct pico_ipv6_hdr *)f->net_hdr; + int valid_lladdr = neigh_options(f, &opt, PICO_ND_OPT_LLADDR_SRC); + + if (!pico_ipv6_is_unspecified(ip->src.addr) && (valid_lladdr > 0)) { + n = pico_nd_find_neighbor(&ip->src); + if (!n) { + n = pico_ipv6_neighbor_from_sol_new(&ip->src, &opt, f->dev); + } else if (memcmp(opt.addr.mac.addr, n->mac.addr, PICO_SIZE_ETH)) { + pico_ipv6_neighbor_update(n, &opt); + n->state = PICO_ND_STATE_STALE; + pico_ipv6_nd_queued_trigger(); + pico_nd_new_expire_time(n); + } + + if (!n) + return; + } +} + +static int neigh_sol_detect_dad(struct pico_frame *f) +{ + struct pico_ipv6_hdr *ipv6_hdr = NULL; + struct pico_icmp6_hdr *icmp6_hdr = NULL; + struct pico_ipv6_link *link = NULL; + ipv6_hdr = (struct pico_ipv6_hdr *)f->net_hdr; + icmp6_hdr = (struct pico_icmp6_hdr *)f->transport_hdr; + link = pico_ipv6_link_istentative(&icmp6_hdr->msg.info.neigh_adv.target); + if (link) { + if (pico_ipv6_is_unicast(&ipv6_hdr->src)) + { + /* RFC4862 5.4.3 : sender is performing address resolution, + * our address is not yet valid, discard silently. + */ + dbg("DAD:Sender performing AR\n"); + } + + else if (pico_ipv6_is_unspecified(ipv6_hdr->src.addr) && + !pico_ipv6_is_allhosts_multicast(ipv6_hdr->dst.addr)) + { + /* RFC4862 5.4.3 : sender is performing DaD */ + dbg("DAD:Sender performing DaD\n"); + ipv6_duplicate_detected(link); + } + + return 0; + } + + return -1; /* Current link is not tentative */ +} + +static int neigh_sol_process(struct pico_frame *f) +{ + struct pico_icmp6_hdr *icmp6_hdr = NULL; + struct pico_ipv6_link *link = NULL; + int valid_lladdr; + struct pico_icmp6_opt_lladdr opt = { + 0 + }; + icmp6_hdr = (struct pico_icmp6_hdr *)f->transport_hdr; + + valid_lladdr = neigh_options(f, &opt, PICO_ND_OPT_LLADDR_SRC); + pico_ipv6_neighbor_from_unsolicited(f); + + if ((valid_lladdr == 0) && (neigh_sol_detect_dad(f) == 0)) + return 0; + + if (valid_lladdr < 0) + return -1; /* Malformed packet. */ + + link = pico_ipv6_link_get(&icmp6_hdr->msg.info.neigh_adv.target); + if (!link) { /* Not for us. */ + return -1; + } + + pico_icmp6_neighbor_advertisement(f, &icmp6_hdr->msg.info.neigh_adv.target); + return 0; +} + +static int icmp6_initial_checks(struct pico_frame *f) +{ + /* Common "step 0" validation */ + struct pico_ipv6_hdr *ipv6_hdr = NULL; + struct pico_icmp6_hdr *icmp6_hdr = NULL; + + ipv6_hdr = (struct pico_ipv6_hdr *)f->net_hdr; + icmp6_hdr = (struct pico_icmp6_hdr *)f->transport_hdr; + + /* RFC4861 - 7.1.2 : + * - The IP Hop Limit field has a value of 255, i.e., the packet + * could not possibly have been forwarded by a router. + * - ICMP Checksum is valid. + * - ICMP Code is 0. + */ + if (ipv6_hdr->hop != 255 || pico_icmp6_checksum(f) != 0 || icmp6_hdr->code != 0) + return -1; + + return 0; +} + +static int neigh_adv_option_len_validity_check(struct pico_frame *f) +{ + /* Step 4 validation */ + struct pico_icmp6_hdr *icmp6_hdr = NULL; + uint8_t *opt; + int optlen = f->transport_len - PICO_ICMP6HDR_NEIGH_ADV_SIZE; + /* RFC4861 - 7.1.2 : + * - All included options have a length that is greater than zero. + */ + icmp6_hdr = (struct pico_icmp6_hdr *)f->transport_hdr; + opt = ((uint8_t *)&icmp6_hdr->msg.info.neigh_adv) + sizeof(struct neigh_adv_s); + + while(optlen > 0) { + int opt_size = (opt[1] << 3); + if (opt_size == 0) + return -1; + + opt = opt + opt_size; + optlen -= opt_size; + } + return 0; +} + +static int neigh_adv_mcast_validity_check(struct pico_frame *f) +{ + /* Step 3 validation */ + struct pico_ipv6_hdr *ipv6_hdr = NULL; + struct pico_icmp6_hdr *icmp6_hdr = NULL; + /* RFC4861 - 7.1.2 : + * - If the IP Destination Address is a multicast address the + * Solicited flag is zero. + */ + ipv6_hdr = (struct pico_ipv6_hdr *)f->net_hdr; + icmp6_hdr = (struct pico_icmp6_hdr *)f->transport_hdr; + if (pico_ipv6_is_multicast(ipv6_hdr->dst.addr) && IS_SOLICITED(icmp6_hdr)) + return -1; + + return neigh_adv_option_len_validity_check(f); +} + +static int neigh_adv_validity_checks(struct pico_frame *f) +{ + /* Step 2 validation */ + /* RFC4861 - 7.1.2: + * - ICMP length (derived from the IP length) is 24 or more octets. + */ + if (f->transport_len < PICO_ICMP6HDR_NEIGH_ADV_SIZE) + return -1; + + return neigh_adv_mcast_validity_check(f); +} + + +static int neigh_sol_mcast_validity_check(struct pico_frame *f) +{ + struct pico_icmp6_hdr *icmp6_hdr = NULL; + icmp6_hdr = (struct pico_icmp6_hdr *)f->transport_hdr; + if (pico_ipv6_is_solnode_multicast(icmp6_hdr->msg.info.neigh_sol.target.addr, f->dev) == 0) + return -1; + + return 0; +} + +static int neigh_sol_unicast_validity_check(struct pico_frame *f) +{ + struct pico_ipv6_link *link; + struct pico_icmp6_hdr *icmp6_hdr = NULL; + + link = pico_ipv6_link_by_dev(f->dev); + icmp6_hdr = (struct pico_icmp6_hdr *)f->transport_hdr; + while(link) { + if (pico_ipv6_compare(&link->address, &icmp6_hdr->msg.info.neigh_sol.target) == 0) + return 0; + + link = pico_ipv6_link_by_dev_next(f->dev, link); + } + return -1; + +} + +static int neigh_sol_validate_unspec(struct pico_frame *f) +{ + /* RFC4861, 7.1.1: + * + * - If the IP source address is the unspecified address, the IP + * destination address is a solicited-node multicast address. + * + * - If the IP source address is the unspecified address, there is no + * source link-layer address option in the message. + * + */ + + struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)(f->net_hdr); + struct pico_icmp6_opt_lladdr opt = { + 0 + }; + int valid_lladdr = neigh_options(f, &opt, PICO_ND_OPT_LLADDR_SRC); + if (pico_ipv6_is_solnode_multicast(hdr->dst.addr, f->dev) == 0) { + return -1; + } + + if (valid_lladdr) { + return -1; + } + + return 0; +} + +static int neigh_sol_validity_checks(struct pico_frame *f) +{ + /* Step 2 validation */ + struct pico_icmp6_hdr *icmp6_hdr = NULL; + struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)(f->net_hdr); + if (f->transport_len < PICO_ICMP6HDR_NEIGH_ADV_SIZE) + return -1; + + if ((pico_ipv6_is_unspecified(hdr->src.addr)) && (neigh_sol_validate_unspec(f) < 0)) + { + return -1; + } + + icmp6_hdr = (struct pico_icmp6_hdr *)f->transport_hdr; + if (pico_ipv6_is_multicast(icmp6_hdr->msg.info.neigh_adv.target.addr)) { + return neigh_sol_mcast_validity_check(f); + } + + return neigh_sol_unicast_validity_check(f); +} + +static int router_adv_validity_checks(struct pico_frame *f) +{ + /* Step 2 validation */ + if (f->transport_len < PICO_ICMP6HDR_ROUTER_ADV_SIZE) + return -1; + + return 0; +} + +static int neigh_adv_checks(struct pico_frame *f) +{ + /* Step 1 validation */ + if (icmp6_initial_checks(f) < 0) + return -1; + + return neigh_adv_validity_checks(f); +} + + +static int pico_nd_router_sol_recv(struct pico_frame *f) +{ + pico_ipv6_neighbor_from_unsolicited(f); + /* Host only: router solicitation is discarded. */ + return 0; +} + +static int radv_process(struct pico_frame *f) +{ + struct pico_icmp6_hdr *icmp6_hdr = NULL; + uint8_t *nxtopt, *opt_start; + struct pico_ipv6_link *link; + struct pico_ipv6_hdr *hdr; + struct pico_ip6 zero = { + .addr = {0} + }; + int optlen; + + hdr = (struct pico_ipv6_hdr *)f->net_hdr; + icmp6_hdr = (struct pico_icmp6_hdr *)f->transport_hdr; + optlen = f->transport_len - PICO_ICMP6HDR_ROUTER_ADV_SIZE; + opt_start = ((uint8_t *)&icmp6_hdr->msg.info.router_adv) + sizeof(struct router_adv_s); + nxtopt = opt_start; + + while (optlen > 0) { + uint8_t *type = (uint8_t *)nxtopt; + switch (*type) { + case PICO_ND_OPT_PREFIX: + { + pico_time now = PICO_TIME_MS(); + struct pico_icmp6_opt_prefix *prefix = + (struct pico_icmp6_opt_prefix *) nxtopt; + /* RFC4862 5.5.3 */ + /* a) If the Autonomous flag is not set, silently ignore the Prefix + * Information option. + */ + if (prefix->aac == 0) + goto ignore_opt_prefix; + + /* b) If the prefix is the link-local prefix, silently ignore the + * Prefix Information option + */ + if (pico_ipv6_is_linklocal(prefix->prefix.addr)) + goto ignore_opt_prefix; + + /* c) If the preferred lifetime is greater than the valid lifetime, + * silently ignore the Prefix Information option + */ + if (long_be(prefix->pref_lifetime) > long_be(prefix->val_lifetime)) + goto ignore_opt_prefix; + + if (prefix->val_lifetime == 0) + goto ignore_opt_prefix; + + + if (prefix->prefix_len != 64) { + return -1; + } + + link = pico_ipv6_prefix_configured(&prefix->prefix); + if (link) { + pico_ipv6_lifetime_set(link, now + (pico_time)(1000 * (long_be(prefix->val_lifetime)))); + goto ignore_opt_prefix; + } + + link = pico_ipv6_link_add_local(f->dev, &prefix->prefix); + if (link) { + pico_ipv6_lifetime_set(link, now + (pico_time)(1000 * (long_be(prefix->val_lifetime)))); + pico_ipv6_route_add(zero, zero, hdr->src, 10, link); + } + +ignore_opt_prefix: + optlen -= (prefix->len << 3); + nxtopt += (prefix->len << 3); + } + break; + case PICO_ND_OPT_LLADDR_SRC: + { + struct pico_icmp6_opt_lladdr *lladdr_src = + (struct pico_icmp6_opt_lladdr *) nxtopt; + optlen -= (lladdr_src->len << 3); + nxtopt += (lladdr_src->len << 3); + } + break; + case PICO_ND_OPT_MTU: + { + struct pico_icmp6_opt_mtu *mtu = + (struct pico_icmp6_opt_mtu *) nxtopt; + /* Skip this */ + optlen -= (mtu->len << 3); + nxtopt += (mtu->len << 3); + } + break; + case PICO_ND_OPT_REDIRECT: + { + struct pico_icmp6_opt_redirect *redirect = + (struct pico_icmp6_opt_redirect *) nxtopt; + /* Skip this */ + optlen -= (redirect->len << 3); + nxtopt += (redirect->len << 3); + + } + break; + case PICO_ND_OPT_RDNSS: + { + struct pico_icmp6_opt_rdnss *rdnss = + (struct pico_icmp6_opt_rdnss *) nxtopt; + /* Skip this */ + optlen -= (rdnss->len << 3); + nxtopt += (rdnss->len << 3); + } + break; + default: + pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_IPV6OPT, + (uint32_t)sizeof(struct pico_ipv6_hdr) + (uint32_t)PICO_ICMP6HDR_ROUTER_ADV_SIZE + (uint32_t)(nxtopt - opt_start)); + return -1; + } + } + if (icmp6_hdr->msg.info.router_adv.retrans_time != 0u) { + f->dev->hostvars.retranstime = long_be(icmp6_hdr->msg.info.router_adv.retrans_time); + } + + return 0; +} + + +static int pico_nd_router_adv_recv(struct pico_frame *f) +{ + if (icmp6_initial_checks(f) < 0) + return -1; + + if (router_adv_validity_checks(f) < 0) + return -1; + + pico_ipv6_neighbor_from_unsolicited(f); + return radv_process(f); +} + +static int pico_nd_neigh_sol_recv(struct pico_frame *f) +{ + if (icmp6_initial_checks(f) < 0) + return -1; + + if (neigh_sol_validity_checks(f) < 0) + return -1; + + return neigh_sol_process(f); +} + +static int pico_nd_neigh_adv_recv(struct pico_frame *f) +{ + struct pico_icmp6_hdr *icmp6_hdr = NULL; + struct pico_ipv6_link *link = NULL; + + icmp6_hdr = (struct pico_icmp6_hdr *)f->transport_hdr; + if (neigh_adv_checks(f) < 0) { + return -1; + } + + link = pico_ipv6_link_istentative(&icmp6_hdr->msg.info.neigh_adv.target); + if (link) + ipv6_duplicate_detected(link); + + return neigh_adv_process(f); +} + +static int pico_nd_redirect_recv(struct pico_frame *f) +{ + pico_ipv6_neighbor_from_unsolicited(f); + /* TODO */ + return 0; +} + +static void pico_ipv6_nd_timer_elapsed(pico_time now, struct pico_ipv6_neighbor *n) +{ + (void)now; + switch(n->state) { + case PICO_ND_STATE_INCOMPLETE: + /* intentional fall through */ + case PICO_ND_STATE_PROBE: + if (n->failure_count > PICO_ND_MAX_SOLICIT) { + pico_ipv6_nd_unreachable(&n->address); + pico_tree_delete(&NCache, n); + PICO_FREE(n); + return; + } + + n->expire = 0ull; + pico_nd_discover(n); + break; + + case PICO_ND_STATE_REACHABLE: + n->state = PICO_ND_STATE_STALE; + /* dbg("IPv6_ND: neighbor expired!\n"); */ + return; + + case PICO_ND_STATE_STALE: + break; + + case PICO_ND_STATE_DELAY: + n->expire = 0ull; + n->state = PICO_ND_STATE_PROBE; + break; + default: + dbg("IPv6_ND: neighbor in wrong state!\n"); + } + pico_nd_new_expire_time(n); +} + +static void pico_ipv6_nd_timer_callback(pico_time now, void *arg) +{ + struct pico_tree_node *index = NULL, *_tmp = NULL; + struct pico_ipv6_neighbor *n; + + (void)arg; + pico_tree_foreach_safe(index, &NCache, _tmp) + { + n = index->keyValue; + if ( now > n->expire) { + pico_ipv6_nd_timer_elapsed(now, n); + } + } + pico_timer_add(200, pico_ipv6_nd_timer_callback, NULL); +} + +#define PICO_IPV6_ND_MIN_RADV_INTERVAL (5000) +#define PICO_IPV6_ND_MAX_RADV_INTERVAL (15000) + +static void pico_ipv6_nd_ra_timer_callback(pico_time now, void *arg) +{ + struct pico_tree_node *devindex = NULL; + struct pico_tree_node *rindex = NULL; + struct pico_device *dev; + struct pico_ipv6_route *rt; + struct pico_ip6 nm64 = { {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0 } }; + pico_time next_timer_expire = 0u; + + (void)arg; + (void)now; + pico_tree_foreach(rindex, &IPV6Routes) + { + rt = rindex->keyValue; + if (pico_ipv6_compare(&nm64, &rt->netmask) == 0) { + pico_tree_foreach(devindex, &Device_tree) { + dev = devindex->keyValue; + if ((!pico_ipv6_is_linklocal(rt->dest.addr)) && dev->hostvars.routing && (rt->link) && (dev != rt->link->dev)) { + pico_icmp6_router_advertisement(dev, &rt->dest); + } + } + } + } + next_timer_expire = PICO_IPV6_ND_MIN_RADV_INTERVAL + (pico_rand() % (PICO_IPV6_ND_MAX_RADV_INTERVAL - PICO_IPV6_ND_MIN_RADV_INTERVAL)); + pico_timer_add(next_timer_expire, pico_ipv6_nd_ra_timer_callback, NULL); +} + +/* Public API */ + +struct pico_eth *pico_ipv6_get_neighbor(struct pico_frame *f) +{ + struct pico_ipv6_hdr *hdr = NULL; + struct pico_ipv6_link *l = NULL; + if (!f) + return NULL; + + hdr = (struct pico_ipv6_hdr *)f->net_hdr; + /* If we are still probing for Duplicate Address, abort now. */ + if (pico_ipv6_link_istentative(&hdr->src)) + return NULL; + + /* address belongs to ourselves? */ + l = pico_ipv6_link_get(&hdr->dst); + if (l) + return &l->dev->eth->mac; + + return pico_nd_get(&hdr->dst, f->dev); +} + +void pico_ipv6_nd_postpone(struct pico_frame *f) +{ + int i; + static int last_enq = -1; + struct pico_frame *cp = pico_frame_copy(f); + for (i = 0; i < PICO_ND_MAX_FRAMES_QUEUED; i++) + { + if (!frames_queued_v6[i]) { + frames_queued_v6[i] = cp; + last_enq = i; + return; + } + } + /* Overwrite the oldest frame in the buffer */ + if (++last_enq >= PICO_ND_MAX_FRAMES_QUEUED) { + last_enq = 0; + } + + if (frames_queued_v6[last_enq]) + pico_frame_discard(frames_queued_v6[last_enq]); + + frames_queued_v6[last_enq] = cp; +} + + +int pico_ipv6_nd_recv(struct pico_frame *f) +{ + + struct pico_icmp6_hdr *hdr = (struct pico_icmp6_hdr *)f->transport_hdr; + int ret = -1; + switch(hdr->type) { + case PICO_ICMP6_ROUTER_SOL: + nd_dbg("ICMP6: received ROUTER SOL\n"); + ret = pico_nd_router_sol_recv(f); + break; + + case PICO_ICMP6_ROUTER_ADV: + ret = pico_nd_router_adv_recv(f); + break; + + case PICO_ICMP6_NEIGH_SOL: + nd_dbg("ICMP6: received NEIGH SOL\n"); + ret = pico_nd_neigh_sol_recv(f); + break; + + case PICO_ICMP6_NEIGH_ADV: + nd_dbg("ICMP6: received NEIGH ADV\n"); + ret = pico_nd_neigh_adv_recv(f); + break; + + case PICO_ICMP6_REDIRECT: + ret = pico_nd_redirect_recv(f); + break; + } + pico_frame_discard(f); + return ret; +} + +void pico_ipv6_nd_init(void) +{ + pico_timer_add(200, pico_ipv6_nd_timer_callback, NULL); + pico_timer_add(200, pico_ipv6_nd_ra_timer_callback, NULL); + pico_timer_add(1000, pico_ipv6_check_lifetime_expired, NULL); +} + +#endif diff --git a/ext/picotcp/modules/pico_ipv6_nd.h b/ext/picotcp/modules/pico_ipv6_nd.h new file mode 100644 index 0000000..f709b1e --- /dev/null +++ b/ext/picotcp/modules/pico_ipv6_nd.h @@ -0,0 +1,26 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + *********************************************************************/ +#ifndef _INCLUDE_PICO_ND +#define _INCLUDE_PICO_ND +#include "pico_frame.h" + +/* RFC constants */ +#define PICO_ND_REACHABLE_TIME 30000 /* msec */ +#define PICO_ND_RETRANS_TIMER 1000 /* msec */ + +struct pico_nd_hostvars { + uint8_t routing; + uint8_t hoplimit; + pico_time basetime; + pico_time reachabletime; + pico_time retranstime; +}; + +void pico_ipv6_nd_init(void); +struct pico_eth *pico_ipv6_get_neighbor(struct pico_frame *f); +void pico_ipv6_nd_postpone(struct pico_frame *f); +int pico_ipv6_nd_recv(struct pico_frame *f); +#endif diff --git a/ext/picotcp/modules/pico_mdns.c b/ext/picotcp/modules/pico_mdns.c new file mode 100644 index 0000000..6b5c5e9 --- /dev/null +++ b/ext/picotcp/modules/pico_mdns.c @@ -0,0 +1,3417 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2014-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + . + Author: Toon Stegen, Jelle De Vleeschouwer + *********************************************************************/ +#include "pico_config.h" +#include "pico_stack.h" +#include "pico_addressing.h" +#include "pico_socket.h" +#include "pico_ipv4.h" +#include "pico_ipv6.h" +#include "pico_tree.h" +#include "pico_mdns.h" + +#ifdef PICO_SUPPORT_MDNS + +/* --- Debugging --- */ +#define mdns_dbg(...) do {} while(0) +//#define mdns_dbg dbg + +#define PICO_MDNS_QUERY_TIMEOUT (10000) /* Ten seconds */ +#define PICO_MDNS_RR_TTL_TICK (1000) /* One second */ + +/* mDNS MTU size */ +#define PICO_MDNS_MAXBUF (1400u) + +/* --- Cookie flags --- */ +#define PICO_MDNS_PACKET_TYPE_ANNOUNCEMENT (0x01u) +#define PICO_MDNS_PACKET_TYPE_ANSWER (0x02u) +#define PICO_MDNS_PACKET_TYPE_QUERY (0x04u) +#define PICO_MDNS_PACKET_TYPE_PROBE (0x08u) +#define PICO_MDNS_PACKET_TYPE_QUERY_ANY (0x00u) +/* --- Cookie status --- */ +#define PICO_MDNS_COOKIE_STATUS_ACTIVE (0xffu) +#define PICO_MDNS_COOKIE_STATUS_INACTIVE (0x00u) +#define PICO_MDNS_COOKIE_STATUS_CANCELLED (0x77u) +#define PICO_MDNS_COOKIE_TIMEOUT (10u) + +#define PICO_MDNS_SECTION_ANSWERS (0) +#define PICO_MDNS_SECTION_AUTHORITIES (1) +#define PICO_MDNS_SETCTIO_ADDITIONALS (2) + +/* --- Question flags --- */ +#define PICO_MDNS_QUESTION_FLAG_PROBE (0x01u) +#define PICO_MDNS_QUESTION_FLAG_NO_PROBE (0x00u) +#define PICO_MDNS_QUESTION_FLAG_UNICAST_RES (0x02u) +#define PICO_MDNS_QUESTION_FLAG_MULTICAST_RES (0x00u) + +#define IS_QUESTION_PROBE_FLAG_SET(x) \ + (((x) & PICO_MDNS_QUESTION_FLAG_PROBE) ? (1) : (0)) +#define IS_QUESTION_UNICAST_FLAG_SET(x) \ + (((x) & PICO_MDNS_QUESTION_FLAG_UNICAST_RES) ? (1) : (0)) +#define IS_QUESTION_MULTICAST_FLAG_SET(x) \ + (((x) & PICO_MDNS_QUESTION_FLAG_UNICAST_RES) ? (0) : (1)) + +/* Resource Record flags */ +#define PICO_MDNS_RECORD_ADDITIONAL (0x08u) +#define PICO_MDNS_RECORD_SEND_UNICAST (0x10u) +#define PICO_MDNS_RECORD_CURRENTLY_PROBING (0x20u) +#define PICO_MDNS_RECORD_PROBED (0x40u) +#define PICO_MDNS_RECORD_CLAIMED (0x80u) + +#define IS_SHARED_RECORD(x) \ + ((x)->flags & PICO_MDNS_RECORD_SHARED) +#define IS_UNIQUE_RECORD(x) \ + (!((x)->flags & PICO_MDNS_RECORD_SHARED)) +#define IS_RECORD_PROBING(x) \ + ((x)->flags & PICO_MDNS_RECORD_CURRENTLY_PROBING) +#define IS_UNICAST_REQUESTED(x) \ + ((x)->flags & PICO_MDNS_RECORD_SEND_UNICAST) +#define IS_RECORD_VERIFIED(x) \ + ((x)->flags & PICO_MDNS_RECORD_PROBED) +#define IS_RECORD_CLAIMED(x) \ + ((x)->flags & PICO_MDNS_RECORD_CLAIMED) + +/* Set and clear flags */ +#define PICO_MDNS_SET_FLAG(x, b) (x = ((x) | (uint8_t)(b))) +#define PICO_MDNS_CLR_FLAG(x, b) (x = (uint8_t)(((x) & (~((uint8_t)(b)))))) + +/* Set and clear MSB of BE short */ +#define PICO_MDNS_SET_MSB(x) (x = x | (uint16_t)(0x8000u)) +#define PICO_MDNS_CLR_MSB(x) (x = x & (uint16_t)(0x7fffu)) +#define PICO_MDNS_SET_MSB_BE(x) (x = x | (uint16_t)(short_be(0x8000u))) +#define PICO_MDNS_CLR_MSB_BE(x) (x = x & (uint16_t)(short_be(0x7fffu))) +#define PICO_MDNS_IS_MSB_SET(x) ((x & 0x8000u) ? 1 : 0) + +/* **************************************************************************** + * mDNS cookie + * ****************************************************************************/ +struct pico_mdns_cookie +{ + pico_dns_qtree qtree; /* Question tree */ + pico_mdns_rtree antree; /* Answer tree */ + pico_mdns_rtree artree; /* Additional record tree */ + uint8_t count; /* Times to send the query */ + uint8_t type; /* QUERY/ANNOUNCE/PROBE/ANSWER */ + uint8_t status; /* Active status */ + uint8_t timeout; /* Timeout counter */ + uint32_t send_timer; /* For sending events */ + void (*callback)(pico_mdns_rtree *, + char *, + void *); /* Callback */ + void *arg; /* Argument to pass to callback */ +}; + +/* MARK: TREES & GLOBAL VARIABLES */ + +/* MDNS Communication variables */ +static struct pico_socket *mdns_sock_ipv4 = NULL; +static uint16_t mdns_port = 5353u; +static struct pico_ip4 inaddr_any = { + 0 +}; + +/* **************************************************************************** + * Hostname for this machine, only 1 hostname can be set. + * ****************************************************************************/ +static char *_hostname = NULL; +static void (*init_callback)(pico_mdns_rtree *, char *, void *) = 0; + +/* **************************************************************************** + * Compares 2 mDNS records by name and type only + * + * @param a mDNS record A + * @param b mDNS record B + * @return 0 when name and type of records are equal, returns difference when + * they're not. + * ****************************************************************************/ +static int +pico_mdns_record_cmp_name_type( void *a, void *b ) +{ + struct pico_mdns_record *_a = NULL, *_b = NULL; + + /* Check params */ + if (!(_a = (struct pico_mdns_record *)a) || + !(_b = (struct pico_mdns_record *)b)) { + pico_err = PICO_ERR_EINVAL; + return -1; /* Don't want a wrong result when NULL-pointers are passed */ + } + + return pico_dns_record_cmp_name_type(_a->record, _b->record); +} + +/* **************************************************************************** + * Compares 2 mDNS records by type, name AND rdata for a truly unique result + * + * @param ra mDNS record A + * @param rb mDNS record B + * @return 0 when records are equal, returns difference when they're not. + * ****************************************************************************/ +int +pico_mdns_record_cmp( void *a, void *b ) +{ + /* Check params */ + if (!a || !b) { + if (!a && !b) + return 0; + pico_err = PICO_ERR_EINVAL; + return -1; /* Don't want a wrong result when NULL-pointers are passed */ + } + + return pico_dns_record_cmp((void*)(((struct pico_mdns_record *)a)->record), + (void*)(((struct pico_mdns_record *)b)->record)); +} + +/* **************************************************************************** + * Compares 2 mDNS cookies again each other. Only compares questions since a + * only a cookie query will be added to the tree. And there shouldn't be 2 + * different cookies with the same questions in the tree. + * + * @param ka mDNS cookie A + * @param kb mDNS cookie B + * @return 0 when cookies are equal, returns difference when they're not. + * ****************************************************************************/ +static int +pico_mdns_cookie_cmp( void *ka, void *kb ) +{ + struct pico_mdns_cookie *a = (struct pico_mdns_cookie *)ka; + struct pico_mdns_cookie *b = (struct pico_mdns_cookie *)kb; + struct pico_dns_question *qa = NULL, *qb = 0; + struct pico_tree_node *na = NULL, *nb = 0; + uint16_t ca = 0, cb = 0; + int ret = 0; + + /* Check params */ + if (!a || !b) { + pico_err = PICO_ERR_EINVAL; + return -1; /* Don't want a wrong result when NULL-pointers are passed */ + } + + /* Start comparing the questions */ + for (na = pico_tree_firstNode(a->qtree.root), + nb = pico_tree_firstNode(b->qtree.root); + (na != &LEAF) && (nb != &LEAF); + na = pico_tree_next(na), + nb = pico_tree_next(nb)) { + qa = na->keyValue; + qb = nb->keyValue; + if ((qa) && (qb) && (ret = pico_dns_question_cmp(qa, qb))) + return ret; + } + /* Check for lengths difference */ + ca = pico_tree_count(&(a->qtree)); + cb = pico_tree_count(&(b->qtree)); + if (ca != cb) + return (int)((int)ca - (int)cb); + + /* Cookies contain same questions, shouldn't happen */ + return 0; +} + +#if PICO_MDNS_ALLOW_CACHING == 1 +/* Cache records from mDNS peers on the network */ +PICO_TREE_DECLARE(Cache, &pico_mdns_record_cmp); +#endif + +/* My records for which I want to have the authority */ +PICO_TREE_DECLARE(MyRecords, &pico_mdns_record_cmp_name_type); + +/* Cookie-tree */ +PICO_TREE_DECLARE(Cookies, &pico_mdns_cookie_cmp); + +/* **************************************************************************** + * MARK: PROTOTYPES */ +static int +pico_mdns_getrecord_generic( const char *url, uint16_t type, + void (*callback)(pico_mdns_rtree *, + char *, + void *), + void *arg); + +static void +pico_mdns_send_probe_packet( pico_time now, void *arg ); + +static int +pico_mdns_reclaim( pico_mdns_rtree record_tree, + void (*callback)(pico_mdns_rtree *, + char *, + void *), + void *arg ); +/* EOF PROTOTYPES + * ****************************************************************************/ + +/* MARK: v MDNS NAMES */ + +#define IS_NUM(c) (((c) >= '0') && ((c) <= '9')) +/* **************************************************************************** + * Tries to convert the characters after '-' to a numeric value. + * + * @param opening Pointer to dash index. + * @param closing Pointer to end of label. + * @return Numeric value of suffix on success + * ****************************************************************************/ +static inline uint16_t +pico_mdns_suffix_to_uint16( char *opening, char *closing) +{ + uint16_t n = 0; + char *i = 0; + + /* Check params */ + if (!opening || !closing || + ((closing - opening) > 5) || + ((closing - opening) < 0)) + return 0; + + for (i = (char *)(opening + 1); i < closing; i++) { + if (!IS_NUM(*i)) + return 0; + + n = (uint16_t)((n * 10) + (*i - '0')); + } + return n; +} + +#define iterate_first_label_name_reverse(iterator, name) \ + for ((iterator) = \ + (*name < (char)63) ? ((char *)(name + *name)) : (name); \ + (iterator) > (name); \ + (iterator)--) + +/* **************************************************************************** + * Checks whether there is already a conflict-suffix already present in the + * first label of a name or not. + * + * @param name Name in DNS name notation you want to check for a suffix. + * @param o_i Pointer-pointer, will get filled with location to '-'-char. + * @param c_i Pointer-pointer, will get filled with end of label. + * @return Returns value of the suffix, when it's present, 0 when no correct + * suffix is present. + * ****************************************************************************/ +static uint16_t +pico_mdns_is_suffix_present( char name[], + char **o_i, + char **c_i ) +{ + char *i = NULL; + uint16_t n = 0; + + *o_i = NULL; /* Clear out indexes */ + *c_i = NULL; + + /* Find the end of label. */ + *c_i = (name + *name + 1); + + iterate_first_label_name_reverse(i, name) { + /* Find the last dash */ + if ((*c_i) && (i < *c_i) && *i == '-') { + *o_i = i; + break; + } + } + + /* Convert the string suffix to a number */ + if (!(n = pico_mdns_suffix_to_uint16(*o_i, *c_i))) { + *o_i = NULL; + *c_i = NULL; + } + + return n; +} + +/* **************************************************************************** + * Manual string to uint16_t conversion. + * + * @param n Numeric value you want to convert. + * @param s String to convert to + * @return void + * ****************************************************************************/ +static void pico_itoa( uint16_t n, char s[] ) +{ + int i = 0, j = 0; + char c = 0; + + /* Get char values */ + do { + s[i++] = (char)(n % 10 + '0'); + } while ((n /= 10) > 0); + + /* Reverse the string */ + for (i = 0, j = (int)(pico_dns_strlen(s) - 1); i < j; i++, j--) { + c = s[i]; + s[i] = s[j]; + s[j] = c; + } +} + +/* **************************************************************************** + * Generates a new name by appending a conflict resolution suffix to the first + * label of an FQDN. + * + * @param rname Name you want to append the suffix to + * @return Newly created FQDN with suffix appended to first label. + * ****************************************************************************/ +static char * +pico_mdns_resolve_name_conflict( char rname[] ) +{ + char *new_rname = NULL; + char suffix[5] = { + 0 + }, nsuffix[5] = { + 0 + }, copy_offset = 0; + char *o_i = NULL, *c_i = NULL; + uint16_t new_len = (uint16_t)(pico_dns_strlen(rname) + 1); + uint8_t nslen = 0, slen = 0, ns = 0; + + /* Check params */ + if (pico_dns_check_namelen(new_len)) { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + + /* Check whether a conflict-suffix is already present in the name */ + if ((ns = (uint8_t)pico_mdns_is_suffix_present(rname, &o_i, &c_i))) { + pico_itoa(ns, suffix); + pico_itoa(++ns, nsuffix); + slen = (uint8_t)pico_dns_strlen(suffix); + nslen = (uint8_t)pico_dns_strlen(nsuffix); + new_len = (uint16_t)(new_len + nslen - slen); + } else { + /* If no suffix is present */ + c_i = (o_i = rname + *rname) + 1; + new_len = (uint16_t)(new_len + 2u); + memcpy((void *)nsuffix, "-2\0", (size_t)3); + } + + /* Provide space for the new name */ + if (!(new_rname = PICO_ZALLOC(new_len))) { + pico_err = PICO_ERR_ENOMEM; + return NULL; + } + + /* Assemble the new name again */ + copy_offset = (char)((o_i - rname + 1)); + memcpy(new_rname, rname, (size_t)(copy_offset)); + strcpy(new_rname + copy_offset, nsuffix); + strcpy(new_rname + copy_offset + pico_dns_strlen(nsuffix), c_i); + /* Set the first length-byte */ + new_rname[0] = (char)(new_rname[0] + new_len - pico_dns_strlen(rname) - 1); + return new_rname; +} + +/* MARK: ^ MDNS NAMES */ +/* MARK: v MDNS QUESTIONS */ + +/* **************************************************************************** + * Creates a standalone mDNS Question with a given name and type. + * + * @param url DNS question name in URL format. Will be converted to DNS + * name notation format. + * @param len Will be filled with the total length of the DNS question. + * @param proto Protocol for which you want to create a question. Can be + * either PICO_PROTO_IPV4 or PICO_PROTO_IPV6. + * @param qtype DNS type of the question to be. + * @param flags With the flags you can specify if the question should be + * a QU-question rather than a QM-question + * @param reverse When this is true, a reverse resolution name will be gene- + * from the URL + * @return Returns pointer to the created mDNS Question on success, NULL on + * failure. + * ****************************************************************************/ +static struct pico_dns_question * +pico_mdns_question_create( const char *url, + uint16_t *len, + uint8_t proto, + uint16_t qtype, + uint8_t flags, + uint8_t reverse ) +{ + uint16_t qclass = PICO_DNS_CLASS_IN; + + /* Set the MSB of the qclass field according to the mDNS format */ + if (IS_QUESTION_UNICAST_FLAG_SET(flags)) + PICO_MDNS_SET_MSB(qclass); + + /* Fill in the question suffix */ + if (IS_QUESTION_PROBE_FLAG_SET(flags)) + qtype = PICO_DNS_TYPE_ANY; + + /* Create a question as you would with plain DNS */ + return pico_dns_question_create(url, len, proto, qtype, qclass, reverse); +} + +/* MARK: ^ MDNS QUESTIONS */ +/* MARK: v MDNS RECORDS */ + +/* **************************************************************************** + * Just makes a hardcopy from a single mDNS resource record. + * + * @param record mDNS record you want to create a copy from + * @return Pointer to copied mDNS resource record + * ****************************************************************************/ +static struct pico_mdns_record * +pico_mdns_record_copy( struct pico_mdns_record *record ) +{ + struct pico_mdns_record *copy = NULL; + + /* Check params */ + if (!record) { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + + /* Provide space for the copy */ + if (!(copy = PICO_ZALLOC(sizeof(struct pico_mdns_record)))) { + pico_err = PICO_ERR_ENOMEM; + return NULL; + } + + /* Copy the DNS record */ + if (!(copy->record = pico_dns_record_copy(record->record))) { + PICO_FREE(copy); + return NULL; + } + + /* Copy the fields */ + copy->current_ttl = record->current_ttl; + copy->flags = record->flags; + copy->claim_id = record->claim_id; + + return copy; +} + +/* **************************************************************************** + * Looks for multiple mDNS records in a tree with the same name. + * + * @param tree Tree in which you want to search. + * @param name Name you want to search for. + * @return Tree with found hits, can possibly be empty + * ****************************************************************************/ +static pico_mdns_rtree +pico_mdns_rtree_find_name( pico_mdns_rtree *tree, + const char *name, + uint8_t copy ) +{ + PICO_MDNS_RTREE_DECLARE(hits); + struct pico_tree_node *node = NULL; + struct pico_mdns_record *record = NULL; + + /* Check params */ + if (!name || !tree) { + pico_err = PICO_ERR_EINVAL; + return hits; + } + + /* Iterate over tree */ + pico_tree_foreach(node, tree) { + record = node->keyValue; + if (record && strcasecmp(record->record->rname, name) == 0) { + if (copy) + record = pico_mdns_record_copy(record); + if (record) + pico_tree_insert(&hits, record); + } + } + + return hits; +} + +/* **************************************************************************** + * Looks for (possibly) multiple mDNS records in a tree with the same name and + * type. + * + * @param tree Tree in which you want to search. + * @param name Name you want to search for. + * @param rtype DNS type you want to search for. + * @return Tree with found hits, can possibly be empty. + * ****************************************************************************/ +static pico_mdns_rtree +pico_mdns_rtree_find_name_type( pico_mdns_rtree *tree, + char *name, + uint16_t rtype, + uint8_t copy ) +{ + PICO_MDNS_RTREE_DECLARE(hits); + + struct pico_dns_record_suffix test_dns_suffix = { 0, 1, 0, 0 }; + struct pico_dns_record test_dns_record = { 0 }; + struct pico_mdns_record test = { 0 }; + struct pico_tree_node *node = NULL; + struct pico_mdns_record *record = NULL; + test_dns_record.rsuffix = &test_dns_suffix; + test.record = &test_dns_record; + + /* Check params */ + if (!name || !tree) { + pico_err = PICO_ERR_EINVAL; + return hits; + } + + test.record->rname = name; + test.record->rsuffix->rtype = short_be(rtype); + + /* Iterate over the tree */ + pico_tree_foreach(node, tree) { + record = node->keyValue; + if ((record) && (0 == pico_mdns_record_cmp_name_type(record, &test))) { + if (copy) + record = pico_mdns_record_copy(record); + if (record) + pico_tree_insert(&hits, record); + } + } + + return hits; +} + +/* **************************************************************************** + * Deletes multiple mDNS records in a tree with the same name. + * + * @param tree Tree from which you want to delete records by name. + * @param name Name of records you want to delete from the tree. + * @return 0 on success, something else on failure. + * ****************************************************************************/ +static int +pico_mdns_rtree_del_name( pico_mdns_rtree *tree, + const char *name ) +{ + struct pico_tree_node *node = NULL, *safe = NULL; + struct pico_mdns_record *record = NULL; + + /* Check params */ + if (!name || !tree) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* Iterate over tree */ + pico_tree_foreach_safe(node, tree, safe) { + record = node->keyValue; + if (record && strcasecmp(record->record->rname, name) == 0) { + record = pico_tree_delete(tree, record); + pico_mdns_record_delete((void **)&record); + } + } + + return 0; +} + +/* **************************************************************************** + * Deletes (possibly) multiple mDNS records from a tree with same name and + * type. + * + * @param tree Tree from which you want to delete records by name and type. + * @param name Name of records you want to delete. + * @param type DNS type of records you want to delete. + * @return 0 on success, something else on failure. + * ****************************************************************************/ +#if PICO_MDNS_ALLOW_CACHING == 1 +static int +pico_mdns_rtree_del_name_type( pico_mdns_rtree *tree, + char *name, + uint16_t type ) +{ + struct pico_tree_node *node = NULL, *next = NULL; + struct pico_mdns_record *record = NULL; + struct pico_dns_record_suffix test_dns_suffix = { 0, 1, 0, 0 }; + struct pico_dns_record test_dns_record = { 0 }; + struct pico_mdns_record test = { 0 }; + + test_dns_record.rsuffix = &test_dns_suffix; + test.record = &test_dns_record; + + /* Check params */ + if (!name || !tree) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + test.record->rname = name; + test.record->rsuffix->rtype = short_be(type); + + /* Iterate over the tree */ + pico_tree_foreach_safe(node, tree, next) { + record = node->keyValue; + if ((record) && (0 == pico_mdns_record_cmp_name_type(record, &test))) { + record = pico_tree_delete(tree, record); + pico_mdns_record_delete((void **)&record); + } + } + + return 0; +} +#endif + +/* **************************************************************************** + * Makes a hardcopy from a single mDNS resource record, but sets a new name + * for the copy. + * + * @param record mDNS record you want to copy. + * @param new_rname New name you want to set the name of the record to. + * @return Pointer to the copy on success, NULL-pointer on failure. + * ****************************************************************************/ +static struct pico_mdns_record * +pico_mdns_record_copy_with_new_name( struct pico_mdns_record *record, + const char *new_rname ) +{ + struct pico_mdns_record *copy = NULL; + uint16_t slen = (uint16_t)(pico_dns_strlen(new_rname) + 1u); + + /* Check params */ + if (pico_dns_check_namelen(slen)) { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + + /* Copy the record */ + if (!(copy = pico_mdns_record_copy(record))) + return NULL; + + /* Provide a new string */ + PICO_FREE(copy->record->rname); + if (!(copy->record->rname = PICO_ZALLOC(slen))) { + pico_err = PICO_ERR_ENOMEM; + pico_mdns_record_delete((void **)©); + return NULL; + } + + memcpy((void *)(copy->record->rname), new_rname, slen); + copy->record->rname_length = slen; + + return copy; +} + +/* **************************************************************************** + * Generates (copies) new records from conflicting ones with another name. + * deletes + * + * @param conflict_records mDNS record tree that contains conflicting records + * @param conflict_name Name for which the conflict occurred. This is to be + * able to delete the conflicting records from the tree + * @param new_name To generate new records from the conflicting ones, + * with this new name. + * @return A mDNS record tree that contains all the newly generated records. + * ****************************************************************************/ +static pico_mdns_rtree +pico_mdns_generate_new_records( pico_mdns_rtree *conflict_records, + char *conflict_name, + char *new_name ) +{ + PICO_MDNS_RTREE_DECLARE(new_records); + struct pico_tree_node *node = NULL, *next = NULL; + struct pico_mdns_record *record = NULL, *new_record = NULL; + + /* Delete all the conflicting records from MyRecords */ + if (pico_mdns_rtree_del_name(&MyRecords, conflict_name)) + return new_records; + + pico_tree_foreach_safe(node, conflict_records, next) { + record = node->keyValue; + if (record && strcasecmp(record->record->rname, conflict_name) == 0) { + /* Create a new record */ + new_record = pico_mdns_record_copy_with_new_name(record, new_name); + if (!new_record) { + mdns_dbg("Could not create new non-conflicting record!\n"); + return new_records; + } + + new_record->flags &= (uint8_t)(~(PICO_MDNS_RECORD_PROBED | + PICO_MDNS_RECORD_SHARED | + PICO_MDNS_RECORD_CURRENTLY_PROBING)); + + /* Add the record to the new tree */ + pico_tree_insert(&new_records, new_record); + + /* Delete the old conflicting record */ + record = pico_tree_delete(conflict_records, record); + if (pico_mdns_record_delete((void **)&record)) { + mdns_dbg("Could not delete old conflict record from tree!\n"); + return new_records; + } + } + } + + return new_records; +} + +/* **************************************************************************** + * When hosts observe an unsolicited record, no cookie is currently active + * for that, so it has to check in MyRecords if no conflict occurred for a + * record it has already registered. When this occurs the conflict should be + * resolved as with a normal cookie, just without the cookie. + * + * @param record mDNS record for which the conflict occurred. + * @param rname DNS name for which the conflict occurred in DNS name notation. + * @return 0 when the resolving is applied successfully, 1 otherwise. + * ****************************************************************************/ +static int +pico_mdns_record_resolve_conflict( struct pico_mdns_record *record, + char *rname ) +{ + int retval; + PICO_MDNS_RTREE_DECLARE(new_records); + struct pico_mdns_record *copy = NULL; + char *new_name = NULL; + + /* Check params */ + if (!record || !rname || IS_SHARED_RECORD(record)) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* Step 2: Create a new name depending on current name */ + if (!(new_name = pico_mdns_resolve_name_conflict(rname))) + return -1; + + copy = pico_mdns_record_copy_with_new_name(record, new_name); + PICO_FREE(new_name); + if (copy) + pico_tree_insert(&new_records, copy); + + /* Step 3: delete conflicting record from my records */ + pico_tree_delete(&MyRecords, record); + pico_mdns_record_delete((void **)&record); + + /* Step 4: Try to reclaim the newly created records */ + retval = pico_mdns_reclaim(new_records, init_callback, NULL); + pico_tree_destroy(&new_records, NULL); + return retval; +} + +/* **************************************************************************** + * Determines if my_record is lexicographically later than peer_record, returns + * positive value when this is the case. Check happens by comparing rtype first + * and then rdata as prescribed by RFC6762. + * + * @param my_record Record this hosts want to claim. + * @param peer_record Record the peer host wants to claim (the enemy!) + * @return positive value when my record is lexicographically later + * ****************************************************************************/ +static int +pico_mdns_record_am_i_lexi_later( struct pico_mdns_record *my_record, + struct pico_mdns_record *peer_record) +{ + struct pico_dns_record *my = NULL, *peer = NULL; + uint16_t mclass = 0, pclass = 0, mtype = 0, ptype = 0; + int dif = 0; + + /* Check params */ + if (!my_record || !peer_record || + !(my = my_record->record) || !(peer = peer_record->record)) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* + * First compare the record class (excluding cache-flush bit described in + * section 10.2) + * The numerically greater class wins + */ + mclass = PICO_MDNS_CLR_MSB_BE(my->rsuffix->rclass); + pclass = PICO_MDNS_CLR_MSB_BE(peer->rsuffix->rclass); + if ((dif = (int)((int)mclass - (int)pclass))){ + return dif; + } + + /* Second, compare the rrtypes */ + mtype = (my->rsuffix->rtype); + ptype = (peer->rsuffix->rtype); + if ((dif = (int)((int)mtype - (int)ptype))){ + return dif; + } + + /* Third compare binary content of rdata (no regard for meaning or structure) */ + + /* When using name compression, names MUST be uncompressed before comparison. See secion 8.2 in RFC 6762 + This is already the case, but we won't check for it here. + The current execution stack to get here is: + > pico_mdns_handle_data_as_answers_generic + > > pico_dns_record_decompress + > > pico_mdns_handle_single_authority + > > > pico_mdns_cookie_apply_spt + > > > > pico_mdns_record_am_i_lexi_later + + Make sure pico_dns_record_decompress is executed before pico_mdns_record_am_i_lexi_later gets called, if problems ever arise with this function. + */ + + /* Then compare rdata */ + return pico_dns_rdata_cmp(my->rdata, peer->rdata, + short_be(my->rsuffix->rdlength), + short_be(peer->rsuffix->rdlength), 0); +} + +/* **************************************************************************** + * Deletes a single mDNS resource record. + * + * @param record Void-pointer to mDNS Resource Record. Can be used with pico_- + * tree-destroy. + * @return Returns 0 on success, something else on failure. + * ****************************************************************************/ +int +pico_mdns_record_delete( void **record ) +{ + struct pico_mdns_record **rr = (struct pico_mdns_record **)record; + + /* Check params */ + if (!rr || !(*rr)) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* Delete DNS record contained */ + if (((*rr)->record)) + pico_dns_record_delete((void **)&((*rr)->record)); + + /* Delete the record itself */ + PICO_FREE(*rr); + *record = NULL; + + return 0; +} + +/* **************************************************************************** + * Set the MSB of the QCLASS field of a DNS record depending on flags. + * + * @param record DNS record you want the set or clear the class MSB for. + * @param flags Depending on this the MSB will be set or not. Can be either + * PICO_MDNS_RECORD_UNIQUE or PICO_MDNS_RECORD_SHARED + * @return 0 on success, something else on failure. + * ****************************************************************************/ +static inline int +pico_mdns_record_set_class( struct pico_dns_record *record, + uint8_t flags ) +{ + uint16_t c = PICO_DNS_CLASS_IN; + PICO_MDNS_SET_MSB(c); + c = short_be(c); + + /* Set the MSB of the rclass field according to the mDNS format */ + if (!((flags) & PICO_MDNS_RECORD_SHARED)) + record->rsuffix->rclass = c; + + return 0; +} + +/* **************************************************************************** + * Creates a single standalone mDNS resource record with given name, type and + * data. + * + * @param url DNS rrecord name in URL format. Will be converted to DNS + * name notation format. + * @param _rdata Memory buffer with data to insert in the resource record. If + * data of record should contain a DNS name, the name in the + * data buffer needs to be in URL-format. + * @param datalen The exact length in bytes of the _rdata-buffer. If data of + * record should contain a DNS name, datalen needs to be + * pico_dns_strlen(_rdata). + * @param len Will be filled with the total length of the DNS rrecord. + * @param rtype DNS type of the resource record to be. + * @param rclass DNS class of the resource record to be. + * @param rttl DNS ttl of the resource record to be. + * @param flags You can specify if the mDNS record should be a shared record + * rather than a unique record. + * @return Pointer to newly created mDNS resource record. + * ****************************************************************************/ +struct pico_mdns_record * +pico_mdns_record_create( const char *url, + void *_rdata, + uint16_t datalen, + uint16_t rtype, + uint32_t rttl, + uint8_t flags ) +{ + struct pico_mdns_record *record = NULL; + uint16_t len = 0; + + /* Check params */ + if (!url || !_rdata) { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + + /* Provide space for the new mDNS resource record */ + if (!(record = PICO_ZALLOC(sizeof(struct pico_mdns_record)))) { + pico_err = PICO_ERR_ENOMEM; + return NULL; + } + + /* Try to create the actual DNS record */ + if (!(record->record = pico_dns_record_create(url, _rdata, datalen, + &len, rtype, + PICO_DNS_CLASS_IN, rttl))) { + mdns_dbg("Could not create DNS record for mDNS!\n"); + PICO_FREE(record); + return NULL; + } + + /* Initialise fields */ + record->current_ttl = rttl; + pico_mdns_record_set_class(record->record, flags); + record->flags = flags; + record->claim_id = 0; + + return record; +} + +/* MARK: ^ MDNS RECORDS */ +/* MARK: v MDNS COOKIES */ + +/* **************************************************************************** + * Deletes a single mDNS packet cookie and frees memory. + * + * @param cookie Void-pointer to mDNS cookie, allow to be used with pico_tree- + * destroy. + * @return Returns 0 on success, something else on failure. + * ****************************************************************************/ +static int +pico_mdns_cookie_delete( struct pico_mdns_cookie **c ) +{ + /* Check params */ + if (!c || !(*c)) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* Destroy the vectors contained */ + PICO_DNS_QTREE_DESTROY(&((*c)->qtree)); + PICO_MDNS_RTREE_DESTROY(&((*c)->antree)); + PICO_MDNS_RTREE_DESTROY(&((*c)->artree)); + + /* Delete the cookie itself */ + PICO_FREE(*c); + *c = NULL; + + return 0; +} + +/* **************************************************************************** + * Creates a single standalone mDNS cookie + * + * @param qtree DNS questions you want to insert in the cookie. + * @param antree mDNS answers/authority records you want to add to cookie. + * @param artree mDNS additional records you want to add to cookie. + * @param count Times you want to send the cookie as a packet on the wire. + * @param type Type of packet you want to create from the cookie. + * @param callback Callback when the host receives responses for the cookie. + * @return Pointer to newly create cookie, NULL on failure. + * ****************************************************************************/ +static struct pico_mdns_cookie * +pico_mdns_cookie_create( pico_dns_qtree qtree, + pico_mdns_rtree antree, + pico_mdns_rtree artree, + uint8_t count, + uint8_t type, + void (*callback)(pico_mdns_rtree *, + char *, + void *), + void *arg ) +{ + struct pico_mdns_cookie *cookie = NULL; /* Packet cookie to send */ + + /* Provide space for the mDNS packet cookie */ + cookie = PICO_ZALLOC(sizeof(struct pico_mdns_cookie)); + if (!cookie) { + pico_err = PICO_ERR_ENOMEM; + return NULL; + } + + /* Fill in the fields */ + cookie->qtree = qtree; + cookie->antree = antree; + cookie->artree = artree; + cookie->count = count; + cookie->type = type; + cookie->status = PICO_MDNS_COOKIE_STATUS_INACTIVE; + cookie->timeout = PICO_MDNS_COOKIE_TIMEOUT; + cookie->callback = callback; + cookie->arg = arg; + return cookie; +} + +/* **************************************************************************** + * Apply Simultaneous Probe Tiebreakin (S.P.T.) on a probe-cookie. + * + * @param cookie Cookie which contains the record which is simult. probed. + * @param answer Authority record received from peer which is simult. probed. + * @return 0 when SPT is applied correctly, 0 otherwise. + * ****************************************************************************/ +static int +pico_mdns_cookie_apply_spt( struct pico_mdns_cookie *cookie, + struct pico_dns_record *answer) +{ + struct pico_mdns_record *my_record = NULL; + struct pico_mdns_record peer_record; + + /* Check params */ + if ((!cookie) || !answer || (cookie->type != PICO_MDNS_PACKET_TYPE_PROBE)) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + cookie->status = PICO_MDNS_COOKIE_STATUS_INACTIVE; + + /* Implement Simultaneous Probe Tiebreaking */ + peer_record.record = answer; + my_record = pico_tree_findKey(&MyRecords, &peer_record); + if (!my_record || !IS_RECORD_PROBING(my_record)) { + mdns_dbg("This is weird! My record magically removed...\n"); + return -1; + } + + if (pico_mdns_record_am_i_lexi_later(my_record, &peer_record) > 0) { + mdns_dbg("My record is lexicographically later! Yay!\n"); + cookie->status = PICO_MDNS_COOKIE_STATUS_ACTIVE; + } else { + pico_timer_cancel(cookie->send_timer); + cookie->timeout = PICO_MDNS_COOKIE_TIMEOUT; + cookie->count = PICO_MDNS_PROBE_COUNT; + cookie->send_timer = pico_timer_add(1000, pico_mdns_send_probe_packet, + cookie); + mdns_dbg("Probing postponed with 1s because of S.P.T.\n"); + } + + return 0; +} + +/* **************************************************************************** + * Applies conflict resolution mechanism to a cookie, when a conflict occurs + * for a name which is present in the cookie. + * + * @param cookie Cookie on which you want to apply the conflict resolution- + * mechanism. + * @param rname Name for which the conflict occurred. A new non-conflicting + * name will be generated from this string. + * @return Returns 0 on success, something else on failure. + * ****************************************************************************/ +static int +pico_mdns_cookie_resolve_conflict( struct pico_mdns_cookie *cookie, + char *rname ) +{ + struct pico_tree_node *node = NULL; + struct pico_dns_question *question = NULL; + PICO_MDNS_RTREE_DECLARE(new_records); + PICO_MDNS_RTREE_DECLARE(antree); + char *new_name = NULL; + void (*callback)(pico_mdns_rtree *, char *, void *); + void *arg = NULL; + uint16_t qc = 0; + int retval; + + /* Check params */ + if ((!cookie) || !rname || (cookie->type != PICO_MDNS_PACKET_TYPE_PROBE)) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* Convert rname to url */ + mdns_dbg("CONFLICT for probe query with name '%s' occurred!\n", rname); + + /* DNS conflict is case-insensitive. However, we want to keep the original + * capitalisation for the new probe. */ + pico_tree_foreach(node, &(cookie->qtree)) { + question = (struct pico_dns_question *)node->keyValue; + if ((question) && (strcasecmp(question->qname, rname) == 0)) + /* Create a new name depending on current name */ + new_name = pico_mdns_resolve_name_conflict(question->qname); + } + + /* Step 1: Remove question with that name from cookie and store some + * useful information */ + pico_dns_qtree_del_name(&(cookie->qtree), rname); + antree = cookie->antree; + callback = cookie->callback; + arg = cookie->arg; + cookie->antree.root = &LEAF; + + /* Check if there are no questions left, cancel events if so and delete */ + if (!(qc = pico_tree_count(&(cookie->qtree)))) { + pico_timer_cancel(cookie->send_timer); + cookie = pico_tree_delete(&Cookies, cookie); + pico_mdns_cookie_delete(&cookie); + } + + /* Step 2: Check if the new name succeeded, if not: error. */ + if (!(new_name)) + return -1; + + /* Step 3: Create records with new name for the records with that name */ + new_records = pico_mdns_generate_new_records(&antree, rname, new_name); + PICO_FREE(new_name); + + /* Step 4: Try to reclaim the newly created records */ + retval = pico_mdns_reclaim(new_records, callback, arg); + pico_tree_destroy(&new_records, NULL); + return retval; +} + +/* **************************************************************************** + * Find a query cookie that contains a question for a specific name. + * + * @param name Name of question you want to look for. + * @return Pointer to cookie in tree when one is found, NULL on failure. + * ****************************************************************************/ +static struct pico_mdns_cookie * +pico_mdns_ctree_find_cookie( const char *name, uint8_t type ) +{ + struct pico_mdns_cookie *cookie = NULL; + struct pico_tree_node *node = NULL; + + /* Check params */ + if (!name) { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + + /* Find the cookie in the tree wherein the question is present */ + pico_tree_foreach(node, &Cookies) { + if ((cookie = node->keyValue) && + pico_dns_qtree_find_name(&(cookie->qtree), name)) { + if (type == PICO_MDNS_PACKET_TYPE_QUERY_ANY) + return cookie; + else if (cookie->type == type) + return cookie; + } + } + + return NULL; +} + +/* MARK: ^ MDNS COOKIES */ +/* MARK: v MY RECORDS */ + +/* **************************************************************************** + * Adds records contained in records-tree to MyRecords. Suppresses adding of + * duplicates. + * + * @param records Tree with records to add to 'MyRecords'. + * @param reclaim If the records contained in records are claimed again. + * @return 0 on success, something else on failure. + * ****************************************************************************/ +static int +pico_mdns_my_records_add( pico_mdns_rtree *records, uint8_t reclaim ) +{ + struct pico_tree_node *node = NULL; + struct pico_mdns_record *record = NULL; + static uint8_t claim_id_count = 0; + + if (!reclaim) + ++claim_id_count; + + /* Iterate over record vector */ + pico_tree_foreach(node, records) { + record = node->keyValue; + if (record) { + /* Set probed flag if record is a shared record */ + if (IS_SHARED_RECORD(record)) + PICO_MDNS_SET_FLAG(record->flags, PICO_MDNS_RECORD_PROBED); + + /* If record is not claimed again, set new claim-ID */ + if (!reclaim) + record->claim_id = claim_id_count; + pico_tree_insert(&MyRecords, record); + } + } + return 0; +} + +/* **************************************************************************** + * Generates a tree of all My Records for which the probe flag already has + * been set. Copies the records from MyRecords into a new tree. + * + * @return Tree with all records in MyRecords with the PROBED-flag set. + * ****************************************************************************/ +static pico_mdns_rtree +pico_mdns_my_records_find_probed( void ) +{ + PICO_MDNS_RTREE_DECLARE(probed); + struct pico_tree_node *node = NULL; + struct pico_mdns_record *record = NULL, *copy = NULL; + + /* Iterate over MyRecords */ + pico_tree_foreach(node, &MyRecords) { + record = node->keyValue; + if (record && IS_RECORD_VERIFIED(record) && !IS_RECORD_CLAIMED(record)) { + copy = pico_mdns_record_copy(record); + if (copy && pico_tree_insert(&probed, copy)) + pico_mdns_record_delete((void **)©); + } + } + + return probed; +} + +/* **************************************************************************** + * Generates a tree of all My Records for which the probe flag has not yet + * been set. Copies the record from MyRecords into a new tree. + * + * @return Tree with all records in MyRecords with the PROBED-flag not set. + * ****************************************************************************/ +static pico_mdns_rtree +pico_mdns_my_records_find_to_probe( void ) +{ + PICO_MDNS_RTREE_DECLARE(toprobe); + struct pico_tree_node *node = NULL; + struct pico_mdns_record *record = NULL, *copy = NULL; + + pico_tree_foreach(node, &MyRecords) { + record = node->keyValue; + /* Check if probed flag is not set of a record */ + if (record && + IS_UNIQUE_RECORD(record) && + !IS_RECORD_VERIFIED(record) && + !IS_RECORD_PROBING(record)) { + /* Set record to currently being probed status */ + record->flags |= PICO_MDNS_RECORD_CURRENTLY_PROBING; + copy = pico_mdns_record_copy(record); + if (copy && pico_tree_insert(&toprobe, copy)) + pico_mdns_record_delete((void **)©); + } + } + return toprobe; +} + +/* **************************************************************************** + * Checks whether all MyRecords with a certain claim ID are claimed or not. + * + * @param claim_id Claim ID of the records to check for already been probed. + * @param reg_records Tree in which all MyRecords with claim ID are inserted. + * @return 1 when all MyRecords with claim ID are probed, 0 when they're not. + * ****************************************************************************/ +static uint8_t +pico_mdns_my_records_claimed_id( uint8_t claim_id, + pico_mdns_rtree *reg_records ) +{ + struct pico_tree_node *node = NULL; + struct pico_mdns_record *record = NULL; + + /* Initialise the iterator for iterating over my records */ + pico_tree_foreach(node, &MyRecords) { + record = node->keyValue; + if (record && record->claim_id == claim_id) { + if (IS_RECORD_VERIFIED(record)) { + pico_tree_insert(reg_records, record); + } else { + return 0; + } + } + } + + return 1; +} + +/* **************************************************************************** + * Marks mDNS resource records in the tree as registered. Checks MyRecords for + * for other records with the same claim ID. If all records with the same claim + * ID as the records in the tree are claimed, the callback will get called. + * + * @param rtree Tree with mDNS records that are registered. + * @param callback Callback will get called when all records are registered. + * @return Returns 0 when everything went smooth, something else otherwise. + * ****************************************************************************/ +static int +pico_mdns_my_records_claimed( pico_mdns_rtree rtree, + void (*callback)(pico_mdns_rtree *, + char *, + void *), + void *arg ) +{ + PICO_MDNS_RTREE_DECLARE(claimed_records); + struct pico_mdns_record *record = NULL, *myrecord = NULL; + struct pico_tree_node *node = NULL; + uint8_t claim_id = 0; + + /* Iterate over records and set the PROBED flag */ + pico_tree_foreach(node, &rtree) { + if ((record = node->keyValue)) { + if (!claim_id) + claim_id = record->claim_id; + } + + if ((myrecord = pico_tree_findKey(&MyRecords, record))) + PICO_MDNS_SET_FLAG(myrecord->flags, PICO_MDNS_RECORD_CLAIMED); + } + + /* If all_claimed is still true */ + if (pico_mdns_my_records_claimed_id(claim_id, &claimed_records)) + callback(&claimed_records, _hostname, arg); + pico_tree_destroy(&claimed_records, NULL); + + mdns_dbg(">>>>>> DONE - CLAIM SESSION: %d\n", claim_id); + + return 0; +} + +/* **************************************************************************** + * Makes sure the cache flush bit is set of the records which are probed, and + * set the corresponding MyRecords to 'being probed'-state. + * + * @param records mDNS records which are probed. + * ****************************************************************************/ +static void +pico_mdns_my_records_probed( pico_mdns_rtree *records ) +{ + struct pico_tree_node *node = NULL; + struct pico_mdns_record *record = NULL, *found = NULL; + + pico_tree_foreach(node, records) { + if ((record = node->keyValue)) { + /* Set the cache flush bit again */ + PICO_MDNS_SET_MSB_BE(record->record->rsuffix->rclass); + if ((found = pico_tree_findKey(&MyRecords, record))) { + if (IS_HOSTNAME_RECORD(found)) { + if (_hostname) + PICO_FREE(_hostname); + + _hostname = pico_dns_qname_to_url(found->record->rname); + } + + PICO_MDNS_SET_FLAG(found->flags, PICO_MDNS_RECORD_PROBED); + PICO_MDNS_CLR_FLAG(found->flags, PICO_MDNS_RECORD_CURRENTLY_PROBING); + } else + mdns_dbg("Could not find my corresponding record...\n"); + } + } +} + +/* MARK: ^ MY RECORDS */ +/* MARK: v CACHE COHERENCY */ +#if PICO_MDNS_ALLOW_CACHING == 1 +/* **************************************************************************** + * Updates TTL of a cache entry. + * + * @param record Record of which you want to update the TTL of + * @param ttl TTL you want to update the TTL of the record to. + * @return void + * ****************************************************************************/ +static inline void +pico_mdns_cache_update_ttl( struct pico_mdns_record *record, + uint32_t ttl ) +{ + if(ttl > 0) { + /* Update the TTL's */ + record->record->rsuffix->rttl = long_be(ttl); + record->current_ttl = ttl; + } else { + /* TTL 0 means delete from cache but we need to wait one second */ + record->record->rsuffix->rttl = long_be(1u); + record->current_ttl = 1u; + } +} + +/* **************************************************************************** + * Adds a mDNS record to the cache. + * + * @param record mDNS record to add to the Cache. + * @return 0 when entry successfully added, something else when it all went ho- + * rribly wrong... + * ****************************************************************************/ +static int +pico_mdns_cache_add( struct pico_mdns_record *record ) +{ + struct pico_dns_record_suffix *suffix = NULL; + char *name = NULL; + uint16_t type = 0; + uint32_t rttl = 0; + + /* Check params */ + if (!record) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + suffix = record->record->rsuffix; + name = record->record->rname; + type = short_be(suffix->rtype); + rttl = long_be(suffix->rttl); + + /* Check if cache flush bit is set */ + if (PICO_MDNS_IS_MSB_SET(short_be(suffix->rclass))) { + mdns_dbg("FLUSH - Cache flush bit was set, triggered flush.\n"); + if (pico_mdns_rtree_del_name_type(&Cache, name, type)) { + mdns_dbg("Could not flush records from cache!\n"); + return -1; + } + } + + /* Check if the TTL is not 0*/ + if (!rttl) + return -1; + /* Set current TTL to the original TTL before inserting */ + record->current_ttl = rttl; + + if (pico_tree_insert(&Cache, record) != NULL) + return -1; + + mdns_dbg("RR cached. TICK TACK TICK TACK...\n"); + + return 0; +} + +/* **************************************************************************** + * Add a copy of an mDNS resource record to the cache tree. Checks whether the + * entry is already present in the Cache or not. + * + * @param record Record to add to the Cache-tree + * @return 0 on grrrreat success, something else on awkward failure. + * ****************************************************************************/ +static int +pico_mdns_cache_add_record( struct pico_mdns_record *record ) +{ + struct pico_mdns_record *found = NULL, *copy = NULL; + uint32_t rttl = 0; + + /* Check params */ + if (!record) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* See if the record is already contained in the cache */ + if ((found = pico_tree_findKey(&Cache, record))) { + rttl = long_be(record->record->rsuffix->rttl); + pico_mdns_cache_update_ttl(found, rttl); + } else if ((copy = pico_mdns_record_copy(record))) { + if (pico_mdns_cache_add(copy)) { + pico_mdns_record_delete((void **)©); + return -1; + } + } else + return -1; + + return 0; +} + +#if PICO_MDNS_CONTINUOUS_REFRESH == 1 +/* **************************************************************************** + * Determine if the current TTL is at a refreshing point. + * + * @param original Original TTL to calculate refreshing points + * @param current Current TTL to check. + * @return 1 when Current TTL is at refresh point. 0 when it's not. + * ****************************************************************************/ +static int +pico_mdns_ttl_at_refresh_time( uint32_t original, + uint32_t current ) +{ + uint32_t rnd = 0; + rnd = pico_rand() % 3; + + if (((original - current == + ((original * (80 + rnd)) / 100)) ? 1 : 0) || + ((original - current == + ((original * (85 + rnd)) / 100)) ? 1 : 0) || + ((original - current == + ((original * (90 + rnd)) / 100)) ? 1 : 0) || + ((original - current == + ((original * (95 + rnd)) / 100)) ? 1 : 0)) + return 1; + else + return 0; +} +#endif + +/* **************************************************************************** + * Utility function to update the TTL of cache entries and check for expired + * ones. When continuous refreshing is enabled the records will be reconfirmed + * @ 80%, 85%, 90% and 95% of their original TTL. + * ****************************************************************************/ +static void +pico_mdns_cache_check_expiries( void ) +{ + struct pico_tree_node *node = NULL, *next = NULL; + struct pico_mdns_record *record = NULL; +#if PICO_MDNS_CONTINUOUS_REFRESH == 1 + uint32_t current = 0, original = 0; + uint16_t type 0; + char *url = NULL; +#endif + + /* Check for expired cache records */ + pico_tree_foreach_safe(node, &Cache, next) { + if ((record = node->keyValue)) { + /* Update current ttl and delete when TTL is 0*/ + if ((--(record->current_ttl)) == 0) { + record = pico_tree_delete(&Cache, record); + pico_mdns_record_delete((void **)&record); + } + +#if PICO_MDNS_CONTINUOUS_REFRESH == 1 + /* Determine original and current ttl */ + original = long_be(record->record->rsuffix->rttl); + current = record->current_ttl; + + /* Cache refresh at 80 or 85/90/95% of TTL + 2% rnd */ + if (pico_mdns_ttl_at_refresh_time(original, current)) { + url = pico_dns_qname_to_url(record->record->rname); + type = short_be(record->record->rsuffix->rtype) + pico_mdns_getrecord_generic(url, type, NULL, NULL); + PICO_FREE(url); + } + +#endif + } + } +} +#endif /* PICO_MDNS_ALLOW_CACHING */ + +/* **************************************************************************** + * Utility function to update the TTL of cookies and check for expired + * ones. Deletes the expired ones as well. + * ****************************************************************************/ +static void +pico_mdns_cookies_check_timeouts( void ) +{ + struct pico_tree_node *node = NULL, *next = NULL; + struct pico_mdns_cookie *cookie = NULL; + + pico_tree_foreach_safe(node, &Cookies, next) { + if ((cookie = node->keyValue) && --(cookie->timeout) == 0) { + /* Call callback to allow error checking */ + if (cookie->callback) { + cookie->callback(NULL, NULL, cookie->arg); + } + + /* Delete cookie */ + cookie = pico_tree_delete(&Cookies, cookie); + pico_mdns_cookie_delete(&cookie); + + /* If the request was for a reconfirmation of a record, + flush the corresponding record after the timeout */ + } + } +} + +/* **************************************************************************** + * Global mDNS module tick-function, central point where all the timing is + * handled. + * + * @param now Ignore + * @param _arg Ignore + * ****************************************************************************/ +static void +pico_mdns_tick( pico_time now, void *_arg ) +{ + IGNORE_PARAMETER(now); + IGNORE_PARAMETER(_arg); + +#if PICO_MDNS_ALLOW_CACHING == 1 + /* Update the cache */ + pico_mdns_cache_check_expiries(); +#endif + + /* Update the cookies */ + pico_mdns_cookies_check_timeouts(); + + /* Schedule new tick */ + pico_timer_add(PICO_MDNS_RR_TTL_TICK, pico_mdns_tick, NULL); +} + +/* MARK: v MDNS PACKET UTILITIES */ + +/* **************************************************************************** + * Sends a Multicast packet on the wire to the mDNS destination port. + * + * @param packet Packet buffer in memory + * @param len Size of the packet in bytes + * @return 0 When the packet is passed successfully on to the lower layers of + * picoTCP. Doesn't mean the packet is successfully send on the wire. + * ****************************************************************************/ +static int +pico_mdns_send_packet( pico_dns_packet *packet, uint16_t len ) +{ + struct pico_ip4 dst4; + + /* Set the destination address to the mDNS multicast-address */ + pico_string_to_ipv4(PICO_MDNS_DEST_ADDR4, &dst4.addr); + + /* Send packet to IPv4 socket */ + return pico_socket_sendto(mdns_sock_ipv4, packet, (int)len, &dst4, + short_be(mdns_port)); +} + +/* **************************************************************************** + * Sends a Unicast packet on the wire to the mDNS destination port of specific + * peer in the network + * + * @param packet Packet buffer in memory + * @param len Size of the packet in bytes + * @param peer Peer in the network you want to send the packet to. + * @return 0 When the packet is passed successfully on to the lower layers of + * picoTCP. Doesn't mean the packet is successfully send on the wire. + * ****************************************************************************/ +static int +pico_mdns_send_packet_unicast( pico_dns_packet *packet, + uint16_t len, + struct pico_ip4 peer ) +{ + /* Send packet to IPv4 socket */ + return pico_socket_sendto(mdns_sock_ipv4, packet, (int)len, &peer, + short_be(mdns_port)); +} + + +/* **************************************************************************** + * Send DNS records as answers to a peer via unicast + * + * @param unicast_tree Tree with DNS records to send as answers. + * @param peer Peer IPv4-address + * @return 0 when the packet is properly send, something else otherwise. + * ****************************************************************************/ +static int +pico_mdns_unicast_reply( pico_dns_rtree *unicast_tree, + pico_dns_rtree *artree, + struct pico_ip4 peer ) +{ + union pico_address *local_addr = NULL; + pico_dns_packet *packet = NULL; + uint16_t len = 0; + + if (pico_tree_count(unicast_tree) > 0) { + /* Create response DNS packet */ + packet = pico_dns_answer_create(unicast_tree, NULL, artree, &len); + if (!packet || !len) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + packet->id = 0; + + /* Check if source address is on the local link */ + local_addr = (union pico_address *) pico_ipv4_source_find(&peer); + if (!local_addr) { + mdns_dbg("Peer not on same link!\n"); + /* Forced response via multicast */ + if (pico_mdns_send_packet(packet, len) != (int)len) { + mdns_dbg("Could not send multicast response!\n"); + return -1; + } + } else { + /* Send the packet via unicast */ + if (pico_mdns_send_packet_unicast(packet, len, peer) != (int)len) { + mdns_dbg("Could not send unicast response!\n"); + return -1; + } + + mdns_dbg("Unicast response sent successfully!\n"); + } + PICO_FREE(packet); + } + + return 0; +} + +/* **************************************************************************** + * Send DNS records as answers to mDNS peers via multicast + * + * @param multicast_tree Tree with DNS records to send as answers. + * @return 0 when the packet is properly send, something else otherwise. + * ****************************************************************************/ +static int +pico_mdns_multicast_reply( pico_dns_rtree *multicast_tree, + pico_dns_rtree *artree ) +{ + pico_dns_packet *packet = NULL; + uint16_t len = 0; + + /* If there are any multicast records */ + if (pico_tree_count(multicast_tree) > 0) { + /* Create response DNS packet */ + packet = pico_dns_answer_create(multicast_tree, NULL, artree, &len); + if (!packet || len == 0) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + packet->id = 0; + + /* Send the packet via multicast */ + if (pico_mdns_send_packet(packet, len) != (int)len) { + mdns_dbg("Could not send multicast response!\n"); + return -1; + } + + mdns_dbg("Multicast response sent successfully!\n"); + + PICO_FREE(packet); + } + + return 0; +} + +/* MARK: ^ MDNS PACKET UTILITIES */ +/* MARK: ASYNCHRONOUS MDNS RECEPTION */ + +/* **************************************************************************** + * Merges 2 pico_trees with each other. + * + * @param dest Destination tree to merge the other tree in. + * @param src Source tree to get the node from to insert into the dest-tree. + * @return Returns 0 when properly merged, or not.. + * ****************************************************************************/ +static int +pico_tree_merge( struct pico_tree *dest, struct pico_tree *src ) +{ + struct pico_tree_node *node = NULL; + + /* Check params */ + if (!dest || !src) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* Insert source nodes */ + pico_tree_foreach(node, src) { + if (node->keyValue) { + pico_tree_insert(dest, node->keyValue); + } + } + + return 0; +} + +/* **************************************************************************** + * Populates an mDNS record tree with answers from MyRecords depending on name + * , qtype and qclass. + * + * @param name Name of records to look for in MyRecords + * @param qtype Type of records to look for in MyRecords + * @param qclass Whether the answer should be sent via unicast or not. + * @return mDNS record tree with possible answers from MyRecords + * ****************************************************************************/ +static pico_mdns_rtree +pico_mdns_populate_antree( char *name, uint16_t qtype, uint16_t qclass ) +{ + PICO_MDNS_RTREE_DECLARE(antree); + struct pico_tree_node *node = NULL, *next; + struct pico_mdns_record *record = NULL; + + /* Create an answer record vector */ + if (PICO_DNS_TYPE_ANY == qtype) + antree = pico_mdns_rtree_find_name(&MyRecords, name, 1); + else + antree = pico_mdns_rtree_find_name_type(&MyRecords, name, qtype, 1); + + /* Remove answers which aren't successfully registered yet */ + pico_tree_foreach_safe(node, &antree, next) { + if ((record = node->keyValue) && !IS_RECORD_VERIFIED(record)) { + pico_tree_delete(&antree, record); + } + } + + /* Check if question is a QU-question */ + if (PICO_MDNS_IS_MSB_SET(qclass)) { + /* Set all the flags of the answer accordingly */ + pico_tree_foreach(node, &antree) { + if ((record = node->keyValue)) + PICO_MDNS_SET_FLAG(record->flags, + PICO_MDNS_RECORD_SEND_UNICAST); + } + } + + return antree; +} + +/* **************************************************************************** + * Handles a single received question. + * + * @param question DNS question to parse and handle. + * @param packet Received packet in which the DNS question was present. + * @return mDNS record tree with possible answer to the question. Can possibly + * be empty. + * ****************************************************************************/ +static pico_mdns_rtree +pico_mdns_handle_single_question( struct pico_dns_question *question, + pico_dns_packet *packet ) +{ + struct pico_mdns_cookie *cookie = NULL; + PICO_MDNS_RTREE_DECLARE(antree); + char *qname_original = NULL; + uint16_t qtype = 0, qclass = 0; + + /* Check params */ + if (!question || !packet) { + pico_err = PICO_ERR_EINVAL; + return antree; + } + + /* Decompress single DNS question */ + qname_original = pico_dns_question_decompress(question, packet); + mdns_dbg("Question RCVD for '%s'\n", question->qname); + + /* Find currently active query cookie */ + if ((cookie = pico_mdns_ctree_find_cookie(question->qname, + PICO_MDNS_PACKET_TYPE_QUERY))) { + mdns_dbg("Query cookie found for question, suppress duplicate.\n"); + cookie->status = PICO_MDNS_COOKIE_STATUS_CANCELLED; + } else { + qtype = short_be(question->qsuffix->qtype); + qclass = short_be(question->qsuffix->qclass); + antree = pico_mdns_populate_antree(question->qname, qtype, qclass); + } + + PICO_FREE(question->qname); + question->qname = qname_original; + return antree; +} + +/* **************************************************************************** + * When a query-cookie is found for a RCVD answer, the cookie should be + * handled accordingly. This function does that. + * + * @param cookie Cookie that contains the question for the RCVD answer. + * @param answer RCVD answer to handle cookie with + * @return Returns 0 when handling went OK, something else when it didn't. + * ****************************************************************************/ +static int +pico_mdns_handle_cookie_with_answer( struct pico_mdns_cookie *cookie, + struct pico_mdns_record *answer ) +{ + PICO_MDNS_RTREE_DECLARE(antree); + uint8_t type = 0, status = 0; + + /* Check params */ + if (!cookie || !answer) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + type = cookie->type; + status = cookie->status; + if (PICO_MDNS_COOKIE_STATUS_ACTIVE == status) { + if (PICO_MDNS_PACKET_TYPE_PROBE == type) { + /* Conflict occurred, resolve it! */ + pico_mdns_cookie_resolve_conflict(cookie, answer->record->rname); + } else if (PICO_MDNS_PACKET_TYPE_QUERY == type) { + if (cookie->callback) { + /* RCVD Answer on query, callback with answer. Callback is + * responsible for aggregating all the received answers. */ + pico_tree_insert(&antree, answer); + cookie->callback(&antree, NULL, cookie->arg); + } + } else { /* Don't handle answer cookies with answer */ + } + } + + return 0; +} + +/* **************************************************************************** + * Handles a single received answer record. + * + * @param answer Answer mDNS record. + * @return 0 when answer is properly handled, something else when it's not. + * ****************************************************************************/ +static int +pico_mdns_handle_single_answer( struct pico_mdns_record *answer ) +{ + struct pico_mdns_cookie *found = NULL; + struct pico_mdns_record *record = NULL; + + mdns_dbg("Answer RCVD for '%s'\n", answer->record->rname); + + /* Find currently active query cookie */ + found = pico_mdns_ctree_find_cookie(answer->record->rname, + PICO_MDNS_PACKET_TYPE_QUERY_ANY); + if (found && pico_mdns_handle_cookie_with_answer(found, answer)) { + mdns_dbg("Could not handle found cookie correctly!\n"); + return -1; + } else { + mdns_dbg("RCVD an unsolicited record!\n"); + if ((record = pico_tree_findKey(&MyRecords, answer)) && + !IS_RECORD_PROBING(record)) + return pico_mdns_record_resolve_conflict(record, + answer->record->rname); + } + + return 0; +} + +/* **************************************************************************** + * Handles a single received authority record. + * + * @param answer Authority mDNS record. + * @return 0 when authority is properly handled. -1 when it's not. + * ****************************************************************************/ +static int +pico_mdns_handle_single_authority( struct pico_mdns_record *answer ) +{ + struct pico_mdns_cookie *found = NULL; + char *name = NULL; + + name = answer->record->rname; + mdns_dbg("Authority RCVD for '%s'\n", name); + + /* Find currently active probe cookie */ + if ((found = pico_mdns_ctree_find_cookie(name, PICO_MDNS_PACKET_TYPE_PROBE)) + && PICO_MDNS_COOKIE_STATUS_ACTIVE == found->status) { + mdns_dbg("Simultaneous Probing occurred, went tiebreaking...\n"); + if (pico_mdns_cookie_apply_spt(found, answer->record) < 0) { + mdns_dbg("Could not apply S.P.T. to cookie!\n"); + return -1; + } + } + + return 0; +} + +/* **************************************************************************** + * Handles a single received additional [Temporarily unused] + * + * @param answer Additional mDNS record. + * @return 0 + * ****************************************************************************/ +static int +pico_mdns_handle_single_additional( struct pico_mdns_record *answer ) +{ + /* Don't need this for now ... */ + IGNORE_PARAMETER(answer); + return 0; +} + +/* **************************************************************************** + * Handles a flat chunk of memory as if it were all questions in it. + * Generates a tree with responses if there are any questions for records for + * which host has the authority to answer. + * + * @param ptr Pointer-Pointer to location of question section of packet. + * Will point to right after the question section on return. + * @param qdcount Amount of questions contained in the packet + * @param packet DNS packet where the questions are present. + * @return Tree with possible responses on the questions. + * ****************************************************************************/ +static pico_mdns_rtree +pico_mdns_handle_data_as_questions ( uint8_t **ptr, + uint16_t qdcount, + pico_dns_packet *packet ) +{ + PICO_MDNS_RTREE_DECLARE(antree); + PICO_MDNS_RTREE_DECLARE(rtree); + struct pico_dns_question question; + uint16_t i = 0; + + /* Check params */ + if ((!ptr) || !packet || !(*ptr)) { + pico_err = PICO_ERR_EINVAL; + return antree; + } + + for (i = 0; i < qdcount; i++) { + /* Set qname of the question to the correct location */ + question.qname = (char *)(*ptr); + + /* Set qsuffix of the question to the correct location */ + question.qsuffix = (struct pico_dns_question_suffix *) + (question.qname + pico_dns_namelen_comp(question.qname) + 1); + + /* Handle a single question and merge the returned tree */ + rtree = pico_mdns_handle_single_question(&question, packet); + pico_tree_merge(&antree, &rtree); + pico_tree_destroy(&rtree, NULL); + + /* Move to next question */ + *ptr = (uint8_t *)question.qsuffix + + sizeof(struct pico_dns_question_suffix); + } + if (pico_tree_count(&antree) == 0) { + mdns_dbg("No 'MyRecords' found that corresponds with this query.\n"); + } + + return antree; +} + +static int +pico_mdns_handle_data_as_answers_generic( uint8_t **ptr, + uint16_t count, + pico_dns_packet *packet, + uint8_t type ) +{ + struct pico_mdns_record mdns_answer = { + .record = NULL, .current_ttl = 0, + .flags = 0, .claim_id = 0 + }; + struct pico_dns_record answer; + char *orname = NULL; + uint16_t i = 0; + + /* Check params */ + if ((!ptr) || !packet || !(*ptr)) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + for (i = 0; i < count; i++) { + /* Set rname of the record to the correct location */ + answer.rname = (char *)(*ptr); + + /* Set rsuffix of the record to the correct location */ + answer.rsuffix = (struct pico_dns_record_suffix *) + (answer.rname + + pico_dns_namelen_comp(answer.rname) + 1u); + + /* Set rdata of the record to the correct location */ + answer.rdata = (uint8_t *) answer.rsuffix + + sizeof(struct pico_dns_record_suffix); + + /* Make an mDNS record from the DNS answer */ + orname = pico_dns_record_decompress(&answer, packet); + mdns_answer.record = &answer; + mdns_answer.record->rname_length = (uint16_t)(pico_dns_strlen(answer.rname) + 1u); + + /* Handle a single aswer */ + switch (type) { + case 1: + pico_mdns_handle_single_authority(&mdns_answer); + break; + case 2: + pico_mdns_handle_single_additional(&mdns_answer); + break; + default: + pico_mdns_handle_single_answer(&mdns_answer); +#if PICO_MDNS_ALLOW_CACHING == 1 + pico_mdns_cache_add_record(&mdns_answer); +#endif + break; + } + + /* Free decompressed name and mDNS record */ + PICO_FREE(mdns_answer.record->rname); + answer.rname = orname; + + /* Move to next record */ + *ptr = (uint8_t *) answer.rdata + short_be(answer.rsuffix->rdlength); + } + return 0; +} + +/* **************************************************************************** + * Splits an mDNS record tree into two DNS record tree, one to send via + * unicast, one to send via multicast. + * + * @param answers mDNS record tree to split up + * @param unicast_tree DNS record tree with unicast answers. + * @param multicast_tree DNS record tee with multicast answers. + * @return 0 when the tree is properly split up. + * ****************************************************************************/ +static int +pico_mdns_sort_unicast_multicast( pico_mdns_rtree *answers, + pico_dns_rtree *unicast_tree, + pico_dns_rtree *multicast_tree ) +{ + struct pico_mdns_record *record = NULL; + struct pico_tree_node *node = NULL; + + /* Check params */ + if (!answers || !unicast_tree || !multicast_tree) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + pico_tree_foreach(node, answers) { + record = node->keyValue; + if ((record = node->keyValue)) { + if (IS_UNICAST_REQUESTED(record)) { + if (record->record) + pico_tree_insert(unicast_tree, record->record); + } else { + if (record->record) + pico_tree_insert(multicast_tree, record->record); + } + } + } + + return 0; +} + +static uint16_t +pico_mdns_nsec_highest_type( pico_mdns_rtree *rtree ) +{ + struct pico_tree_node *node = NULL, *next = NULL; + struct pico_mdns_record *record = NULL; + uint16_t highest_type = 0, type = 0; + + pico_tree_foreach_safe(node, rtree, next) { + if ((record = node->keyValue)) { + if (IS_SHARED_RECORD(record)) + pico_tree_delete(rtree, record); + + type = short_be(record->record->rsuffix->rtype); + highest_type = (type > highest_type) ? (type) : (highest_type); + } + } + + return highest_type; +} + +static void +pico_mdns_nsec_gen_bitmap( uint8_t *ptr, pico_mdns_rtree *rtree ) +{ + struct pico_tree_node *node = NULL; + struct pico_mdns_record *record = NULL; + uint16_t type = 0; + + pico_tree_foreach(node, rtree) { + if ((record = node->keyValue)) { + type = short_be(record->record->rsuffix->rtype); + *(ptr + 1 + (type / 8)) = (uint8_t)(0x80 >> (type % 8)); + } + } +} + +/* **************************************************************************** + * Generates an NSEC record for a specific name. Looks in MyRecords for unique + * records with given name and generates the NSEC bitmap from them. + * + * @param name Name of the records you want to generate a bitmap for. + * @return Pointer to newly created NSEC record on success, NULL on failure. + * ****************************************************************************/ +static struct pico_mdns_record * +pico_mdns_gen_nsec_record( char *name ) +{ + PICO_MDNS_RTREE_DECLARE(rtree); + struct pico_mdns_record *record = NULL; + uint16_t highest_type = 0, rdlen = 0; + uint8_t bitmap_len = 0, *rdata = NULL, *ptr = NULL; + char *url = NULL; + + if (!name) { /* Check params */ + pico_err = PICO_ERR_EINVAL; + return NULL; + } + + /* Determine the highest type of my unique records with this name */ + rtree = pico_mdns_rtree_find_name(&MyRecords, name, 0); + highest_type = pico_mdns_nsec_highest_type(&rtree); + + /* Determine the bimap_len */ + bitmap_len = (uint8_t)(highest_type / 8); + bitmap_len = (uint8_t)(bitmap_len + ((highest_type % 8) ? (1) : (0))); + + /* Provide rdata */ + rdlen = (uint16_t)(pico_dns_strlen(name) + 3u + bitmap_len); + if (!(rdata = PICO_ZALLOC((size_t)rdlen))) { + pico_err = PICO_ERR_ENOMEM; + pico_tree_destroy(&rtree, NULL); + return NULL; + } + + /* Set the next domain name */ + strcpy((char *)rdata, name); + /* Set the bitmap length */ + *(ptr = (uint8_t *)(rdata + pico_dns_strlen(name) + 2)) = bitmap_len; + /* Generate the bitmap */ + pico_mdns_nsec_gen_bitmap(ptr, &rtree); + pico_tree_destroy(&rtree, NULL); + + /* Generate the actual mDNS NSEC record */ + if (!(url = pico_dns_qname_to_url(name))) { + PICO_FREE(rdata); + return NULL; + } + + record = pico_mdns_record_create(url, (void *)rdata, rdlen, + PICO_DNS_TYPE_NSEC, + PICO_MDNS_SERVICE_TTL, + PICO_MDNS_RECORD_UNIQUE); + PICO_FREE(rdata); + PICO_FREE(url); + return record; +} + +/* **************************************************************************** + * Checks in additionals if there is an NSEC record already present with given + * name. If there's not, a new NSEC records will be generated and added to the + * additional tree. + * + * @param artree mDNS record-tree containing additional records. + * @param name Name to check for. + * @return 0 when NSEC is present in additional, whether it was already present + * or a new one is generated doesn't matter. + * ****************************************************************************/ +static int +pico_mdns_additionals_add_nsec( pico_mdns_rtree *artree, + char *name ) +{ + struct pico_mdns_record *record = NULL, *nsec = NULL; + struct pico_tree_node *node = NULL; + uint16_t type = 0; + + /* Check params */ + if (!artree || !name) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* Check if there is a NSEC already for this name */ + pico_tree_foreach(node, artree) { + if (node != &LEAF && (record = node->keyValue)) { + type = short_be(record->record->rsuffix->rtype); + if (PICO_DNS_TYPE_NSEC == type) { + if (strcasecmp(record->record->rname, name) == 0) + return 0; + } + } + } + + /* If there is none present generate one for given name */ + if ((nsec = pico_mdns_gen_nsec_record(name))) + pico_tree_insert(artree, nsec); + return 0; +} + +/* **************************************************************************** + * Adds hostname records to the additional records + * + * @param artree mDNS record-tree containing additional records. + * @return 0 when hostname records are added successfully to additionals. Rets + * something else on failure. + * ****************************************************************************/ +static int +pico_mdns_additionals_add_host( pico_mdns_rtree *artree ) +{ + struct pico_tree_node *node = NULL; + struct pico_mdns_record *record = NULL, *copy = NULL; + + pico_tree_foreach(node, &MyRecords) { + if ((record = node->keyValue) && + IS_HOSTNAME_RECORD(record) && + IS_RECORD_VERIFIED(record)) { + copy = pico_mdns_record_copy(record); + if (copy && pico_tree_insert(artree, copy)) + pico_mdns_record_delete((void **)©); + } + } + + return 0; +} + +static void +pico_rtree_add_copy( pico_mdns_rtree *tree, struct pico_mdns_record *record ) +{ + struct pico_mdns_record *copy = NULL; + + if (!tree || !record) { + pico_err = PICO_ERR_EINVAL; + return; + } + + if ((copy = pico_mdns_record_copy(record))) { + if (pico_tree_insert(tree, copy)) + pico_mdns_record_delete((void **)©); + } +} + +/* **************************************************************************** + * When a service is found additional records should be generated and added to + * the either the answer section or the additional sections. This happens here + * + * @param antree mDNS record tree with answers to send + * @param artree mDNS record tree with additionals to send + * @param srv_record Found SRV record in the answers + * @return 0 When additional records are properly generated + * ****************************************************************************/ +static int +pico_mdns_gather_service_meta( pico_mdns_rtree *antree, + pico_mdns_rtree *artree, + struct pico_mdns_record *srv_record ) +{ + struct pico_mdns_record *ptr_record = NULL, *meta_record = NULL; + char *sin = NULL, *service = NULL; + uint32_t ttl = 0; + + if (!antree || !artree || !srv_record) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* Add hostname records */ + pico_mdns_additionals_add_host(artree); + + /* Generate proper service instance name and service */ + if (!(sin = pico_dns_qname_to_url(srv_record->record->rname))) + return -1; + + service = sin + pico_dns_first_label_length(sin) + 1u; + ttl = long_be(srv_record->record->rsuffix->rttl); + + /* Generate PTR records */ + ptr_record = pico_mdns_record_create(service, (void *)sin, + (uint16_t)strlen(sin), + PICO_DNS_TYPE_PTR, + ttl, PICO_MDNS_RECORD_SHARED); + /* Meta DNS-SD record */ + meta_record = pico_mdns_record_create("_services._dns-sd._udp.local", + (void *)service, + (uint16_t)strlen(service), + PICO_DNS_TYPE_PTR, + ttl, PICO_MDNS_RECORD_SHARED); + PICO_FREE(sin); + if (!meta_record || !ptr_record) { + mdns_dbg("Could not generate META or PTR records!\n"); + pico_mdns_record_delete((void **)&ptr_record); + pico_mdns_record_delete((void **)&meta_record); + return -1; + } + + ptr_record->flags |= (PICO_MDNS_RECORD_PROBED | PICO_MDNS_RECORD_CLAIMED); + meta_record->flags |= (PICO_MDNS_RECORD_PROBED | PICO_MDNS_RECORD_CLAIMED); + + /* Add copies to the answer tree */ + pico_rtree_add_copy(antree, meta_record); + pico_rtree_add_copy(antree, ptr_record); + + /* Insert the created service record in MyRecords, if it's already in, destroy them */ + if (meta_record && pico_tree_insert(&MyRecords, meta_record)) { + pico_mdns_record_delete((void **)&meta_record); + } + if (ptr_record && pico_tree_insert(&MyRecords, ptr_record)) { + pico_mdns_record_delete((void **)&ptr_record); + } + + return 0; +} + +/* **************************************************************************** + * Gathers additional records for a to send response. Checks for services and + * whether or not there should be NSEC records added to the additional section + * + * @param antree mDNS record tree with answers to send + * @param artree mDNS record tree with additionals to send + * @return Returns 0 when additionals are properly generated and added + * ****************************************************************************/ +static int +pico_mdns_gather_additionals( pico_mdns_rtree *antree, + pico_mdns_rtree *artree ) +{ + struct pico_tree_node *node = NULL; + struct pico_mdns_record *record = NULL; + int ret = 0; + + /* Check params */ + if (!antree || !artree) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* Look for SRV records in the answer tree */ + pico_tree_foreach(node, antree) { + if ((record = node->keyValue) && + short_be(record->record->rsuffix->rtype) == PICO_DNS_TYPE_SRV && + (ret = pico_mdns_gather_service_meta(antree, artree, record))) + return ret; + } + + /* Look for unique records in the answer tree to generate NSEC records */ + pico_tree_foreach(node, antree) { + if ((record = node->keyValue) && IS_UNIQUE_RECORD(record) && + (ret = pico_mdns_additionals_add_nsec(artree, + record->record->rname))) + return ret; + } + + /* Look for unique records in the additional tree to generate NSEC records*/ + pico_tree_foreach(node, artree) { + if ((record = node->keyValue) && IS_UNIQUE_RECORD(record) && + (ret = pico_mdns_additionals_add_nsec(artree, + record->record->rname))) + return ret; + } + + return 0; +} + +/* **************************************************************************** + * Sends mDNS records to either multicast peer via unicast to a single peer. + * + * @param antree Tree with mDNS records to send as answers + * @param peer IPv4-address of peer who this host has RCVD a packet. + * @return 0 when answers are properly handled, something else otherwise. + * ****************************************************************************/ +static int +pico_mdns_reply( pico_mdns_rtree *antree, struct pico_ip4 peer ) +{ + PICO_DNS_RTREE_DECLARE(antree_m); + PICO_DNS_RTREE_DECLARE(antree_u); + PICO_MDNS_RTREE_DECLARE(artree); + PICO_DNS_RTREE_DECLARE(artree_dummy); + PICO_DNS_RTREE_DECLARE(artree_dns); + + /* Try to gather additionals for the to send response */ + if (pico_mdns_gather_additionals(antree, &artree)) { + mdns_dbg("Could not gather additionals properly!\n"); + return -1; + } + + /* Sort the answers into multicast and unicast answers */ + pico_mdns_sort_unicast_multicast(antree, &antree_u, &antree_m); + + /* Convert the mDNS additional tree to a DNS additional tree to send with + * the the unicast AND the multicast response */ + pico_mdns_sort_unicast_multicast(&artree, &artree_dummy, &artree_dns); + + /* Send response via unicast */ + if (pico_mdns_unicast_reply(&antree_u, &artree_dns, peer)) { + mdns_dbg("Could not sent reply via unicast!\n"); + return -1; + } + + /* Send response via multicast */ + if (pico_mdns_multicast_reply(&antree_m, &artree_dns)) { + mdns_dbg("Could not sent reply via multicast!\n"); + return -1; + } + + pico_tree_destroy(&antree_m, NULL); + pico_tree_destroy(&antree_u, NULL); + pico_tree_destroy(&artree_dummy, NULL); + pico_tree_destroy(&artree_dns, NULL); + PICO_MDNS_RTREE_DESTROY(&artree); + + return 0; +} + +/* **************************************************************************** + * Parses DNS records from a plain chunk of data and looks for them in the + * answer tree. If they're found, they will be removed from the tree. + * + * @param rtree Tree to look in for known answers + * @param packet DNS packet in which to look for known answers + * @param ancount Amount of answers in the DNS packet + * @param data Answer section of the DNS packet as a flat chunk of memory. + * @return 0 K.A.S. could be properly applied, something else when not. + * ****************************************************************************/ +static int +pico_mdns_apply_k_a_s( pico_mdns_rtree *rtree, + pico_dns_packet *packet, + uint16_t ancount, + uint8_t **data ) +{ + struct pico_tree_node *node = NULL, *next = NULL; + struct pico_mdns_record *record = NULL, ka = { + 0 + }; + struct pico_dns_record answer = { + 0 + }; + uint16_t i = 0; + + /* Check params */ + if ((!data) || !rtree || !packet || !(*data)) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + for (i = 0; i < ancount; i++) { + /* Set rname of the record to the correct location */ + answer.rname = (char *)(*data); + + /* Set rsuffix of the record to the correct location */ + answer.rsuffix = (struct pico_dns_record_suffix *) + (answer.rname + pico_dns_namelen_comp(answer.rname) + 1u); + + /* Set rdata of the record to the correct location */ + answer.rdata = (uint8_t *) answer.rsuffix + + sizeof(struct pico_dns_record_suffix); + + pico_dns_record_decompress(&answer, packet); + ka.record = &answer; + + /* If the answer is in the record vector */ + pico_tree_foreach_safe(node, rtree, next) { + if ((record = node->keyValue)) { + if (pico_mdns_record_cmp(record, &ka) == 0) + record = pico_tree_delete(rtree, record); + } + } + PICO_FREE(ka.record->rname); + ka.record = NULL; + + /* Move to next record */ + *data = (uint8_t *) answer.rdata + short_be(answer.rsuffix->rdlength); + } + return 0; +} + +/* **************************************************************************** + * Handles a single incoming query packet. Applies Known Answer Suppression + * after handling as well. + * + * @param packet Received packet + * @param peer IPv4 address of the peer who sent the received packet. + * @return Returns 0 when the query packet is properly handled. + * ****************************************************************************/ +static int +pico_mdns_handle_query_packet( pico_dns_packet *packet, struct pico_ip4 peer ) +{ + PICO_MDNS_RTREE_DECLARE(antree); + uint16_t qdcount = 0, ancount = 0; + uint8_t *data = NULL; + + /* Move to the data section of the packet */ + data = (uint8_t *)packet + sizeof(struct pico_dns_header); + + /* Generate a list of answers */ + qdcount = short_be(packet->qdcount); + antree = pico_mdns_handle_data_as_questions(&data, qdcount, packet); + if (pico_tree_count(&antree) == 0) { + mdns_dbg("No records found that correspond with this query!\n"); + return 0; + } + + /* Apply Known Answer Suppression */ + ancount = short_be(packet->ancount); + if (pico_mdns_apply_k_a_s(&antree, packet, ancount, &data)) { + mdns_dbg("Could not apply known answer suppression!\n"); + return -1; + } + + /* Try to reply with the left-over answers */ + pico_mdns_reply(&antree, peer); + PICO_MDNS_RTREE_DESTROY(&antree); + + return 0; +} + +/* **************************************************************************** + * Handles a single incoming probe packet. Checks for Simultaneous Probe + * Tiebreaking as well. + * + * @param packet Received probe packet. + * @param peer IPv4 address of the peer who sent the probe packet. + * @return Returns 0 when the probe packet is properly handled. + * ****************************************************************************/ +static int +pico_mdns_handle_probe_packet( pico_dns_packet *packet, struct pico_ip4 peer ) +{ + PICO_MDNS_RTREE_DECLARE(antree); + uint16_t qdcount = 0, nscount = 0; + uint8_t *data = NULL; + + /* Move to the data section of the packet */ + data = (uint8_t *)packet + sizeof(struct pico_dns_header); + + /* Generate a list of answers */ + qdcount = short_be(packet->qdcount); + antree = pico_mdns_handle_data_as_questions(&data, qdcount, packet); + + /* Check for Simultaneous Probe Tiebreaking */ + nscount = short_be(packet->nscount); + pico_mdns_handle_data_as_answers_generic(&data, nscount, packet, 1); + + /* Try to reply with the answers */ + if (pico_tree_count(&antree) != 0){ + int retval = pico_mdns_reply(&antree, peer); + PICO_MDNS_RTREE_DESTROY(&antree); + return retval; + } + + return 0; +} + +/* **************************************************************************** + * Handles a single incoming answer packet. + * + * @param packet Received answer packet. + * @return Returns 0 when the response packet is properly handled. + * ****************************************************************************/ +static int +pico_mdns_handle_response_packet( pico_dns_packet *packet ) +{ + uint8_t *data = NULL; + uint16_t ancount = 0; + + /* Move to the data section of the packet */ + data = (uint8_t *)packet + sizeof(struct pico_dns_header); + + /* Generate a list of answers */ + ancount = short_be(packet->ancount); + if (pico_mdns_handle_data_as_answers_generic(&data, ancount, packet, 0)) { + mdns_dbg("Could not handle data as answers\n"); + return -1; + } + + return 0; +} + +/* **************************************************************************** + * Parses an incoming packet and handles it according to the type of the + * packet. Packet type determination happens in this function. + * + * @param buf Memory buffer containing the received packet + * @param buflen Length in bytes of the memory buffer + * @param peer IPv4 address of the peer who sent the received packet. + * @return 0 when the packet is properly handled. Something else when it's not + * ****************************************************************************/ +static int +pico_mdns_recv( void *buf, int buflen, struct pico_ip4 peer ) +{ + pico_dns_packet *packet = (pico_dns_packet *) buf; + uint16_t qdcount = short_be(packet->qdcount); + uint16_t ancount = short_be(packet->ancount); + uint16_t authcount = short_be(packet->nscount); + uint16_t addcount = short_be(packet->arcount); + + mdns_dbg(">>>>>>> QDcount: %u, ANcount: %u, NScount: %u, ARcount: %u\n", + qdcount, ancount, authcount, addcount); + + IGNORE_PARAMETER(buflen); + IGNORE_PARAMETER(addcount); + + /* DNS PACKET TYPE DETERMINATION */ + if ((qdcount > 0)) { + if (authcount > 0) { + mdns_dbg(">>>>>>> RCVD a mDNS probe query:\n"); + /* Packet is probe query */ + if (pico_mdns_handle_probe_packet(packet, peer) < 0) { + mdns_dbg("Could not handle mDNS probe query!\n"); + return -1; + } + } else { + mdns_dbg(">>>>>>> RCVD a plain mDNS query:\n"); + /* Packet is a plain query */ + if (pico_mdns_handle_query_packet(packet, peer) < 0) { + mdns_dbg("Could not handle plain DNS query!\n"); + return -1; + } + } + } else { + if (ancount > 0) { + mdns_dbg(">>>>>>> RCVD a mDNS response:\n"); + /* Packet is a response */ + if (pico_mdns_handle_response_packet(packet) < 0) { + mdns_dbg("Could not handle DNS response!\n"); + return -1; + } + } else { + /* Something went wrong here... */ + mdns_dbg("RCVD Packet contains no questions or answers...\n"); + return -1; + } + } + + return 0; +} + +/* **************************************************************************** + * picoTCP callback for UDP IPv4 Socket events + * + * @param ev Determination of the occurred event + * @param s Socket on which the event occurred + * ****************************************************************************/ +static void +pico_mdns_event4( uint16_t ev, struct pico_socket *s ) +{ + char *recvbuf = NULL; + struct pico_ip4 peer = { + 0 + }; + int pico_read = 0; + uint16_t port = 0; + + /* process read event, data available */ + if (ev == PICO_SOCK_EV_RD) { + mdns_dbg("\n>>>>>>> READ EVENT! <<<<<<<\n"); + recvbuf = PICO_ZALLOC(PICO_MDNS_MAXBUF); + if (!recvbuf) { + pico_err = PICO_ERR_ENOMEM; + return; + } + + /* Receive while data is available in socket buffer */ + while((pico_read = pico_socket_recvfrom(s, recvbuf, PICO_MDNS_MAXBUF, + &peer, &port)) > 0) { + /* Handle the MDNS data received */ + pico_mdns_recv(recvbuf, pico_read, peer); + } + PICO_FREE(recvbuf); + mdns_dbg(">>>>>>>>>>>>>><<<<<<<<<<<<<\n\n"); + } else + mdns_dbg("Socket Error received. Bailing out.\n"); +} + +/* MARK: ADDRESS RESOLUTION */ + +/* **************************************************************************** + * Send a mDNS query packet on the wire. This is scheduled with a pico_timer- + * event. + * + * @param now Ignore + * @param arg Void-pointer to query-cookie + * ****************************************************************************/ +static void +pico_mdns_send_query_packet( pico_time now, void *arg ) +{ + struct pico_mdns_cookie *cookie = (struct pico_mdns_cookie *)arg; + pico_dns_qtree *questions = NULL; + pico_dns_packet *packet = NULL; + uint16_t len = 0; + + IGNORE_PARAMETER(now); + + /* Parse in the cookie */ + cookie = (struct pico_mdns_cookie *)arg; + if (!cookie || cookie->type != PICO_MDNS_PACKET_TYPE_QUERY) + return; + + /* Create DNS query packet */ + questions = &(cookie->qtree); + if (!(packet = pico_dns_query_create(questions, NULL, NULL, NULL, &len))) { + mdns_dbg("Could not create query packet!\n"); + return; + } + + packet->id = 0; + + if (cookie->status != PICO_MDNS_COOKIE_STATUS_CANCELLED) { + cookie->status = PICO_MDNS_COOKIE_STATUS_ACTIVE; + if(pico_mdns_send_packet(packet, len) != (int)len) { + mdns_dbg("Send error occurred!\n"); + return; + } + + mdns_dbg("DONE - Sent query.\n"); + } else { + mdns_dbg("DONE - Duplicate query suppressed.\n"); + pico_timer_cancel(cookie->send_timer); + /* Remove cookie from Cookies */ + cookie = pico_tree_delete(&Cookies, cookie); + pico_mdns_cookie_delete(&cookie); + } + PICO_FREE(packet); +} + +/* **************************************************************************** + * Generates a mDNS query packet and schedules a sending on the wire. + * + * @param url URL for the name of the question contained in the query + * @param type DNS type of the question contained in the query + * @param callback Callback to call when a response on this query is RCVD. + * @return 0 When the query is successfully generated and scheduled for sending + * ****************************************************************************/ +static int +pico_mdns_getrecord_generic( const char *url, uint16_t type, + void (*callback)(pico_mdns_rtree *, + char *, + void *), + void *arg) +{ + struct pico_mdns_cookie *cookie = NULL; + PICO_DNS_QTREE_DECLARE(qtree); + PICO_MDNS_RTREE_DECLARE(antree); + PICO_MDNS_RTREE_DECLARE(artree); + struct pico_dns_question *q = NULL; + uint16_t l = 0; + + /* Create a single question and add it to the tree */ + q = pico_mdns_question_create(url, &l, PICO_PROTO_IPV4, type, 0, 0); + if (!q) { + mdns_dbg("question_create returned NULL!\n"); + return -1; + } + pico_tree_insert(&qtree, q); + + /* Create a mDNS cookie to send */ + if (!(cookie = pico_mdns_cookie_create(qtree, antree, artree, 1, + PICO_MDNS_PACKET_TYPE_QUERY, + callback, arg))) { + PICO_DNS_QTREE_DESTROY(&qtree); + mdns_dbg("cookie_create returned NULL!\n"); + return -1; + } + + /* Add cookie to Cookies to be able to find it afterwards */ + pico_tree_insert(&Cookies, cookie); + /* Create new pico_timer-event to send packet */ + pico_timer_add((pico_rand() % 120) + 20, pico_mdns_send_query_packet, + (void *)cookie); + return 0; +} + +/* **************************************************************************** + * API-call to query a record with a certain URL and type. First checks the + * Cache for this record. If no cache-entry is found, a query will be sent on + * the wire for this record. + * + * @param url URL to query for. + * @param type DNS type top query for. + * @param callback Callback to call when records are found for the query. + * @return 0 when query is correctly parsed, something else on failure. + * ****************************************************************************/ +int +pico_mdns_getrecord( const char *url, uint16_t type, + void (*callback)(pico_mdns_rtree *, + char *, + void *), + void *arg ) +{ +#if PICO_MDNS_ALLOW_CACHING == 1 + PICO_MDNS_RTREE_DECLARE(cache_hits); + char *name = NULL; +#endif + + /* Check params */ + if (!url) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* First, try to find records in the cache */ +#if PICO_MDNS_ALLOW_CACHING == 1 + name = pico_dns_url_to_qname(url); + cache_hits = pico_mdns_rtree_find_name_type(&Cache, name, type, 0); + PICO_FREE(name); + if (pico_tree_count(&cache_hits) > 0) { + mdns_dbg("CACHE HIT! Passed cache records to callback.\n"); + callback(&cache_hits, NULL, arg); + } else { +#endif + mdns_dbg("CACHE MISS! Trying to resolve URL '%s'...\n", url); + return pico_mdns_getrecord_generic(url, type, callback, arg); +#if PICO_MDNS_ALLOW_CACHING == 1 +} +#endif + + return 0; +} + +/* MARK: PROBING & ANNOUNCING */ + +/* **************************************************************************** + * Function to create an announcement from an mDNS cookie and send it on the + * wire. + * + * @param now Ignore + * @param arg Void-pointer to mDNS announcement cookie + * ****************************************************************************/ +static void +pico_mdns_send_announcement_packet( pico_time now, void *arg ) +{ + struct pico_mdns_cookie *cookie = (struct pico_mdns_cookie *)arg; + + /* Check params */ + IGNORE_PARAMETER(now); + if (!cookie) + return; + + cookie->status = PICO_MDNS_COOKIE_STATUS_ACTIVE; + if (cookie->count > 0) { + /* Send the announcement on the wire */ + pico_mdns_reply(&(cookie->antree), inaddr_any); + mdns_dbg("DONE - Sent announcement!\n"); + + /* The host needs to send 2 announcements 1 second apart */ + if (--(cookie->count) == 0) { + cookie->status = PICO_MDNS_COOKIE_STATUS_INACTIVE; + + /* Update the states of the records */ + pico_mdns_my_records_claimed(cookie->antree, + cookie->callback, + cookie->arg); + + /* Try to delete the cookie */ + pico_tree_delete(&Cookies, cookie); + pico_mdns_cookie_delete(&cookie); + } + else /* Announcement should be sent with a delay of 1s in between. */ + pico_timer_add(1000, pico_mdns_send_announcement_packet, cookie); + } +} + +/* **************************************************************************** + * Announces all 'my records' which passed the probing-step or just shared + * records. + * + * @param callback Gets called when all records in the cookie are announced. + * @return 0 When the host successfully started announcing. + * ****************************************************************************/ +static int +pico_mdns_announce( void (*callback)(pico_mdns_rtree *, + char *, + void *), + void *arg ) +{ + struct pico_mdns_cookie *announcement_cookie = NULL; + PICO_DNS_QTREE_DECLARE(qtree); + PICO_MDNS_RTREE_DECLARE(antree); + PICO_MDNS_RTREE_DECLARE(artree); + + /* Check params */ + if (!callback) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + IGNORE_PARAMETER(arg); + + /* Find out which resource records can be announced */ + antree = pico_mdns_my_records_find_probed(); + if (pico_tree_count(&antree) == 0) + return 0; + + /* Create a mDNS packet cookie */ + if (!(announcement_cookie = pico_mdns_cookie_create(qtree, antree, artree, + PICO_MDNS_ANNOUNCEMENT_COUNT, + PICO_MDNS_PACKET_TYPE_ANNOUNCEMENT, + callback, arg))) { + mdns_dbg("cookie_create returned NULL!\n"); + PICO_MDNS_RTREE_DESTROY(&antree); + return -1; + } + + /* Send a first unsolicited announcement */ + pico_mdns_send_announcement_packet(0, announcement_cookie); + mdns_dbg("DONE - Started announcing.\n"); + + return 0; +} + +/* **************************************************************************** + * Makes sure the cache flush bit of the to probe records is cleared, and + * generates a DNS record tree to insert in the Authority Section of the DNS + * packet + * + * @param records mDNS records to probe. + * @return DNS record tree to with actual DNS records to insert in Authority + * Section of probe packet. + * ****************************************************************************/ +static pico_dns_rtree +pico_mdns_gen_probe_auths( pico_mdns_rtree *records ) +{ + PICO_DNS_RTREE_DECLARE(nstree); + struct pico_tree_node *node = NULL; + struct pico_mdns_record *record = NULL; + + pico_tree_foreach(node, records) { + if ((record = node->keyValue) && record->record) { + /* Clear the cache flush bit for authority records in probes */ + PICO_MDNS_CLR_MSB_BE(record->record->rsuffix->rclass); + /* Only the actual DNS records is required */ + pico_tree_insert(&nstree, record->record); + } + } + + return nstree; +} + +/* **************************************************************************** + * Function to create a probe form an mDNS cookie and send it on the wire. + * + * @param now Ignore + * @param arg Void-pointer to mDNS probe cookie + * ****************************************************************************/ +static void +pico_mdns_send_probe_packet( pico_time now, void *arg ) +{ + struct pico_mdns_cookie *cookie = (struct pico_mdns_cookie *)arg; + pico_dns_packet *packet = NULL; + PICO_DNS_RTREE_DECLARE(nstree); + uint16_t len = 0; + + /* Check params */ + IGNORE_PARAMETER(now); + if (!cookie || (cookie->type == PICO_MDNS_COOKIE_STATUS_INACTIVE)) { + pico_err = PICO_ERR_EINVAL; + return; + } + + /* Set the cookie to the active state */ + cookie->status = PICO_MDNS_COOKIE_STATUS_ACTIVE; + if (cookie->count-- > 0) { + /* Generate authority records */ + nstree = pico_mdns_gen_probe_auths(&(cookie->antree)); + + /* Create an mDNS answer */ + if (!(packet = pico_dns_query_create(&(cookie->qtree), NULL, + &nstree, NULL, &len))) { + PICO_DNS_RTREE_DESTROY(&nstree); + mdns_dbg("Could not create probe packet!\n"); + return; + } + pico_tree_destroy(&nstree, NULL); + + /* Send the mDNS answer unsolicited via multicast */ + if(pico_mdns_send_packet(packet, len) != (int)len) { + mdns_dbg("Send error occurred!\n"); + return; + } + PICO_FREE(packet); + + mdns_dbg("DONE - Sent probe!\n"); + + /* Probes should be sent with a delay in between of 250 ms */ + if (PICO_MDNS_COOKIE_STATUS_ACTIVE == cookie->status ) + cookie->send_timer = pico_timer_add(250, + pico_mdns_send_probe_packet, + (void *)cookie); + } else { + mdns_dbg("DONE - Probing.\n"); + + pico_mdns_my_records_probed(&(cookie->antree)); + + /* Start announcing */ + cookie->count = PICO_MDNS_ANNOUNCEMENT_COUNT; + cookie->type = PICO_MDNS_PACKET_TYPE_ANNOUNCEMENT; + pico_mdns_send_announcement_packet(0, (void*) cookie); + } + + + return; +} + +/* **************************************************************************** + * Adds a new probe question to the probe cookie questions, if a probe question + * for a new is already present in the question-tree, it will not be generated + * and inserted again + * + * @param qtree Probe question tree + * @param name Name for which the function has to create a probe question + * @return 0 when the probe question is already present or added successfully. + * ****************************************************************************/ +static int +pico_mdns_add_probe_question( pico_dns_qtree *qtree, + char *name ) +{ + struct pico_dns_question *new = NULL; + char *url = NULL; + uint16_t qlen = 0; + uint8_t flags = PICO_MDNS_QUESTION_FLAG_PROBE; + +#if PICO_MDNS_PROBE_UNICAST == 1 + flags |= PICO_MDNS_QUESTION_FLAG_UNICAST_RES; +#endif + + /* Convert name to URL and try to create a new probe question */ + if (!(url = pico_dns_qname_to_url(name))) + return -1; + + mdns_dbg("Probe question for URL: %s\n", url); + if (!(new = pico_mdns_question_create(url, &qlen, PICO_PROTO_IPV4, + PICO_DNS_TYPE_ANY, flags, 0))) { + PICO_FREE(url); + return -1; + } + + PICO_FREE(url); + + /* Try to find an existing question in the vector */ + if (pico_tree_insert(qtree, new)) + pico_dns_question_delete((void **)&new); + + return 0; +} + +/* **************************************************************************** + * Find any of my record that need to be probed and try to probe them. + * + * @param callback Callback to call when all records are properly registered + * @return When host successfully started probing. + * ****************************************************************************/ +static int pico_mdns_probe( void (*callback)(pico_mdns_rtree *, + char *, + void *), + void *arg ) +{ + struct pico_mdns_cookie *cookie = NULL; + struct pico_mdns_record *record = NULL; + struct pico_tree_node *node = NULL; + PICO_DNS_QTREE_DECLARE(qtree); + PICO_MDNS_RTREE_DECLARE(antree); + PICO_MDNS_RTREE_DECLARE(artree); + + /* Check params */ + if (!callback) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* Find my records that need to pass the probing step first */ + antree = pico_mdns_my_records_find_to_probe(); + + /* Crate probe questions for the to probe records */ + pico_tree_foreach(node, &antree) { + if ((record = node->keyValue)) { + pico_mdns_add_probe_question(&qtree, record->record->rname); + } + } + + /* Create a mDNS packet to send */ + cookie = pico_mdns_cookie_create(qtree, antree, artree, + PICO_MDNS_PROBE_COUNT, + PICO_MDNS_PACKET_TYPE_PROBE, + callback, arg); + if (!cookie) { + mdns_dbg("Cookie_create returned NULL @ probe()!\n"); + PICO_DNS_QTREE_DESTROY(&qtree); + PICO_MDNS_RTREE_DESTROY(&antree); + return -1; + } + + /* Add the probe cookie to the cookie tree */ + if (pico_tree_insert(&Cookies, cookie)) { + pico_mdns_cookie_delete(&cookie); + return -1; + } + + /* When the host is ready to send his probe query he SHOULD delay it's + transmission with a randomly chosen time between 0 and 250 ms. */ + cookie->send_timer = pico_timer_add(pico_rand() % 250, + pico_mdns_send_probe_packet, + (void *)cookie); + mdns_dbg("DONE - Started probing.\n"); + + return 0; +} + +/* MARK: API functions */ + +/* **************************************************************************** + * Claim or reclaim all the mDNS records contain in a tree in one single call + * + * @param rtree mDNS record tree with records to claim + * @param reclaim Whether or not the records in tree should be reclaimed. + * @param callback Callback to call when all records are properly registered + * @return 0 When claiming didn't horribly fail. + * ****************************************************************************/ +static int +pico_mdns_claim_generic( pico_mdns_rtree rtree, + uint8_t reclaim, + void (*callback)(pico_mdns_rtree *, + char *, + void *), + void *arg ) +{ + /* Check if arguments are passed correctly */ + if (!callback) { + mdns_dbg("NULL pointers passed to 'pico_mdns_claim()'!\n"); + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* Check if module is initialised */ + if (!mdns_sock_ipv4) { + mdns_dbg("Socket not initialised, did you call 'pico_mdns_init()'?\n"); + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* 1.) Appending records to 'my records' */ + pico_mdns_my_records_add(&rtree, reclaim); + + /* 2a.) Try to probe any records */ + pico_mdns_probe(callback, arg); + + /* 2b.) Try to announce any records */ + pico_mdns_announce(callback, arg); + + return 0; +} + +/* **************************************************************************** + * Claim all different mDNS records in a tree in a single API-call. All records + * in tree are called in a single new claim-session. + * + * @param rtree mDNS record tree with records to claim + * @param callback Callback to call when all record are properly claimed. + * @return 0 When claiming didn't horribly fail. + * ****************************************************************************/ +int +pico_mdns_claim( pico_mdns_rtree rtree, + void (*callback)(pico_mdns_rtree *, + char *, + void *), + void *arg ) +{ + return pico_mdns_claim_generic(rtree, 0, callback, arg); +} + +/* **************************************************************************** + * Reclaim records when a conflict occurred, claim-session will stay the same + * as the session in which the conflict occurred. + * + * @param rtree mDNS record tree with records to claim + * @param callback Callback to call when all record are properly claimed. + * @return 0 When claiming didn't horribly fail. + * ****************************************************************************/ +static int +pico_mdns_reclaim( pico_mdns_rtree rtree, + void (*callback)(pico_mdns_rtree *, + char *, + void *), + void *arg ) +{ + return pico_mdns_claim_generic(rtree, 1, callback, arg); +} + +/* **************************************************************************** + * Sets the hostname for this machine. Claims automatically a unique A record + * with the IPv4-address of this host. The hostname won't be set directly when + * this functions returns, but only if the claiming of the unique record succ- + * eeded. Init-callback will be called when the hostname-record is successfully + * registered. + * + * @param url URL to set the hostname to. + * @param arg Argument to pass to the init-callback. + * @return 0 when the host started registering the hostname-record successfully, + * Returns something else when it didn't succeeded. + * ****************************************************************************/ +int +pico_mdns_set_hostname( const char *url, void *arg ) +{ + PICO_MDNS_RTREE_DECLARE(rtree); + struct pico_mdns_record *record = NULL; + + /* Check params */ + if (!url) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* Check if module is initialised */ + if (!mdns_sock_ipv4) { + mdns_dbg("mDNS socket not initialised, did you call 'pico_mdns_init()'?\n"); + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* Create an A record for hostname */ + record = pico_mdns_record_create(url, + &(mdns_sock_ipv4->local_addr.ip4.addr), + PICO_SIZE_IP4, PICO_DNS_TYPE_A, + PICO_MDNS_DEFAULT_TTL, + (PICO_MDNS_RECORD_UNIQUE | + PICO_MDNS_RECORD_HOSTNAME)); + if (!record) { + mdns_dbg("Could not create A record for hostname %s!\n", + strerror(pico_err)); + return -1; + } + + /* TODO: Create IPv6 record */ + /* TODO: Create a reverse resolution record */ + + /* Try to claim the record */ + if (pico_tree_insert(&rtree, record)) { + pico_mdns_record_delete((void **)&record); + return -1; + } + + if (pico_mdns_claim(rtree, init_callback, arg)) { + mdns_dbg("Could not claim record for hostname %s!\n", url); + PICO_MDNS_RTREE_DESTROY(&rtree); + return -1; + } + pico_tree_destroy(&rtree, NULL); + + return 0; +} + +/* **************************************************************************** + * Get the hostname for this machine. + * + * @return Returns the hostname for this machine when the module is initialised + * Returns NULL when the module is not initialised. + * ****************************************************************************/ +const char * +pico_mdns_get_hostname( void ) +{ + /* Check if module is initialised */ + if (!mdns_sock_ipv4) { + mdns_dbg("mDNS socket not initialised, did you call 'pico_mdns_init()'?\n"); + pico_err = PICO_ERR_EINVAL; + return NULL; + } + + return (const char *)_hostname; +} + +/* **************************************************************************** + * Initialises the entire mDNS-module and sets the hostname for this machine. + * Sets up the global mDNS socket properly and calls callback when succeeded. + * Only when the module is properly initialised records can be registered on + * the module. + * + * @param hostname_url URL to set the hostname to. + * @param address IPv4-address of this host to bind to. + * @param callback Callback to call when the hostname is registered and + * also the global mDNS module callback. Gets called when + * Passive conflicts occur, so changes in records can be + * tracked in this callback. + * @param arg Argument to pass to the init-callback. + * @return 0 when the module is properly initialised and the host started regis- + * tering the hostname. Returns something else went the host failed + * initialising the module or registering the hostname. + * ****************************************************************************/ +int +pico_mdns_init( const char *hostname, + struct pico_ip4 address, + void (*callback)(pico_mdns_rtree *, + char *, + void *), + void *arg ) +{ + struct pico_ip_mreq mreq4; + uint16_t proto4 = PICO_PROTO_IPV4, port = 0, loop = 0, ttl = 255; + + /* Initialise port */ + port = short_be(mdns_port); + + /* Check callback parameter */ + if(!callback || !hostname) { + mdns_dbg("No callback function supplied!\n"); + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* Open global IPv4 mDNS socket */ + mdns_sock_ipv4 = pico_socket_open(proto4, PICO_PROTO_UDP, &pico_mdns_event4); + if(!mdns_sock_ipv4) { + mdns_dbg("pico_socket_open returned NULL-ptr...\n"); + return -1; + } + + /* Convert the mDNS IPv4 destination address to struct */ + if(pico_string_to_ipv4(PICO_MDNS_DEST_ADDR4, &mreq4.mcast_group_addr.ip4.addr)) { + mdns_dbg("String to IPv4 error\n"); + return -1; + } + + /* Receive data on any network interface */ + mreq4.mcast_link_addr.ip4 = inaddr_any; + + /* Don't want the multicast data to be looped back to the host */ + if(pico_socket_setoption(mdns_sock_ipv4, PICO_IP_MULTICAST_LOOP, &loop)) { + mdns_dbg("socket_setoption PICO_IP_MULTICAST_LOOP failed\n"); + return -1; + } + + /* Tell the stack we're interested in this particular multicast group */ + if(pico_socket_setoption(mdns_sock_ipv4, PICO_IP_ADD_MEMBERSHIP, &mreq4)) { + mdns_dbg("socket_setoption PICO_IP_ADD_MEMBERSHIP failed\n"); + return -1; + } + + /* RFC: + * All multicast responses (including answers sent via unicast) SHOULD + * be send with IP TTL set to 255 for backward-compatibility reasons + */ + if(pico_socket_setoption(mdns_sock_ipv4, PICO_IP_MULTICAST_TTL, &ttl)) { + mdns_dbg("socket_setoption PICO_IP_MULTICAST_TTL failed\n"); + return -1; + } + + /* Bind to mDNS port */ + if (pico_socket_bind(mdns_sock_ipv4, (void *)&address, &port)) { + mdns_dbg("Bind error!\n"); + return -1; + } + + /* Set the global init callback variable */ + init_callback = callback; + pico_timer_add(PICO_MDNS_RR_TTL_TICK, pico_mdns_tick, NULL); + + /* Set the hostname eventually */ + return pico_mdns_set_hostname(hostname, arg); +} + +#endif /* PICO_SUPPORT_MDNS */ diff --git a/ext/picotcp/modules/pico_mdns.h b/ext/picotcp/modules/pico_mdns.h new file mode 100644 index 0000000..d4e6a4e --- /dev/null +++ b/ext/picotcp/modules/pico_mdns.h @@ -0,0 +1,185 @@ +/* **************************************************************************** + * PicoTCP. Copyright (c) 2014 TASS Belgium NV. Some rights reserved. + * See LICENSE and COPYING for usage. + * . + * Author: Toon Stegen, Jelle De Vleeschouwer + * ****************************************************************************/ +#ifndef INCLUDE_PICO_MDNS +#define INCLUDE_PICO_MDNS + +#include "pico_dns_common.h" +#include "pico_tree.h" +#include "pico_ipv4.h" + +/* ********************************* CONFIG ***********************************/ +#define PICO_MDNS_PROBE_UNICAST 1 /* Probe queries as QU-questions */ +#define PICO_MDNS_CONTINUOUS_REFRESH 0 /* Continuously update cache */ +#define PICO_MDNS_ALLOW_CACHING 1 /* Enable caching on this host */ +#define PICO_MDNS_DEFAULT_TTL 120 /* Default TTL of mDNS records */ +#define PICO_MDNS_SERVICE_TTL 120 /* Default TTL of SRV/TXT/PTR/NSEC */ +#define PICO_MDNS_PROBE_COUNT 4 /* Amount of probes to send */ +#define PICO_MDNS_ANNOUNCEMENT_COUNT 3 /* Amount of announcements to send */ +/* ****************************************************************************/ + +#define PICO_MDNS_DEST_ADDR4 "224.0.0.251" + +/* To make mDNS records unique or shared records */ +#define PICO_MDNS_RECORD_UNIQUE 0x00u +#define PICO_MDNS_RECORD_SHARED 0x01u + +/* Flag to check for when records are returned, to determine the hostname */ +#define PICO_MDNS_RECORD_HOSTNAME 0x02u +#define IS_HOSTNAME_RECORD(x) \ + (((x)->flags) & PICO_MDNS_RECORD_HOSTNAME) ? (1) : (0) + +/* --- MDNS resource record --- */ +struct pico_mdns_record +{ + struct pico_dns_record *record; /* DNS Resource Record */ + uint32_t current_ttl; /* Current TTL */ + uint8_t flags; /* Resource Record flags */ + uint8_t claim_id; /* Claim ID number */ +}; + +/* **************************************************************************** + * Compares 2 mDNS records by type, name AND rdata for a truly unique result + * + * @param ra mDNS record A + * @param rb mDNS record B + * @return 0 when records are equal, returns difference when they're not. + * ****************************************************************************/ +int +pico_mdns_record_cmp( void *a, void *b ); + +/* **************************************************************************** + * Deletes a single mDNS resource record. + * + * @param record Void-pointer to mDNS Resource Record. Can be used with pico_- + * tree-destroy. + * @return Returns 0 on success, something else on failure. + * ****************************************************************************/ +int +pico_mdns_record_delete( void **record ); + +/* **************************************************************************** + * Creates a single standalone mDNS resource record with given name, type and + * data to register on the network. + * + * @param url DNS rrecord name in URL format. Will be converted to DNS + * name notation format. + * @param _rdata Memory buffer with data to insert in the resource record. If + * data of record should contain a DNS name, the name in the + * databuffer needs to be in URL-format. + * @param datalen The exact length in bytes of the _rdata-buffer. If data of + * record should contain a DNS name, datalen needs to be + * pico_dns_strlen(_rdata). + * @param rtype DNS type of the resource record to be. + * @param rclass DNS class of the resource record to be. + * @param rttl DNS ttl of the resource record to be. + * @param flags You can specify if the mDNS record should be a shared record + * rather than a unique record. + * @return Pointer to newly created mDNS resource record. + * ****************************************************************************/ +struct pico_mdns_record * +pico_mdns_record_create( const char *url, + void *_rdata, + uint16_t datalen, + uint16_t rtype, + uint32_t rttl, + uint8_t flags ); + + + +/* **************************************************************************** + * Definition of DNS record tree + * ****************************************************************************/ +typedef struct pico_tree pico_mdns_rtree; +#define PICO_MDNS_RTREE_DECLARE(name) \ + pico_mdns_rtree (name) = {&LEAF, pico_mdns_record_cmp} +#define PICO_MDNS_RTREE_DESTROY(rtree) \ + pico_tree_destroy((rtree), pico_mdns_record_delete) +#define PICO_MDNS_RTREE_ADD(tree, record) \ + pico_tree_insert((tree), (record)) + +/* **************************************************************************** + * API-call to query a record with a certain URL and type. First checks the + * Cache for this record. If no cache-entry is found, a query will be sent on + * the wire for this record. + * + * @param url URL to query for. + * @param type DNS type top query for. + * @param callback Callback to call when records are found for the query. + * @return 0 when query is correctly parsed, something else on failure. + * ****************************************************************************/ +int +pico_mdns_getrecord( const char *url, uint16_t type, + void (*callback)(pico_mdns_rtree *, + char *, + void *), + void *arg ); + +/* **************************************************************************** + * Claim all different mDNS records in a tree in a single API-call. All records + * in tree are called in a single new claim-session. + * + * @param rtree mDNS record tree with records to claim + * @param callback Callback to call when all record are properly claimed. + * @return 0 When claiming didn't horribly fail. + * ****************************************************************************/ +int +pico_mdns_claim( pico_mdns_rtree record_tree, + void (*callback)(pico_mdns_rtree *, + char *, + void *), + void *arg ); + +/* **************************************************************************** + * Sets the hostname for this machine. Claims automatically a unique A record + * with the IPv4-address of this host. The hostname won't be set directly when + * this functions returns, but only if the claiming of the unique record succ- + * eeded. Init-callback will be called when the hostname-record is successfully + * registered. + * + * @param url URL to set the hostname to. + * @param arg Argument to pass to the init-callback. + * @return 0 when the host started registering the hostname-record successfully, + * Returns something else when it didn't succeeded. + * ****************************************************************************/ +int +pico_mdns_set_hostname( const char *url, void *arg ); + +/* **************************************************************************** + * Get the current hostname for this machine. + * + * @return Returns the hostname for this machine when the module is initialised + * Returns NULL when the module is not initialised. + * ****************************************************************************/ +const char * +pico_mdns_get_hostname( void ); + +/* **************************************************************************** + * Initialises the entire mDNS-module and sets the hostname for this machine. + * Sets up the global mDNS socket properly and calls callback when succeeded. + * Only when the module is properly initialised records can be registered on + * the module. + * + * @param hostname URL to set the hostname to. + * @param address IPv4-address of this host to bind to. + * @param callback Callback to call when the hostname is registered and + * also the global mDNS module callback. Gets called when + * Passive conflicts occur, so changes in records can be + * tracked in this callback. + * @param arg Argument to pass to the init-callback. + * @return 0 when the module is properly initialised and the host started regis- + * tering the hostname. Returns something else went the host failed + * initialising the module or registering the hostname. + * ****************************************************************************/ +int +pico_mdns_init( const char *hostname, + struct pico_ip4 address, + void (*callback)(pico_mdns_rtree *, + char *, + void *), + void *arg ); + +#endif /* _INCLUDE_PICO_MDNS */ diff --git a/ext/picotcp/modules/pico_mld.c b/ext/picotcp/modules/pico_mld.c new file mode 100644 index 0000000..67dd11f --- /dev/null +++ b/ext/picotcp/modules/pico_mld.c @@ -0,0 +1,1179 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. + See LICENSE and COPYING for usage. + + RFC 2710 3019 3590 3810 4604 6636 + + Authors: Roel Postelmans + *********************************************************************/ + +#include "pico_stack.h" +#include "pico_ipv6.h" +#include "pico_mld.h" +#include "pico_config.h" +#include "pico_eth.h" +#include "pico_addressing.h" +#include "pico_frame.h" +#include "pico_tree.h" +#include "pico_device.h" +#include "pico_socket.h" +#include "pico_icmp6.h" +#include "pico_dns_client.h" +#include "pico_mld.h" +#include "pico_constants.h" + +#if defined(PICO_SUPPORT_MLD) && defined(PICO_SUPPORT_IPV6) && defined(PICO_SUPPORT_MCAST) + +#define mld_dbg(...) do {} while(0) +/* MLD groups */ +#define MLD_ALL_HOST_GROUP "FF01:0:0:0:0:0:0:1" +#define MLD_ALL_ROUTER_GROUP "FF01:0:0:0:0:0:0:2" +#define MLDV2_ALL_ROUTER_GROUP "FF02:0:0:0:0:0:0:16" +#define MLD_ROUTER_ALERT_LEN (8) + +uint8_t pico_mld_flag = 0; + +PACKED_STRUCT_DEF mld_message { + uint8_t type; + uint8_t code; + uint16_t crc; + uint16_t max_resp_delay; + uint16_t reserved; + struct pico_ip6 mcast_group; +}; +PACKED_STRUCT_DEF mldv2_group_record { + uint8_t type; + uint8_t aux; + uint16_t nbr_src; + struct pico_ip6 mcast_group; + struct pico_ip6 src[0]; +}; +PACKED_STRUCT_DEF mldv2_report { + uint8_t type; + uint8_t res; + uint16_t crc; + uint16_t res1; + uint16_t nbr_gr; + struct mldv2_group_record record[0]; +}; +PACKED_STRUCT_DEF mldv2_query { + uint8_t type; + uint8_t code; + uint16_t crc; + uint16_t max_resp_delay; + uint16_t res; + struct pico_ip6 mcast_group; + uint8_t rsq; + uint8_t qqic; + uint16_t nbr_src; + struct pico_ip6 source_addr[0]; +}; +typedef int (*mld_callback) (struct mld_parameters *); +static int pico_mld_process_event(struct mld_parameters *p); +static struct mld_parameters *pico_mld_find_parameter(struct pico_ip6 *mcast_link, struct pico_ip6 *mcast_group); + +static uint8_t *pico_mld_fill_hopbyhop(struct pico_ipv6_hbhoption *hbh) { + uint8_t *p; + if(hbh == NULL) + return NULL; + hbh->type = PICO_PROTO_ICMP6; + hbh->len=0; + // ROUTER ALERT, RFC2711 + p = (uint8_t *)hbh + sizeof(struct pico_ipv6_hbhoption); + *(p++) = PICO_IPV6_EXTHDR_OPT_ROUTER_ALERT; + *(p++) = PICO_IPV6_EXTHDR_OPT_ROUTER_ALERT_DATALEN; + *(p++) = 0; + *(p++) = 0; + //PadN allignment with N=2 + *(p++) = 1; + *(p++) = 0; /* N-2 */ + return p; +} +static int pico_mld_check_hopbyhop(struct pico_ipv6_hbhoption *hbh) { + uint8_t options[8] = { PICO_PROTO_ICMP6, 0, PICO_IPV6_EXTHDR_OPT_ROUTER_ALERT,\ + PICO_IPV6_EXTHDR_OPT_ROUTER_ALERT_DATALEN,0,0,1,0 }; + int i; + uint8_t *p; + if(hbh == NULL) + return -1; + if(hbh->type != options[0] || hbh->len != options[1]) + return -1; + + p = (uint8_t *)hbh + sizeof(struct pico_ipv6_hbhoption); + for(i=0; itype < b->type) + return -1; + + if (a->type > b->type) + return 1; + + return 0; +} +static inline int mldt_group_compare(struct mld_timer *a, struct mld_timer *b) { + return pico_ipv6_compare(&a->mcast_group, &b->mcast_group); +} + +static inline int mldt_link_compare(struct mld_timer *a, struct mld_timer *b) { + return pico_ipv6_compare(&a->mcast_link, &b->mcast_link); +} +static int mld_timer_cmp(void *ka, void *kb) { + struct mld_timer *a = ka, *b = kb; + int cmp = mldt_type_compare(a, b); + if (cmp) + return cmp; + + cmp = mldt_group_compare(a, b); + if (cmp) + return cmp; + + return mldt_link_compare(a, b); +} +static void pico_mld_report_expired(struct mld_timer *t) { + struct mld_parameters *p = NULL; + + p = pico_mld_find_parameter(&t->mcast_link, &t->mcast_group); + if (!p) + return; + + p->event = MLD_EVENT_TIMER_EXPIRED; + pico_mld_process_event(p); +} +PICO_TREE_DECLARE(MLDTimers, mld_timer_cmp); +static void pico_mld_v1querier_expired(struct mld_timer *t) +{ + struct pico_ipv6_link *link = NULL; + struct pico_tree_node *index = NULL, *_tmp = NULL; + + link = pico_ipv6_link_by_dev(t->f->dev); + if (!link) + return; + + /* When changing compatibility mode, cancel all pending response + * and retransmission timers. + */ + pico_tree_foreach_safe(index, &MLDTimers, _tmp) + { + ((struct mld_timer *)index->keyValue)->stopped = MLD_TIMER_STOPPED; + pico_tree_delete(&MLDTimers, index->keyValue); + } + mld_dbg("MLD: switch to compatibility mode MLDv2\n"); + link->mcast_compatibility = PICO_MLDV2; + return; +} + + +static inline int mldparm_group_compare(struct mld_parameters *a, struct mld_parameters *b) { + return pico_ipv6_compare(&a->mcast_group, &b->mcast_group); +} +static inline int mldparm_link_compare(struct mld_parameters *a, struct mld_parameters *b) { + return pico_ipv6_compare(&a->mcast_link, &b->mcast_link); +} +static int mld_parameters_cmp(void *ka, void *kb) { + struct mld_parameters *a = ka, *b = kb; + int cmp = mldparm_group_compare(a, b); + if (cmp) + return cmp; + + return mldparm_link_compare(a, b); +} + +PICO_TREE_DECLARE(MLDParameters, mld_parameters_cmp); + +static int pico_mld_delete_parameter(struct mld_parameters *p) { + if (pico_tree_delete(&MLDParameters, p)) + PICO_FREE(p); + else + return -1; + return 0; +} +static void pico_mld_timer_expired(pico_time now, void *arg){ + struct mld_timer *t = NULL, *timer = NULL, test = { + 0 + }; +#ifdef PICO_DEBUG_MLD + char ipstr[PICO_IPV6_STRING] = { + 0 + }, grpstr[PICO_IPV6_STRING] = { + 0 + }; +#endif + + IGNORE_PARAMETER(now); + t = (struct mld_timer *)arg; + test.type = t->type; + test.mcast_link = t->mcast_link; + test.mcast_group = t->mcast_group; +#ifdef PICO_DEBUG_MLD + pico_ipv6_to_string(ipstr, t->mcast_link.addr); + pico_ipv6_to_string(grpstr, t->mcast_group.addr); + mld_dbg("MLD: timer expired for %s link %s type %u, delay %llu\n", grpstr, ipstr, t->type, (uint64_t) t->delay); +#endif + timer = pico_tree_findKey(&MLDTimers, &test); + if (!timer) { + return; + } + if (timer->stopped == MLD_TIMER_STOPPED) { + pico_tree_delete(&MLDTimers, timer); + PICO_FREE(t); + return; + } + if (timer->start + timer->delay < PICO_TIME_MS()) { + pico_tree_delete(&MLDTimers, timer); + if (timer->mld_callback) + timer->mld_callback(timer); + + PICO_FREE(timer); + } else { +#ifdef PICO_DEBUG_MLD + mld_dbg("MLD: restart timer for %s, delay %llu, new delay %llu\n", grpstr, t->delay, (timer->start + timer->delay) - PICO_TIME_MS()); +#endif + pico_timer_add((timer->start + timer->delay) - PICO_TIME_MS(), &pico_mld_timer_expired, timer); + } + return; +} + +static int pico_mld_timer_reset(struct mld_timer *t) { + struct mld_timer *timer = NULL, test = { + 0 + }; +#ifdef PICO_DEBUG_MLD + char grpstr[PICO_IPV6_STRING] = { + 0 + }; + pico_ipv6_to_string(grpstr, t->mcast_group.addr); + mld_dbg("MLD: reset timer for %s, delay %llu\n", grpstr, t->delay); +#endif + test.type = t->type; + test.mcast_link = t->mcast_link; + test.mcast_group = t->mcast_group; + timer = pico_tree_findKey(&MLDTimers, &test); + if (!timer) + return -1; + + *timer = *t; + timer->start = PICO_TIME_MS(); + return 0; +} + +static int pico_mld_timer_start(struct mld_timer *t) { + struct mld_timer *timer = NULL, test = { + 0 + }; +#ifdef PICO_DEBUG_MLD + char ipstr[PICO_IPV6_STRING] = { + 0 + }, grpstr[PICO_IPV6_STRING] = { + 0 + }; + pico_ipv6_to_string(ipstr, t->mcast_link.addr); + pico_ipv6_to_string(grpstr, t->mcast_group.addr); + mld_dbg("MLD: start timer for %s link %s type %u, delay %llu\n", grpstr, ipstr, t->type, t->delay); +#endif + test.type = t->type; + test.mcast_link = t->mcast_link; + test.mcast_group = t->mcast_group; + timer = pico_tree_findKey(&MLDTimers, &test); + if (timer) + return pico_mld_timer_reset(t); + + timer = PICO_ZALLOC(sizeof(struct mld_timer)); + if (!timer) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + *timer = *t; + timer->start = PICO_TIME_MS(); + pico_tree_insert(&MLDTimers, timer); + pico_timer_add(timer->delay, &pico_mld_timer_expired, timer); + return 0; +} + +static int pico_mld_timer_stop(struct mld_timer *t) { + struct mld_timer *timer = NULL, test = { + 0 + }; +#ifdef PICO_DEBUG_MLD + char grpstr[PICO_IPV6_STRING] = { + 0 + }; +#endif + test.type = t->type; + test.mcast_link = t->mcast_link; + test.mcast_group = t->mcast_group; + timer = pico_tree_findKey(&MLDTimers, &test); + if (!timer) + return -1; +#ifdef PICO_DEBUG_MLD + pico_ipv6_to_string(grpstr, timer->mcast_group.addr); + mld_dbg("MLD: stop timer for %s, delay %llu\n", grpstr, timer->delay); +#endif + timer->stopped = MLD_TIMER_STOPPED; + return 0; +} + +static int pico_mld_timer_is_running(struct mld_timer *t) { + struct mld_timer *timer = NULL, test = { + 0 + }; + + test.type = t->type; + test.mcast_link = t->mcast_link; + test.mcast_group = t->mcast_group; + timer = pico_tree_findKey(&MLDTimers, &test); + if (timer) + return 1; + + return 0; +} + +static struct mld_timer *pico_mld_find_timer(uint8_t type, struct pico_ip6 *mcast_link, struct pico_ip6 *mcast_group) { + struct mld_timer test = { + 0 + }; + + test.type = type; + test.mcast_link = *mcast_link; + test.mcast_group = *mcast_group; + return pico_tree_findKey(&MLDTimers, &test); +} + +static int mld_sources_cmp(void *ka, void *kb) { + struct pico_ip6 *a = ka, *b = kb; + return pico_ipv6_compare(a, b); +} + +PICO_TREE_DECLARE(MLDAllow, mld_sources_cmp); +PICO_TREE_DECLARE(MLDBlock, mld_sources_cmp); + +static struct mld_parameters *pico_mld_find_parameter(struct pico_ip6 *mcast_link, struct pico_ip6 *mcast_group) { + struct mld_parameters test = { + 0 + }; + if (!mcast_link || !mcast_group) + return NULL; + test.mcast_link = *mcast_link; + test.mcast_group = *mcast_group; + return pico_tree_findKey(&MLDParameters, &test); +} +static int pico_mld_is_checksum_valid(struct pico_frame *f) { + if( pico_icmp6_checksum(f) == 0) + return 1; + mld_dbg("ICMP6 (MLD) : invalid checksum\n"); + return 0; +} +uint16_t pico_mld_checksum(struct pico_frame *f) { + struct pico_ipv6_pseudo_hdr pseudo; + struct pico_ipv6_hdr *ipv6_hdr = (struct pico_ipv6_hdr *)f->net_hdr; + struct mldv2_report *icmp6_hdr = (struct mldv2_report *)(f->transport_hdr + MLD_ROUTER_ALERT_LEN); + uint16_t len = (uint16_t) (f->transport_len - MLD_ROUTER_ALERT_LEN); + + pseudo.src = ipv6_hdr->src; + pseudo.dst = ipv6_hdr->dst; + pseudo.len = long_be(len); + pseudo.nxthdr = PICO_PROTO_ICMP6; + + pseudo.zero[0] = 0; + pseudo.zero[1] = 0; + pseudo.zero[2] = 0; + return pico_dualbuffer_checksum(&pseudo, sizeof(struct pico_ipv6_pseudo_hdr), icmp6_hdr, len); +} +/* RFC 3810 $8 */ +static int pico_mld_compatibility_mode(struct pico_frame *f) { + struct pico_ipv6_link *link = NULL; + struct mld_timer t = { + 0 + }; + uint16_t datalen; + struct pico_tree_node *index = NULL, *_tmp = NULL; + link = pico_ipv6_link_by_dev(f->dev); + if (!link) + return -1; + + datalen = (uint16_t)(f->buffer_len - PICO_SIZE_IP6HDR - MLD_ROUTER_ALERT_LEN); + if (f->dev->eth) { + datalen = (uint16_t)(datalen - PICO_SIZE_ETHHDR); + } + if( datalen >= 28) { + /* MLDv2 */ + t.type = MLD_TIMER_V2_QUERIER; + if (pico_mld_timer_is_running(&t)) { /* MLDv1 querier present timer still running */ + mld_dbg("Timer is already running\n"); + return -1; + } else { + link->mcast_compatibility = PICO_MLDV2; + mld_dbg("MLD Compatibility: v2\n"); + return 0; + } + } else if( datalen == 24) { + pico_tree_foreach_safe(index, &MLDTimers, _tmp) + { + ((struct mld_timer *)index->keyValue)->stopped = MLD_TIMER_STOPPED; + pico_tree_delete(&MLDTimers, index->keyValue); + } + mld_dbg("MLD: switch to compatibility mode MLDv1\n"); + link->mcast_compatibility = PICO_MLDV1; + t.type = MLD_TIMER_V1_QUERIER; + t.delay =(pico_time) ((MLD_ROBUSTNESS * link->mcast_last_query_interval) + MLD_QUERY_RESPONSE_INTERVAL) * 1000; + t.f = f; + t.mld_callback = pico_mld_v1querier_expired; + pico_mld_timer_start(&t); + } else { + /* invalid query, silently ignored */ + return -1; + } + return 0; +} + +int pico_mld_state_change(struct pico_ip6 *mcast_link, struct pico_ip6 *mcast_group, uint8_t filter_mode, struct pico_tree *_MCASTFilter, uint8_t state) { + struct mld_parameters *p = NULL; + struct pico_ip6 ipv6; + + pico_string_to_ipv6(MLD_ALL_HOST_GROUP, &ipv6.addr[0]); + + if (!memcmp(&mcast_group->addr, &ipv6, sizeof(struct pico_ip6))) + return 0; + + p = pico_mld_find_parameter(mcast_link, mcast_group); + if (!p && state == PICO_MLD_STATE_CREATE) { + p = PICO_ZALLOC(sizeof(struct mld_parameters)); + if (!p) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + if (!mcast_link || !mcast_group) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + p->state = MLD_STATE_NON_LISTENER; + p->mcast_link = *mcast_link; + p->mcast_group = *mcast_group; + pico_tree_insert(&MLDParameters, p); + } else if (!p) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + switch (state) { + case PICO_MLD_STATE_CREATE: + p->event = MLD_EVENT_START_LISTENING; + break; + + case PICO_MLD_STATE_UPDATE: + p->event = MLD_EVENT_UPDATE_GROUP; + break; + + case PICO_MLD_STATE_DELETE: + p->event = MLD_EVENT_STOP_LISTENING; + break; + default: + return -1; + } + p->filter_mode = filter_mode; + p->MCASTFilter = _MCASTFilter; + return pico_mld_process_event(p); +} +/* finite state machine caller */ +static int pico_mld_process_event(struct mld_parameters *p); + +static struct mld_parameters *pico_mld_analyse_packet(struct pico_frame *f) { + struct pico_icmp6_hdr *hdr = (struct pico_icmp6_hdr *) (f->transport_hdr+MLD_ROUTER_ALERT_LEN); + struct pico_ipv6_hdr *ipv6_hdr = (struct pico_ipv6_hdr *) f->net_hdr; + struct pico_ipv6_link *link = NULL; + struct mld_parameters *p = NULL; + struct pico_ip6 mcast_group = {{ + 0 + }}; + struct mld_message *mld_report = (struct mld_message *) hdr; + struct pico_ipv6_exthdr *hbh; + + link = pico_ipv6_link_by_dev(f->dev); + if(!link) + return NULL; + mcast_group = mld_report->mcast_group; + /* Package check */ + if(ipv6_hdr->hop != MLD_HOP_LIMIT) { + mld_dbg("MLD: Hop limit > 1, ignoring frame\n"); + return NULL; + } + hbh = (struct pico_ipv6_exthdr *) (f->transport_hdr); + if(pico_mld_check_hopbyhop((struct pico_ipv6_hbhoption *)hbh) < 0) { + mld_dbg("MLD: Router Alert option is not set\n"); + return NULL; + } + if(!pico_ipv6_is_linklocal(ipv6_hdr->src.addr) || pico_ipv6_is_unspecified(ipv6_hdr->src.addr) ) { + mld_dbg("MLD Source is invalid link-local address\n"); + return NULL; + } + /* end package check */ + p = pico_mld_find_parameter(&link->address, &mcast_group); + if(!p) { + mld_dbg("Alloc-ing MLD parameters\n"); + p = PICO_ZALLOC(sizeof(struct mld_parameters)); + if(!p) + return NULL; + p->state = MLD_STATE_NON_LISTENER; + p->mcast_link = link->address; + pico_tree_insert(&MLDParameters,p); + } + mld_dbg("Analyse package, type = %d\n", hdr->type); + switch(hdr->type) { + case PICO_MLD_QUERY: + p->max_resp_time = mld_report->max_resp_delay; + p->event = MLD_EVENT_QUERY_RECV; + break; + case PICO_MLD_REPORT: + p->event = MLD_EVENT_REPORT_RECV; + break; + case PICO_MLD_DONE: + p->event = MLD_EVENT_DONE_RECV; + break; + case PICO_MLD_REPORTV2: + p->event = MLD_EVENT_REPORT_RECV; + break; + default: + return NULL; + } + p->f = f; + return p; +} +int pico_mld_process_in(struct pico_frame *f) { + struct mld_parameters *p = NULL; + + if (!pico_mld_is_checksum_valid(f)) + goto out; + + if (pico_mld_compatibility_mode(f) < 0) + goto out; + + if((p = pico_mld_analyse_packet(f)) == NULL) + goto out; + + return pico_mld_process_event(p); +out: + mld_dbg("FRAME DISCARD\n"); + pico_frame_discard(f); + return 0; +} + + + +static int8_t pico_mld_send_done(struct mld_parameters *p, struct pico_frame *f) { + struct mld_message *report = NULL; + uint8_t report_type = PICO_MLD_DONE; + struct pico_ipv6_exthdr *hbh; + struct pico_ip6 dst = {{ + 0 + }}; +#ifdef PICO_DEBUG_MLD + char ipstr[PICO_IPV6_STRING] = { + 0 + }, grpstr[PICO_IPV6_STRING] ={ + 0 + }; +#endif + IGNORE_PARAMETER(f); + pico_string_to_ipv6(MLD_ALL_ROUTER_GROUP, &dst.addr[0]); + p->f = pico_proto_ipv6.alloc(&pico_proto_ipv6, sizeof(struct mld_message)+MLD_ROUTER_ALERT_LEN); + p->f->dev = pico_ipv6_link_find(&p->mcast_link); + /* p->f->len is correctly set by alloc */ + hbh = (struct pico_ipv6_exthdr *)(p->f->transport_hdr); + report = (struct mld_message *)(pico_mld_fill_hopbyhop((struct pico_ipv6_hbhoption*)hbh)); + if(!report) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + report->type = report_type; + report->max_resp_delay = 0; + report->mcast_group = p->mcast_group; + + report->crc = 0; + //Checksum done in ipv6 module, no need to do it twice + //report->crc = short_be(pico_icmp6_checksum(p->f)); +#ifdef PICO_DEBUG_MLD + pico_ipv6_to_string(ipstr, dst.addr); + pico_ipv6_to_string(grpstr, mcast_group.addr); + mld_dbg("MLD: send membership done on group %s to %s\n", grpstr, ipstr); +#endif + pico_ipv6_frame_push(p->f, NULL, &dst, 0,0); + return 0; +} +static int pico_mld_send_report(struct mld_parameters *p, struct pico_frame *f) { + struct pico_ip6 dst = {{ + 0 + }}; + struct pico_ip6 mcast_group = {{ + 0 + }}; +#ifdef PICO_DEBUG_MLD + char ipstr[PICO_IPV6_STRING] = { + 0 + }, grpstr[PICO_IPV6_STRING] ={ + 0 + }; +#endif + struct pico_ipv6_link *link = NULL; + link = pico_ipv6_link_get(&p->mcast_link); + if (!link) + return -1; + + mcast_group = p->mcast_group; + switch (link->mcast_compatibility) { + case PICO_MLDV1: + if (p->event == MLD_EVENT_STOP_LISTENING) + pico_string_to_ipv6(MLD_ALL_ROUTER_GROUP, &dst.addr[0]); + else + dst = mcast_group; + break; + case PICO_MLDV2: + pico_string_to_ipv6(MLDV2_ALL_ROUTER_GROUP, &dst.addr[0]); + break; + default: + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; + } +#ifdef PICO_DEBUG_MLD + pico_ipv6_to_string(ipstr, dst.addr); + pico_ipv6_to_string(grpstr, mcast_group.addr); + mld_dbg("MLD: send membership report on group %s to %s\n", grpstr, ipstr); +#endif + pico_ipv6_frame_push(f, NULL, &dst, 0,0); + return 0; +} + +static int8_t pico_mld_generate_report(struct mld_parameters *p) { + struct pico_ipv6_link *link = NULL; + uint8_t i = 0; + link = pico_ipv6_link_get(&p->mcast_link); + if (!link) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + if( !pico_ipv6_is_multicast(p->mcast_group.addr) ) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + switch (link->mcast_compatibility) { + + case PICO_MLDV1: { + struct mld_message *report = NULL; + uint8_t report_type = PICO_MLD_REPORT; + struct pico_ipv6_exthdr *hbh; + p->f = pico_proto_ipv6.alloc(&pico_proto_ipv6, sizeof(struct mld_message)+MLD_ROUTER_ALERT_LEN ); + p->f->dev = pico_ipv6_link_find(&p->mcast_link); + /* p->f->len is correctly set by alloc */ + + hbh = (struct pico_ipv6_exthdr *)(p->f->transport_hdr); + report = (struct mld_message *)(pico_mld_fill_hopbyhop((struct pico_ipv6_hbhoption *)hbh)); + report->type = report_type; + report->max_resp_delay = MLD_DEFAULT_MAX_RESPONSE_TIME; + report->mcast_group = p->mcast_group; + + report->crc = 0; + //Checksum done in ipv6 module, no need to do it twice + //report->crc = short_be(pico_icmp6_checksum(p->f)); + break; + } + case PICO_MLDV2: { + struct mldv2_report *report = NULL; + struct mldv2_group_record *record = NULL; + struct pico_ipv6_mcast_group *g = NULL, test = { + 0 + }; + struct pico_tree_node *index = NULL, *_tmp = NULL; + struct pico_tree *MLDFilter = NULL; + struct pico_ip6 *source = NULL; + struct pico_ipv6_hbhoption *hbh; + uint8_t record_type = 0; + uint8_t sources = 0; + uint16_t len = 0; + test.mcast_addr = p->mcast_group; + g = pico_tree_findKey(link->MCASTGroups, &test); + if (!g) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + /* "non-existent" state of filter mode INCLUDE and empty source list */ + if (p->event == MLD_EVENT_DELETE_GROUP) { + p->filter_mode = PICO_IP_MULTICAST_INCLUDE; + p->MCASTFilter = NULL; + } + if (p->event == MLD_EVENT_QUERY_RECV) + goto mld2_report; + /* cleanup filters */ + pico_tree_foreach_safe(index, &MLDAllow, _tmp) + { + pico_tree_delete(&MLDAllow, index->keyValue); + } + pico_tree_foreach_safe(index, &MLDBlock, _tmp) + { + pico_tree_delete(&MLDBlock, index->keyValue); + } + switch (g->filter_mode) { + + case PICO_IP_MULTICAST_INCLUDE: + switch (p->filter_mode) { + case PICO_IP_MULTICAST_INCLUDE: + /* all ADD_SOURCE_MEMBERSHIP had an equivalent DROP_SOURCE_MEMBERSHIP */ + if (p->event == MLD_EVENT_DELETE_GROUP) { + /* TO_IN (B) */ + record_type = MLD_CHANGE_TO_INCLUDE_MODE; + MLDFilter = &MLDAllow; + if (p->MCASTFilter) { + pico_tree_foreach(index, p->MCASTFilter) /* B */ + { + pico_tree_insert(&MLDAllow, index->keyValue); + sources++; + } + } /* else { MLDAllow stays empty } */ + + break; + } + + /* ALLOW (B-A) */ + /* if event is CREATE A will be empty, thus only ALLOW (B-A) has sense */ + if (p->event == MLD_EVENT_CREATE_GROUP) /* first ADD_SOURCE_MEMBERSHIP */ + record_type = MLD_CHANGE_TO_INCLUDE_MODE; + else + record_type = MLD_ALLOW_NEW_SOURCES; + + MLDFilter = &MLDAllow; + pico_tree_foreach(index, p->MCASTFilter) /* B */ + { + pico_tree_insert(&MLDAllow, index->keyValue); + sources++; + } + pico_tree_foreach(index, &g->MCASTSources) /* A */ + { + source = pico_tree_findKey(&MLDAllow, index->keyValue); + if (source) { + pico_tree_delete(&MLDAllow, source); + sources--; + } + } + if (!pico_tree_empty(&MLDAllow)) /* record type is ALLOW */ + break; + + /* BLOCK (A-B) */ + record_type = MLD_BLOCK_OLD_SOURCES; + MLDFilter = &MLDBlock; + pico_tree_foreach(index, &g->MCASTSources) /* A */ + { + pico_tree_insert(&MLDBlock, index->keyValue); + sources++; + } + pico_tree_foreach(index, p->MCASTFilter) /* B */ + { + source = pico_tree_findKey(&MLDBlock, index->keyValue); + if (source) { + pico_tree_delete(&MLDBlock, source); + sources--; + } + } + if (!pico_tree_empty(&MLDBlock)) /* record type is BLOCK */ + break; + + /* ALLOW (B-A) and BLOCK (A-B) are empty: do not send report */ + p->f = NULL; + return 0; + case PICO_IP_MULTICAST_EXCLUDE: + /* TO_EX (B) */ + record_type = MLD_CHANGE_TO_EXCLUDE_MODE; + MLDFilter = &MLDBlock; + pico_tree_foreach(index, p->MCASTFilter) /* B */ + { + pico_tree_insert(&MLDBlock, index->keyValue); + sources++; + } + break; + default: + pico_err = PICO_ERR_EINVAL; + return -1; + } + break; + case PICO_IP_MULTICAST_EXCLUDE: + switch (p->filter_mode) { + case PICO_IP_MULTICAST_INCLUDE: + /* TO_IN (B) */ + record_type = MLD_CHANGE_TO_INCLUDE_MODE; + MLDFilter = &MLDAllow; + if (p->MCASTFilter) { + pico_tree_foreach(index, p->MCASTFilter) /* B */ + { + pico_tree_insert(&MLDAllow, index->keyValue); + sources++; + } + } /* else { MLDAllow stays empty } */ + + break; + case PICO_IP_MULTICAST_EXCLUDE: + /* BLOCK (B-A) */ + record_type = MLD_BLOCK_OLD_SOURCES; + MLDFilter = &MLDBlock; + pico_tree_foreach(index, p->MCASTFilter) + { + pico_tree_insert(&MLDBlock, index->keyValue); + sources++; + } + pico_tree_foreach(index, &g->MCASTSources) /* A */ + { + source = pico_tree_findKey(&MLDBlock, index->keyValue); /* B */ + if (source) { + pico_tree_delete(&MLDBlock, source); + sources--; + } + } + if (!pico_tree_empty(&MLDBlock)) /* record type is BLOCK */ + break; + /* ALLOW (A-B) */ + record_type = MLD_ALLOW_NEW_SOURCES; + MLDFilter = &MLDAllow; + pico_tree_foreach(index, &g->MCASTSources) + { + pico_tree_insert(&MLDAllow, index->keyValue); + sources++; + } + pico_tree_foreach(index, p->MCASTFilter) /* B */ + { + source = pico_tree_findKey(&MLDAllow, index->keyValue); /* A */ + if (source) { + pico_tree_delete(&MLDAllow, source); + sources--; + } + } + if (!pico_tree_empty(&MLDAllow)) /* record type is ALLOW */ + break; + /* BLOCK (B-A) and ALLOW (A-B) are empty: do not send report */ + p->f = NULL; + return 0; + default: + pico_err = PICO_ERR_EINVAL; + return -1; + } + break; + default: + pico_err = PICO_ERR_EINVAL; + return -1; + } +mld2_report: + /* RFC3810 $5.1.10 */ + if(sources > MLD_MAX_SOURCES) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + len = (uint16_t)(sizeof(struct mldv2_report) + sizeof(struct mldv2_group_record) \ + + (sources * sizeof(struct pico_ip6))+MLD_ROUTER_ALERT_LEN); + + p->f = pico_proto_ipv6.alloc(&pico_proto_ipv6, len); + p->f->dev = pico_ipv6_link_find(&p->mcast_link); + /* p->f->len is correctly set by alloc */ + + hbh = (struct pico_ipv6_hbhoption *) p->f->transport_hdr; + report = (struct mldv2_report *)(pico_mld_fill_hopbyhop(hbh)); + report->type = PICO_MLD_REPORTV2; + report->res = 0; + report->crc = 0; + report->res1 = 0; + report->nbr_gr = short_be(1); + + record = &report->record[0]; + record->type = record_type; + record->aux = 0; + record->nbr_src = short_be(sources); + record->mcast_group = p->mcast_group; + if (MLDFilter && !pico_tree_empty(MLDFilter)) { + i = 0; + pico_tree_foreach(index, MLDFilter) + { + record->src[i] = (*(struct pico_ip6 *)index->keyValue); + i++; + } + } + if(i != sources) + return -1; + //Checksum done in ipv6 module, no need to do it twice + //report->crc= short_be(pico_mld_checksum(p->f)); + break; + } + default: + pico_err = PICO_ERR_EINVAL; + return -1; + } + + return 0; +} +/* stop timer, send done if flag set */ +static int mld_stsdifs(struct mld_parameters *p) { + struct mld_timer t = { + 0 + }; + struct pico_ipv6_link *link = NULL; + struct pico_frame *copy_frame = NULL; + link = pico_ipv6_link_get(&p->mcast_link); + if (!link) + return -1; + + mld_dbg("MLD: event = stop listening | action = stop timer, send done if flag set\n"); + + t.type = MLD_TIMER_GROUP_REPORT; + t.mcast_link = p->mcast_link; + t.mcast_group = p->mcast_group; + if (pico_mld_timer_stop(&t) < 0) + return -1; + switch(link->mcast_compatibility){ + case PICO_MLDV2: + if (pico_mld_generate_report(p) < 0) { + return -1; + } + copy_frame = pico_frame_copy(p->f); + if (!copy_frame) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + if (pico_mld_send_report(p, copy_frame) < 0) { + return -1; + } + break; + case PICO_MLDV1: + /* Send done if flag is set */ + if (pico_mld_flag && pico_mld_send_done(p, p->f) < 0) + return -1; + break; + } + + pico_mld_delete_parameter(p); + mld_dbg("MLD: new state = Non-Listener\n"); + return 0; +} +/* send report, set flag, start timer */ +static int mld_srsfst(struct mld_parameters *p) { + struct mld_timer t = { + 0 + }; + struct pico_frame *copy_frame = NULL; + mld_dbg("MLD: event = start listening | action = send report, set flag, start timer\n"); + + p->last_host = MLD_HOST_LAST; + if (pico_mld_generate_report(p) < 0) + return -1; + + if (!p->f) + return 0; + copy_frame = pico_frame_copy(p->f); + if (!copy_frame) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + if (pico_mld_send_report(p, copy_frame) < 0) + return -1; + + t.type = MLD_TIMER_GROUP_REPORT; + t.mcast_link = p->mcast_link; + t.mcast_group = p->mcast_group; + + t.delay = (pico_rand() % (MLD_UNSOLICITED_REPORT_INTERVAL * 10000)); + t.f = p->f; + t.mld_callback = pico_mld_report_expired; + pico_mld_timer_start(&t); + pico_mld_flag = 1; + p->state = MLD_STATE_DELAYING_LISTENER; + mld_dbg("MLD: new state = Delaying Listener\n"); + return 0; +} + +/* stop timer, clear flag */ +static int mld_stcl(struct mld_parameters *p) { + struct mld_timer t = { + 0 + }; + + mld_dbg("MLD: event = report received | action = stop timer, clear flag\n"); + + t.type = MLD_TIMER_GROUP_REPORT; + t.mcast_link = p->mcast_link; + t.mcast_group = p->mcast_group; + if (pico_mld_timer_stop(&t) < 0) + return -1; + pico_mld_flag = 0; + p->last_host = MLD_HOST_NOT_LAST; + p->state = MLD_STATE_IDLE_LISTENER; + mld_dbg("MLD: new state = Idle Listener\n"); + return 0; +} +/* send report, set flag */ +static int mld_srsf(struct mld_parameters *p) { + mld_dbg("MLD: event = timer expired | action = send report, set flag\n"); + + if (pico_mld_send_report(p, p->f) < 0) + return -1; + pico_mld_flag = 1; + p->state = MLD_STATE_IDLE_LISTENER; + mld_dbg("MLD: new state = Idle Listener\n"); + return 0; +} +/* reset timer if max response time < current timer */ +static int mld_rtimrtct(struct mld_parameters *p) { + struct mld_timer *t = NULL; + uint32_t current_timer = 0; + + mld_dbg("MLD: event = query received | action = reset timer if max response time < current timer\n"); + + t = pico_mld_find_timer(MLD_TIMER_GROUP_REPORT, &p->mcast_link, &p->mcast_group); + if (!t) + return -1; + + current_timer = (uint32_t)(t->start + t->delay - PICO_TIME_MS()); + if ((p->max_resp_time * 100u) < current_timer) { /* max_resp_time in units of 1/10 seconds */ + t->delay = pico_rand() % ((1u + p->max_resp_time) * 100u); + pico_mld_timer_reset(t); + } + p->state = MLD_STATE_DELAYING_LISTENER; + mld_dbg("MLD: new state = Delaying Listener\n"); + return 0; +} +/* merge report, send report, reset timer (MLDv2 only) */ +static int mld_mrsrrt(struct mld_parameters *p) { + struct mld_timer *t = NULL; + struct pico_frame *copy_frame = NULL; + struct pico_ipv6_link *link = NULL; + mld_dbg("MLD: event = update group | action = merge report, send report, reset timer (MLDv2 only)\n"); + + link = pico_ipv6_link_get(&p->mcast_link); + if (!link) + return -1; + + if (link->mcast_compatibility != PICO_MLDV2) { + mld_dbg("MLD: no MLDv3 compatible router on network\n"); + return -1; + } + + /* XXX: merge with pending report rfc 3376 $5.1 */ + + copy_frame = pico_frame_copy(p->f); + if (!copy_frame) + return -1; + + if (pico_mld_send_report(p, copy_frame) < 0) + return -1; + + t = pico_mld_find_timer(MLD_TIMER_GROUP_REPORT, &p->mcast_link, &p->mcast_group); + if (!t) + return -1; + + t->delay = (pico_rand() % (MLD_UNSOLICITED_REPORT_INTERVAL * 10000)); + pico_mld_timer_reset(t); + + p->state = MLD_STATE_DELAYING_LISTENER; + mld_dbg("MLD: new state = delaying member\n"); + return 0; +} + +/* send report, start timer (MLDv2 only) */ +static int mld_srst(struct mld_parameters *p){ + struct mld_timer t = { + 0 + }; + struct pico_frame *copy_frame = NULL; + struct pico_ipv6_link *link = NULL; + + mld_dbg("MLD: event = update group | action = send report, start timer (MLDv2 only)\n"); + + link = pico_ipv6_link_get(&p->mcast_link); + if (!link) + return -1; + + if (link->mcast_compatibility != PICO_MLDV2) { + mld_dbg("MLD: no MLDv2 compatible router on network\n"); + return -1; + } + + if (pico_mld_generate_report(p) < 0) + return -1; + + if (!p->f) + return 0; + + copy_frame = pico_frame_copy(p->f); + if (!copy_frame) + return -1; + + if (pico_mld_send_report(p, copy_frame) < 0) + return -1; + + t.type = MLD_TIMER_GROUP_REPORT; + t.mcast_link = p->mcast_link; + t.mcast_group = p->mcast_group; + t.delay = (pico_rand() % (MLD_UNSOLICITED_REPORT_INTERVAL * 10000)); + t.f = p->f; + t.mld_callback = pico_mld_report_expired; + pico_mld_timer_start(&t); + + p->state = MLD_STATE_DELAYING_LISTENER; + mld_dbg("MLD: new state = delaying member\n"); + return 0; +} +static int mld_discard(struct mld_parameters *p) { + mld_dbg("MLD: ignore and mld_discard frame\n"); + // the frame will be discared bij the ipv6 module!!! + IGNORE_PARAMETER(p); + return 0; +} + +/* finite state machine table */ +static const mld_callback mld_state_diagram[3][6] = +{ /* event | Stop Listening | Start Listening | Update Group |Query reveive |Report receive |Timer expired */ +/* none listener*/ { mld_discard , mld_srsfst, mld_srsfst, mld_discard, mld_discard, mld_discard}, +/* idle listener */ { mld_stsdifs , mld_mrsrrt, mld_mrsrrt, mld_rtimrtct, mld_stcl, mld_srsf }, +/* delaying listener */ { mld_rtimrtct, mld_srst, mld_srst, mld_srsf, mld_stsdifs, mld_discard } +}; + +static int pico_mld_process_event(struct mld_parameters *p) { + struct pico_tree_node *index= NULL; + struct mld_parameters *_p; +#ifdef PICO_DEBUG_MLD + char ipv6[PICO_IPV6_STRING]; + pico_ipv6_to_string(ipv6, p->mcast_group.addr); + mld_dbg("MLD: process event on group address %s\n", ipv6); +#endif + if (p->event == MLD_EVENT_QUERY_RECV && p->general_query) { /* general query */ + pico_tree_foreach(index, &MLDParameters) { + _p = index->keyValue; + _p->max_resp_time = p->max_resp_time; + _p->event = MLD_EVENT_QUERY_RECV; +#ifdef PICO_DEBUG_MLD + mld_dbg("MLD: for each mcast_group = %s | state = %u\n", ipv6, _p->state); +#endif + return mld_state_diagram[_p->state][_p->event](_p); + } + } else { + mld_dbg("MLD: state = %u (0: non-listener - 1: delaying listener - 2: idle listener) event = %u\n", p->state, p->event); + return mld_state_diagram[p->state][p->event](p); + } + return 0; +} +#else +uint16_t pico_mld_checksum(struct pico_frame *f) { + IGNORE_PARAMETER(f); + return 0; +} +int pico_mld_process_in(struct pico_frame *f) { + IGNORE_PARAMETER(f); + return -1; +} + +int pico_mld_state_change(struct pico_ip6 *mcast_link, struct pico_ip6 *mcast_group, uint8_t filter_mode, struct pico_tree *_MCASTFilter, uint8_t state) { + IGNORE_PARAMETER(mcast_link); + IGNORE_PARAMETER(mcast_group); + IGNORE_PARAMETER(filter_mode); + IGNORE_PARAMETER(_MCASTFilter); + IGNORE_PARAMETER(state); + return -1; +} +#endif diff --git a/ext/picotcp/modules/pico_mld.h b/ext/picotcp/modules/pico_mld.h new file mode 100644 index 0000000..219ef4a --- /dev/null +++ b/ext/picotcp/modules/pico_mld.h @@ -0,0 +1,119 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. + See LICENSE and COPYING for usage. + + . + + Authors: Roel Postelmans + *********************************************************************/ + +#ifndef INCLUDE_PICO_MLD +#define INCLUDE_PICO_MLD + +#define PICO_MLDV1 1 +#define PICO_MLDV2 2 + +#define PICO_MLD_QUERY 130 +#define PICO_MLD_REPORT 131 +#define PICO_MLD_DONE 132 +#define PICO_MLD_REPORTV2 143 + +/*RFC 3810 $6.2 */ +#define MLD_HOP_LIMIT 1 + +/* states */ +#define MLD_STATE_NON_LISTENER (0x0) +#define MLD_STATE_DELAYING_LISTENER (0x1) +#define MLD_STATE_IDLE_LISTENER (0x2) + +#define PICO_MLD_STATE_CREATE 1 +#define PICO_MLD_STATE_UPDATE 2 +#define PICO_MLD_STATE_DELETE 3 +/* group record types */ +#define MLD_MODE_IS_INCLUDE (1) +#define MLD_MODE_IS_EXCLUDE (2) +#define MLD_CHANGE_TO_INCLUDE_MODE (3) +#define MLD_CHANGE_TO_EXCLUDE_MODE (4) +#define MLD_ALLOW_NEW_SOURCES (5) +#define MLD_BLOCK_OLD_SOURCES (6) +/* events */ + +#define MLD_EVENT_START_LISTENING (0x1) +#define MLD_EVENT_STOP_LISTENING (0x0) +#define MLD_EVENT_QUERY_RECV (0x3) +#define MLD_EVENT_REPORT_RECV (0x4) +#define MLD_EVENT_TIMER_EXPIRED (0x5) +/*Not needed?*/ +#define MLD_EVENT_DONE_RECV (0x1) + +#define MLD_EVENT_DELETE_GROUP (0x0) +#define MLD_EVENT_CREATE_GROUP (0x1) +#define MLD_EVENT_UPDATE_GROUP (0x2) +#define MLD_EVENT_QUERY_RECV (0x3) +#define MLD_EVENT_REPORT_RECV (0x4) +#define MLD_EVENT_TIMER_EXPIRED (0x5) +/* (default) Variabels for times/counters */ +/* ALL IN SECONDS */ +#define MLD_ROBUSTNESS (2) +#define MLD_QUERY_INTERVAL (125) +#define MLD_QUERY_RESPONSE_INTERVAL (10) +#define MLD_DEFAULT_MAX_RESPONSE_TIME (100) +#define MLD_MULTICAST_LISTENER_INTERVAL (MLD_ROBUSTNESS * MLD_QUERY_INTERVAL) + MLD_QUERY_RESPONSE_INTERVAL +#define MLD_OTHER_QUERIER_PRESENT_INTERVAL (MLD_ROBUSTNESS * MLD_QUERY_INTERVAL) + (0.5 * MLD_QUERY_RESPONSE_INTERVAL) +#define MLD_STARTUP_QUERY_INTERVAL (0.25 * MLD_QUERY_INTERVAL) +#define MLD_STARTUP_QUERY_COUNT MLD_ROBUSTNESS +#define MLD_LAST_LISTENER_QUERY_INTERVAL 1 +#define MLD_LISTENER_QUERY_COUNT MLD_ROBUSTNESS +#define MLD_UNSOLICITED_REPORT_INTERVAL 10 + +/* custom timers types */ +#define MLD_TIMER_GROUP_REPORT (1) +#define MLD_TIMER_V1_QUERIER (2) +#define MLD_TIMER_V2_QUERIER (2) + + +/* Who has send the last report message */ +#define MLD_HOST_LAST (0x1) +#define MLD_HOST_NOT_LAST (0x0) + + +#define MLD_TIMER_STOPPED (1) +#define MLD_MAX_SOURCES (89) +extern struct pico_protocol pico_proto_mld; + +struct mld_multicast_address_record { + uint8_t type; + uint8_t aux_len; + uint16_t nbr_src; + struct pico_ip6 multicast; + struct pico_ip6 src[0]; +}; + +struct mld_parameters { + uint8_t event; + uint8_t state; + uint8_t general_query; + uint8_t filter_mode; + uint8_t last_host; + uint16_t max_resp_time; + struct pico_ip6 mcast_link; + struct pico_ip6 mcast_group; + struct pico_tree *MCASTFilter; + struct pico_frame *f; +}; + +struct mld_timer { + uint8_t type; + uint8_t stopped; + pico_time start; + pico_time delay; + struct pico_ip6 mcast_link; + struct pico_ip6 mcast_group; + struct pico_frame *f; + void (*mld_callback)(struct mld_timer *t); +}; + +uint16_t pico_mld_checksum(struct pico_frame *f); +int pico_mld_process_in(struct pico_frame *f); +int pico_mld_state_change(struct pico_ip6 *mcast_link, struct pico_ip6 *mcast_group, uint8_t filter_mode, struct pico_tree *_MCASTFilter, uint8_t state); +#endif /* _INCLUDE_PICO_MLD */ diff --git a/ext/picotcp/modules/pico_mm.c b/ext/picotcp/modules/pico_mm.c new file mode 100644 index 0000000..5a6517b --- /dev/null +++ b/ext/picotcp/modules/pico_mm.c @@ -0,0 +1,1605 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + Authors: Gustav Janssens, Jonas Van Nieuwenberg, Sam Van Den Berge + *********************************************************************/ + +#include "pico_config.h" +#include "pico_mm.h" +#include "pico_tree.h" +#include "pico_config.h" +#include "pico_protocol.h" /* For pico_err */ + +#define DBG_MM(x, args ...) /* dbg("[%s:%s:%i] "x" \n",__FILE__,__func__,__LINE__ ,##args ) */ +#define DBG_MM_RED(x, args ...) /* dbg("\033[31m[%s:%s:%i] "x" \033[0m\n",__FILE__,__func__,__LINE__ ,##args ) */ +#define DBG_MM_GREEN(x, args ...) /* dbg("\033[32m[%s:%s:%i] "x" \033[0m\n",__FILE__,__func__,__LINE__ ,##args ) */ +#define DBG_MM_YELLOW(x, args ...) /* dbg("\033[33m[%s:%s:%i] "x" \033[0m\n",__FILE__,__func__,__LINE__ ,##args ) */ +#define DBG_MM_BLUE(x, args ...) /* dbg("\033[34m[%s:%s:%i] "x" \033[0m\n",__FILE__,__func__,__LINE__ ,##args ) */ + +/* The memory manager also uses the pico_tree to keep track of all the different slab sizes it has. + * These nodes should be placed in the manager page which is in a different memory region then the nodes + * which are used for the pico stack in general. + * Therefore the following 2 functions are created so that pico_tree can use them to to put these nodes + * into the correct memory regions. + */ +void*pico_mem_page0_zalloc(size_t len); +void pico_mem_page0_free(void*ptr); + + +/* this is a wrapper function for pico_tree_insert. The function pointers that are used by pico_tree + * to zalloc/free are modified so that pico_tree will insert the node in another memory region + */ +static void *manager_tree_insert(struct pico_tree*tree, void *key) +{ + return (void*) pico_tree_insert_implementation(tree, key, USE_PICO_PAGE0_ZALLOC); +} + +/* this is a wrapper function for pico_tree_insert. The function pointers that are used by pico_tree + * to zalloc/free are modified so that pico_tree will insert the node in another memory region + */ +static void *manager_tree_delete(struct pico_tree *tree, void *key) +{ + return (void *) pico_tree_delete_implementation(tree, key, USE_PICO_PAGE0_ZALLOC); +} + + +static const uint32_t slab_sizes[] = { + 1200, 1400, 1600 +}; /* Sizes must be from small to big */ +static uint32_t slab_size_statistics[] = { + 0, 0, 0 +}; +static uint32_t slab_size_global = PICO_MEM_DEFAULT_SLAB_SIZE; +/* + typedef struct pico_mem_manager pico_mem_manager; + typedef struct pico_mem_manager_extra pico_mem_manager_extra; + typedef struct pico_mem_page pico_mem_page; + typedef struct pico_mem_heap pico_mem_heap; + typedef struct pico_mem_slab pico_mem_slab; + typedef struct pico_mem_heap_block pico_mem_heap_block; + typedef struct pico_mem_slab_block pico_mem_slab_block; + typedef struct pico_mem_slab_node pico_mem_slab_node; + typedef struct pico_mem_block pico_mem_block; + typedef struct pico_tree pico_tree; + typedef struct pico_tree_node pico_tree_node; + typedef union block_internals block_internals; + */ +#define HEAP_BLOCK_NOT_FREE 0xCAFED001 +#define HEAP_BLOCK_FREE 0xCAFED00E + +#define SLAB_BLOCK_TYPE 0 +#define HEAP_BLOCK_TYPE 1 +/* + * page + * <----------------------------------------------------------------------> + * + * + * +------------<------------+----------<-----------+ + * | ^ ^ + * v | | + * +---------+------------+--+----+---------------+-+-----+---------------+ + * | | | | | | | + * | pico_ | | pico_ | | pico_ | | + * | mem_ | ...HEAP... | mem_ | slab | mem_ | slab | + * | page | | block | | block | | + * | | | | | | | + * +---------+------------+-------+-----+---------+-------+----------+----+ + * ^ | ^ | + * +-------+ | | | + * | | +-+ | + * +------|-----+ | | + * | | +-----|--------------------+ + * v | v | + * +---------+-----+-------+------+-+-----+-----+--+ + * | | | | | | | + * | pico_ | | pico_ | pico_ | | pico_ | + * | mem_ | ... | tree_ | mem_ | ... | mem_ | + * | manager | | node | slab_ | | slab_ | + * | | | | node | | node | + * +---------+-----+-+-----+-----+--+-----+-----+--+ + * | ^ | ^ | + * | | +---->---+ | + * +-->--+----<-----------<---+ + * + * <-----------------------------------------------> + * manager page + * + * + * +----------------+ + * | | + * | pico_tree_node +-------------------------------------+ + * | (size x) | | + * +--+----------+--+ | + * | | +---------v----------+ + * | | | | + * v v +----> pico_mem_slab_node +----+ + * | | | | | | + * +----<----+ +---->----+ | +--------------------+ | + * | | | | + * | | ^ v + * | | | | + * | | | +--------------------+ | + * +------v---------+ +---------v------+ +----+ <----+ + * | | | | | pico_mem_slab_node | + * | pico_tree_node | | pico_tree_node | +----> +----+ + * | (size x/2) | | (size 2x) | | +--------------------+ | + * +----------------+ +----------------+ | | + * | | + * | | + * ^ v + * ... ... + * + */ + +/* Housekeeping memory manager (start of page 0) */ +struct pico_mem_manager +{ + uint32_t size; /* Maximum size in bytes */ + uint32_t used_size; /* Used size in bytes */ + struct pico_tree tree; + struct pico_mem_page*first_page; + struct pico_mem_manager_extra*manager_extra; /* this is a pointer to a page with extra heap space used by the manager */ +}; +/* Housekeeping additionnal memory manager heap pages */ +struct pico_mem_manager_extra +{ + struct pico_mem_manager_extra*next; + uint32_t timestamp; + uint32_t blocks; +}; +/* Housekeeping of every page (start of all pages except the manager pages) */ +struct pico_mem_page +{ + uint32_t slab_size; + uint16_t slabs_max; + uint16_t slabs_free; + uint32_t heap_max_size; + uint32_t heap_max_free_space; + uint32_t timestamp; + struct pico_mem_page*next_page; +}; +/* Housekeeping struct for a heap block (kept per block of memory in heap) */ +struct pico_mem_heap_block +{ + uint32_t size; + /* uint8_t free; */ + uint32_t free; +}; +/* Housekeeping struct for a slab block (kept per block of memory in slabs) */ +struct pico_mem_slab_block +{ + struct pico_mem_page*page; + struct pico_mem_slab_node*slab_node; +}; +union block_internals +{ + struct pico_mem_heap_block heap_block; + struct pico_mem_slab_block slab_block; +}; +struct pico_mem_block +{ + union block_internals internals; /* Union has to be in first place!!! */ + uint8_t type; +}; +/* Used to store the slab objects in the RB-tree */ +struct pico_mem_slab_node +{ + struct pico_mem_block*slab; + struct pico_mem_slab_node*prev; + struct pico_mem_slab_node*next; +}; + +static struct pico_mem_manager*manager = NULL; + +/* + * This compare function will be called by pico_tree.c to compare 2 keyValues (type: struct pico_mem_slab_nodes) + * We want to compare slab_nodes by their size. We also want to be able to directly compare an integer, which explains + * the casts from void* to uint32_t*** + */ +static int compare_slab_keys(void*keyA, void*keyB) +{ + /* keyValues are pico_mem_slab_nodes */ + /* We want to compare the sizes */ + /* first element of pico_mem_slab_node: pico_mem_block* slab_block */ + /* first element of pico_mem_block: (slab_block in union): pico_mem_page* page */ + /* first element of pico_mem_page: uint32_t slab_size */ + uint32_t sizeKeyA = ***(uint32_t***) keyA; + uint32_t sizeKeyB = ***(uint32_t***) keyB; + DBG_MM_BLUE("Compare called: sizeA = %i, sizeB = %i", sizeKeyA, sizeKeyB); + if(sizeKeyA == sizeKeyB) + { + return 0; + } + else if(sizeKeyA < sizeKeyB) + { + return 1; + } + else + { + return -1; + } +} + +/* + * Pico_mem_init_page is called to initialize a block of memory pointed to by pico_mem_page* page. + * Slabs of size slabsize are created, and the page, heap and slab housekeeping is initialized. + */ +static void _pico_mem_init_page(struct pico_mem_page*page, size_t slabsize) +{ + uint8_t*byteptr = (uint8_t*) page; + struct pico_mem_block*slab_block; + struct pico_mem_block*heap_block; + struct pico_tree_node*tree_node; + struct pico_mem_slab_node*slab_node; + void*temp; + uint16_t i; + + DBG_MM_YELLOW("Initializing page %p with slabsize %u", page, slabsize); + + page->next_page = manager->first_page; + manager->first_page = page; + page->slab_size = (uint32_t)slabsize; + page->slabs_max = (uint16_t)((PICO_MEM_PAGE_SIZE - sizeof(struct pico_mem_page) - sizeof(struct pico_mem_block)) / (slabsize + sizeof(struct pico_mem_block))); + page->heap_max_size = (uint32_t)(PICO_MEM_PAGE_SIZE - sizeof(struct pico_mem_page) - sizeof(struct pico_mem_block) - (page->slabs_max * (sizeof(struct pico_mem_block) + slabsize))); + if(page->heap_max_size < PICO_MIN_HEAP_SIZE) + { + DBG_MM_BLUE("Not enough heap size available with slabsize %u, allocating one slab to heap.", slabsize); + page->slabs_max--; + /* DBG_MM_BLUE("Heap size %u -> %lu",page->heap_max_size, page->heap_max_size + sizeof(pico_mem_slab_block) + slabsize); */ + DBG_MM_BLUE("Heap size %u -> %lu", page->heap_max_size, page->heap_max_size + sizeof(struct pico_mem_block) + slabsize); + page->heap_max_size += (uint32_t)(sizeof(struct pico_mem_block) + slabsize); + } + + page->slabs_free = page->slabs_max; + page->heap_max_free_space = page->heap_max_size; + page->timestamp = 0; + DBG_MM_BLUE("max slab objects = %i, object_size = %i", page->slabs_max, page->slab_size); + DBG_MM_BLUE("Heap size: %i", page->heap_max_size); + byteptr += sizeof(struct pico_mem_page); /* jump over page struct so byteptr points to start of heap */ + + /* Init HEAP at the beginning of the page */ + heap_block = (struct pico_mem_block*) byteptr; + heap_block->type = HEAP_BLOCK_TYPE; + heap_block->internals.heap_block.free = HEAP_BLOCK_FREE; + heap_block->internals.heap_block.size = page->heap_max_free_space; + + byteptr += sizeof(struct pico_mem_block) + heap_block->internals.heap_block.size; + for(i = 0; i < page->slabs_max; i++) + { + slab_block = (struct pico_mem_block*) byteptr; + DBG_MM_BLUE("Slab object %i at %p. Start of object data at %p", i, slab_block, (uint8_t*) slab_block + sizeof(struct pico_mem_slab_block)); + slab_block->type = SLAB_BLOCK_TYPE; + slab_block->internals.slab_block.page = page; + + DBG_MM_BLUE("Calling find_node with size %u", **((uint32_t**) slab_block)); + tree_node = pico_tree_findNode(&(manager->tree), &slab_block); + + DBG_MM("Creating slab_node.."); + slab_node = pico_mem_page0_zalloc(sizeof(struct pico_mem_slab_node)); + if(slab_node == NULL) + { + DBG_MM_RED("No more space in the manager heap for the housekeeping of slab %i, and no more space for extra manager pages!", i + 1); + DBG_MM_RED("Debug info:\nUsed size: %u/%u\nmanager_extra = %p", manager->used_size, manager->size, manager->manager_extra); + DBG_MM_RED("This page will be initialized with %u slabs instead of %u slabs", i, page->slabs_max); + page->slabs_max = i; + page->slabs_free = page->slabs_max; + return; + /* exit(1); */ + } + + slab_node->slab = slab_block; + slab_node->prev = NULL; + slab_node->next = NULL; + + slab_block->internals.slab_block.slab_node = slab_node; + + if(tree_node != NULL) + { + struct pico_mem_slab_node*first_node = (struct pico_mem_slab_node*) tree_node->keyValue; + tree_node->keyValue = slab_node; + slab_node->next = first_node; + first_node->prev = slab_node; + } + else + { + /* Insert new slab_node */ + DBG_MM_BLUE("Inserting new slab node in the tree of size %u", slabsize); + /* pico_err_t pico_err_backup = pico_err; */ + /* pico_err = 0; */ + + + /* temp = pico_tree_insert(&manager->tree, slab_node); */ + temp = manager_tree_insert(&manager->tree, slab_node); + + /* IF SLAB_NODE COULDN'T BE INSERTED */ + /* if(pico_err == PICO_ERR_ENOMEM) */ + /* if(temp == &LEAF) */ + if(temp != NULL) + { + DBG_MM_RED("No more space in the manager heap for the housekeeping of slab %i, and no more space for extra manager pages!", i + 1); + DBG_MM_RED("This page will be initialized without slabs."); + pico_mem_page0_free(slab_node); + page->slabs_max = (uint16_t) i; + page->slabs_free = page->slabs_max; + /* pico_err = pico_err_backup; */ + return; + } + } + + /* byteptr = (uint8_t*) (slab_block+1); */ + byteptr = (uint8_t*) slab_block; + byteptr += sizeof(struct pico_mem_block); + byteptr += page->slab_size; + } + DBG_MM_GREEN("Initialized page %p with slabsize %u", page, slabsize); +} + +/* + * Initializes the memory by creating a memory manager page and one page with default slab size + * A maximum space of memsize can be occupied by the memory manager at any time + */ +void pico_mem_init(uint32_t memsize) +{ + struct pico_mem_block*first_block; + struct pico_mem_page*page; + uint8_t*startofmanagerheap; + + DBG_MM_YELLOW("Initializing memory with memsize %u", memsize); + if(memsize < PICO_MEM_PAGE_SIZE * 2) + { + /* Not enough memory was provided to initialize a manager page and a data page, return without initializing memory */ + /* Set pico_err to an appropriate value */ + pico_err = PICO_ERR_ENOMEM; + DBG_MM_RED("The memsize provided is too small, memory not initialized!"); + + return; + } + + /* First pico_mem_page is already included in pico_mem_manager. Others are added. */ + /* manager = pico_azalloc(sizeof(pico_mem_manager) + sizeof(pico_mem_page*)*(pages - 1)); //Points to usermanager if one present */ + manager = pico_zalloc(PICO_MEM_PAGE_SIZE); + if( NULL != manager ) + { + manager->size = memsize; + manager->used_size = PICO_MEM_PAGE_SIZE; + manager->first_page = NULL; + manager->manager_extra = NULL; + + manager->tree.compare = compare_slab_keys; + manager->tree.root = &LEAF; + DBG_MM_BLUE("Manager page is at %p", manager); + + DBG_MM_BLUE("Start of tree: %p, sizeof(pico_tree): %lu", &manager->tree, sizeof(struct pico_tree)); + DBG_MM_BLUE("Root node of tree at %p", manager->tree.root); + + /* Init manager heap. Used to store the RB-tree nodes which store pointers to free slab objects */ + startofmanagerheap = (uint8_t*) manager + sizeof(struct pico_mem_manager); /* manager heap is after struct pico_mem_manager */ + DBG_MM_BLUE("Start of manager heap = %p", startofmanagerheap); + first_block = (struct pico_mem_block*) startofmanagerheap; + first_block->type = HEAP_BLOCK_TYPE; + first_block->internals.heap_block.free = HEAP_BLOCK_FREE; + first_block->internals.heap_block.size = PICO_MEM_PAGE_SIZE - sizeof(struct pico_mem_manager) - sizeof(struct pico_mem_block); + + /* Initialize the first page only! */ + page = pico_zalloc(PICO_MEM_PAGE_SIZE); + if(page != NULL) + { + manager->used_size += PICO_MEM_PAGE_SIZE; + DBG_MM_BLUE("Page 1 at %p, manager used size = %u", page, manager->used_size); + _pico_mem_init_page(page, PICO_MEM_DEFAULT_SLAB_SIZE); + } + else + { + /* Not enough memory was provided to initialize a manager page and a data page, return without initializing memory */ + /* Set pico_err to an appropriate value */ + pico_err = PICO_ERR_ENOMEM; + /* Free the manager page */ + pico_free(manager); + manager = NULL; + DBG_MM_RED("Not enough space to allocate page 1, memory not initialized!"); + return; + } + + DBG_MM_GREEN("Memory initialized. Returning from pico_mem_init."); + } + else + { + /* Not enough memory was provided to initialize a manager page and a data page, return without initializing memory */ + /* Set pico_err to an appropriate value */ + pico_err = PICO_ERR_ENOMEM; + DBG_MM_RED("Not enough space to allocate manager page, memory not initialized!"); + return; + } +} + +/* + * Deinitializes the memory manager, returning all its memory to the system's control. + */ +void pico_mem_deinit() +{ + struct pico_mem_page*next_page; + struct pico_mem_manager_extra*next_manager_page; + + DBG_MM_YELLOW("Pico_mem_deinit called"); + if(manager == NULL) + { + DBG_MM_GREEN("No memory instance initialized, returning"); + } + else + { + while(manager->first_page != NULL) + { + next_page = manager->first_page->next_page; + pico_free(manager->first_page); + manager->first_page = next_page; + } + while(manager->manager_extra != NULL) + { + next_manager_page = manager->manager_extra->next; + pico_free(manager->manager_extra); + manager->manager_extra = next_manager_page; + } + DBG_MM_BLUE("Freeing manager page at %p", manager); + pico_free(manager); + manager = NULL; + slab_size_global = PICO_MEM_DEFAULT_SLAB_SIZE; + DBG_MM_GREEN("Memory manager reset"); + } +} + +/* + * This function is called internally by page0_zalloc if there isn't enough space left in the heap of the initial memory page + * This function allocates heap space in extra manager pages, creating new pages as necessary. + */ +static void*_pico_mem_manager_extra_alloc(struct pico_mem_manager_extra*heap_page, size_t len) +{ + struct pico_mem_manager_extra*extra_heap_page; + struct pico_mem_block*heap_block; + struct pico_mem_block*first_block; + struct pico_mem_block*new_block; + uint8_t*startOfData; + uint8_t*byteptr; + uint32_t sizeleft; + + DBG_MM_YELLOW("Searching for a block of len %u in extra manager page %p (%u blocks in use)", len, heap_page, heap_page->blocks); + /* Linearly search for a free heap block */ + + /* heap_block = (pico_mem_block*) (heap_page+1); */ + byteptr = (uint8_t*) heap_page + sizeof(struct pico_mem_manager_extra); + heap_block = (struct pico_mem_block*) byteptr; + + sizeleft = PICO_MEM_PAGE_SIZE - sizeof(struct pico_mem_manager_extra); + + while(heap_block->internals.heap_block.free == HEAP_BLOCK_NOT_FREE || heap_block->internals.heap_block.size < len) + { + sizeleft -= (uint32_t)sizeof(struct pico_mem_block); + sizeleft -= heap_block->internals.heap_block.size; + /* DBG_MM("Sizeleft=%i", sizeleft); */ + /* byteptr = (uint8_t*) (heap_block+1); */ + byteptr = (uint8_t*) heap_block + sizeof(struct pico_mem_block); + byteptr += heap_block->internals.heap_block.size; + heap_block = (struct pico_mem_block*) byteptr; + if(sizeleft <= sizeof(struct pico_mem_block)) + { + DBG_MM_RED("No more heap space left in the extra manager heap page!"); + if(heap_page->next == NULL) + { + /* TODO: Probably need another function for this */ + DBG_MM_RED("Trying to allocate a new page for extra heap space: space usage %uB/%uB", manager->used_size, manager->size); + if(manager->used_size + PICO_MEM_PAGE_SIZE > manager->size) + { + DBG_MM_RED("No more space left for this page!"); + /* exit(1); */ + return NULL; + } + + extra_heap_page = pico_zalloc(PICO_MEM_PAGE_SIZE); + if(extra_heap_page != NULL) + { + extra_heap_page->blocks = 0; + extra_heap_page->next = NULL; + extra_heap_page->timestamp = 0; + byteptr = (uint8_t*) extra_heap_page + sizeof(struct pico_mem_manager_extra); + first_block = (struct pico_mem_block*) byteptr; + first_block->type = HEAP_BLOCK_TYPE; + first_block->internals.heap_block.free = HEAP_BLOCK_FREE; + first_block->internals.heap_block.size = PICO_MEM_PAGE_SIZE - sizeof(struct pico_mem_manager_extra) - sizeof(struct pico_mem_block); + extra_heap_page->next = heap_page; + manager->manager_extra = extra_heap_page; + manager->used_size += PICO_MEM_PAGE_SIZE; + DBG_MM_BLUE("Allocated an extra manager heap page at %p, manager space usage: %uB/%uB", extra_heap_page, manager->used_size, manager->size); + return _pico_mem_manager_extra_alloc(extra_heap_page, len); + } + else + { + /* This should be a dirty crash */ + DBG_MM_RED("Page not allocated even though the max size for the memory manager hasn't been reached yet!"); + /* exit(1); */ + return NULL; + } + } + else + { + DBG_MM_RED("This should never happen: debug information:"); + DBG_MM_RED("manager->manager_extra = %p", manager->manager_extra); + DBG_MM_RED("heap_page = %p", heap_page); + DBG_MM_RED("heap_page->next = %p", heap_page->next); + /* exit(1); */ + return NULL; + } + } + } + heap_page->blocks++; + heap_page->timestamp = 0; + DBG_MM_BLUE("Found free heap block in extra manager page %p at: %p (%u blocks in use)", heap_page, heap_block, heap_page->blocks); + heap_block->internals.heap_block.free = HEAP_BLOCK_NOT_FREE; + + if(heap_block->internals.heap_block.size == sizeleft - sizeof(struct pico_mem_block)) + { + DBG_MM_BLUE("End of heap, splitting up into a new block"); + heap_block->internals.heap_block.size = (uint32_t)len; + sizeleft = (uint32_t)(sizeleft - (uint32_t)sizeof(struct pico_mem_block) - len); + if(sizeleft > sizeof(struct pico_mem_block)) + { + sizeleft -= (uint32_t)sizeof(struct pico_mem_block); + byteptr = (uint8_t*) heap_block + sizeof(struct pico_mem_block); + byteptr += len; + new_block = (struct pico_mem_block*) byteptr; + new_block->type = HEAP_BLOCK_TYPE; + new_block->internals.heap_block.free = HEAP_BLOCK_FREE; + new_block->internals.heap_block.size = sizeleft; + DBG_MM_BLUE("New block: %p, size = %u", new_block, new_block->internals.heap_block.size); + } + else + { + DBG_MM_RED("No more space in extra manager heap page left to initialize a new heap block!"); + DBG_MM_RED("A new page will be allocated when even more space is needed"); + } + } + + startOfData = (uint8_t*) heap_block + sizeof(struct pico_mem_block); + DBG_MM_GREEN("Start of data = %p", startOfData); + + return startOfData; +} + +/* + * Page0 zalloc is called by pico_tree.c so that nodes which contain pointers to the free slab objects are put in the + * manager page. Additional manager pages can be created if necessary. + */ +void*pico_mem_page0_zalloc(size_t len) +{ + struct pico_mem_manager_extra*heap_page; + struct pico_mem_block*heap_block; + struct pico_mem_block*first_block; + struct pico_mem_block*new_block; + uint8_t*startOfData; + uint8_t*byteptr; + uint32_t sizeleft; + + DBG_MM_YELLOW("pico_mem_page0_zalloc(%u) called", len); + + byteptr = (uint8_t*) manager + sizeof(struct pico_mem_manager); + heap_block = (struct pico_mem_block*) byteptr; + + /* If heap_block == NULL then a free block at the end of the list is found. */ + /* Else, if the block is free and the size > len, an available block is also found. */ + sizeleft = PICO_MEM_PAGE_SIZE - sizeof(struct pico_mem_manager); + /* this would mean that heap_block is never NULL */ + /* while(heap_block != NULL && ( heap_block->internals.heap_block.free == HEAP_BLOCK_NOT_FREE || heap_block->internals.heap_block.size < len)) */ + while(heap_block->internals.heap_block.free == HEAP_BLOCK_NOT_FREE || heap_block->internals.heap_block.size < len) + { + sizeleft -= (uint32_t)sizeof(struct pico_mem_block); + sizeleft -= heap_block->internals.heap_block.size; + /* DBG_MM("Sizeleft=%i", sizeleft); */ + byteptr = (uint8_t*) heap_block + sizeof(struct pico_mem_block); /* byteptr points to start of heap block data */ + byteptr += heap_block->internals.heap_block.size; /* jump over that data to start of next heap_block */ + heap_block = (struct pico_mem_block*) byteptr; + if(sizeleft <= sizeof(struct pico_mem_block)) + { + DBG_MM_RED("No more heap space left in the manager page!"); + if(manager->manager_extra == NULL) + { + DBG_MM_RED("Trying to allocate a new page for extra heap space: space usage: %uB/%uB", manager->used_size, manager->size); + if(manager->used_size + PICO_MEM_PAGE_SIZE > manager->size) + { + DBG_MM_RED("No more space left for this page!"); + /* exit(1); */ + return NULL; + } + + heap_page = pico_zalloc(PICO_MEM_PAGE_SIZE); + if(heap_page != NULL) + { + /* Initialize the new heap page */ + heap_page->blocks = 0; + heap_page->next = NULL; + heap_page->timestamp = 0; + byteptr = (uint8_t*) heap_page + sizeof(struct pico_mem_manager_extra); + first_block = (struct pico_mem_block*) byteptr; + first_block->type = HEAP_BLOCK_TYPE; + first_block->internals.heap_block.free = HEAP_BLOCK_FREE; + first_block->internals.heap_block.size = PICO_MEM_PAGE_SIZE - sizeof(struct pico_mem_manager_extra) - sizeof(struct pico_mem_block); + manager->manager_extra = heap_page; + manager->used_size += PICO_MEM_PAGE_SIZE; + DBG_MM_BLUE("Allocated an extra manager heap page at %p, manager space usage: %uB/%uB", heap_page, manager->used_size, manager->size); + return _pico_mem_manager_extra_alloc(heap_page, len); + } + else + { + /* This should be a dirty crash */ + DBG_MM_RED("Page not allocated even though the max size for the memory manager hasn't been reached yet!"); + /* exit(1); */ + return NULL; + } + } + else + { + return _pico_mem_manager_extra_alloc(manager->manager_extra, len); + } + } + } + DBG_MM_BLUE("Found free heap block in manager page at : %p", heap_block); + heap_block->internals.heap_block.free = HEAP_BLOCK_NOT_FREE; + + if(heap_block->internals.heap_block.size == sizeleft - sizeof(struct pico_mem_block)) + { + sizeleft = (uint32_t)(sizeleft - (uint32_t)sizeof(struct pico_mem_block) - len); + if(sizeleft > sizeof(struct pico_mem_block)) + { + DBG_MM_BLUE("End of heap, splitting up into a new block"); + heap_block->internals.heap_block.size = (uint32_t)len; + sizeleft -= (uint32_t)sizeof(struct pico_mem_block); + byteptr = (uint8_t*) heap_block + sizeof(struct pico_mem_block); + byteptr += len; + new_block = (struct pico_mem_block*) byteptr; + new_block->internals.heap_block.free = HEAP_BLOCK_FREE; + new_block->internals.heap_block.size = sizeleft; + DBG_MM_BLUE("New block: %p, size = %u", new_block, new_block->internals.heap_block.size); + } + else + { + /* DBG_MM_RED("ERROR! No more space in manager heap left to initialise a new heap_block!"); */ + /* exit(1); */ + DBG_MM_RED("No more space in manager heap left to initialize a new heap block!"); + DBG_MM_RED("A new page will be allocated when more space is needed"); + } + } + + startOfData = (uint8_t*) heap_block + sizeof(struct pico_mem_block); + DBG_MM_GREEN("Start of data = %p", startOfData); + + return startOfData; +} + + +/* + * This method will free a given heap block and try to merge it with + * surrounding blocks if they are free. + */ +static void _pico_mem_free_and_merge_heap_block(struct pico_mem_page*page, struct pico_mem_block*mem_block) +{ + uint8_t*byteptr; + /* pico_mem_block* prev = NULL; */ + struct pico_mem_block*prev; + struct pico_mem_block*curr; + struct pico_mem_block*next; + + DBG_MM_YELLOW("Freeing heap block %p with size %u in page %p", mem_block, mem_block->internals.heap_block.size, page); + + mem_block->internals.heap_block.free = HEAP_BLOCK_FREE; + + byteptr = (uint8_t*) page + sizeof(struct pico_mem_page); + curr = (struct pico_mem_block*) byteptr; + byteptr = (uint8_t*) curr + sizeof(struct pico_mem_block); + byteptr += curr->internals.heap_block.size; + next = (struct pico_mem_block*) byteptr; + + while(curr->type == HEAP_BLOCK_TYPE && next->type == HEAP_BLOCK_TYPE) + { + DBG_MM("Checking heap block (%s) with size %u at %p", (curr->internals.heap_block.free == HEAP_BLOCK_FREE) ? "free" : "not free", curr->internals.heap_block.size, curr); + if(curr->internals.heap_block.free == HEAP_BLOCK_FREE && next->internals.heap_block.free == HEAP_BLOCK_FREE) + { + DBG_MM_BLUE("Merging blocks with sizes %u and %u", curr->internals.heap_block.size, next->internals.heap_block.size); + curr->internals.heap_block.size += (uint32_t)sizeof(struct pico_mem_block) + next->internals.heap_block.size; + } + + prev = curr; + byteptr = (uint8_t*) curr + sizeof(struct pico_mem_block); + byteptr += curr->internals.heap_block.size; + curr = (struct pico_mem_block*) byteptr; + byteptr = (uint8_t*) curr + sizeof(struct pico_mem_block); + byteptr += curr->internals.heap_block.size; + next = (struct pico_mem_block*) byteptr; + } + DBG_MM("Checking heap block (%s) with size %u at %p", (curr->internals.heap_block.free == HEAP_BLOCK_FREE) ? "free" : "not free", curr->internals.heap_block.size, curr); + if(curr->type == HEAP_BLOCK_TYPE && prev->internals.heap_block.free == HEAP_BLOCK_FREE && curr->internals.heap_block.free == HEAP_BLOCK_FREE) + { + DBG_MM_BLUE("Merging blocks with sizes %u and %u", prev->internals.heap_block.size, curr->internals.heap_block.size); + prev->internals.heap_block.size += (uint32_t)sizeof(struct pico_mem_block) + curr->internals.heap_block.size; + } + + DBG_MM_GREEN("Heap block freed and heap space defragmentized"); +} + +/* + * This method will return the max. available contiguous free space in the heap + * from a given page. + */ +static uint32_t _pico_mem_determine_max_free_space(struct pico_mem_page*page) +{ + uint32_t maxfreespace = 0; + uint8_t*byteptr; + struct pico_mem_block*mem_block; + + DBG_MM_YELLOW("Determining new maximum free space in page %p (old free space: %u)", page, page->heap_max_free_space); + + /* pico_mem_block* mem_block = (pico_mem_block*) (page+1); //reset mem_block to first block in the heap */ + byteptr = (uint8_t*) page + sizeof(struct pico_mem_page); + mem_block = (struct pico_mem_block*) byteptr; /* reset mem_block to first block in the heap */ + + /* Determine max free space by iterating trough the list */ + /* while(mem_block != NULL && mem_block->type == HEAP_BLOCK_TYPE) */ + while(mem_block->type == HEAP_BLOCK_TYPE) + { + /* DBG_MM("Memblock %p of size %i is free %i\n",block, block->size, block->free); */ + DBG_MM("Memblock %s (size %u) at %p", (mem_block->internals.heap_block.free == HEAP_BLOCK_FREE) ? "not in use" : "in use", mem_block->internals.heap_block.size, mem_block); + if(mem_block->internals.heap_block.free == HEAP_BLOCK_FREE && mem_block->internals.heap_block.size > maxfreespace) + { + maxfreespace = mem_block->internals.heap_block.size; + page->heap_max_free_space = maxfreespace; + } + + byteptr = (uint8_t*) mem_block + sizeof(struct pico_mem_block); + byteptr += mem_block->internals.heap_block.size; + mem_block = (struct pico_mem_block*) byteptr; + } + page->heap_max_free_space = maxfreespace; + DBG_MM_GREEN("New free space: %u", page->heap_max_free_space); + return maxfreespace; +} + +/* + * This method will make a slab object available again by putting it in the RB-tree. + * Slab objects of the same size are stored in a double linked list. One pico_tree_node represents + * all the slab objects of the same size by making the keyvalue of a pico_tree_node point to + * the first element of the linked list. + * An element in this linked list is a struct pico_mem_slab_node. All the elements are also + * stored in the heap of the manager page (page0), or in the heap of extra manager spaces if there isn't enough space. + */ +static void _pico_mem_free_slab_block(struct pico_mem_block*slab_block) +{ + struct pico_mem_slab_node*slab_node; + struct pico_mem_slab_node*first_slab_node; + struct pico_tree_node*tree_node; + void*temp; + + DBG_MM_YELLOW("Freeing slab object"); + + slab_node = pico_mem_page0_zalloc(sizeof(struct pico_mem_slab_node)); + + if(slab_node == NULL) + { + /* Update the page householding without making the slab available again! */ + DBG_MM_RED("No more space in the manager heap and no more space for extra pages!"); + DBG_MM_RED("This slab will be leaked, but the leak will be plugged at the next cleanup, if and when the page is empty"); + slab_block->internals.slab_block.page->slabs_free++; + return; + } + + slab_node->slab = slab_block; + slab_block->internals.slab_block.slab_node = slab_node; + tree_node = pico_tree_findNode(&manager->tree, slab_node); + if(tree_node != NULL) + { + first_slab_node = (struct pico_mem_slab_node*) tree_node->keyValue; + tree_node->keyValue = slab_node; + first_slab_node->prev = slab_node; + slab_node->prev = NULL; + slab_node->next = first_slab_node; + } + else{ + DBG_MM_BLUE("No node found for size %i so calling pico_tree_insert", slab_node->slab->internals.slab_block.page->slab_size); + slab_node->next = NULL; + slab_node->prev = NULL; + /* pico_err_t pico_err_backup = pico_err; */ + /* pico_err = 0; */ + + + /* temp = pico_tree_insert(&manager->tree, slab_node); */ + temp = manager_tree_insert(&manager->tree, slab_node); + + /* if(pico_err == PICO_ERR_ENOMEM) */ + if(temp == &LEAF) + { + DBG_MM_RED("No more space in the manager heap and no more space for extra pages!"); + DBG_MM_RED("This slab will be leaked, but the leak will be plugged at the next cleanup, if and when the page is empty"); + pico_mem_page0_free(slab_node); + /* pico_err = pico_err_backup; */ + slab_block->internals.slab_block.page->slabs_free++; + return; + } + } + + /* Update free slabs in page householding */ + slab_block->internals.slab_block.page->slabs_free++; + DBG_MM_GREEN("Freed slab object, there are now %i free slab objects in the corresponding page", slab_block->internals.slab_block.page->slabs_free); +} + +/* + * This method zero initializes a block of memory pointed to by startOfData, of size len + */ +static void _pico_mem_zero_initialize(void*startOfData, size_t len) +{ + if(startOfData != NULL) + { + DBG_MM_YELLOW("Zero initializing user memory at %p of %u bytes", startOfData, len); + memset(startOfData, 0, len); + DBG_MM_GREEN("Zero initialized."); + } + else + { + DBG_MM_RED("Got a NULL pointer to zero initialize!"); + } +} + +/* + * This method will try to find a free heap block of size len in a given page. + */ +static void*_pico_mem_find_heap_block(struct pico_mem_page*page, size_t len) +{ + struct pico_mem_block*mem_block; + struct pico_mem_block*inserted_block; + uint8_t*startOfData; + uint8_t*byteptr; + + DBG_MM_YELLOW("Searching for a heap block of length %u in page %p (largest free block size = %u)", len, page, page->heap_max_free_space); + if(page->heap_max_free_space < len ) + { + DBG_MM_RED("Size %u > max free space %u of the page. This should only happen when this page is newly created, and its heap space is not large enough for the heap length!", len, page->heap_max_free_space); + return NULL; + } + + byteptr = (uint8_t*) page + sizeof(struct pico_mem_page); + mem_block = (struct pico_mem_block*) byteptr; /* Jump over the page struct to the start of the heap */ + + /* If mem_block == NULL then a free block at the end of the list is found. */ + /* Else, if the block is free and the size > len, an available block is also found. */ + /* while(mem_block != NULL && mem_block->type == HEAP_BLOCK_TYPE && ( mem_block->internals.heap_block.free == HEAP_BLOCK_NOT_FREE || mem_block->internals.heap_block.size < len)) */ + while(mem_block->type == HEAP_BLOCK_TYPE && (mem_block->internals.heap_block.free == HEAP_BLOCK_NOT_FREE || mem_block->internals.heap_block.size < len)) + { + /* DBG_MM_RED("Skipping heap block in use at %p of size %i", mem_block, mem_block->size); */ + DBG_MM_BLUE("Skipping heap block %s (size %u) at %p", (mem_block->internals.heap_block.free == HEAP_BLOCK_FREE) ? "not in use" : "in use", mem_block->internals.heap_block.size, mem_block); + byteptr = (uint8_t*) mem_block + sizeof(struct pico_mem_block); + byteptr += mem_block->internals.heap_block.size; + mem_block = (struct pico_mem_block*) byteptr; + } + if(mem_block->type == SLAB_BLOCK_TYPE) + { + DBG_MM_RED("No free heap block of contiguous size %u could be found in page %p", len, page); + /* exit(1); */ + return NULL; + } + + DBG_MM_BLUE("Found free heap block of size %u at %p", mem_block->internals.heap_block.size, mem_block); + mem_block->internals.heap_block.free = HEAP_BLOCK_NOT_FREE; + page->timestamp = 0; + + /* Check to split the block into two smaller blocks */ + if(mem_block->internals.heap_block.size >= (len + sizeof(struct pico_mem_block) + PICO_MEM_MINIMUM_OBJECT_SIZE)) + { + byteptr = (uint8_t*) mem_block + sizeof(struct pico_mem_block); + byteptr += len; + inserted_block = (struct pico_mem_block*) byteptr; + + /* Update newly inserted block */ + inserted_block->type = HEAP_BLOCK_TYPE; + inserted_block->internals.heap_block.free = HEAP_BLOCK_FREE; + inserted_block->internals.heap_block.size = (uint32_t)(mem_block->internals.heap_block.size - (uint32_t)sizeof(struct pico_mem_block) - len); + /* Update block that was split up */ + mem_block->internals.heap_block.size = (uint32_t)len; + DBG_MM_BLUE("Splitting up the block, creating a new block of size %u at %p", inserted_block->internals.heap_block.size, inserted_block); + } + + startOfData = (uint8_t*) mem_block + sizeof(struct pico_mem_block); + + page->heap_max_free_space = _pico_mem_determine_max_free_space(page); + + /* Zero-initialize */ + _pico_mem_zero_initialize(startOfData, len); + DBG_MM_GREEN("Returning %p", startOfData); + return startOfData; +} + +/* + * This method will be called from pico_mem_zalloc. If an appropriate slab object is found, + * it is deleted from the RB tree and a pointer to the start of data in the slab object + * is returned. + */ +static void*_pico_mem_find_slab(size_t len) +{ + size_t*lenptr = &len; + size_t**doublelenptr = &lenptr; + struct pico_tree_node*node; + uint8_t *returnVal = NULL; + + DBG_MM_YELLOW("Finding slab with size %u", len); + /* The compare function takes an int*** length */ + node = pico_tree_findNode(&manager->tree, &doublelenptr); + + if(node != NULL) { + /* DBG_MM_BLUE("Found node, size = %d ", ((pico_mem_slab_node*) node->keyValue)->slab->size); */ + struct pico_mem_slab_node*slab_node = node->keyValue; + slab_node->slab->internals.slab_block.page->slabs_free--; + slab_node->slab->internals.slab_block.page->timestamp = 0; + DBG_MM_BLUE("Found node, size = %u at page %p, %u free slabs left in page", slab_node->slab->internals.slab_block.page->slab_size, slab_node->slab->internals.slab_block.page, slab_node->slab->internals.slab_block.page->slabs_free); + if(slab_node->next == NULL) + { + DBG_MM_BLUE("This was the last available slab object. Deleting the tree node now."); + /* if this is the last slab object of this size in the tree, then also delete the tree_node! */ + + + /* pico_tree_delete(&manager->tree, &doublelenptr); */ + manager_tree_delete(&manager->tree, &doublelenptr); + + + } + else + { + /* Remove the pico_mem_slab_node by making the keyvalue of the pico_tree_node point to the next element. */ + slab_node->next->prev = NULL; + node->keyValue = slab_node->next; + } + + returnVal = ((uint8_t*) (slab_node->slab)) + sizeof(struct pico_mem_block); + DBG_MM_BLUE("Start of slab: %p -> start of data : %p", slab_node->slab, returnVal); + /* Update the slab block housekeeping */ + slab_node->slab->internals.slab_block.slab_node = NULL; + /* Zero-initialize */ + _pico_mem_zero_initialize(returnVal, len); + /* Free the struct that was used by the linked list in the RB-tree */ + pico_mem_page0_free(slab_node); + } + + DBG_MM_GREEN("Returning %p", returnVal); + return returnVal; +} + +/* + * This method is called by the picotcp stack to free memory. + */ +void pico_mem_free(void*ptr) +{ + struct pico_mem_block*generic_block; + struct pico_mem_page*page; + /*Uncomment i for debugging!*/ + /*uint16_t i = 0;*/ + + DBG_MM_YELLOW("Free called on %p", ptr); + + if(ptr == NULL) return; + + generic_block = (struct pico_mem_block*) ptr; + generic_block--; + + if(generic_block->type == SLAB_BLOCK_TYPE) + { + if(generic_block->internals.slab_block.slab_node) + { + DBG_MM_RED("ERROR: Double free on a slab block (recovered)!"); + return; + } + + DBG_MM_BLUE("Request to free a slab block"); + _pico_mem_free_slab_block(generic_block); + } + else if(generic_block->type == HEAP_BLOCK_TYPE) + { + if(generic_block->internals.heap_block.free == HEAP_BLOCK_FREE) + { + DBG_MM_RED("ERROR: Double free on a heap block (recovered)!"); + return; + } + + DBG_MM_BLUE("Request to free a heap block"); + + /* Update the page housekeeping */ + /* Update the housekeeping of the extra manager pages */ + page = manager->first_page; + while(page != NULL) + { + DBG_MM_BLUE("Checking page %i at %p", i++, page); + if(((uint8_t*) page < (uint8_t*) ptr) && ((uint8_t*) ptr < (uint8_t*) page + PICO_MEM_PAGE_SIZE)) + { + /* DBG_MM_RED("page < ptr < page + PICO_MEM_PAGE_SIZE"); */ + /* DBG_MM_RED("%p < %p < %p", (uint8_t*) page, (uint8_t*) ptr, (uint8_t*) page + PICO_MEM_PAGE_SIZE); */ + _pico_mem_free_and_merge_heap_block(page, generic_block); + _pico_mem_determine_max_free_space(page); + break; + } + + page = page->next_page; + } + } + else + { + DBG_MM_RED("ERROR: You tried to free a pointer from which the type ( heap block or slab object ) could not be determined!!"); + } +} + +/************************NEW***************************/ +static void _pico_mem_reset_slab_statistics(void) +{ + slab_size_statistics[0] = 0; + slab_size_statistics[1] = 0; + slab_size_statistics[2] = 0; +} + +static size_t _pico_mem_determine_slab_size(size_t len) +{ + DBG_MM_YELLOW("Determining slab size to use, request for %u bytes", len); + if (len > slab_sizes[1]) + { + slab_size_statistics[2]++; + if(slab_size_statistics[2] > 3) + { + _pico_mem_reset_slab_statistics(); + if(slab_size_global != slab_sizes[2]) + { + slab_size_global = slab_sizes[2]; + } + } + + if(slab_size_global != slab_sizes[2]) + { + DBG_MM_RED("Using slab size %u, but we have to use a slab size of %u for the request of %u bytes", slab_size_global, slab_sizes[2], len); + return slab_sizes[2]; + } + } + else if(len > slab_sizes[0]) + { + slab_size_statistics[1]++; + if (slab_size_statistics[1] > 3) + { + _pico_mem_reset_slab_statistics(); + if(slab_size_global != slab_sizes[1]) + { + slab_size_global = slab_sizes[1]; + } + } + + if(len > slab_size_global) + { + DBG_MM_RED("Using slab size %u, but we have to use a slab size of %u for the request of %u bytes", slab_size_global, slab_sizes[1], len); + return slab_sizes[1]; + } + } + else + { + slab_size_statistics[0]++; + if (slab_size_statistics[0] > 3) + { + _pico_mem_reset_slab_statistics(); + if(slab_size_global != slab_sizes[0]) + { + slab_size_global = slab_sizes[0]; + } + } + } + + DBG_MM_GREEN("Using slab size %u", slab_size_global); + return slab_size_global; +} +/************************NEW***************************/ + +/* + * This method will be called by the picotcp stack to allocate new memory. + * If the requested size is bigger than the threshold of a slab object, + * then the manager will try to find an appropriate slab object and return a pointer + * to the beginning of the data in that slab object. + * + * If no slab objects could be found, or the requested size is less then the threshold + * of a slab object, the manager will try to allocate a heap block and return a pointer + * to the beginning of the data of that heap block. + * + * If still no memory could be found, then the manager will check again if the + * requested size is smaller than the threshold of a slab object. + * If so, the manager will try to find a slab object again but now ignoring the threshold. + * By doing so, there will be a large amount of internal fragmentation, but at least the + * memory request could be fulfilled. + * + * In any other case, the manager will return NULL. + */ +void*pico_mem_zalloc(size_t len) +{ + struct pico_mem_page*page; + void*returnCandidate; + uint32_t pagenr; + void *ret; + + DBG_MM_YELLOW("===> pico_mem_zalloc(%i) called", len); + len += (len % 4 == 0) ? 0 : 4 - len % 4; + DBG_MM_YELLOW("Aligned size: %i", len); + + if(manager == NULL) + { + DBG_MM_RED("Invalid alloc, a memory manager hasn't been instantiated yet!"); + return NULL; + } + + if(len > PICO_MAX_SLAB_SIZE) + { + DBG_MM_RED("Invalid alloc, the size you requested is larger than the maximum slab size! (%uB>%uB)", len, PICO_MAX_SLAB_SIZE); + return NULL; + } + + /* /////// FIND SLAB OBJECTS ///////// */ + if(len >= PICO_MIN_SLAB_SIZE) + { + /* feed the size into a statistic engine that determines the slabsize to use */ + /* DBG_MM_RED("Placeholder: determine correct slab size to use!"); */ + len = _pico_mem_determine_slab_size(len); + ret = _pico_mem_find_slab(len); + if(ret != NULL) return ret; + + /* No slab object could be found. => Init new page? */ + + DBG_MM_BLUE("No free slab found, trying to create a new page (Used size = %u, max size = %u)", manager->used_size, manager->size); + if(manager->used_size + PICO_MEM_PAGE_SIZE <= manager->size) + { + struct pico_mem_page*newpage = pico_zalloc(PICO_MEM_PAGE_SIZE); + if(newpage != NULL) + { + manager->used_size += PICO_MEM_PAGE_SIZE; + DBG_MM_BLUE("Created new page at %p -> used size = %u", newpage, manager->used_size); + _pico_mem_init_page(newpage, len); + /* Return pointer to first slab in that page */ + return _pico_mem_find_slab(len); /* Find the new slab object! */ + } + else + { + DBG_MM_RED("Not enough space to allocate a new page, even though the max size hasn't been reached yet!"); + return NULL; + } + } + else + { + DBG_MM_RED("Not enough space to allocate a new page!"); + return NULL; + } + } + + /* /////// FIND HEAP BLOCKS ///////// */ + if(len < PICO_MEM_MINIMUM_OBJECT_SIZE) + len = PICO_MEM_MINIMUM_OBJECT_SIZE; + + DBG_MM_BLUE("Searching for heap space of length %u now.", len); + + pagenr = 1; + page = manager->first_page; + + /* The algorithm to find a heap block is based on first fit. */ + /* But when the internal fragmentation is too big, the block is split. */ + while(page != NULL) + { + /* DBG_MM_RED("Max free space in page %i = %i bytes", pagecounter+1, page->heap.max_free_space); */ + DBG_MM_BLUE("Max free space in page %u = %uB (page=%p)", pagenr, page->heap_max_free_space, page); + if(len <= page->heap_max_free_space) + { + return _pico_mem_find_heap_block(page, len); + } + + pagenr++; + page = page->next_page; + } + /* No free heap block could be found, try to alloc a new page */ + DBG_MM_BLUE("No free heap block found, trying to create a new page (Used size = %u, max size = %u)", manager->used_size, manager->size); + if(manager->used_size + PICO_MEM_PAGE_SIZE <= manager->size) + { + struct pico_mem_page*newpage = pico_zalloc(PICO_MEM_PAGE_SIZE); + if(newpage != NULL) + { + manager->used_size += PICO_MEM_PAGE_SIZE; + DBG_MM_BLUE("Created new page at %p -> used size = %u", newpage, manager->used_size); + /* TODO: Careful, if the current slabsize is determined in another way, this needs to change too */ + _pico_mem_init_page(newpage, slab_size_global); + returnCandidate = _pico_mem_find_heap_block(newpage, len); + if(returnCandidate != NULL) + return returnCandidate; + } + else + { + DBG_MM_RED("Not enough space to allocate a new page, even though the max size hasn't been reached yet!"); + return NULL; + } + } + + /* DBG_MM_RED("NO HEAP BLOCK FOUND!"); */ + + /* /////// TRY TO FIND NEW SLAB OBJECT, BUT INCREASE SIZE ///////// */ + DBG_MM_RED("TRYING TO FIND FREE SLAB OBJECT WITH DANGER OF LARGE INTERNAL FRAGMENTATION"); + /* TODO: Careful, if the current slabsize is determined in another way, this needs to change too */ + return _pico_mem_find_slab(slab_size_global); +} +/* + * This method frees heap space used in the manager page, or in one of the extra manager pages + */ +void pico_mem_page0_free(void*ptr) +{ + struct pico_mem_block*node = ptr; + struct pico_mem_manager_extra*heap_page; + /* Uncomment for debugging! */ + /* int i = 0; */ + + /* TODO: should be able to merge free neighbouring blocks (??) */ + DBG_MM_YELLOW("page0_free called"); + + node--; + node->internals.heap_block.free = HEAP_BLOCK_FREE; + /* Update the housekeeping of the extra manager pages */ + heap_page = manager->manager_extra; + while(heap_page != NULL) + { + DBG_MM_BLUE("Checking extra heap page %i at %p", i++, heap_page); + if(((uint8_t*) heap_page < (uint8_t*) ptr) && ((uint8_t*) ptr < (uint8_t*) heap_page + PICO_MEM_PAGE_SIZE)) + { + /* DBG_MM_RED("heap_page < ptr < heap_page + PICO_MEM_PAGE_SIZE"); */ + /* DBG_MM_RED("%p < %p < %p", (uint8_t*) heap_page, (uint8_t*) ptr, (uint8_t*) heap_page + PICO_MEM_PAGE_SIZE); */ + heap_page->blocks--; + DBG_MM_BLUE("Updating heap page housekeeping: %u->%u used blocks", heap_page->blocks + 1, heap_page->blocks); + break; + } + + heap_page = heap_page->next; + } + DBG_MM_GREEN("Heap block (located in %s) succesfully freed", (i != -1) ? "main manager page" : "extra manager page"); +} + +/* + * This cleanup function must be called externally at downtime moments. A system timestamp must be passed to the function. + * All pages and extra manager pages will be checked. If they are empty, the timestamp of the page will be updated. If the + * page has been empty for a time longer than PICO_MEM_PAGE_LIFETIME, the page is returned to the system's control, and all + * the housekeeping is updated. + */ +void pico_mem_cleanup(uint32_t timestamp) +{ + struct pico_mem_slab_node*slab_node; + struct pico_tree_node*tree_node; + struct pico_mem_block*slab_block; + struct pico_mem_page*next_page; + struct pico_mem_page*prev_page; + struct pico_mem_page*page; + struct pico_mem_manager_extra*heap_page; + struct pico_mem_manager_extra*next; + struct pico_mem_manager_extra*prev_heap_page; + uint8_t*byteptr; + int pagenr = 1; + int i; + + DBG_MM_YELLOW("Starting cleanup with timestamp %u", timestamp); + /* Iterate over all pages */ + page = manager->first_page; + prev_page = NULL; + while(page != NULL) + { + DBG_MM_BLUE("Checking page %i at %p", pagenr, page); + /* Check the timestamp of the page. If it doesn't have one (0), update it with the new timestamp if the page is completely empty. */ + if(page->timestamp == 0) + { + if((page->heap_max_size == page->heap_max_free_space) && (page->slabs_free == page->slabs_max)) + { + DBG_MM_BLUE("Page %i empty, updating timestamp", pagenr); + page->timestamp = timestamp; + } + } + /* If the timestamp is old enough, remove the page and all its slabs. This means we have to: */ + /* > Remove all slabs out of the RB tree */ + /* > Update the page list */ + /* > Return the page to the system's control */ + /* > Update manager housekeeping */ + else if(timestamp > page->timestamp) + { + if(timestamp - page->timestamp > PICO_MEM_PAGE_LIFETIME) + { + DBG_MM_BLUE("Page %i is empty and has exceeded the lifetime (%u > lifetime=%u)", pagenr, timestamp - page->timestamp, PICO_MEM_PAGE_LIFETIME); + /* Remove all the slabs out of the RB tree */ + byteptr = (uint8_t*) page + sizeof(struct pico_mem_page); /* byteptr points to the start of the heap (a pico_mem_block), after page housekeeping */ + byteptr += sizeof(struct pico_mem_block); /* jump over pico_mem_block, containing the housekeeping for the heap space */ + byteptr += page->heap_max_size; /* jump over heap space, byteptr now points to the start of the slabs */ + slab_block = (struct pico_mem_block*) byteptr; + slab_node = slab_block->internals.slab_block.slab_node; + /* The corresponding tree_node */ + tree_node = pico_tree_findNode(&manager->tree, slab_node); + for(i = 0; i < page->slabs_max; i++) + { + DBG_MM("Removing slab %i at %p", i, slab_block); + if(slab_node->prev == NULL && slab_node->next == NULL) + { + DBG_MM("This node is the last node in the tree_node, removing tree_node"); + /* slab_node is the last node in the tree leaf, delete it */ + + + /* pico_tree_delete(&manager->tree, slab_node); */ + manager_tree_delete(&manager->tree, slab_node); + + + } + else if(slab_node->prev == NULL) + { + DBG_MM("This node is the first node in the linked list, adjusting tree_node"); + tree_node->keyValue = slab_node->next; + slab_node->next->prev = NULL; + } + else if(slab_node->next == NULL) + { + DBG_MM("This node is the last node in the linked list"); + slab_node->prev->next = NULL; + } + else + { + DBG_MM("This node is neither the first, nor the last node in the list"); + slab_node->prev->next = slab_node->next; + slab_node->next->prev = slab_node->prev; + } + + pico_mem_page0_free(slab_node); + byteptr = (uint8_t*) slab_block + sizeof(struct pico_mem_block); /* byteptr points to the start of the slab data, after the housekeeping */ + byteptr += page->slab_size; /* jump over the slab data, byteptr now points to the start of the next slab block */ + slab_block = (struct pico_mem_block*) byteptr; + slab_node = slab_block->internals.slab_block.slab_node; + } + /* Update the page list */ + if(prev_page == NULL) /* prev_page == NULL when pagenr=1, or when previous pages were deleted */ + { + DBG_MM("Updating page list, manager->first_page = page->next_page"); + manager->first_page = page->next_page; + } + else + { + DBG_MM("Updating page list, prev_page->next_page = page->next_page"); + prev_page->next_page = page->next_page; + } + + /* Return the page to the system's control */ + next_page = page->next_page; + DBG_MM("Freeing page, manager used size = %u", manager->used_size); + pico_free(page); + /* Update the manager housekeeping */ + manager->used_size -= PICO_MEM_PAGE_SIZE; + DBG_MM("Freed page, manager used size = %u, down from %u", manager->used_size, manager->used_size + PICO_MEM_PAGE_SIZE); + /* ITERATION */ + page = next_page; + pagenr++; + continue; + } + else + { + DBG_MM_BLUE("Page %i is empty, but has not exceeded the lifetime (%u < lifetime=%u)", pagenr, timestamp - page->timestamp, PICO_MEM_PAGE_LIFETIME); + } + } + else /* timestamp < page->timestamp */ + { + DBG_MM_RED("Page %i is empty, but the system timestamp < page timestamp! (%u<%u)", pagenr, timestamp, page->timestamp); + DBG_MM_RED("Updating page %i timestamp!", pagenr); + page->timestamp = timestamp; + } + + pagenr++; + prev_page = page; + page = page->next_page; + } + /* Check all extra manager pages if they are empty */ + heap_page = manager->manager_extra; + prev_heap_page = NULL; + pagenr = 1; + while(heap_page != NULL) + { + DBG_MM_BLUE("Checking extra manager page %i at %p", pagenr, heap_page); + if(heap_page->timestamp == 0) + { + if( heap_page->blocks == 0 ) + { + DBG_MM_BLUE("Extra manager page %i empty, updating timestamp", pagenr); + heap_page->timestamp = timestamp; + } + } + else if(timestamp > heap_page->timestamp) + { + if(timestamp - heap_page->timestamp > PICO_MEM_PAGE_LIFETIME) + { + DBG_MM_BLUE("Extra manager page %i empty and has exceeded the lifetime (%u > lifetime=%u)", pagenr, timestamp - heap_page->timestamp, PICO_MEM_PAGE_LIFETIME); + /* Update the page list */ + if(prev_heap_page == NULL) + { + DBG_MM("Updating page list, manager->manager_extra = heap_page->next"); + manager->manager_extra = heap_page->next; + } + else + { + DBG_MM("Updating page list, prev_heap_page->next = heap_page->next"); + prev_heap_page->next = heap_page->next; + } + + /* Return the page to the system's control */ + next = heap_page->next; + DBG_MM("Freeing page, manager used size = %u", manager->used_size); + pico_free(heap_page); + /* Update the manager housekeeping */ + manager->used_size -= PICO_MEM_PAGE_SIZE; + DBG_MM("Freed page, manager used size = %u, down from %u", manager->used_size, manager->used_size + PICO_MEM_PAGE_SIZE); + /* ITERATION */ + heap_page = next; + pagenr++; + continue; + } + else + { + DBG_MM_BLUE("Page %i is empty, but has not exceeded the lifetime (%u < lifetime=%u)", pagenr, timestamp - heap_page->timestamp, PICO_MEM_PAGE_LIFETIME); + } + } + else + { + DBG_MM_RED("Page %i is empty, but the system timestamp < page timestamp! (%u<%u)", pagenr, timestamp, heap_page->timestamp); + DBG_MM_RED("Updating page %i timestamp!", pagenr); + heap_page->timestamp = timestamp; + } + + /* ITERATION */ + pagenr++; + prev_heap_page = heap_page; + heap_page = heap_page->next; + } +} + + + + + + +#ifdef PICO_SUPPORT_MM_PROFILING +/*********************************************************************************************************************** + *********************************************************************************************************************** + MEMORY PROFILING FUNCTIONS + *********************************************************************************************************************** + ***********************************************************************************************************************/ + +static struct pico_mem_manager*manager_profile; + +static void _pico_mem_print_tree(struct pico_tree_node*root) +{ + struct pico_mem_slab_node*iterator; + int j; + + if (root == &LEAF || root == NULL) + { + DBG_MM("No tree nodes at this time.\n"); + return; + } + + iterator = (struct pico_mem_slab_node*) root->keyValue; + DBG_MM("Tree node for size %u:\n", iterator->slab->internals.slab_block.page->slab_size); + j = 0; + while(iterator != NULL) + { + DBG_MM("\tSlab_node %i at %p:\n", j, iterator); + DBG_MM("\t\tPrev:%p\n", iterator->prev); + DBG_MM("\t\tNext:%p\n", iterator->next); + DBG_MM("\t\tSlab:%p\n", iterator->slab); + j++; + iterator = iterator->next; + } + if(root->leftChild != &LEAF && root->leftChild != NULL) + _pico_mem_print_tree(root->leftChild); + + if(root->rightChild != &LEAF && root->rightChild != NULL) + _pico_mem_print_tree(root->rightChild); +} + +void pico_mem_profile_scan_data() +{ + if(manager == NULL) + { + DBG_MM("No memory manager instantiated!\n"); + } + else + { + int manager_pages = 0; + int pages = 0; + int counter = 0; + struct pico_mem_manager_extra*heap_page; + struct pico_mem_page*page; + uint8_t*byteptr; + struct pico_mem_block*mem_block; + + DBG_MM("Memory manager: %uB/%uB in use\n", manager->used_size, manager->size); + _pico_mem_print_tree(manager->tree.root); + + /* Iterate over every extra manager page: */ + heap_page = manager->manager_extra; + while(heap_page != NULL) + { + manager_pages++; + DBG_MM("Extra manager page %i:\n\tBlocks in use: %u\n\tTimestamp: %u\n", manager_pages, heap_page->blocks, heap_page->timestamp); + heap_page = heap_page->next; + } + /* Iterate over every page: */ + pages = (manager->used_size / PICO_MEM_PAGE_SIZE) - manager_pages - 1; + page = manager->first_page; + while(page != NULL) + { + counter++; + DBG_MM("Page %i/%i:\n\tSlabsize: %u\n\tSlabs free: %u/%u\n\tTimestamp: %u\n", counter, pages, page->slab_size, page->slabs_free, page->slabs_max, page->timestamp); + byteptr = (uint8_t*) page + sizeof(struct pico_mem_page); + mem_block = (struct pico_mem_block*) byteptr; + DBG_MM("\tHeap:\n"); + while(mem_block->type == HEAP_BLOCK_TYPE) + { + DBG_MM("\t\tBlock: size %u, %s\n", mem_block->internals.heap_block.size, (mem_block->internals.heap_block.free == HEAP_BLOCK_FREE) ? "free" : "not free"); + byteptr = (uint8_t*) mem_block + sizeof(struct pico_mem_block); + byteptr += mem_block->internals.heap_block.size; + mem_block = (struct pico_mem_block*) byteptr; + } + page = page->next_page; + } + } +} + +void pico_mem_profile_collect_data(struct profiling_data*profiling_struct) +{ + struct pico_mem_block*mem_block; + uint8_t*byteptr; + + profiling_struct->free_heap_space = 0; + profiling_struct->free_slab_space = 0; + profiling_struct->used_heap_space = 0; + profiling_struct->used_slab_space = 0; + if(manager != NULL) + { + struct pico_mem_page*page = manager->first_page; + while(page != NULL) + { + profiling_struct->free_slab_space += page->slab_size * page->slabs_free; + profiling_struct->used_slab_space += page->slab_size * page->slabs_max; + + byteptr = (uint8_t*) page + sizeof(struct pico_mem_page); + mem_block = (struct pico_mem_block*) byteptr; + + while(mem_block->type == HEAP_BLOCK_TYPE) + { + if(mem_block->internals.heap_block.free == HEAP_BLOCK_FREE) + { + profiling_struct->free_heap_space += mem_block->internals.heap_block.size; + } + else + { + /* dbg("Block: size=%u\n", mem_block->internals.heap_block.size); */ + profiling_struct->used_heap_space += mem_block->internals.heap_block.size; + } + + byteptr += sizeof(struct pico_mem_block) + mem_block->internals.heap_block.size; + mem_block = (struct pico_mem_block*) byteptr; + } + page = page->next_page; + } + } +} + +uint32_t pico_mem_profile_used_size() +{ + if(manager != NULL) + { + return manager->used_size; + } + else + { + return 0; + } +} + +struct pico_mem_manager*pico_mem_profile_manager() +{ + return manager; +} +#endif /* PICO_SUPPORT_MM_PROFILING */ + diff --git a/ext/picotcp/modules/pico_mm.h b/ext/picotcp/modules/pico_mm.h new file mode 100644 index 0000000..29366d0 --- /dev/null +++ b/ext/picotcp/modules/pico_mm.h @@ -0,0 +1,98 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + Authors: Gustav Janssens, Jonas Van Nieuwenberg, Sam Van Den Berge + *********************************************************************/ + + +#ifndef _INCLUDE_PICO_MM +#define _INCLUDE_PICO_MM + +#include "pico_config.h" + +/* + * Memory init function, this will create a memory manager instance + * A memory_manager page will be created, along with one page of memory + * Memory can be asked for via the pico_mem_zalloc function + * More memory will be allocated to the memory manager according to its needs + * A maximum amount of memory of uint32_t memsize can be allocated + */ +void pico_mem_init(uint32_t memsize); +/* + * Memory deinit function, this will free all memory occupied by the current + * memory manager instance. + */ +void pico_mem_deinit(void); +/* + * Zero-initialized malloc function, will reserve a memory segment of length uint32_t len + * This memory will be quickly allocated in a slab of fixed size if possible + * or less optimally in the heap for a small variable size + * The fixed size of the slabs can be changed dynamically via a statistics engine + */ +void*pico_mem_zalloc(size_t len); +/* + * Free function, free a block of memory pointed to by ptr. + * Unused memory is only returned to the system's control by pico_mem_cleanup + */ +void pico_mem_free(void*ptr); +/* + * This cleanup function will be provided by the memory manager + * It can be called during processor downtime + * This function will return unused pages to the system's control + * Pages are unused if they no longer contain slabs or heap, and they have been idle for a longer time + */ +void pico_mem_cleanup(uint32_t timestamp); + + + +#ifdef PICO_SUPPORT_MM_PROFILING +/*********************************************************************************************************************** + *********************************************************************************************************************** + MEMORY PROFILING FUNCTIONS + *********************************************************************************************************************** + ***********************************************************************************************************************/ +/* General info struct */ +struct profiling_data +{ + uint32_t free_heap_space; + uint32_t free_slab_space; + uint32_t used_heap_space; + uint32_t used_slab_space; +}; + +/* + * This function fills up a struct with used and free slab and heap space in the memory manager + * The user is responsible for resource managment + */ +void pico_mem_profile_collect_data(struct profiling_data*profiling_page_struct); + +/* + * This function prints the general structure of the memory manager + * Printf in this function can be rerouted to send this data over a serial port, or to write it away to memory + */ +void pico_mem_profile_scan_data(void); + +/* + * This function returns the total size that the manager has received from the system + * This can give an indication of the total system resource commitment, but keep in mind that + * there can be many free blocks in this "used" size + * Together with pico_mem_profile_collect_data, this can give a good estimation of the total + * resource commitment + */ +uint32_t pico_mem_profile_used_size(void); + +/* + * This function returns a pointer to page 0, the main memory manager housekeeping (struct pico_mem_manager). + * This can be used to collect data about the memory in user defined functions. + * Use with care! + */ +void*pico_mem_profile_manager(void); + +/* + * paramter manager is a pointer to a struct pico_mem_manager + */ +void pico_mem_init_profiling(void*manager, uint32_t memsize); +#endif /* PICO_SUPPORT_MM_PROFILING */ + +#endif /* _INCLUDE_PICO_MM */ diff --git a/ext/picotcp/modules/pico_nat.c b/ext/picotcp/modules/pico_nat.c new file mode 100644 index 0000000..19f85e0 --- /dev/null +++ b/ext/picotcp/modules/pico_nat.c @@ -0,0 +1,576 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + . + + Authors: Kristof Roelants, Brecht Van Cauwenberghe, + Simon Maes, Philippe Mariman + *********************************************************************/ + +#include "pico_stack.h" +#include "pico_frame.h" +#include "pico_tcp.h" +#include "pico_udp.h" +#include "pico_ipv4.h" +#include "pico_addressing.h" +#include "pico_nat.h" + +#ifdef PICO_SUPPORT_IPV4 +#ifdef PICO_SUPPORT_NAT + +#define nat_dbg(...) do {} while(0) +/* #define nat_dbg dbg */ +#define PICO_NAT_TIMEWAIT 240000 /* msec (4 mins) */ + +#define PICO_NAT_INBOUND 0 +#define PICO_NAT_OUTBOUND 1 + +struct pico_nat_tuple { + uint8_t proto; + uint16_t conn_active : 11; + uint16_t portforward : 1; + uint16_t rst : 1; + uint16_t syn : 1; + uint16_t fin_in : 1; + uint16_t fin_out : 1; + uint16_t src_port; + uint16_t dst_port; + uint16_t nat_port; + struct pico_ip4 src_addr; + struct pico_ip4 dst_addr; + struct pico_ip4 nat_addr; +}; + +static struct pico_ipv4_link *nat_link = NULL; + +static int nat_cmp_natport(struct pico_nat_tuple *a, struct pico_nat_tuple *b) +{ + + if (a->nat_port < b->nat_port) + return -1; + + if (a->nat_port > b->nat_port) + + return 1; + + return 0; + +} + +static int nat_cmp_srcport(struct pico_nat_tuple *a, struct pico_nat_tuple *b) +{ + + if (a->src_port < b->src_port) + return -1; + + if (a->src_port > b->src_port) + + return 1; + + return 0; + +} + +static int nat_cmp_proto(struct pico_nat_tuple *a, struct pico_nat_tuple *b) +{ + if (a->proto < b->proto) + return -1; + + if (a->proto > b->proto) + return 1; + + return 0; +} + +static int nat_cmp_address(struct pico_nat_tuple *a, struct pico_nat_tuple *b) +{ + return pico_ipv4_compare(&a->src_addr, &b->src_addr); +} + +static int nat_cmp_inbound(void *ka, void *kb) +{ + struct pico_nat_tuple *a = ka, *b = kb; + int cport = nat_cmp_natport(a, b); + if (cport) + return cport; + + return nat_cmp_proto(a, b); +} + + +static int nat_cmp_outbound(void *ka, void *kb) +{ + struct pico_nat_tuple *a = ka, *b = kb; + int caddr, cport; + + caddr = nat_cmp_address(a, b); + if (caddr) + return caddr; + + cport = nat_cmp_srcport(a, b); + + if (cport) + return cport; + + return nat_cmp_proto(a, b); +} + +PICO_TREE_DECLARE(NATOutbound, nat_cmp_outbound); +PICO_TREE_DECLARE(NATInbound, nat_cmp_inbound); + +void pico_ipv4_nat_print_table(void) +{ + struct pico_nat_tuple *t = NULL; + struct pico_tree_node *index = NULL; + (void)t; + + nat_dbg("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"); + nat_dbg("+ NAT table +\n"); + nat_dbg("+------------------------------------------------------------------------------------------------------------------------+\n"); + nat_dbg("+ src_addr | src_port | dst_addr | dst_port | nat_addr | nat_port | proto | conn active | FIN1 | FIN2 | SYN | RST | FORW +\n"); + nat_dbg("+------------------------------------------------------------------------------------------------------------------------+\n"); + + pico_tree_foreach(index, &NATOutbound) + { + t = index->keyValue; + nat_dbg("+ %08X | %05u | %08X | %05u | %08X | %05u | %03u | %03u | %u | %u | %u | %u | %u +\n", + long_be(t->src_addr.addr), t->src_port, long_be(t->dst_addr.addr), t->dst_port, long_be(t->nat_addr.addr), t->nat_port, + t->proto, t->conn_active, t->fin_in, t->fin_out, t->syn, t->rst, t->portforward); + } + nat_dbg("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"); +} + +/* + 2 options: + find on nat_port and proto + find on src_addr, src_port and proto + zero the unused parameters + */ +static struct pico_nat_tuple *pico_ipv4_nat_find_tuple(uint16_t nat_port, struct pico_ip4 *src_addr, uint16_t src_port, uint8_t proto) +{ + struct pico_nat_tuple *found = NULL, test = { + 0 + }; + + test.nat_port = nat_port; + test.src_port = src_port; + test.proto = proto; + if (src_addr) + test.src_addr = *src_addr; + + if (nat_port) + found = pico_tree_findKey(&NATInbound, &test); + else + found = pico_tree_findKey(&NATOutbound, &test); + + if (found) + return found; + else + return NULL; +} + +int pico_ipv4_nat_find(uint16_t nat_port, struct pico_ip4 *src_addr, uint16_t src_port, uint8_t proto) +{ + struct pico_nat_tuple *t = NULL; + + t = pico_ipv4_nat_find_tuple(nat_port, src_addr, src_port, proto); + if (t) + return 1; + else + return 0; +} + +static struct pico_nat_tuple *pico_ipv4_nat_add(struct pico_ip4 dst_addr, uint16_t dst_port, struct pico_ip4 src_addr, uint16_t src_port, + struct pico_ip4 nat_addr, uint16_t nat_port, uint8_t proto) +{ + struct pico_nat_tuple *t = PICO_ZALLOC(sizeof(struct pico_nat_tuple)); + if (!t) { + pico_err = PICO_ERR_ENOMEM; + return NULL; + } + + t->dst_addr = dst_addr; + t->dst_port = dst_port; + t->src_addr = src_addr; + t->src_port = src_port; + t->nat_addr = nat_addr; + t->nat_port = nat_port; + t->proto = proto; + t->conn_active = 1; + t->portforward = 0; + t->rst = 0; + t->syn = 0; + t->fin_in = 0; + t->fin_out = 0; + + if (pico_tree_insert(&NATOutbound, t)) { + PICO_FREE(t); + return NULL; + } + + if (pico_tree_insert(&NATInbound, t)) { + pico_tree_delete(&NATOutbound, t); + PICO_FREE(t); + return NULL; + } + + return t; +} + +static int pico_ipv4_nat_del(uint16_t nat_port, uint8_t proto) +{ + struct pico_nat_tuple *t = NULL; + t = pico_ipv4_nat_find_tuple(nat_port, NULL, 0, proto); + if (t) { + pico_tree_delete(&NATOutbound, t); + pico_tree_delete(&NATInbound, t); + PICO_FREE(t); + } + + return 0; +} + +static struct pico_trans *pico_nat_generate_tuple_trans(struct pico_ipv4_hdr *net, struct pico_frame *f) +{ + struct pico_trans *trans = NULL; + switch (net->proto) { + case PICO_PROTO_TCP: + { + struct pico_tcp_hdr *tcp = (struct pico_tcp_hdr *)f->transport_hdr; + trans = (struct pico_trans *)&tcp->trans; + break; + } + case PICO_PROTO_UDP: + { + struct pico_udp_hdr *udp = (struct pico_udp_hdr *)f->transport_hdr; + trans = (struct pico_trans *)&udp->trans; + break; + } + case PICO_PROTO_ICMP4: + /* XXX: implement */ + break; + } + return trans; +} + +static struct pico_nat_tuple *pico_ipv4_nat_generate_tuple(struct pico_frame *f) +{ + struct pico_trans *trans = NULL; + struct pico_ipv4_hdr *net = (struct pico_ipv4_hdr *)f->net_hdr; + uint16_t nport = 0; + uint8_t retry = 32; + + /* generate NAT port */ + do { + uint32_t rand = pico_rand(); + nport = (uint16_t) (rand & 0xFFFFU); + nport = (uint16_t)((nport % (65535 - 1024)) + 1024U); + nport = short_be(nport); + + if (pico_is_port_free(net->proto, nport, NULL, &pico_proto_ipv4)) + break; + } while (--retry); + + if (!retry) + return NULL; + + trans = pico_nat_generate_tuple_trans(net, f); + if(!trans) + return NULL; + + return pico_ipv4_nat_add(net->dst, trans->dport, net->src, trans->sport, nat_link->address, nport, net->proto); + /* XXX return pico_ipv4_nat_add(nat_link->address, port, net->src, trans->sport, net->proto); */ +} + +static inline void pico_ipv4_nat_set_tcp_flags(struct pico_nat_tuple *t, struct pico_frame *f, uint8_t direction) +{ + struct pico_tcp_hdr *tcp = (struct pico_tcp_hdr *)f->transport_hdr; + if (tcp->flags & PICO_TCP_SYN) + t->syn = 1; + + if (tcp->flags & PICO_TCP_RST) + t->rst = 1; + + if ((tcp->flags & PICO_TCP_FIN) && (direction == PICO_NAT_INBOUND)) + t->fin_in = 1; + + if ((tcp->flags & PICO_TCP_FIN) && (direction == PICO_NAT_OUTBOUND)) + t->fin_out = 1; +} + +static int pico_ipv4_nat_sniff_session(struct pico_nat_tuple *t, struct pico_frame *f, uint8_t direction) +{ + struct pico_ipv4_hdr *net = (struct pico_ipv4_hdr *)f->net_hdr; + + switch (net->proto) { + case PICO_PROTO_TCP: + { + pico_ipv4_nat_set_tcp_flags(t, f, direction); + break; + } + + case PICO_PROTO_UDP: + t->conn_active = 1; + break; + + case PICO_PROTO_ICMP4: + /* XXX: implement */ + break; + + default: + return -1; + } + + return 0; +} + +static void pico_ipv4_nat_table_cleanup(pico_time now, void *_unused) +{ + struct pico_tree_node *index = NULL, *_tmp = NULL; + struct pico_nat_tuple *t = NULL; + IGNORE_PARAMETER(now); + IGNORE_PARAMETER(_unused); + nat_dbg("NAT: before table cleanup:\n"); + pico_ipv4_nat_print_table(); + + pico_tree_foreach_reverse_safe(index, &NATOutbound, _tmp) + { + t = index->keyValue; + switch (t->proto) + { + case PICO_PROTO_TCP: + if (t->portforward) + break; + else if (t->conn_active == 0 || t->conn_active > 360) /* conn active for > 24 hours */ + pico_ipv4_nat_del(t->nat_port, t->proto); + else if (t->rst || (t->fin_in && t->fin_out)) + t->conn_active = 0; + else + t->conn_active++; + + break; + + case PICO_PROTO_UDP: + if (t->portforward) + break; + else if (t->conn_active > 1) + pico_ipv4_nat_del(t->nat_port, t->proto); + else + t->conn_active++; + + break; + + case PICO_PROTO_ICMP4: + if (t->conn_active > 1) + pico_ipv4_nat_del(t->nat_port, t->proto); + else + t->conn_active++; + + default: + /* unknown protocol in NAT table, delete when it has existed NAT_TIMEWAIT */ + if (t->conn_active > 1) + pico_ipv4_nat_del(t->nat_port, t->proto); + else + t->conn_active++; + } + } + + nat_dbg("NAT: after table cleanup:\n"); + pico_ipv4_nat_print_table(); + pico_timer_add(PICO_NAT_TIMEWAIT, pico_ipv4_nat_table_cleanup, NULL); +} + +int pico_ipv4_port_forward(struct pico_ip4 nat_addr, uint16_t nat_port, struct pico_ip4 src_addr, uint16_t src_port, uint8_t proto, uint8_t flag) +{ + struct pico_nat_tuple *t = NULL; + struct pico_ip4 any_addr = { + 0 + }; + uint16_t any_port = 0; + + switch (flag) + { + case PICO_NAT_PORT_FORWARD_ADD: + t = pico_ipv4_nat_add(any_addr, any_port, src_addr, src_port, nat_addr, nat_port, proto); + if (!t) { + pico_err = PICO_ERR_EAGAIN; + return -1; + } + + t->portforward = 1; + break; + + case PICO_NAT_PORT_FORWARD_DEL: + return pico_ipv4_nat_del(nat_port, proto); + + default: + pico_err = PICO_ERR_EINVAL; + return -1; + } + + pico_ipv4_nat_print_table(); + return 0; +} + +int pico_ipv4_nat_inbound(struct pico_frame *f, struct pico_ip4 *link_addr) +{ + struct pico_nat_tuple *tuple = NULL; + struct pico_trans *trans = NULL; + struct pico_ipv4_hdr *net = (struct pico_ipv4_hdr *)f->net_hdr; + + if (!pico_ipv4_nat_is_enabled(link_addr)) + return -1; + + switch (net->proto) { +#ifdef PICO_SUPPORT_TCP + case PICO_PROTO_TCP: + { + struct pico_tcp_hdr *tcp = (struct pico_tcp_hdr *)f->transport_hdr; + trans = (struct pico_trans *)&tcp->trans; + tuple = pico_ipv4_nat_find_tuple(trans->dport, 0, 0, net->proto); + if (!tuple) + return -1; + + /* replace dst IP and dst PORT */ + net->dst = tuple->src_addr; + trans->dport = tuple->src_port; + /* recalculate CRC */ + tcp->crc = 0; + tcp->crc = short_be(pico_tcp_checksum_ipv4(f)); + break; + } +#endif +#ifdef PICO_SUPPORT_UDP + case PICO_PROTO_UDP: + { + struct pico_udp_hdr *udp = (struct pico_udp_hdr *)f->transport_hdr; + trans = (struct pico_trans *)&udp->trans; + tuple = pico_ipv4_nat_find_tuple(trans->dport, 0, 0, net->proto); + if (!tuple) + return -1; + + /* replace dst IP and dst PORT */ + net->dst = tuple->src_addr; + trans->dport = tuple->src_port; + /* recalculate CRC */ + udp->crc = 0; + udp->crc = short_be(pico_udp_checksum_ipv4(f)); + break; + } +#endif + case PICO_PROTO_ICMP4: + /* XXX reimplement */ + break; + + default: + nat_dbg("NAT ERROR: inbound NAT on erroneous protocol\n"); + return -1; + } + + pico_ipv4_nat_sniff_session(tuple, f, PICO_NAT_INBOUND); + net->crc = 0; + net->crc = short_be(pico_checksum(net, f->net_len)); + + nat_dbg("NAT: inbound translation {dst.addr, dport}: {%08X,%u} -> {%08X,%u}\n", + tuple->nat_addr.addr, short_be(tuple->nat_port), tuple->src_addr.addr, short_be(tuple->src_port)); + + return 0; +} + +int pico_ipv4_nat_outbound(struct pico_frame *f, struct pico_ip4 *link_addr) +{ + struct pico_nat_tuple *tuple = NULL; + struct pico_trans *trans = NULL; + struct pico_ipv4_hdr *net = (struct pico_ipv4_hdr *)f->net_hdr; + + if (!pico_ipv4_nat_is_enabled(link_addr)) + return -1; + + switch (net->proto) { +#ifdef PICO_SUPPORT_TCP + case PICO_PROTO_TCP: + { + struct pico_tcp_hdr *tcp = (struct pico_tcp_hdr *)f->transport_hdr; + trans = (struct pico_trans *)&tcp->trans; + tuple = pico_ipv4_nat_find_tuple(0, &net->src, trans->sport, net->proto); + if (!tuple) + tuple = pico_ipv4_nat_generate_tuple(f); + + /* replace src IP and src PORT */ + net->src = tuple->nat_addr; + trans->sport = tuple->nat_port; + /* recalculate CRC */ + tcp->crc = 0; + tcp->crc = short_be(pico_tcp_checksum_ipv4(f)); + break; + } +#endif +#ifdef PICO_SUPPORT_UDP + case PICO_PROTO_UDP: + { + struct pico_udp_hdr *udp = (struct pico_udp_hdr *)f->transport_hdr; + trans = (struct pico_trans *)&udp->trans; + tuple = pico_ipv4_nat_find_tuple(0, &net->src, trans->sport, net->proto); + if (!tuple) + tuple = pico_ipv4_nat_generate_tuple(f); + + /* replace src IP and src PORT */ + net->src = tuple->nat_addr; + trans->sport = tuple->nat_port; + /* recalculate CRC */ + udp->crc = 0; + udp->crc = short_be(pico_udp_checksum_ipv4(f)); + break; + } +#endif + case PICO_PROTO_ICMP4: + /* XXX reimplement */ + break; + + default: + nat_dbg("NAT ERROR: outbound NAT on erroneous protocol\n"); + return -1; + } + + pico_ipv4_nat_sniff_session(tuple, f, PICO_NAT_OUTBOUND); + net->crc = 0; + net->crc = short_be(pico_checksum(net, f->net_len)); + + nat_dbg("NAT: outbound translation {src.addr, sport}: {%08X,%u} -> {%08X,%u}\n", + tuple->src_addr.addr, short_be(tuple->src_port), tuple->nat_addr.addr, short_be(tuple->nat_port)); + + return 0; +} + +int pico_ipv4_nat_enable(struct pico_ipv4_link *link) +{ + if (link == NULL) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + nat_link = link; + pico_timer_add(PICO_NAT_TIMEWAIT, pico_ipv4_nat_table_cleanup, NULL); + return 0; +} + +int pico_ipv4_nat_disable(void) +{ + nat_link = NULL; + return 0; +} + +int pico_ipv4_nat_is_enabled(struct pico_ip4 *link_addr) +{ + if (!nat_link) + return 0; + + if (nat_link->address.addr != link_addr->addr) + return 0; + + return 1; +} + +#endif +#endif diff --git a/ext/picotcp/modules/pico_nat.h b/ext/picotcp/modules/pico_nat.h new file mode 100644 index 0000000..5237501 --- /dev/null +++ b/ext/picotcp/modules/pico_nat.h @@ -0,0 +1,90 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + . + + Authors: Kristof Roelants, Simon Maes, Brecht Van Cauwenberghe + *********************************************************************/ + +#ifndef INCLUDE_PICO_NAT +#define INCLUDE_PICO_NAT +#include "pico_frame.h" + +#define PICO_NAT_PORT_FORWARD_DEL 0 +#define PICO_NAT_PORT_FORWARD_ADD 1 + +#ifdef PICO_SUPPORT_NAT +void pico_ipv4_nat_print_table(void); +int pico_ipv4_nat_find(uint16_t nat_port, struct pico_ip4 *src_addr, uint16_t src_port, uint8_t proto); +int pico_ipv4_port_forward(struct pico_ip4 nat_addr, uint16_t nat_port, struct pico_ip4 src_addr, uint16_t src_port, uint8_t proto, uint8_t flag); + +int pico_ipv4_nat_inbound(struct pico_frame *f, struct pico_ip4 *link_addr); +int pico_ipv4_nat_outbound(struct pico_frame *f, struct pico_ip4 *link_addr); +int pico_ipv4_nat_enable(struct pico_ipv4_link *link); +int pico_ipv4_nat_disable(void); +int pico_ipv4_nat_is_enabled(struct pico_ip4 *link_addr); +#else + +#define pico_ipv4_nat_print_table() do {} while(0) +static inline int pico_ipv4_nat_inbound(struct pico_frame *f, struct pico_ip4 *link_addr) +{ + (void)f; + (void)link_addr; + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} + +static inline int pico_ipv4_nat_outbound(struct pico_frame *f, struct pico_ip4 *link_addr) +{ + (void)f; + (void)link_addr; + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} + +static inline int pico_ipv4_nat_enable(struct pico_ipv4_link *link) +{ + (void)link; + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} + +static inline int pico_ipv4_nat_disable(void) +{ + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} + +static inline int pico_ipv4_nat_is_enabled(struct pico_ip4 *link_addr) +{ + (void)link_addr; + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} + +static inline int pico_ipv4_nat_find(uint16_t nat_port, struct pico_ip4 *src_addr, uint16_t src_port, uint8_t proto) +{ + (void)nat_port; + (void)src_addr; + (void)src_port; + (void)proto; + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} + +static inline int pico_ipv4_port_forward(struct pico_ip4 nat_addr, uint16_t nat_port, struct pico_ip4 src_addr, uint16_t src_port, uint8_t proto, uint8_t flag) +{ + (void)nat_addr; + (void)nat_port; + (void)src_addr; + (void)src_port; + (void)proto; + (void)flag; + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} +#endif + +#endif /* _INCLUDE_PICO_NAT */ + diff --git a/ext/picotcp/modules/pico_olsr.c b/ext/picotcp/modules/pico_olsr.c new file mode 100644 index 0000000..e5300a9 --- /dev/null +++ b/ext/picotcp/modules/pico_olsr.c @@ -0,0 +1,1143 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + Authors: Daniele Lacamera + ********************************************************************/ + +#include "pico_stack.h" +#include "pico_config.h" +#include "pico_device.h" +#include "pico_ipv4.h" +#include "pico_arp.h" +#include "pico_socket.h" +#include "pico_olsr.h" +#ifdef PICO_SUPPORT_OLSR +#define DGRAM_MAX_SIZE (100 - 28) +#define MAX_OLSR_MEM (4 * DGRAM_MAX_SIZE) +#define olsr_dbg(...) do {} while(0) + +int OOM(void); + + +#define OLSR_HELLO_INTERVAL ((uint32_t)5000) +#define OLSR_TC_INTERVAL ((uint32_t)9000) +#define OLSR_MAXJITTER ((uint32_t)(OLSR_HELLO_INTERVAL >> 2)) +static const struct pico_ip4 HOST_NETMASK = { + 0xffffffff +}; +#ifndef MIN +# define MIN(a, b) (a < b ? a : b) +#endif + +#define fresher(a, b) ((a > b) || ((b - a) > 32768)) + + +static uint16_t msg_counter; /* Global message sequence number */ + +/* Objects */ +struct olsr_dev_entry +{ + struct olsr_dev_entry *next; + struct pico_device *dev; + uint16_t pkt_counter; +}; + + +/* OLSR Protocol */ +#define OLSRMSG_HELLO 0xc9 +#define OLSRMSG_MID 0x03 +#define OLSRMSG_TC 0xca + +#define OLSRLINK_SYMMETRIC 0x06 +#define OLSRLINK_UNKNOWN 0x08 +#define OLSRLINK_MPR 0x0a + + +#define OLSR_PORT (short_be((uint16_t)698)) + + +/* Headers */ + +PACKED_STRUCT_DEF olsr_link +{ + uint8_t link_code; + uint8_t reserved; + uint16_t link_msg_size; +}; + +PACKED_STRUCT_DEF olsr_neighbor +{ + uint32_t addr; + uint8_t lq; + uint8_t nlq; + uint16_t reserved; +}; + +PACKED_STRUCT_DEF olsr_hmsg_hello +{ + uint16_t reserved; + uint8_t htime; + uint8_t willingness; +}; + +PACKED_STRUCT_DEF olsr_hmsg_tc +{ + uint16_t ansn; + uint16_t reserved; +}; + + +PACKED_STRUCT_DEF olsrmsg +{ + uint8_t type; + uint8_t vtime; + uint16_t size; + struct pico_ip4 orig; + uint8_t ttl; + uint8_t hop; + uint16_t seq; +}; + +PACKED_STRUCT_DEF olsrhdr +{ + uint16_t len; + uint16_t seq; +}; + + + +/* Globals */ +static struct pico_socket *udpsock = NULL; +uint16_t my_ansn = 0; +static struct olsr_route_entry *Local_interfaces = NULL; +static struct olsr_dev_entry *Local_devices = NULL; + +static struct olsr_dev_entry *olsr_get_deventry(struct pico_device *dev) +{ + struct olsr_dev_entry *cur = Local_devices; + while(cur) { + if (cur->dev == dev) + return cur; + + cur = cur->next; + } + return NULL; +} + +struct olsr_route_entry *olsr_get_ethentry(struct pico_device *vif) +{ + struct olsr_route_entry *cur = Local_interfaces; + while(cur) { + if (cur->iface == vif) + return cur; + + cur = cur->next; + } + return NULL; +} + +static struct olsr_route_entry *get_next_hop(struct olsr_route_entry *dst) +{ + struct olsr_route_entry *hop = dst; + while(hop) { + /* olsr_dbg("Finding next hop to %08x m=%d\n", hop->destination.addr, hop->metric); */ + if(hop->metric <= 1) + return hop; + + hop = hop->gateway; + } + return NULL; +} + +static inline void olsr_route_add(struct olsr_route_entry *el) +{ + /* char dest[16],nxdest[16]; */ + struct olsr_route_entry *nexthop; + + if(!el) + return; + + my_ansn++; + + nexthop = get_next_hop(el); + if (el->gateway && nexthop && (nexthop->destination.addr != el->destination.addr)) { + /* 2-hops route or more */ + el->next = el->gateway->children; + el->gateway->children = el; + el->link_type = OLSRLINK_MPR; + olsr_dbg("[OLSR] ----------Adding route to %08x via %08x metric %d\n", el->destination.addr, nexthop->destination.addr, el->metric); + pico_ipv4_route_add(el->destination, HOST_NETMASK, nexthop->destination, (int) el->metric, NULL); + } else if (el->iface) { + /* neighbor */ + struct olsr_route_entry *ei = olsr_get_ethentry(el->iface); + struct pico_ip4 no_gw = { + 0U + }; + if (el->link_type == OLSRLINK_UNKNOWN) + el->link_type = OLSRLINK_SYMMETRIC; + + if (ei) { + el->next = ei->children; + ei->children = el; + } + + olsr_dbg("[OLSR] ----------Adding neighbor %08x iface %s\n", el->destination.addr, el->iface->name); + + pico_ipv4_route_add(el->destination, HOST_NETMASK, no_gw, 1, pico_ipv4_link_by_dev(el->iface)); + } +} + +static inline void olsr_route_del(struct olsr_route_entry *r) +{ + struct olsr_route_entry *cur, *prev = NULL, *lst; + /* olsr_dbg("[OLSR] DELETING route..................\n"); */ + my_ansn++; + if (r->gateway) { + lst = r->gateway->children; + } else if (r->iface) { + lst = olsr_get_ethentry(r->iface); + } else { + lst = Local_interfaces; + } + + cur = lst, prev = NULL; + while(cur) { + if (cur == r) { + /* found */ + if (r->gateway) { + pico_ipv4_route_del(r->destination, HOST_NETMASK, r->metric); + olsr_dbg("[OLSR] Deleting route to %08x \n", r->destination.addr); + if (!prev) + r->gateway->children = r->next; + else + prev->next = r->next; + } + + while (r->children) { + olsr_route_del(r->children); + /* Orphans must die. */ + /* PICO_FREE(r->children); */ + } + return; + } + + prev = cur; + cur = cur->next; + } +} + +static struct olsr_route_entry *get_route_by_address(struct olsr_route_entry *lst, uint32_t ip) +{ + struct olsr_route_entry *found; + if(lst) { + if (lst->destination.addr == ip) { + return lst; + } + + /* recursive function, could be dangerous for stack overflow if a lot of routes are available... */ + found = get_route_by_address(lst->children, ip); + if (found) + return found; + + found = get_route_by_address(lst->next, ip); + if (found) + return found; + } + + return NULL; +} + +#define OLSR_C_SHIFT (uint32_t)4 /* 1/16 */ +#define DEFAULT_VTIME 288UL + +static uint8_t seconds2olsr(uint32_t seconds) +{ + uint16_t a, b; + /* olsr_dbg("seconds=%u\n", (uint16_t)seconds); */ + + if (seconds > 32767) + seconds = 32767; + + /* find largest b such as seconds/C >= 2^b */ + for (b = 1; b <= 0x0fu; b++) { + if ((uint16_t)(seconds * 16u) < (1u << b)) { + b--; + break; + } + } + /* olsr_dbg("b=%u", b); */ + /* compute the expression 16*(T/(C*(2^b))-1), which may not be a + integer, and round it up. This results in the value for 'a' */ + /* a = (T / ( C * (1u << b) ) ) - 1u; */ + { + uint16_t den = ((uint16_t)(1u << b) >> 4u); + /* olsr_dbg(" den=%u ", den); */ + if (den == 0) + { + /* olsr_dbg("div by 0!\n"); */ + den = 1u; + } + + a = (uint16_t)(((uint16_t)seconds / den) - (uint16_t)1); + } + /* a = a & 0x0Fu; */ + + /* olsr_dbg(" a=%u\n", a); */ + + /* if 'a' is equal to 16: increment 'b' by one, and set 'a' to 0 */ + if (16u == a) { + b++; + a = 0u; + } + + return (uint8_t)((a << 4u) + b); +} + +static uint32_t olsr2seconds(uint8_t olsr) +{ + uint8_t a, b; + uint16_t seconds; + /* olsr_dbg("olsr format: %u -- ", olsr); */ + a = (olsr >> 4) & 0xFu; + b = olsr & 0x0f; + /* olsr_dbg("o2s: a=%u, b=%u\n", a,b); */ + if (b < 4) + seconds = (uint16_t)(((1u << b) + (uint16_t)(((uint16_t)(a << b) >> 4u) & 0xFu)) >> OLSR_C_SHIFT); + else + seconds = (uint16_t)(((1u << b) + (uint16_t)(((uint16_t)(a << (b - 4))) & 0xFu)) >> OLSR_C_SHIFT); + + /* olsr_dbg("o2s: seconds: %u\n", seconds); */ + return seconds; +} + +static void olsr_garbage_collector(struct olsr_route_entry *sublist) +{ + if(!sublist) + return; + + if (sublist->time_left <= 0) { + olsr_route_del(sublist); + PICO_FREE(sublist); + return; + } else { + /* sublist->time_left -= 2u; */ + sublist->time_left -= 8u; + } + + olsr_garbage_collector(sublist->children); + olsr_garbage_collector(sublist->next); +} + +struct olsr_fwd_pkt +{ + void *buf; + uint16_t len; + struct pico_device *pdev; +}; + +static uint32_t buffer_mem_used = 0U; + +static void olsr_process_out(pico_time now, void *arg) +{ + struct olsr_fwd_pkt *p = (struct olsr_fwd_pkt *)arg; + struct pico_ip4 bcast; + struct pico_ipv4_link *addr; + struct olsr_dev_entry *pdev = Local_devices; + struct olsrhdr *ohdr; + (void)now; + + /* Send the thing out */ + ohdr = (struct olsrhdr *)p->buf; + ohdr->len = short_be((uint16_t)p->len); + + if (p->pdev) { + struct olsr_dev_entry *odev = olsr_get_deventry(p->pdev); + if (!odev) { + goto out_free; + } + + addr = pico_ipv4_link_by_dev(p->pdev); + if (!addr) + goto out_free; + + ohdr->seq = short_be((uint16_t)(odev->pkt_counter)++); + if (addr->address.addr) + bcast.addr = (addr->netmask.addr & addr->address.addr) | (~addr->netmask.addr); + else + bcast.addr = 0xFFFFFFFFu; + + if ( 0 > pico_socket_sendto(udpsock, p->buf, p->len, &bcast, OLSR_PORT)) { + olsr_dbg("olsr send\n"); + } + } else { + while(pdev) { + ohdr->seq = short_be((uint16_t)(pdev->pkt_counter++)); + addr = pico_ipv4_link_by_dev(pdev->dev); + if (!addr) + continue; + + if (addr->address.addr) + bcast.addr = (addr->netmask.addr & addr->address.addr) | (~addr->netmask.addr); + else + bcast.addr = 0xFFFFFFFFu; + + if ( 0 > pico_socket_sendto(udpsock, p->buf, p->len, &bcast, OLSR_PORT)) { + olsr_dbg("olsr send\n"); + } + + pdev = pdev->next; + } + } + +out_free: + PICO_FREE(p->buf); /* XXX <-- broken? */ + buffer_mem_used -= DGRAM_MAX_SIZE; + PICO_FREE(p); +} + +static void olsr_scheduled_output(uint32_t when, void *buffer, uint16_t size, struct pico_device *pdev) +{ + struct olsr_fwd_pkt *p; + /* olsr_dbg("Scheduling olsr packet, type:%s, size: %x\n", when == OLSR_HELLO_INTERVAL?"HELLO":"TC", size); */ + if ((buffer_mem_used + DGRAM_MAX_SIZE) > MAX_OLSR_MEM) { + PICO_FREE(buffer); + return; + } + + p = PICO_ZALLOC(sizeof(struct olsr_fwd_pkt)); + if (!p) { + OOM(); + PICO_FREE(buffer); + return; + } + + p->buf = buffer; + p->len = size; + p->pdev = pdev; + buffer_mem_used += DGRAM_MAX_SIZE; + pico_timer_add(1 + when - ((pico_rand() % OLSR_MAXJITTER)), &olsr_process_out, p); +} + + +static void refresh_routes(void) +{ + struct olsr_route_entry *local; + struct olsr_dev_entry *icur = Local_devices; + + /* Refresh local entries */ + /* Step 1: set zero expire time for local addresses and neighbors*/ + local = Local_interfaces; + while(local) { + local = local->next; + } + /* Step 2: refresh timer for entries that are still valid. + * Add new entries. + */ + while(icur) { + struct pico_ipv4_link *lnk = NULL; + do { + lnk = pico_ipv4_link_by_dev_next(icur->dev, lnk); + if (!lnk) break; + + local = olsr_get_ethentry(icur->dev); + if (local) { + local->time_left = (OLSR_HELLO_INTERVAL << 2); + } else if (lnk) { + struct olsr_route_entry *e = PICO_ZALLOC(sizeof (struct olsr_route_entry)); + if (!e) { + olsr_dbg("olsr: adding local route entry\n"); + OOM(); + return; + } + + e->destination.addr = lnk->address.addr; /* Always pick the first address */ + e->time_left = (OLSR_HELLO_INTERVAL << 2); + e->iface = icur->dev; + e->metric = 0; + e->lq = 0xFF; + e->nlq = 0xFF; + e->next = Local_interfaces; + Local_interfaces = e; + } + } while (lnk); + + /* disabled if device type != eth */ + /* refresh_neighbors(icur->dev); */ + icur = icur->next; + } +} + +static uint32_t olsr_build_hello_neighbors(uint8_t *buf, uint32_t size, struct olsr_route_entry **bookmark) +{ + uint32_t ret = 0; + struct olsr_route_entry *local, *neighbor; + struct olsr_neighbor *dst = (struct olsr_neighbor *) buf; + uint32_t total_link_size = sizeof(struct olsr_neighbor) + sizeof(struct olsr_link); + local = Local_interfaces; + while (local) { + neighbor = local->children; + if (*bookmark) { + while ((neighbor) && *bookmark != neighbor) + neighbor = neighbor->next; + } + + while (neighbor) { + struct olsr_link *li = (struct olsr_link *) (buf + ret); + + if ((size - ret) < total_link_size) { + /* Incomplete list, new datagram needed. */ + *bookmark = neighbor; + return ret; + } + + li->link_code = neighbor->link_type; + li->reserved = 0; + li->link_msg_size = short_be((uint16_t)total_link_size); + ret += (uint32_t)sizeof(struct olsr_link); + dst = (struct olsr_neighbor *) (buf + ret); + dst->addr = neighbor->destination.addr; + dst->nlq = neighbor->nlq; + dst->lq = neighbor->lq; + dst->reserved = 0; + ret += (uint32_t)sizeof(struct olsr_neighbor); + neighbor = neighbor->next; + } + local = local->next; + } + *bookmark = NULL; /* All the list was visited, no more dgrams needed */ + return ret; +} + +static uint32_t olsr_build_tc_neighbors(uint8_t *buf, uint32_t size, struct olsr_route_entry **bookmark) +{ + uint32_t ret = 0; + struct olsr_route_entry *local, *neighbor; + struct olsr_neighbor *dst = (struct olsr_neighbor *) buf; + local = Local_interfaces; + while (local) { + neighbor = local->children; + if (*bookmark) { + while ((neighbor) && *bookmark != neighbor) + neighbor = neighbor->next; + } + + while (neighbor) { + if (size - ret < sizeof(struct olsr_neighbor)) { + /* Incomplete list, new datagram needed. */ + *bookmark = neighbor; + return ret; + } + + dst->addr = neighbor->destination.addr; + dst->nlq = neighbor->nlq; + dst->lq = neighbor->lq; + dst->reserved = 0; + ret += (uint32_t)sizeof(struct olsr_neighbor); + dst = (struct olsr_neighbor *) (buf + ret); + neighbor = neighbor->next; + } + local = local->next; + } + *bookmark = NULL; /* All the list was visited, no more dgrams needed */ + return ret; +} + +static uint32_t olsr_build_mid(uint8_t *buf, uint32_t size, struct pico_device *excluded) +{ + uint32_t ret = 0; + struct olsr_route_entry *local; + struct pico_ip4 *dst = (struct pico_ip4 *) buf; + local = Local_interfaces; + while (local) { + if (local->iface != excluded) { + dst->addr = local->destination.addr; + ret += (uint32_t)sizeof(uint32_t); + dst = (struct pico_ip4 *) (buf + ret); + if (ret >= size) + return (uint32_t)(ret - sizeof(uint32_t)); + } + + local = local->next; + } + return ret; +} + + +static void olsr_compose_tc_dgram(struct pico_device *pdev, struct pico_ipv4_link *ep) +{ + struct olsrmsg *msg_tc, *msg_mid; + uint32_t size = 0, r; + struct olsr_route_entry *last_neighbor = NULL; + uint8_t *dgram; + struct olsr_hmsg_tc *tc; + do { + dgram = PICO_ZALLOC(DGRAM_MAX_SIZE); + if (!dgram) { + OOM(); + return; + } + + size = (uint32_t)sizeof(struct olsrhdr); + ep = pico_ipv4_link_by_dev(pdev); + if (!ep) { + PICO_FREE(dgram); + return; + } + + + if (!last_neighbor) { + /* MID Message */ + + msg_mid = (struct olsrmsg *)(dgram + size); + size += (uint32_t)sizeof(struct olsrmsg); + msg_mid->type = OLSRMSG_MID; + msg_mid->vtime = seconds2olsr(60); + msg_mid->orig.addr = ep->address.addr; + msg_mid->ttl = 0xFF; + msg_mid->hop = 0; + msg_mid->seq = short_be(msg_counter++); + r = olsr_build_mid(dgram + size, DGRAM_MAX_SIZE - size, pdev); + if (r == 0) { + size -= (uint32_t)sizeof(struct olsrmsg); + } else { + if ((size + r) > DGRAM_MAX_SIZE) + return; + + size += r; + msg_mid->size = short_be((uint16_t)(sizeof(struct olsrmsg) + r)); + } + } + + if (size + sizeof(struct olsrmsg) > DGRAM_MAX_SIZE) + return; + + msg_tc = (struct olsrmsg *) (dgram + size); + size += (uint32_t)sizeof(struct olsrmsg); + msg_tc->type = OLSRMSG_TC; + msg_tc->vtime = seconds2olsr(DEFAULT_VTIME); + msg_tc->orig.addr = ep->address.addr; + msg_tc->ttl = 0xFF; + msg_tc->hop = 0; + msg_tc->seq = short_be(msg_counter++); + tc = (struct olsr_hmsg_tc *)(dgram + size); + size += (uint32_t)sizeof(struct olsr_hmsg_tc); + if (size > DGRAM_MAX_SIZE) + return; + + tc->ansn = short_be(my_ansn); + r = olsr_build_tc_neighbors(dgram + size, DGRAM_MAX_SIZE - size, &last_neighbor); + size += r; + msg_tc->size = short_be((uint16_t)(sizeof(struct olsrmsg) + sizeof(struct olsr_hmsg_tc) + r)); + olsr_scheduled_output(OLSR_TC_INTERVAL, dgram, (uint16_t)size, pdev ); + } while(last_neighbor); +} + +static void olsr_compose_hello_dgram(struct pico_device *pdev, struct pico_ipv4_link *ep) +{ + struct olsrmsg *msg_hello; + uint32_t size = 0, r; + struct olsr_route_entry *last_neighbor = NULL; + uint8_t *dgram; + struct olsr_hmsg_hello *hello; + /* HELLO Message */ + do { + dgram = PICO_ZALLOC(DGRAM_MAX_SIZE); + if (!dgram) { + OOM(); + return; + } + + size = (uint32_t)sizeof(struct olsrhdr); + msg_hello = (struct olsrmsg *) (dgram + size); + size += (uint32_t)sizeof(struct olsrmsg); + msg_hello->type = OLSRMSG_HELLO; + msg_hello->vtime = seconds2olsr(DEFAULT_VTIME); + msg_hello->orig.addr = ep->address.addr; + msg_hello->ttl = 1; + msg_hello->hop = 0; + msg_hello->seq = short_be(msg_counter++); + hello = (struct olsr_hmsg_hello *)(dgram + size); + size += (uint32_t)sizeof(struct olsr_hmsg_hello); + hello->reserved = 0; + hello->htime = seconds2olsr(OLSR_HELLO_INTERVAL); + hello->htime = 0x05; /* Todo: find and define values */ + hello->willingness = 0x07; + if (DGRAM_MAX_SIZE > size) { + r = olsr_build_hello_neighbors(dgram + size, DGRAM_MAX_SIZE - size, &last_neighbor); + if (r == 0) { + /* olsr_dbg("Building hello message\n"); */ + PICO_FREE(dgram); + return; + } + } + + size += r; + msg_hello->size = short_be((uint16_t)(sizeof(struct olsrmsg) + sizeof(struct olsr_hmsg_hello) + r)); + olsr_scheduled_output(OLSR_HELLO_INTERVAL, dgram, (uint16_t)size, pdev ); + } while(last_neighbor); +} + +static void olsr_make_dgram(struct pico_device *pdev, int full) +{ + struct pico_ipv4_link *ep; + ep = pico_ipv4_link_by_dev(pdev); + if (!ep) { + return; + } + + if (!full) { + olsr_compose_hello_dgram(pdev, ep); + } else { + olsr_compose_tc_dgram(pdev, ep); + } /*if full */ + +} + +/* Old code was relying on ethernet arp requests */ +#define arp_storm(...) do {} while(0) + +static void recv_mid(uint8_t *buffer, uint32_t len, struct olsr_route_entry *origin) +{ + uint32_t parsed = 0; + uint32_t *address; + struct olsr_route_entry *e; + + if (len % sizeof(uint32_t)) /*drop*/ + return; + + while (len > parsed) { + address = (uint32_t *)(buffer + parsed); + e = get_route_by_address(Local_interfaces, *address); + if (!e) { + e = PICO_ZALLOC(sizeof(struct olsr_route_entry)); + if (!e) { + olsr_dbg("olsr allocating route\n"); + OOM(); + return; + } + + e->time_left = (OLSR_HELLO_INTERVAL << 2); + e->destination.addr = *address; + e->gateway = origin; + /* e->iface = origin->iface; */ + e->iface = NULL; + e->metric = (uint16_t)(origin->metric + 1u); + e->lq = origin->lq; + e->nlq = origin->nlq; + olsr_route_add(e); + arp_storm(&e->destination); + } else if (e->metric > (origin->metric + 1)) { + olsr_route_del(e); + e->metric = (uint16_t)(origin->metric + 1u); + e->gateway = origin; + e->time_left = (OLSR_HELLO_INTERVAL << 2); + olsr_route_add(e); + } + + parsed += (uint32_t)sizeof(uint32_t); + } +} + +/* static void recv_hello(uint8_t *buffer, uint32_t len, struct olsr_route_entry *origin) */ +static void recv_hello(uint8_t *buffer, uint32_t len, struct olsr_route_entry *origin, uint16_t hops) +{ + struct olsr_link *li; + struct olsr_route_entry *e; + uint32_t parsed = 0; + struct olsr_neighbor *neigh; + + if (!origin) + return; + + /* Don't parse hello messages that were forwarded */ + if (hops > 0 || origin->metric > 1) + return; + + if (pico_ipv4_link_get(&origin->destination)) + return; + + + while (len > parsed) { + li = (struct olsr_link *) buffer; + neigh = (struct olsr_neighbor *)(buffer + parsed + sizeof(struct olsr_link)); + parsed += short_be(li->link_msg_size); + e = get_route_by_address(Local_interfaces, neigh->addr); + if (!e) { + e = PICO_ZALLOC(sizeof(struct olsr_route_entry)); + if (!e) { + olsr_dbg("olsr allocating route\n"); + OOM(); + return; + } + + e->time_left = (OLSR_HELLO_INTERVAL << 2); + e->destination.addr = neigh->addr; + e->gateway = origin; + e->iface = NULL; + e->metric = (uint16_t)(origin->metric + hops + 1); + e->link_type = OLSRLINK_UNKNOWN; + e->lq = MIN(origin->lq, neigh->lq); + e->nlq = MIN(origin->nlq, neigh->nlq); + olsr_route_add(e); + arp_storm(&e->destination); + } else if ((e->gateway != origin) && (origin->metric > 1) && (e->metric > (origin->metric + hops + 1))) { + olsr_route_del(e); + e->metric = (uint16_t)(origin->metric + hops + 1); + e->gateway = origin; + e->time_left = (OLSR_HELLO_INTERVAL << 2); + olsr_route_add(e); + } else { + e->time_left = (OLSR_HELLO_INTERVAL << 2); + } + } +} + +/* static uint32_t reconsider_topology(uint8_t *buf, uint32_t size, struct olsr_route_entry *e) */ +static uint32_t reconsider_topology(uint8_t *buf, uint32_t size, struct olsr_route_entry *e) +{ + struct olsr_hmsg_tc *tc = (struct olsr_hmsg_tc *) buf; + uint16_t new_ansn = short_be(tc->ansn); + uint32_t parsed = sizeof(struct olsr_hmsg_tc); + struct olsr_route_entry *rt; + struct olsr_neighbor *n; + uint32_t retval = 0; + + if (!e->advertised_tc) + retval = 1; + + if (e->advertised_tc && fresher(new_ansn, e->ansn)) + { + PICO_FREE(e->advertised_tc); /* <--- XXX check invalid free? */ + e->advertised_tc = NULL; + retval = 1; + } + + /* Ignore self packets */ + if (pico_ipv4_link_get(&e->destination)) + return 0; + + if (!e->advertised_tc) { + e->advertised_tc = PICO_ZALLOC(size); + if (!e->advertised_tc) { + OOM(); + olsr_dbg("Allocating forward packet\n"); + return 0; + } + + memcpy(e->advertised_tc, buf, size); + e->ansn = new_ansn; + while (parsed < size) { + n = (struct olsr_neighbor *) (buf + parsed); + parsed += (uint32_t)sizeof(struct olsr_neighbor); + rt = get_route_by_address(Local_interfaces, n->addr); + if (rt && (rt->gateway == e)) { + /* Refresh existing node */ + rt->time_left = e->time_left; + } else if (!rt || (rt->metric > (e->metric + 1)) || (rt->nlq < n->nlq)) { + if (!rt) { + rt = PICO_ZALLOC(sizeof (struct olsr_route_entry)); + if (!rt) { + OOM(); + return retval; + } + + rt->destination.addr = n->addr; + rt->link_type = OLSRLINK_UNKNOWN; + } else { + olsr_route_del(rt); + } + + rt->iface = e->iface; + rt->gateway = e; + rt->metric = (uint16_t)(e->metric + 1); + rt->lq = n->lq; /* 0xff */ + rt->nlq = n->nlq; /* 0xff */ + rt->time_left = e->time_left; /* 256 */ + olsr_route_add(rt); + } + } + /* olsr_dbg("Routes changed...\n"); */ + } + + return retval; +} + + +static void olsr_recv(uint8_t *buffer, uint32_t len) +{ + struct olsrmsg *msg; + struct olsrhdr *oh = (struct olsrhdr *) buffer; + struct olsr_route_entry *ancestor; + uint32_t parsed = 0; + uint16_t outsize = 0; + uint8_t *datagram; + + if (len != short_be(oh->len)) { + return; + } + + /* RFC 3626, section 3.4, if a packet is too small, it is silently discarded */ + if (len < 16) { + return; + } + + parsed += (uint32_t)sizeof(struct olsrhdr); + + datagram = PICO_ZALLOC(DGRAM_MAX_SIZE); + if (!datagram) { + OOM(); + return; + } + + outsize = (uint16_t) (outsize + (sizeof(struct olsrhdr))); + /* Section 1: parsing received messages. */ + while (len > parsed) { + struct olsr_route_entry *origin; + msg = (struct olsrmsg *) (buffer + parsed); + origin = get_route_by_address(Local_interfaces, msg->orig.addr); + + if(pico_ipv4_link_find(&msg->orig) != NULL) { + /* olsr_dbg("rebound\n"); */ + parsed += short_be(msg->size); + continue; + } + + /* OLSR's TTL expired. */ + if (msg->ttl < 1u) { + parsed += short_be(msg->size); + continue; + } + + if (!origin) { + if (msg->hop == 0) { + struct olsr_route_entry *e = PICO_ZALLOC(sizeof (struct olsr_route_entry)); + if (!e) { + parsed += short_be(msg->size); + OOM(); + break; + } + + e->destination.addr = msg->orig.addr; + e->link_type = OLSRLINK_SYMMETRIC; + e->time_left = (OLSR_HELLO_INTERVAL << 2); + e->iface = Local_devices->dev; + e->gateway = olsr_get_ethentry(e->iface); + e->metric = 1; + e->lq = 0xFF; + e->nlq = 0xFF; + olsr_route_add(e); + } + + parsed += short_be(msg->size); + continue; + } + + /* We know this is a Master host and a neighbor */ + origin->link_type = OLSRLINK_MPR; + origin->time_left = olsr2seconds(msg->vtime); + switch(msg->type) { + case OLSRMSG_HELLO: + ancestor = olsr_get_ethentry(origin->iface); + if ((origin->metric > 1) && ancestor) { + olsr_route_del(origin); + origin->gateway = ancestor; + origin->metric = 1; + olsr_route_add(origin); + } + + recv_hello(buffer + (uint32_t)parsed + (uint32_t)sizeof(struct olsrmsg) + (uint32_t)sizeof(struct olsr_hmsg_hello), + (uint32_t) ((short_be(msg->size) - (sizeof(struct olsrmsg))) - (uint32_t)sizeof(struct olsr_hmsg_hello)), + origin, msg->hop); + msg->ttl = 0; + break; + case OLSRMSG_MID: + if ((origin->seq != 0) && (!fresher(short_be(msg->seq), origin->seq))) { + msg->ttl = 0; + } else { + recv_mid(buffer + parsed + sizeof(struct olsrmsg), (uint32_t)(short_be(msg->size) - (sizeof(struct olsrmsg))), origin); + /* olsr_dbg("MID forwarded from origin %08x (seq: %u)\n", long_be(msg->orig.addr), short_be(msg->seq)); */ + origin->seq = short_be(msg->seq); + } + + break; + case OLSRMSG_TC: + if(!pico_ipv4_link_find(&origin->destination)) { + reconsider_topology(buffer + parsed + sizeof(struct olsrmsg), (uint32_t)(short_be(msg->size) - (sizeof(struct olsrmsg))), origin); + if ((origin->seq != 0) && (!fresher(short_be(msg->seq), origin->seq))) { + msg->ttl = 0; + } else { + /* olsr_dbg("TC forwarded from origin %08x (seq: %u)\n", long_be(msg->orig.addr), short_be(msg->seq)); */ + origin->seq = short_be(msg->seq); + } + } + + break; + default: + PICO_FREE(datagram); + return; + } + + if (msg->ttl > 1) { + msg->hop++; + msg->ttl--; + memcpy(datagram + outsize, msg, short_be(msg->size)); + outsize = (uint16_t)(outsize + short_be(msg->size)); + } + + parsed += short_be(msg->size); + } + /* Section 2: forwarding parsed messages that got past the filter. */ + if ((outsize > sizeof(struct olsrhdr))) { + /* Finalize FWD packet */ + olsr_scheduled_output(OLSR_MAXJITTER, datagram, outsize, NULL); + } else { + /* Nothing to forward. */ + PICO_FREE(datagram); + } +} + +static void wakeup(uint16_t ev, struct pico_socket *s) +{ + unsigned char *recvbuf; + int r = 0; + struct pico_ip4 ANY = { + 0 + }; + uint16_t port = OLSR_PORT; + recvbuf = PICO_ZALLOC(DGRAM_MAX_SIZE); + if (!recvbuf) { + OOM(); + return; + } + + if (ev & PICO_SOCK_EV_RD) { + r = pico_socket_recv(s, recvbuf, DGRAM_MAX_SIZE); + if (r > 0) + olsr_recv(recvbuf, (uint32_t)r); + } + + if (ev == PICO_SOCK_EV_ERR) { + pico_socket_close(udpsock); + udpsock = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_UDP, &wakeup); + if (udpsock) + pico_socket_bind(udpsock, &ANY, &port); + } + + PICO_FREE(recvbuf); +} + +static void olsr_hello_tick(pico_time when, void *unused) +{ + struct olsr_dev_entry *d; + (void)when; + (void)unused; + olsr_garbage_collector(Local_interfaces); + refresh_routes(); + d = Local_devices; + while(d) { + olsr_make_dgram(d->dev, 0); + d = d->next; + } + pico_timer_add(OLSR_HELLO_INTERVAL, &olsr_hello_tick, NULL); +} + +static void olsr_tc_tick(pico_time when, void *unused) +{ + struct olsr_dev_entry *d; + (void)when; + (void)unused; + d = Local_devices; + while(d) { + olsr_make_dgram(d->dev, 1); + d = d->next; + } + pico_timer_add(OLSR_TC_INTERVAL, &olsr_tc_tick, NULL); +} + + +/* Public interface */ + +void pico_olsr_init(void) +{ + struct pico_ip4 ANY = { + 0 + }; + uint16_t port = OLSR_PORT; + olsr_dbg("OLSR initialized.\n"); + if (!udpsock) { + udpsock = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_UDP, &wakeup); + if (udpsock) + pico_socket_bind(udpsock, &ANY, &port); + } + + pico_timer_add(pico_rand() % 100, &olsr_hello_tick, NULL); + pico_timer_add(pico_rand() % 900, &olsr_tc_tick, NULL); +} + + +int OOM(void) +{ + volatile int c = 3600; + c++; + c++; + c++; + return -1; +} + +int pico_olsr_add(struct pico_device *dev) +{ + struct pico_ipv4_link *lnk = NULL; + struct olsr_dev_entry *od; + + + if (!dev) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* olsr_dbg("OLSR: Adding device %s\n", dev->name); */ + od = PICO_ZALLOC(sizeof(struct olsr_dev_entry)); + if (!od) { + pico_err = PICO_ERR_ENOMEM; + /* OOM(); */ + return -1; + } + + od->dev = dev; + od->next = Local_devices; + Local_devices = od; + + do { + char ipaddr[20]; + lnk = pico_ipv4_link_by_dev_next(dev, lnk); + if (lnk) { + struct olsr_route_entry *e = PICO_ZALLOC(sizeof(struct olsr_route_entry)); + /* olsr_dbg("OLSR: Found IP address %08x\n", long_be(lnk->address.addr)); */ + pico_ipv4_to_string(ipaddr, (lnk->address.addr)); + /* olsr_dbg("OLSR: Found IP address %s\n", ipaddr); */ + if (!e) { + olsr_dbg("olsr allocating route\n"); + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + e->destination.addr = lnk->address.addr; + e->link_type = OLSRLINK_SYMMETRIC; + e->time_left = (OLSR_HELLO_INTERVAL << 2); + e->gateway = NULL; + e->children = NULL; + e->iface = dev; + e->metric = 0; + e->lq = 0xFF; + e->nlq = 0xFF; + e->next = Local_interfaces; + Local_interfaces = e; + + } + } while(lnk); + + return 0; +} + +#endif diff --git a/ext/picotcp/modules/pico_olsr.h b/ext/picotcp/modules/pico_olsr.h new file mode 100644 index 0000000..11c3bf0 --- /dev/null +++ b/ext/picotcp/modules/pico_olsr.h @@ -0,0 +1,32 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + Authors: Daniele Lacamera + *********************************************************************/ +#ifndef PICO_OLSR_H +#define PICO_OLSR_H + + +/* Objects */ +struct olsr_route_entry +{ + struct olsr_route_entry *next; + uint32_t time_left; + struct pico_ip4 destination; + struct olsr_route_entry *gateway; + struct pico_device *iface; + uint16_t metric; + uint8_t link_type; + struct olsr_route_entry *children; + uint16_t ansn; + uint16_t seq; + uint8_t lq, nlq; + uint8_t *advertised_tc; +}; + + +void pico_olsr_init(void); +int pico_olsr_add(struct pico_device *dev); +struct olsr_route_entry *olsr_get_ethentry(struct pico_device *vif); +#endif diff --git a/ext/picotcp/modules/pico_posix.c b/ext/picotcp/modules/pico_posix.c new file mode 100644 index 0000000..4820b12 --- /dev/null +++ b/ext/picotcp/modules/pico_posix.c @@ -0,0 +1,99 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + Authors: Andrei Carp, Maarten Vandersteegen + *********************************************************************/ + +#ifdef PICO_SUPPORT_THREADING + +#include +#include +#include "pico_config.h" + +/* POSIX mutex implementation */ +void *pico_mutex_init(void) +{ + pthread_mutex_t *m; + m = (pthread_mutex_t *)PICO_ZALLOC(sizeof(pthread_mutex_t)); + pthread_mutex_init(m, NULL); + return m; +} + +void pico_mutex_destroy(void *mux) +{ + PICO_FREE(mux); + mux = NULL; +} + +void pico_mutex_lock(void *mux) +{ + if (mux == NULL) return; + + pthread_mutex_t *m = (pthread_mutex_t *)mux; + pthread_mutex_lock(m); +} + +void pico_mutex_unlock(void *mux) +{ + if (mux == NULL) return; + + pthread_mutex_t *m = (pthread_mutex_t *)mux; + pthread_mutex_unlock(m); +} + +/* POSIX semaphore implementation */ +void *pico_sem_init(void) +{ + sem_t *s; + s = (sem_t *)PICO_ZALLOC(sizeof(sem_t)); + sem_init(s, 0, 0); + return s; +} + +void pico_sem_destroy(void *sem) +{ + PICO_FREE(sem); + sem = NULL; +} + +void pico_sem_post(void *sem) +{ + if (sem == NULL) return; + + sem_t *s = (sem_t *)sem; + sem_post(s); +} + +int pico_sem_wait(void *sem, int timeout) +{ + struct timespec t; + if (sem == NULL) return 0; + + sem_t *s = (sem_t *)sem; + + if (timeout < 0) { + sem_wait(s); + } else { + clock_gettime(CLOCK_REALTIME, &t); + t.tv_sec += timeout / 1000; + t.tv_nsec += (timeout % 1000) * 1000000; + if (sem_timedwait(s, &t) == -1) + return -1; + } + + return 0; +} + +/* POSIX thread implementation */ +void *pico_thread_create(void *(*routine)(void *), void *arg) +{ + pthread_t *thread; + thread = (pthread_t *)PICO_ZALLOC(sizeof(pthread_t)); + + if (pthread_create(thread, NULL, routine, arg) == -1) + return NULL; + + return thread; +} +#endif /* PICO_SUPPORT_THREADING */ diff --git a/ext/picotcp/modules/pico_slaacv4.c b/ext/picotcp/modules/pico_slaacv4.c new file mode 100644 index 0000000..f9ac216 --- /dev/null +++ b/ext/picotcp/modules/pico_slaacv4.c @@ -0,0 +1,266 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + Authors: Bogdan Lupu + *********************************************************************/ +#include "pico_slaacv4.h" +#include "pico_arp.h" +#include "pico_constants.h" +#include "pico_stack.h" +#include "pico_hotplug_detection.h" + +#ifdef PICO_SUPPORT_SLAACV4 + +#define SLAACV4_NETWORK ((long_be(0xa9fe0000))) +#define SLAACV4_NETMASK ((long_be(0xFFFF0000))) +#define SLAACV4_MINRANGE (0x00000100) /* In host order */ +#define SLAACV4_MAXRANGE (0x0000FDFF) /* In host order */ + +#define SLAACV4_CREATE_IPV4(seed) ((long_be((seed % SLAACV4_MAXRANGE) + SLAACV4_MINRANGE) & ~SLAACV4_NETMASK) | SLAACV4_NETWORK) + +#define PROBE_WAIT 1 /* delay between two tries during claim */ +#define PROBE_NB 3 /* number of probe packets during claim */ +/* #define PROBE_MIN 1 */ +/* #define PROBE_MAX 2 */ +#define ANNOUNCE_WAIT 2 /* delay before start announcing */ +#define ANNOUNCE_NB 2 /* number of announcement packets */ +#define ANNOUNCE_INTERVAL 2 /* time between announcement packets */ +#define MAX_CONFLICTS 10 /* max conflicts before rate limiting */ +#define MAX_CONFLICTS_FAIL 20 /* max conflicts before declaring failure */ +#define RATE_LIMIT_INTERVAL 60 /* time between successive attempts */ +#define DEFEND_INTERVAL 10 /* minimum interval between defensive ARP */ + +enum slaacv4_state { + SLAACV4_RESET = 0, + SLAACV4_CLAIMING, + SLAACV4_CLAIMED, + SLAACV4_ANNOUNCING, + SLAACV4_ERROR +}; + +struct slaacv4_cookie { + enum slaacv4_state state; + uint8_t probe_try_nb; + uint8_t conflict_nb; + uint8_t announce_nb; + struct pico_ip4 ip; + struct pico_device *device; + uint32_t timer; + void (*cb)(struct pico_ip4 *ip, uint8_t code); +}; + +static void pico_slaacv4_hotplug_cb(struct pico_device *dev, int event); + +static struct slaacv4_cookie slaacv4_local; + +static uint32_t pico_slaacv4_getip(struct pico_device *dev, uint8_t rand) +{ + uint32_t seed = 0; + if (dev->eth != NULL) + { + seed = pico_hash((const uint8_t *)dev->eth->mac.addr, PICO_SIZE_ETH); + } + + if (rand) + { + seed += pico_rand(); + } + + return SLAACV4_CREATE_IPV4(seed); +} + +static void pico_slaacv4_init_cookie(struct pico_ip4 *ip, struct pico_device *dev, struct slaacv4_cookie *ck, void (*cb)(struct pico_ip4 *ip, uint8_t code)) +{ + ck->state = SLAACV4_RESET; + ck->probe_try_nb = 0; + ck->conflict_nb = 0; + ck->announce_nb = 0; + ck->cb = cb; + ck->device = dev; + ck->ip.addr = ip->addr; + ck->timer = 0; +} + +static void pico_slaacv4_cancel_timers(struct slaacv4_cookie *tmp) +{ + pico_timer_cancel(tmp->timer); + tmp->timer = 0; +} + +static void pico_slaacv4_send_announce_timer(pico_time now, void *arg) +{ + struct slaacv4_cookie *tmp = (struct slaacv4_cookie *)arg; + struct pico_ip4 netmask = { 0 }; + netmask.addr = long_be(0xFFFF0000); + + (void)now; + + if (tmp->announce_nb < ANNOUNCE_NB) + { + pico_arp_request(tmp->device, &tmp->ip, PICO_ARP_ANNOUNCE); + tmp->announce_nb++; + tmp->timer = pico_timer_add(ANNOUNCE_INTERVAL * 1000, pico_slaacv4_send_announce_timer, arg); + } + else + { + tmp->state = SLAACV4_CLAIMED; + pico_ipv4_link_add(tmp->device, tmp->ip, netmask); + if (tmp->cb != NULL) + tmp->cb(&tmp->ip, PICO_SLAACV4_SUCCESS); + } +} + +static void pico_slaacv4_send_probe_timer(pico_time now, void *arg) +{ + struct slaacv4_cookie *tmp = (struct slaacv4_cookie *)arg; + (void)now; + + if (tmp->probe_try_nb < PROBE_NB) + { + pico_arp_request(tmp->device, &tmp->ip, PICO_ARP_PROBE); + tmp->probe_try_nb++; + tmp->timer = pico_timer_add(PROBE_WAIT * 1000, pico_slaacv4_send_probe_timer, tmp); + } + else + { + tmp->state = SLAACV4_ANNOUNCING; + tmp->timer = pico_timer_add(ANNOUNCE_WAIT * 1000, pico_slaacv4_send_announce_timer, arg); + } +} + +static void pico_slaacv4_receive_ipconflict(int reason) +{ + struct slaacv4_cookie *tmp = &slaacv4_local; + + tmp->conflict_nb++; + pico_slaacv4_cancel_timers(tmp); + + if(tmp->state == SLAACV4_CLAIMED) + { + if(reason == PICO_ARP_CONFLICT_REASON_CONFLICT) + { + pico_ipv4_link_del(tmp->device, tmp->ip); + } + } + + if (tmp->conflict_nb < MAX_CONFLICTS) + { + tmp->state = SLAACV4_CLAIMING; + tmp->probe_try_nb = 0; + tmp->announce_nb = 0; + tmp->ip.addr = pico_slaacv4_getip(tmp->device, (uint8_t)1); + pico_arp_register_ipconflict(&tmp->ip, &tmp->device->eth->mac, pico_slaacv4_receive_ipconflict); + pico_arp_request(tmp->device, &tmp->ip, PICO_ARP_PROBE); + tmp->probe_try_nb++; + tmp->timer = pico_timer_add(PROBE_WAIT * 1000, pico_slaacv4_send_probe_timer, tmp); + } + else if (tmp->conflict_nb < MAX_CONFLICTS_FAIL) + { + tmp->state = SLAACV4_CLAIMING; + tmp->probe_try_nb = 0; + tmp->announce_nb = 0; + tmp->ip.addr = pico_slaacv4_getip(tmp->device, (uint8_t)1); + pico_arp_register_ipconflict(&tmp->ip, &tmp->device->eth->mac, pico_slaacv4_receive_ipconflict); + tmp->timer = pico_timer_add(RATE_LIMIT_INTERVAL * 1000, pico_slaacv4_send_probe_timer, tmp); + } + else + { + if (tmp->cb != NULL) + { + pico_hotplug_deregister(tmp->device, &pico_slaacv4_hotplug_cb); + tmp->cb(&tmp->ip, PICO_SLAACV4_ERROR); + } + + tmp->state = SLAACV4_ERROR; + } + +} + +static void pico_slaacv4_hotplug_cb(__attribute__((unused)) struct pico_device *dev, int event) +{ + struct slaacv4_cookie *tmp = &slaacv4_local; + + if (event == PICO_HOTPLUG_EVENT_UP ) + { + slaacv4_local.state = SLAACV4_CLAIMING; + tmp->probe_try_nb = 0; + tmp->announce_nb = 0; + + pico_arp_register_ipconflict(&tmp->ip, &tmp->device->eth->mac, pico_slaacv4_receive_ipconflict); + pico_arp_request(tmp->device, &tmp->ip, PICO_ARP_PROBE); + tmp->probe_try_nb++; + tmp->timer = pico_timer_add(PROBE_WAIT * 1000, pico_slaacv4_send_probe_timer, tmp); + + } + else + { + if (tmp->state == SLAACV4_CLAIMED ) + pico_ipv4_link_del(tmp->device, tmp->ip); + pico_slaacv4_cancel_timers(tmp); + } +} + +int pico_slaacv4_claimip(struct pico_device *dev, void (*cb)(struct pico_ip4 *ip, uint8_t code)) +{ + struct pico_ip4 ip; + + if (!dev->eth) { + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; + } + + if( dev->link_state != NULL ) + { + //hotplug detect will work + + ip.addr = pico_slaacv4_getip(dev, 0); + pico_slaacv4_init_cookie(&ip, dev, &slaacv4_local, cb); + + if (pico_hotplug_register(dev, &pico_slaacv4_hotplug_cb)) + { + return -1; + } + if (dev->link_state(dev) == 1) + { + pico_arp_register_ipconflict(&ip, &dev->eth->mac, pico_slaacv4_receive_ipconflict); + pico_arp_request(dev, &ip, PICO_ARP_PROBE); + slaacv4_local.state = SLAACV4_CLAIMING; + slaacv4_local.probe_try_nb++; + slaacv4_local.timer = pico_timer_add(PROBE_WAIT * 1000, pico_slaacv4_send_probe_timer, &slaacv4_local); + } + } + else + { + ip.addr = pico_slaacv4_getip(dev, 0); + + pico_slaacv4_init_cookie(&ip, dev, &slaacv4_local, cb); + pico_arp_register_ipconflict(&ip, &dev->eth->mac, pico_slaacv4_receive_ipconflict); + pico_arp_request(dev, &ip, PICO_ARP_PROBE); + slaacv4_local.state = SLAACV4_CLAIMING; + slaacv4_local.probe_try_nb++; + slaacv4_local.timer = pico_timer_add(PROBE_WAIT * 1000, pico_slaacv4_send_probe_timer, &slaacv4_local); + } + + return 0; +} + +void pico_slaacv4_unregisterip(void) +{ + struct slaacv4_cookie *tmp = &slaacv4_local; + struct pico_ip4 empty = { + .addr = 0x00000000 + }; + + if (tmp->state == SLAACV4_CLAIMED) + { + pico_ipv4_link_del(tmp->device, tmp->ip); + } + + pico_slaacv4_cancel_timers(tmp); + pico_slaacv4_init_cookie(&empty, NULL, tmp, NULL); + pico_arp_register_ipconflict(&tmp->ip, NULL, NULL); + pico_hotplug_deregister(tmp->device, &pico_slaacv4_hotplug_cb); +} + +#endif diff --git a/ext/picotcp/modules/pico_slaacv4.h b/ext/picotcp/modules/pico_slaacv4.h new file mode 100644 index 0000000..d90ef65 --- /dev/null +++ b/ext/picotcp/modules/pico_slaacv4.h @@ -0,0 +1,18 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + Authors: Bogdan Lupu + *********************************************************************/ +#ifndef INCLUDE_PICO_SUPPORT_SLAACV4 +#define INCLUDE_PICO_SUPPORT_SLAACV4 +#include "pico_arp.h" + +#define PICO_SLAACV4_SUCCESS 0 +#define PICO_SLAACV4_ERROR 1 + +int pico_slaacv4_claimip(struct pico_device *dev, void (*cb)(struct pico_ip4 *ip, uint8_t code)); +void pico_slaacv4_unregisterip(void); + +#endif /* _INCLUDE_PICO_SUPPORT_SLAACV4 */ + diff --git a/ext/picotcp/modules/pico_sntp_client.c b/ext/picotcp/modules/pico_sntp_client.c new file mode 100644 index 0000000..10fa0af --- /dev/null +++ b/ext/picotcp/modules/pico_sntp_client.c @@ -0,0 +1,395 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + Author: Toon Stegen + *********************************************************************/ +#include "pico_sntp_client.h" +#include "pico_config.h" +#include "pico_stack.h" +#include "pico_addressing.h" +#include "pico_socket.h" +#include "pico_ipv4.h" +#include "pico_ipv6.h" +#include "pico_dns_client.h" +#include "pico_tree.h" +#include "pico_stack.h" + +#ifdef PICO_SUPPORT_SNTP_CLIENT + +#define sntp_dbg(...) do {} while(0) +/* #define sntp_dbg dbg */ + +#define SNTP_VERSION 4 +#define PICO_SNTP_MAXBUF (1400) + +/* Sntp mode */ +#define SNTP_MODE_CLIENT 3 + +/* SNTP conversion parameters */ +#define SNTP_FRAC_TO_PICOSEC (4294967llu) +#define SNTP_THOUSAND (1000llu) +#define SNTP_UNIX_OFFSET (2208988800llu) /* nr of seconds from 1900 to 1970 */ +#define SNTP_BITMASK (0X00000000FFFFFFFF) /* mask to convert from 64 to 32 */ + +PACKED_STRUCT_DEF pico_sntp_ts +{ + uint32_t sec; /* Seconds */ + uint32_t frac; /* Fraction */ +}; + +PACKED_STRUCT_DEF pico_sntp_header +{ + uint8_t mode : 3; /* Mode */ + uint8_t vn : 3; /* Version number */ + uint8_t li : 2; /* Leap indicator */ + uint8_t stratum; /* Stratum */ + uint8_t poll; /* Poll, only significant in server messages */ + uint8_t prec; /* Precision, only significant in server messages */ + int32_t rt_del; /* Root delay, only significant in server messages */ + int32_t rt_dis; /* Root dispersion, only significant in server messages */ + int32_t ref_id; /* Reference clock ID, only significant in server messages */ + struct pico_sntp_ts ref_ts; /* Reference time stamp */ + struct pico_sntp_ts orig_ts; /* Originate time stamp */ + struct pico_sntp_ts recv_ts; /* Receive time stamp */ + struct pico_sntp_ts trs_ts; /* Transmit time stamp */ + +}; + +struct sntp_server_ns_cookie +{ + int rec; /* Indicates wheter an sntp packet has been received */ + uint16_t proto; /* IPV4 or IPV6 prototype */ + pico_time stamp; /* Timestamp of the moment the sntp packet is sent */ + char *hostname; /* Hostname of the (s)ntp server*/ + struct pico_socket *sock; /* Socket which contains the cookie */ + void (*cb_synced)(pico_err_t status); /* Callback function for telling the user + wheter/when the time is synchronised */ + uint32_t timer; /* Timer that will signal timeout */ +}; + +/* global variables */ +static uint16_t sntp_port = 123u; +static struct pico_timeval server_time = { + 0 +}; +static pico_time tick_stamp = 0ull; +static union pico_address sntp_inaddr_any = { + .ip6.addr = { 0 } +}; + +/*************************************************************************/ + +/* Converts a sntp time stamp to a pico_timeval struct */ +static int timestamp_convert(const struct pico_sntp_ts *ts, struct pico_timeval *tv, pico_time delay) +{ + if(long_be(ts->sec) < SNTP_UNIX_OFFSET) { + pico_err = PICO_ERR_EINVAL; + tv->tv_sec = 0; + tv->tv_msec = 0; + sntp_dbg("Error: input too low\n"); + return -1; + } + + sntp_dbg("Delay: %llu\n", delay); + tv->tv_msec = (pico_time) (((uint32_t)(long_be(ts->frac))) / SNTP_FRAC_TO_PICOSEC + delay); + tv->tv_sec = (pico_time) (long_be(ts->sec) - SNTP_UNIX_OFFSET + (uint32_t)tv->tv_msec / SNTP_THOUSAND); + tv->tv_msec = (uint32_t) (tv->tv_msec & SNTP_BITMASK) % SNTP_THOUSAND; + sntp_dbg("Converted time stamp: %llusec, %llumsec\n", tv->tv_sec, tv->tv_msec); + return 0; +} + +/* Cleanup function that is called when the time is synced or an error occured */ +static void pico_sntp_cleanup(struct sntp_server_ns_cookie *ck, pico_err_t status) +{ + sntp_dbg("Cleanup called\n"); + if(!ck) + return; + + ck->cb_synced(status); + if(ck->sock) + ck->sock->priv = NULL; + + sntp_dbg("FREE!\n"); + PICO_FREE(ck->hostname); + PICO_FREE(ck); + +} + +/* Extracts the current time from a server sntp packet*/ +static int pico_sntp_parse(char *buf, struct sntp_server_ns_cookie *ck) +{ + int ret = 0; + struct pico_sntp_header *hp = (struct pico_sntp_header*) buf; + + if(!ck) { + sntp_dbg("pico_sntp_parse: invalid cookie\n"); + return -1; + } + + sntp_dbg("Received mode: %u, version: %u, stratum: %u\n", hp->mode, hp->vn, hp->stratum); + + tick_stamp = pico_tick; + /* tick_stamp - ck->stamp is the delay between sending and receiving the ntp packet */ + ret = timestamp_convert(&(hp->trs_ts), &server_time, (tick_stamp - ck->stamp) / 2); + if(ret != 0) { + sntp_dbg("Conversion error!\n"); + pico_sntp_cleanup(ck, PICO_ERR_EINVAL); + return ret; + } + + sntp_dbg("Server time: %llu seconds and %llu milisecs since 1970\n", server_time.tv_sec, server_time.tv_msec); + + /* Call back the user saying the time is synced */ + pico_sntp_cleanup(ck, PICO_ERR_NOERR); + return ret; +} + +/* callback for UDP socket events */ +static void pico_sntp_client_wakeup(uint16_t ev, struct pico_socket *s) +{ + struct sntp_server_ns_cookie *ck = (struct sntp_server_ns_cookie *)s->priv; + char *recvbuf; + int read = 0; + uint32_t peer; + uint16_t port; + + if(!ck) { + sntp_dbg("pico_sntp_client_wakeup: invalid cookie\n"); + return; + } + + /* process read event, data available */ + if (ev == PICO_SOCK_EV_RD) { + ck->rec = 1; + /* receive while data available in socket buffer */ + recvbuf = PICO_ZALLOC(PICO_SNTP_MAXBUF); + if (!recvbuf) + return; + + do { + read = pico_socket_recvfrom(s, recvbuf, PICO_SNTP_MAXBUF, &peer, &port); + } while(read > 0); + pico_sntp_parse(recvbuf, s->priv); + pico_timer_cancel(ck->timer); + PICO_FREE(recvbuf); + } + /* socket is closed */ + else if(ev == PICO_SOCK_EV_CLOSE) { + sntp_dbg("Socket is closed. Bailing out.\n"); + pico_sntp_cleanup(ck, PICO_ERR_ENOTCONN); + return; + } + /* process error event, socket error occured */ + else if(ev == PICO_SOCK_EV_ERR) { + sntp_dbg("Socket Error received. Bailing out.\n"); + pico_sntp_cleanup(ck, PICO_ERR_ENOTCONN); + return; + } + + sntp_dbg("Received data from %08X:%u\n", peer, port); +} + +/* Function that is called after the receive timer expires */ +static void sntp_receive_timeout(pico_time now, void *arg) +{ + struct sntp_server_ns_cookie *ck = (struct sntp_server_ns_cookie *)arg; + (void) now; + + if(!ck) { + sntp_dbg("sntp_timeout: invalid cookie\n"); + return; + } + + if(!ck->rec) { + pico_sntp_cleanup(ck, PICO_ERR_ETIMEDOUT); + } +} + +/* Sends an sntp packet on sock to dst*/ +static void pico_sntp_send(struct pico_socket *sock, union pico_address *dst) +{ + struct pico_sntp_header header = { + 0 + }; + struct sntp_server_ns_cookie *ck = (struct sntp_server_ns_cookie *)sock->priv; + + if(!ck) { + sntp_dbg("pico_sntp_sent: invalid cookie\n"); + return; + } + + ck->timer = pico_timer_add(5000, sntp_receive_timeout, ck); + header.vn = SNTP_VERSION; + header.mode = SNTP_MODE_CLIENT; + /* header.trs_ts.frac = long_be(0ul); */ + ck->stamp = pico_tick; + pico_socket_sendto(sock, &header, sizeof(header), dst, short_be(sntp_port)); +} + +/* used for getting a response from DNS servers */ +static void dnsCallback(char *ip, void *arg) +{ + struct sntp_server_ns_cookie *ck = (struct sntp_server_ns_cookie *)arg; + union pico_address address; + struct pico_socket *sock; + int retval = -1; + uint16_t any_port = 0; + + if(!ck) { + sntp_dbg("dnsCallback: Invalid argument\n"); + return; + } + + if (0) { + + } +#ifdef PICO_SUPPORT_IPV6 + else if(ck->proto == PICO_PROTO_IPV6) { + if (ip) { + /* add the ip address to the client, and start a tcp connection socket */ + sntp_dbg("using IPv6 address: %s\n", ip); + retval = pico_string_to_ipv6(ip, address.ip6.addr); + } else { + sntp_dbg("Invalid query response for AAAA\n"); + retval = -1; + pico_sntp_cleanup(ck, PICO_ERR_ENETDOWN); + } + } + +#endif +#ifdef PICO_SUPPORT_IPV4 + else if(ck->proto == PICO_PROTO_IPV4) { + if(ip) { + sntp_dbg("using IPv4 address: %s\n", ip); + retval = pico_string_to_ipv4(ip, (uint32_t *)&address.ip4.addr); + } else { + sntp_dbg("Invalid query response for A\n"); + retval = -1; + pico_sntp_cleanup(ck, PICO_ERR_ENETDOWN); + } + } +#endif + + if (retval >= 0) { + sock = pico_socket_open(ck->proto, PICO_PROTO_UDP, &pico_sntp_client_wakeup); + if (!sock) + return; + + sock->priv = ck; + ck->sock = sock; + if ((pico_socket_bind(sock, &sntp_inaddr_any, &any_port) == 0)) { + pico_sntp_send(sock, &address); + } + } +} + +/* user function to sync the time from a given sntp source */ +int pico_sntp_sync(const char *sntp_server, void (*cb_synced)(pico_err_t status)) +{ + struct sntp_server_ns_cookie *ck; +#ifdef PICO_SUPPORT_IPV6 + struct sntp_server_ns_cookie *ck6; +#endif + int retval = -1, retval6 = -1; + if (sntp_server == NULL) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* IPv4 query */ + ck = PICO_ZALLOC(sizeof(struct sntp_server_ns_cookie)); + if (!ck) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + ck->proto = PICO_PROTO_IPV4; + ck->stamp = 0ull; + ck->rec = 0; + ck->sock = NULL; + ck->hostname = PICO_ZALLOC(strlen(sntp_server) + 1); + if (!ck->hostname) { + PICO_FREE(ck); + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + strcpy(ck->hostname, sntp_server); + + if(cb_synced == NULL) { + PICO_FREE(ck->hostname); + PICO_FREE(ck); + pico_err = PICO_ERR_EINVAL; + return -1; + } + + ck->cb_synced = cb_synced; + +#ifdef PICO_SUPPORT_IPV6 + /* IPv6 query */ + ck6 = PICO_ZALLOC(sizeof(struct sntp_server_ns_cookie)); + if (!ck6) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + ck6->proto = PICO_PROTO_IPV6; + ck6->hostname = PICO_ZALLOC(strlen(sntp_server) + 1); + if (!ck6->hostname) { + PICO_FREE(ck6); + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + strcpy(ck6->hostname, sntp_server); + ck6->proto = PICO_PROTO_IPV6; + ck6->stamp = 0ull; + ck6->rec = 0; + ck6->sock = NULL; + ck6->cb_synced = cb_synced; + sntp_dbg("Resolving AAAA %s\n", ck6->hostname); + retval6 = pico_dns_client_getaddr6(sntp_server, &dnsCallback, ck6); + if (retval6 != 0) { + PICO_FREE(ck6->hostname); + PICO_FREE(ck6); + return -1; + } + +#endif + sntp_dbg("Resolving A %s\n", ck->hostname); + retval = pico_dns_client_getaddr(sntp_server, &dnsCallback, ck); + if (retval != 0) { + PICO_FREE(ck->hostname); + PICO_FREE(ck); + return -1; + } + + return 0; +} + +/* user function to get the current time */ +int pico_sntp_gettimeofday(struct pico_timeval *tv) +{ + pico_time diff, temp; + uint32_t diffH, diffL; + int ret = 0; + if (tick_stamp == 0) { + /* TODO: set pico_err */ + ret = -1; + sntp_dbg("Error: Unsynchronised\n"); + return ret; + } + + diff = pico_tick - tick_stamp; + diffL = ((uint32_t) (diff & SNTP_BITMASK)) / 1000; + diffH = ((uint32_t) (diff >> 32)) / 1000; + + temp = server_time.tv_msec + (uint32_t)(diff & SNTP_BITMASK) % SNTP_THOUSAND; + tv->tv_sec = server_time.tv_sec + ((uint64_t)diffH << 32) + diffL + (uint32_t)temp / SNTP_THOUSAND; + tv->tv_msec = (uint32_t)(temp & SNTP_BITMASK) % SNTP_THOUSAND; + sntp_dbg("Time of day: %llu seconds and %llu milisecs since 1970\n", tv->tv_sec, tv->tv_msec); + return ret; +} + +#endif /* PICO_SUPPORT_SNTP_CLIENT */ diff --git a/ext/picotcp/modules/pico_sntp_client.h b/ext/picotcp/modules/pico_sntp_client.h new file mode 100644 index 0000000..79b02b8 --- /dev/null +++ b/ext/picotcp/modules/pico_sntp_client.h @@ -0,0 +1,22 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + Author: Toon Stegen + *********************************************************************/ +#ifndef INCLUDE_PICO_SNTP_CLIENT +#define INCLUDE_PICO_SNTP_CLIENT + +#include "pico_config.h" +#include "pico_protocol.h" + +struct pico_timeval +{ + pico_time tv_sec; + pico_time tv_msec; +}; + +int pico_sntp_sync(const char *sntp_server, void (*cb_synced)(pico_err_t status)); +int pico_sntp_gettimeofday(struct pico_timeval *tv); + +#endif /* _INCLUDE_PICO_SNTP_CLIENT */ diff --git a/ext/picotcp/modules/pico_socket_tcp.c b/ext/picotcp/modules/pico_socket_tcp.c new file mode 100644 index 0000000..ee977b8 --- /dev/null +++ b/ext/picotcp/modules/pico_socket_tcp.c @@ -0,0 +1,267 @@ +#include "pico_config.h" +#include "pico_socket.h" +#include "pico_ipv4.h" +#include "pico_ipv6.h" +#include "pico_tcp.h" +#include "pico_socket_tcp.h" + + +static int sockopt_validate_args(struct pico_socket *s, void *value) +{ + if (!value) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + if (s->proto->proto_number != PICO_PROTO_TCP) { + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; + } + + return 0; +} + +int pico_getsockopt_tcp(struct pico_socket *s, int option, void *value) +{ + if (sockopt_validate_args(s, value) < 0) + return -1; + +#ifdef PICO_SUPPORT_TCP + if (option == PICO_TCP_NODELAY) { + /* state of the NODELAY option */ + *(int *)value = PICO_SOCKET_GETOPT(s, PICO_SOCKET_OPT_TCPNODELAY); + return 0; + } + else if (option == PICO_SOCKET_OPT_RCVBUF) { + return pico_tcp_get_bufsize_in(s, (uint32_t *)value); + } + + else if (option == PICO_SOCKET_OPT_SNDBUF) { + return pico_tcp_get_bufsize_out(s, (uint32_t *)value); + } + +#endif + return -1; +} + +static void tcp_set_nagle_option(struct pico_socket *s, void *value) +{ + int *val = (int*)value; + if (*val > 0) { + dbg("setsockopt: Nagle algorithm disabled.\n"); + PICO_SOCKET_SETOPT_EN(s, PICO_SOCKET_OPT_TCPNODELAY); + } else { + dbg("setsockopt: Nagle algorithm enabled.\n"); + PICO_SOCKET_SETOPT_DIS(s, PICO_SOCKET_OPT_TCPNODELAY); + } +} + +int pico_setsockopt_tcp(struct pico_socket *s, int option, void *value) +{ + if (sockopt_validate_args(s, value) < 0) + return -1; + +#ifdef PICO_SUPPORT_TCP + if (option == PICO_TCP_NODELAY) { + tcp_set_nagle_option(s, value); + return 0; + } + else if (option == PICO_SOCKET_OPT_RCVBUF) { + uint32_t *val = (uint32_t*)value; + pico_tcp_set_bufsize_in(s, *val); + return 0; + } + else if (option == PICO_SOCKET_OPT_SNDBUF) { + uint32_t *val = (uint32_t*)value; + pico_tcp_set_bufsize_out(s, *val); + return 0; + } + else if (option == PICO_SOCKET_OPT_KEEPCNT) { + uint32_t *val = (uint32_t*)value; + pico_tcp_set_keepalive_probes(s, *val); + return 0; + } + else if (option == PICO_SOCKET_OPT_KEEPIDLE) { + uint32_t *val = (uint32_t*)value; + pico_tcp_set_keepalive_time(s, *val); + return 0; + } + else if (option == PICO_SOCKET_OPT_KEEPINTVL) { + uint32_t *val = (uint32_t*)value; + pico_tcp_set_keepalive_intvl(s, *val); + return 0; + } + else if (option == PICO_SOCKET_OPT_LINGER) { + uint32_t *val = (uint32_t*)value; + pico_tcp_set_linger(s, *val); + return 0; + } + +#endif + pico_err = PICO_ERR_EINVAL; + return -1; +} + +void pico_socket_tcp_cleanup(struct pico_socket *sock) +{ +#ifdef PICO_SUPPORT_TCP + /* for tcp sockets go further and clean the sockets inside queue */ + if(is_sock_tcp(sock)) + pico_tcp_cleanup_queues(sock); + +#endif +} + + +void pico_socket_tcp_delete(struct pico_socket *s) +{ +#ifdef PICO_SUPPORT_TCP + if(s->parent) + s->parent->number_of_pending_conn--; + +#endif +} + +static struct pico_socket *socket_tcp_deliver_ipv4(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_socket *found = NULL; + #ifdef PICO_SUPPORT_IPV4 + struct pico_ip4 s_local, s_remote, p_src, p_dst; + struct pico_ipv4_hdr *ip4hdr = (struct pico_ipv4_hdr*)(f->net_hdr); + struct pico_trans *tr = (struct pico_trans *) f->transport_hdr; + s_local.addr = s->local_addr.ip4.addr; + s_remote.addr = s->remote_addr.ip4.addr; + p_src.addr = ip4hdr->src.addr; + p_dst.addr = ip4hdr->dst.addr; + if ((s->remote_port == tr->sport) && /* remote port check */ + (s_remote.addr == p_src.addr) && /* remote addr check */ + ((s_local.addr == PICO_IPV4_INADDR_ANY) || (s_local.addr == p_dst.addr))) { /* Either local socket is ANY, or matches dst */ + found = s; + return found; + } else if ((s->remote_port == 0) && /* not connected... listening */ + ((s_local.addr == PICO_IPV4_INADDR_ANY) || (s_local.addr == p_dst.addr))) { /* Either local socket is ANY, or matches dst */ + /* listen socket */ + found = s; + } + + #endif + return found; +} + +static struct pico_socket *socket_tcp_deliver_ipv6(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_socket *found = NULL; + #ifdef PICO_SUPPORT_IPV6 + struct pico_trans *tr = (struct pico_trans *) f->transport_hdr; + struct pico_ip6 s_local = {{0}}, s_remote = {{0}}, p_src = {{0}}, p_dst = {{0}}; + struct pico_ipv6_hdr *ip6hdr = (struct pico_ipv6_hdr *)(f->net_hdr); + s_local = s->local_addr.ip6; + s_remote = s->remote_addr.ip6; + p_src = ip6hdr->src; + p_dst = ip6hdr->dst; + if ((s->remote_port == tr->sport) && + (!memcmp(s_remote.addr, p_src.addr, PICO_SIZE_IP6)) && + ((!memcmp(s_local.addr, PICO_IP6_ANY, PICO_SIZE_IP6)) || (!memcmp(s_local.addr, p_dst.addr, PICO_SIZE_IP6)))) { + found = s; + return found; + } else if ((s->remote_port == 0) && /* not connected... listening */ + ((!memcmp(s_local.addr, PICO_IP6_ANY, PICO_SIZE_IP6)) || (!memcmp(s_local.addr, p_dst.addr, PICO_SIZE_IP6)))) { + /* listen socket */ + found = s; + } + + #else + (void) s; + (void) f; + #endif + return found; +} + +static int socket_tcp_do_deliver(struct pico_socket *s, struct pico_frame *f) +{ + if (s != NULL) { + pico_tcp_input(s, f); + if ((s->ev_pending) && s->wakeup) { + s->wakeup(s->ev_pending, s); + if(!s->parent) + s->ev_pending = 0; + } + + return 0; + } + + dbg("TCP SOCKET> Not s.\n"); + return -1; +} + +int pico_socket_tcp_deliver(struct pico_sockport *sp, struct pico_frame *f) +{ + struct pico_socket *found = NULL; + struct pico_tree_node *index = NULL; + struct pico_tree_node *_tmp; + struct pico_socket *s = NULL; + + + pico_tree_foreach_safe(index, &sp->socks, _tmp){ + s = index->keyValue; + /* 4-tuple identification of socket (port-IP) */ + if (IS_IPV4(f)) { + found = socket_tcp_deliver_ipv4(s, f); + } + + if (IS_IPV6(f)) { + found = socket_tcp_deliver_ipv6(s, f); + } + + if (found) + break; + } /* FOREACH */ + + return socket_tcp_do_deliver(found, f); +} + +struct pico_socket *pico_socket_tcp_open(uint16_t family) +{ + struct pico_socket *s = NULL; + (void) family; +#ifdef PICO_SUPPORT_TCP + s = pico_tcp_open(family); + if (!s) { + pico_err = PICO_ERR_ENOMEM; + return NULL; + } + + s->proto = &pico_proto_tcp; + /*check if Nagle enabled */ + /* + if (!IS_NAGLE_ENABLED(s)) + dbg("ERROR Nagle should be enabled here\n\n"); + */ +#endif + return s; +} + +int pico_socket_tcp_read(struct pico_socket *s, void *buf, uint32_t len) +{ +#ifdef PICO_SUPPORT_TCP + /* check if in shutdown state and if no more data in tcpq_in */ + if ((s->state & PICO_SOCKET_STATE_SHUT_REMOTE) && pico_tcp_queue_in_is_empty(s)) { + pico_err = PICO_ERR_ESHUTDOWN; + return -1; + } else { + return (int)(pico_tcp_read(s, buf, (uint32_t)len)); + } + +#else + return 0; +#endif +} + +void transport_flags_update(struct pico_frame *f, struct pico_socket *s) +{ +#ifdef PICO_SUPPORT_TCP + if(is_sock_tcp(s)) + pico_tcp_flags_update(f, s); + +#endif +} diff --git a/ext/picotcp/modules/pico_socket_tcp.h b/ext/picotcp/modules/pico_socket_tcp.h new file mode 100644 index 0000000..6479103 --- /dev/null +++ b/ext/picotcp/modules/pico_socket_tcp.h @@ -0,0 +1,33 @@ +#ifndef PICO_SOCKET_TCP_H +#define PICO_SOCKET_TCP_H +#include "pico_socket.h" + +#ifdef PICO_SUPPORT_TCP + +/* Functions/macros: conditional! */ + +# define IS_NAGLE_ENABLED(s) (!(!(!(s->opt_flags & (1 << PICO_SOCKET_OPT_TCPNODELAY))))) +int pico_setsockopt_tcp(struct pico_socket *s, int option, void *value); +int pico_getsockopt_tcp(struct pico_socket *s, int option, void *value); +int pico_socket_tcp_deliver(struct pico_sockport *sp, struct pico_frame *f); +void pico_socket_tcp_delete(struct pico_socket *s); +void pico_socket_tcp_cleanup(struct pico_socket *sock); +struct pico_socket *pico_socket_tcp_open(uint16_t family); +int pico_socket_tcp_read(struct pico_socket *s, void *buf, uint32_t len); +void transport_flags_update(struct pico_frame *, struct pico_socket *); + +#else +# define pico_getsockopt_tcp(...) (-1) +# define pico_setsockopt_tcp(...) (-1) +# define pico_socket_tcp_deliver(...) (-1) +# define IS_NAGLE_ENABLED(s) (0) +# define pico_socket_tcp_delete(...) do {} while(0) +# define pico_socket_tcp_cleanup(...) do {} while(0) +# define pico_socket_tcp_open(f) (NULL) +# define pico_socket_tcp_read(...) (-1) +# define transport_flags_update(...) do {} while(0) + +#endif + + +#endif diff --git a/ext/picotcp/modules/pico_socket_udp.c b/ext/picotcp/modules/pico_socket_udp.c new file mode 100644 index 0000000..bf3d9f8 --- /dev/null +++ b/ext/picotcp/modules/pico_socket_udp.c @@ -0,0 +1,256 @@ +#include "pico_config.h" +#include "pico_socket.h" +#include "pico_udp.h" +#include "pico_socket_multicast.h" +#include "pico_ipv4.h" +#include "pico_ipv6.h" +#include "pico_socket_udp.h" + +#define UDP_FRAME_OVERHEAD (sizeof(struct pico_frame)) + + +struct pico_socket *pico_socket_udp_open(void) +{ + struct pico_socket *s = NULL; +#ifdef PICO_SUPPORT_UDP + s = pico_udp_open(); + if (!s) { + pico_err = PICO_ERR_ENOMEM; + return NULL; + } + + s->proto = &pico_proto_udp; + s->q_in.overhead = UDP_FRAME_OVERHEAD; + s->q_out.overhead = UDP_FRAME_OVERHEAD; +#endif + return s; +} + + +#ifdef PICO_SUPPORT_IPV4 +static inline int pico_socket_udp_deliver_ipv4_mcast_initial_checks(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_ip4 p_dst; + struct pico_ipv4_hdr *ip4hdr; + + ip4hdr = (struct pico_ipv4_hdr*)(f->net_hdr); + p_dst.addr = ip4hdr->dst.addr; + if (pico_ipv4_is_multicast(p_dst.addr) && (pico_socket_mcast_filter(s, (union pico_address *)&ip4hdr->dst, (union pico_address *)&ip4hdr->src) < 0)) + return -1; + + + if ((pico_ipv4_link_get(&ip4hdr->src)) && (PICO_SOCKET_GETOPT(s, PICO_SOCKET_OPT_MULTICAST_LOOP) == 0u)) { + /* Datagram from ourselves, Loop disabled, discarding. */ + return -1; + } + + return 0; +} + + +static int pico_socket_udp_deliver_ipv4_mcast(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_ip4 s_local; + struct pico_frame *cpy; + struct pico_device *dev = pico_ipv4_link_find(&s->local_addr.ip4); + + s_local.addr = s->local_addr.ip4.addr; + + if (pico_socket_udp_deliver_ipv4_mcast_initial_checks(s, f) < 0) + return 0; + + if ((s_local.addr == PICO_IPV4_INADDR_ANY) || /* If our local ip is ANY, or.. */ + (dev == f->dev)) { /* the source of the bcast packet is a neighbor... */ + cpy = pico_frame_copy(f); + if (!cpy) + return -1; + + if (pico_enqueue(&s->q_in, cpy) > 0) { + if (s->wakeup) + s->wakeup(PICO_SOCK_EV_RD, s); + } + else + pico_frame_discard(cpy); + } + + return 0; +} + +static int pico_socket_udp_deliver_ipv4_unicast(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_frame *cpy; + /* Either local socket is ANY, or matches dst */ + cpy = pico_frame_copy(f); + if (!cpy) + return -1; + + if (pico_enqueue(&s->q_in, cpy) > 0) { + if (s->wakeup) + s->wakeup(PICO_SOCK_EV_RD, s); + } else { + pico_frame_discard(cpy); + } + + return 0; +} + + +static int pico_socket_udp_deliver_ipv4(struct pico_socket *s, struct pico_frame *f) +{ + int ret = 0; + struct pico_ip4 s_local, p_dst; + struct pico_ipv4_hdr *ip4hdr; + ip4hdr = (struct pico_ipv4_hdr*)(f->net_hdr); + s_local.addr = s->local_addr.ip4.addr; + p_dst.addr = ip4hdr->dst.addr; + if ((pico_ipv4_is_broadcast(p_dst.addr)) || pico_ipv4_is_multicast(p_dst.addr)) { + ret = pico_socket_udp_deliver_ipv4_mcast(s, f); + } else if ((s_local.addr == PICO_IPV4_INADDR_ANY) || (s_local.addr == p_dst.addr)) { + ret = pico_socket_udp_deliver_ipv4_unicast(s, f); + } + + pico_frame_discard(f); + return ret; +} +#endif + +#ifdef PICO_SUPPORT_IPV6 +static inline int pico_socket_udp_deliver_ipv6_mcast(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_ipv6_hdr *ip6hdr; + struct pico_frame *cpy; + struct pico_device *dev = pico_ipv6_link_find(&s->local_addr.ip6); + + ip6hdr = (struct pico_ipv6_hdr*)(f->net_hdr); + + if ((pico_ipv6_link_get(&ip6hdr->src)) && (PICO_SOCKET_GETOPT(s, PICO_SOCKET_OPT_MULTICAST_LOOP) == 0u)) { + /* Datagram from ourselves, Loop disabled, discarding. */ + return 0; + } + + + if (pico_ipv6_is_unspecified(s->local_addr.ip6.addr) || /* If our local ip is ANY, or.. */ + (dev == f->dev)) { /* the source of the bcast packet is a neighbor... */ + cpy = pico_frame_copy(f); + if (!cpy) + { + return -1; + } + + if (pico_enqueue(&s->q_in, cpy) > 0) { + if (s->wakeup) + s->wakeup(PICO_SOCK_EV_RD, s); + } + else + pico_frame_discard(cpy); + } + + return 0; +} + +static int pico_socket_udp_deliver_ipv6(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_ip6 s_local, p_dst; + struct pico_ipv6_hdr *ip6hdr; + struct pico_frame *cpy; + ip6hdr = (struct pico_ipv6_hdr*)(f->net_hdr); + s_local = s->local_addr.ip6; + p_dst = ip6hdr->dst; + if ((pico_ipv6_is_multicast(p_dst.addr))) { + int retval = pico_socket_udp_deliver_ipv6_mcast(s, f); + pico_frame_discard(f); + return retval; + } + else if (pico_ipv6_is_unspecified(s->local_addr.ip6.addr) || (pico_ipv6_compare(&s_local, &p_dst) == 0)) + { /* Either local socket is ANY, or matches dst */ + cpy = pico_frame_copy(f); + if (!cpy) + { + pico_frame_discard(f); + return -1; + } + + if (pico_enqueue(&s->q_in, cpy) > 0) { + if (s->wakeup) + s->wakeup(PICO_SOCK_EV_RD, s); + } + } + + pico_frame_discard(f); + return 0; +} +#endif + + +int pico_socket_udp_deliver(struct pico_sockport *sp, struct pico_frame *f) +{ + struct pico_tree_node *index = NULL; + struct pico_tree_node *_tmp; + struct pico_socket *s = NULL; + pico_err = PICO_ERR_EPROTONOSUPPORT; + #ifdef PICO_SUPPORT_UDP + pico_err = PICO_ERR_NOERR; + pico_tree_foreach_safe(index, &sp->socks, _tmp){ + s = index->keyValue; + if (IS_IPV4(f)) { /* IPV4 */ +#ifdef PICO_SUPPORT_IPV4 + return pico_socket_udp_deliver_ipv4(s, f); +#endif + } else if (IS_IPV6(f)) { +#ifdef PICO_SUPPORT_IPV6 + return pico_socket_udp_deliver_ipv6(s, f); +#endif + } else { + /* something wrong in the packet header*/ + } + } /* FOREACH */ + pico_frame_discard(f); + if (s) + return 0; + + pico_err = PICO_ERR_ENXIO; + #endif + return -1; +} + +int pico_setsockopt_udp(struct pico_socket *s, int option, void *value) +{ + switch(option) { + case PICO_SOCKET_OPT_RCVBUF: + s->q_in.max_size = (*(uint32_t*)value); + return 0; + case PICO_SOCKET_OPT_SNDBUF: + s->q_out.max_size = (*(uint32_t*)value); + return 0; + } + + /* switch's default */ +#ifdef PICO_SUPPORT_MCAST + return pico_setsockopt_mcast(s, option, value); +#else + pico_err = PICO_ERR_EINVAL; + return -1; +#endif +} + +int pico_getsockopt_udp(struct pico_socket *s, int option, void *value) +{ + uint32_t *val = (uint32_t *)value; + switch(option) { + case PICO_SOCKET_OPT_RCVBUF: + *val = s->q_in.max_size; + return 0; + case PICO_SOCKET_OPT_SNDBUF: + *val = s->q_out.max_size; + return 0; + } + + /* switch's default */ +#ifdef PICO_SUPPORT_MCAST + return pico_getsockopt_mcast(s, option, value); +#else + pico_err = PICO_ERR_EINVAL; + return -1; +#endif +} + diff --git a/ext/picotcp/modules/pico_socket_udp.h b/ext/picotcp/modules/pico_socket_udp.h new file mode 100644 index 0000000..6b3a4c9 --- /dev/null +++ b/ext/picotcp/modules/pico_socket_udp.h @@ -0,0 +1,19 @@ +#ifndef PICO_SOCKET_UDP_H +#define PICO_SOCKET_UDP_H + +struct pico_socket *pico_socket_udp_open(void); +int pico_socket_udp_deliver(struct pico_sockport *sp, struct pico_frame *f); + + +#ifdef PICO_SUPPORT_UDP +int pico_setsockopt_udp(struct pico_socket *s, int option, void *value); +int pico_getsockopt_udp(struct pico_socket *s, int option, void *value); +# define pico_socket_udp_recv(s, buf, len, addr, port) pico_udp_recv(s, buf, len, addr, port, NULL) +#else +# define pico_socket_udp_recv(...) (0) +# define pico_getsockopt_udp(...) (-1) +# define pico_setsockopt_udp(...) (-1) +#endif + + +#endif diff --git a/ext/picotcp/modules/pico_strings.c b/ext/picotcp/modules/pico_strings.c new file mode 100644 index 0000000..974126f --- /dev/null +++ b/ext/picotcp/modules/pico_strings.c @@ -0,0 +1,101 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2015 Altran ISY BeNeLux. Some rights reserved. + See LICENSE and COPYING for usage. + + + + Author: Michele Di Pede + *********************************************************************/ + +#include +#include +#include "pico_strings.h" + +char *get_string_terminator_position(char *const block, size_t len) +{ + size_t length = pico_strnlen(block, len); + + return (len != length) ? (block + length) : 0; +} + +int pico_strncasecmp(const char *const str1, const char *const str2, size_t n) +{ + int ch1; + int ch2; + size_t i; + + for (i = 0; i < n; ++i) { + ch1 = toupper(*(str1 + i)); + ch2 = toupper(*(str2 + i)); + if (ch1 < ch2) + return -1; + + if (ch1 > ch2) + return 1; + + if ((!ch1) && (!ch2)) + return 0; + } + return 1; +} + +size_t pico_strnlen(const char *str, size_t n) +{ + size_t len = 0; + + if (!str) + return 0; + + for (; len < n && *(str + len); ++len) + ; /* TICS require this empty statement here */ + + return len; +} + +static inline int num2string_validate(int32_t num, char *buf, int len) +{ + if (num < 0) + return -1; + + if (!buf) + return -2; + + if (len < 2) + return -3; + + return 0; +} + +static inline int revert_and_shift(char *buf, int len, int pos) +{ + int i; + + len -= pos; + for (i = 0; i < len; ++i) + buf[i] = buf[i + pos]; + return len; +} + +int num2string(int32_t num, char *buf, int len) +{ + ldiv_t res; + int pos = 0; + + if (num2string_validate(num, buf, len)) + return -1; + + pos = len; + buf[--pos] = '\0'; + + res.quot = (long)num; + + do { + if (!pos) + return -3; + + res = ldiv(res.quot, 10); + buf[--pos] = (char)((res.rem + '0') & 0xFF); + } while (res.quot); + + return revert_and_shift(buf, len, pos); +} diff --git a/ext/picotcp/modules/pico_strings.h b/ext/picotcp/modules/pico_strings.h new file mode 100644 index 0000000..9a6209d --- /dev/null +++ b/ext/picotcp/modules/pico_strings.h @@ -0,0 +1,21 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2015 Altran ISY BeNeLux. Some rights reserved. + See LICENSE and COPYING for usage. + + . + + Author: Michele Di Pede + *********************************************************************/ + +#ifndef PICO_STRINGS_H +#define PICO_STRINGS_H +#include +#include + +char *get_string_terminator_position(char *const block, size_t len); +int pico_strncasecmp(const char *const str1, const char *const str2, size_t n); +size_t pico_strnlen(const char *str, size_t n); + +int num2string(int32_t num, char *buf, int len); + +#endif diff --git a/ext/picotcp/modules/pico_tcp.c b/ext/picotcp/modules/pico_tcp.c new file mode 100644 index 0000000..67e3c73 --- /dev/null +++ b/ext/picotcp/modules/pico_tcp.c @@ -0,0 +1,3236 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + . + + Authors: Daniele Lacamera, Philippe Mariman + *********************************************************************/ + +#include "pico_tcp.h" +#include "pico_config.h" +#include "pico_eth.h" +#include "pico_socket.h" +#include "pico_stack.h" +#include "pico_socket.h" +#include "pico_queue.h" +#include "pico_tree.h" + +#define TCP_IS_STATE(s, st) ((s->state & PICO_SOCKET_STATE_TCP) == st) +#define TCP_SOCK(s) ((struct pico_socket_tcp *)s) +#define SEQN(f) ((f) ? (long_be(((struct pico_tcp_hdr *)((f)->transport_hdr))->seq)) : 0) +#define ACKN(f) ((f) ? (long_be(((struct pico_tcp_hdr *)((f)->transport_hdr))->ack)) : 0) + +#define TCP_TIME (pico_time)(PICO_TIME_MS()) + +#define PICO_TCP_RTO_MIN (70) +#define PICO_TCP_RTO_MAX (120000) +#define PICO_TCP_IW 2 +#define PICO_TCP_SYN_TO 2000u +#define PICO_TCP_ZOMBIE_TO 30000 + +#define PICO_TCP_MAX_RETRANS 10 +#define PICO_TCP_MAX_CONNECT_RETRIES 3 + +#define PICO_TCP_LOOKAHEAD 0x00 +#define PICO_TCP_FIRST_DUPACK 0x01 +#define PICO_TCP_SECOND_DUPACK 0x02 +#define PICO_TCP_RECOVER 0x03 +#define PICO_TCP_BLACKOUT 0x04 +#define PICO_TCP_UNREACHABLE 0x05 +#define PICO_TCP_WINDOW_FULL 0x06 + +#define ONE_GIGABYTE ((uint32_t)(1024UL * 1024UL * 1024UL)) + +/* check if the Nagle algorithm is enabled on the socket */ +#define IS_NAGLE_ENABLED(s) (!(!(!(s->opt_flags & (1u << PICO_SOCKET_OPT_TCPNODELAY))))) +/* check if tcp connection is "idle" according to Nagle (RFC 896) */ +#define IS_TCP_IDLE(t) ((t->in_flight == 0) && (t->tcpq_out.size == 0)) +/* check if the hold queue contains data (again Nagle) */ +#define IS_TCP_HOLDQ_EMPTY(t) (t->tcpq_hold.size == 0) + +#define IS_INPUT_QUEUE(q) (q->pool.compare == input_segment_compare) +#define TCP_INPUT_OVERHEAD (sizeof(struct tcp_input_segment) + sizeof(struct pico_tree_node)) + + +#ifdef PICO_SUPPORT_TCP +#define tcp_dbg_nagle(...) do {} while(0) +#define tcp_dbg_options(...) do {} while(0) + + +#define tcp_dbg(...) do {} while(0) +/* #define tcp_dbg dbg */ + +#ifdef PICO_SUPPORT_MUTEX +static void *Mutex = NULL; +#endif + + + +/* Input segment, used to keep only needed data, not the full frame */ +struct tcp_input_segment +{ + uint32_t seq; + /* Pointer to payload */ + unsigned char *payload; + uint16_t payload_len; +}; + +/* Function to compare input segments */ +static int input_segment_compare(void *ka, void *kb) +{ + struct tcp_input_segment *a = ka, *b = kb; + return pico_seq_compare(a->seq, b->seq); +} + +static struct tcp_input_segment *segment_from_frame(struct pico_frame *f) +{ + struct tcp_input_segment *seg = PICO_ZALLOC(sizeof(struct tcp_input_segment)); + if ((!seg) || (!f->payload_len)) + return NULL; + + seg->payload = PICO_ZALLOC(f->payload_len); + if(!seg->payload) + { + PICO_FREE(seg); + return NULL; + } + + seg->seq = SEQN(f); + seg->payload_len = f->payload_len; + memcpy(seg->payload, f->payload, seg->payload_len); + return seg; +} + +static int segment_compare(void *ka, void *kb) +{ + struct pico_frame *a = ka, *b = kb; + return pico_seq_compare(SEQN(a), SEQN(b)); +} + +struct pico_tcp_queue +{ + struct pico_tree pool; + uint32_t max_size; + uint32_t size; + uint32_t frames; +}; + +static void tcp_discard_all_segments(struct pico_tcp_queue *tq); +static void *peek_segment(struct pico_tcp_queue *tq, uint32_t seq) +{ + if(!IS_INPUT_QUEUE(tq)) + { + struct pico_tcp_hdr H; + struct pico_frame f = { + 0 + }; + f.transport_hdr = (uint8_t *) (&H); + H.seq = long_be(seq); + + return pico_tree_findKey(&tq->pool, &f); + } + else + { + struct tcp_input_segment dummy = { 0 }; + dummy.seq = seq; + + return pico_tree_findKey(&tq->pool, &dummy); + } + +} + +static void *first_segment(struct pico_tcp_queue *tq) +{ + return pico_tree_first(&tq->pool); +} + +static void *next_segment(struct pico_tcp_queue *tq, void *cur) +{ + if (!cur) + return NULL; + + if(IS_INPUT_QUEUE(tq)) + { + return peek_segment(tq, ((struct tcp_input_segment *)cur)->seq + ((struct tcp_input_segment *)cur)->payload_len); + } + else + { + return peek_segment(tq, SEQN((struct pico_frame *)cur) + ((struct pico_frame *)cur)->payload_len); + } +} + +static uint16_t enqueue_segment_len(struct pico_tcp_queue *tq, void *f) +{ + if (IS_INPUT_QUEUE(tq)) { + return ((struct tcp_input_segment *)f)->payload_len; + } else { + return (uint16_t)(((struct pico_frame *)f)->buffer_len); + } +} + + +static int32_t do_enqueue_segment(struct pico_tcp_queue *tq, void *f, uint16_t payload_len) +{ + int32_t ret = -1; + PICOTCP_MUTEX_LOCK(Mutex); + if ((tq->size + payload_len) > tq->max_size) + { + ret = 0; + goto out; + } + + if (pico_tree_insert(&tq->pool, f) != 0) + { + ret = 0; + goto out; + } + + tq->size += (uint16_t)payload_len; + if (payload_len > 0) + tq->frames++; + + ret = (int32_t)payload_len; + +out: + PICOTCP_MUTEX_UNLOCK(Mutex); + return ret; +} + +static int32_t pico_enqueue_segment(struct pico_tcp_queue *tq, void *f) +{ + uint16_t payload_len; + + if (!f) + return -1; + + payload_len = enqueue_segment_len(tq, f); + + + if (payload_len == 0) { + tcp_dbg("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! TRIED TO ENQUEUE INVALID SEGMENT!\n"); + return -1; + } + + return do_enqueue_segment(tq, f, payload_len); +} + +static void pico_discard_segment(struct pico_tcp_queue *tq, void *f) +{ + void *f1; + uint16_t payload_len = (uint16_t)((IS_INPUT_QUEUE(tq)) ? + (((struct tcp_input_segment *)f)->payload_len) : + (((struct pico_frame *)f)->buffer_len)); + PICOTCP_MUTEX_LOCK(Mutex); + f1 = pico_tree_delete(&tq->pool, f); + if (f1) { + tq->size -= (uint16_t)payload_len; + if (payload_len > 0) + tq->frames--; + } + + if(f1 && IS_INPUT_QUEUE(tq)) + { + struct tcp_input_segment *inp = f1; + PICO_FREE(inp->payload); + PICO_FREE(inp); + } + else + pico_frame_discard(f); + + PICOTCP_MUTEX_UNLOCK(Mutex); +} + +/* Structure for TCP socket */ +struct tcp_sack_block { + uint32_t left; + uint32_t right; + struct tcp_sack_block *next; +}; + +struct pico_socket_tcp { + struct pico_socket sock; + + /* Tree/queues */ + struct pico_tcp_queue tcpq_in; /* updated the input queue to hold input segments not the full frame. */ + struct pico_tcp_queue tcpq_out; + struct pico_tcp_queue tcpq_hold; /* buffer to hold delayed frames according to Nagle */ + + /* tcp_output */ + uint32_t snd_nxt; + uint32_t snd_last; + uint32_t snd_old_ack; + uint32_t snd_retry; + uint32_t snd_last_out; + + /* congestion control */ + uint32_t avg_rtt; + uint32_t rttvar; + uint32_t rto; + uint32_t in_flight; + uint32_t retrans_tmr; + pico_time retrans_tmr_due; + uint16_t cwnd_counter; + uint16_t cwnd; + uint16_t ssthresh; + uint16_t recv_wnd; + uint16_t recv_wnd_scale; + + /* tcp_input */ + uint32_t rcv_nxt; + uint32_t rcv_ackd; + uint32_t rcv_processed; + uint16_t wnd; + uint16_t wnd_scale; + uint16_t remote_closed; + + /* options */ + uint32_t ts_nxt; + uint16_t mss; + uint8_t sack_ok; + uint8_t ts_ok; + uint8_t mss_ok; + uint8_t scale_ok; + struct tcp_sack_block *sacks; + uint8_t jumbo; + uint32_t linger_timeout; + + /* Transmission */ + uint8_t x_mode; + uint8_t dupacks; + uint8_t backoff; + uint8_t localZeroWindow; + + /* Keepalive */ + uint32_t keepalive_tmr; + pico_time ack_timestamp; + uint32_t ka_time; + uint32_t ka_intvl; + uint32_t ka_probes; + uint32_t ka_retries_count; + + /* FIN timer */ + uint32_t fin_tmr; +}; + +/* Queues */ +static struct pico_queue tcp_in = { + 0 +}; +static struct pico_queue tcp_out = { + 0 +}; + +/* If Nagle enabled, this function can make 1 new segment from smaller segments in hold queue */ +static struct pico_frame *pico_hold_segment_make(struct pico_socket_tcp *t); + +/* checks if tcpq_in is empty */ +int pico_tcp_queue_in_is_empty(struct pico_socket *s) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *) s; + + if (t->tcpq_in.frames == 0) + return 1; + else + return 0; +} + +/* Useful for getting rid of the beginning of the buffer (read() op) */ +static int release_until(struct pico_tcp_queue *q, uint32_t seq) +{ + void *head = first_segment(q); + int ret = 0; + int32_t seq_result = 0; + + if (!head) + return ret; + + do { + void *cur = head; + + if (IS_INPUT_QUEUE(q)) + seq_result = pico_seq_compare(((struct tcp_input_segment *)head)->seq + ((struct tcp_input_segment *)head)->payload_len, seq); + else + seq_result = pico_seq_compare(SEQN((struct pico_frame *)head) + ((struct pico_frame *)head)->payload_len, seq); + + if (seq_result <= 0) + { + head = next_segment(q, cur); + //tcp_dbg("Releasing %08x, len: %d\n", SEQN((struct pico_frame *)head), ((struct pico_frame *)head)->payload_len); + pico_discard_segment(q, cur); + ret++; + } else { + break; + } + } while (head); + + return ret; +} + +static int release_all_until(struct pico_tcp_queue *q, uint32_t seq, pico_time *timestamp) +{ + void *f = NULL; + struct pico_tree_node *idx, *temp; + int seq_result; + int ret = 0; + *timestamp = 0; + + pico_tree_foreach_safe(idx, &q->pool, temp) + { + f = idx->keyValue; + + if (IS_INPUT_QUEUE(q)) + seq_result = pico_seq_compare(((struct tcp_input_segment *)f)->seq + ((struct tcp_input_segment *)f)->payload_len, seq); + else + seq_result = pico_seq_compare(SEQN((struct pico_frame *)f) + ((struct pico_frame *)f)->payload_len, seq); + + if (seq_result <= 0) { + tcp_dbg("Releasing %p\n", f); + if ((seq_result == 0) && !IS_INPUT_QUEUE(q)) + *timestamp = ((struct pico_frame *)f)->timestamp; + + pico_discard_segment(q, f); + ret++; + } else { + return ret; + } + } + return ret; +} + + +/* API calls */ + +uint16_t pico_tcp_checksum_ipv4(struct pico_frame *f) +{ + struct pico_ipv4_hdr *hdr = (struct pico_ipv4_hdr *) f->net_hdr; + struct pico_tcp_hdr *tcp_hdr = (struct pico_tcp_hdr *) f->transport_hdr; + struct pico_socket *s = f->sock; + struct pico_ipv4_pseudo_hdr pseudo; + + if (s) { + /* Case of outgoing frame */ + /* dbg("TCP CRC: on outgoing frame\n"); */ + pseudo.src.addr = s->local_addr.ip4.addr; + pseudo.dst.addr = s->remote_addr.ip4.addr; + } else { + /* Case of incoming frame */ + /* dbg("TCP CRC: on incoming frame\n"); */ + pseudo.src.addr = hdr->src.addr; + pseudo.dst.addr = hdr->dst.addr; + } + + pseudo.zeros = 0; + pseudo.proto = PICO_PROTO_TCP; + pseudo.len = (uint16_t)short_be(f->transport_len); + + return pico_dualbuffer_checksum(&pseudo, sizeof(struct pico_ipv4_pseudo_hdr), tcp_hdr, f->transport_len); +} + +#ifdef PICO_SUPPORT_IPV6 +uint16_t pico_tcp_checksum_ipv6(struct pico_frame *f) +{ + struct pico_ipv6_hdr *ipv6_hdr = (struct pico_ipv6_hdr *)f->net_hdr; + struct pico_tcp_hdr *tcp_hdr = (struct pico_tcp_hdr *)f->transport_hdr; + struct pico_ipv6_pseudo_hdr pseudo; + struct pico_socket *s = f->sock; + + /* XXX If the IPv6 packet contains a Routing header, the Destination + * Address used in the pseudo-header is that of the final destination */ + if (s) { + /* Case of outgoing frame */ + pseudo.src = s->local_addr.ip6; + pseudo.dst = s->remote_addr.ip6; + } else { + /* Case of incoming frame */ + pseudo.src = ipv6_hdr->src; + pseudo.dst = ipv6_hdr->dst; + } + + pseudo.zero[0] = 0; + pseudo.zero[1] = 0; + pseudo.zero[2] = 0; + pseudo.len = long_be(f->transport_len); + pseudo.nxthdr = PICO_PROTO_TCP; + + return pico_dualbuffer_checksum(&pseudo, sizeof(struct pico_ipv6_pseudo_hdr), tcp_hdr, f->transport_len); +} +#endif + +#ifdef PICO_SUPPORT_IPV4 +static inline int checksum_is_ipv4(struct pico_frame *f) +{ + return (IS_IPV4(f) || (f->sock && (f->sock->net == &pico_proto_ipv4))); +} +#endif + +#ifdef PICO_SUPPORT_IPV6 +static inline int checksum_is_ipv6(struct pico_frame *f) +{ + return ((IS_IPV6(f)) || (f->sock && (f->sock->net == &pico_proto_ipv6))); +} +#endif + +uint16_t pico_tcp_checksum(struct pico_frame *f) +{ + (void)f; + + #ifdef PICO_SUPPORT_IPV4 + if (checksum_is_ipv4(f)) + return pico_tcp_checksum_ipv4(f); + + #endif + + #ifdef PICO_SUPPORT_IPV6 + if (checksum_is_ipv6(f)) + return pico_tcp_checksum_ipv6(f); + + #endif + return 0xffff; +} + +static void tcp_send_fin(struct pico_socket_tcp *t); +static int pico_tcp_process_out(struct pico_protocol *self, struct pico_frame *f) +{ + struct pico_tcp_hdr *hdr; + struct pico_socket_tcp *t = (struct pico_socket_tcp *)f->sock; + IGNORE_PARAMETER(self); + hdr = (struct pico_tcp_hdr *)f->transport_hdr; + f->sock->timestamp = TCP_TIME; + if (f->payload_len > 0) { + tcp_dbg("Process out: sending %p (%d bytes)\n", f, f->payload_len); + } else { + tcp_dbg("Sending empty packet\n"); + } + + if (f->payload_len > 0) { + if (pico_seq_compare(SEQN(f) + f->payload_len, t->snd_nxt) > 0) { + t->snd_nxt = SEQN(f) + f->payload_len; + tcp_dbg("%s: snd_nxt is now %08x\n", __FUNCTION__, t->snd_nxt); + } + } else if (hdr->flags == PICO_TCP_ACK) { /* pure ack */ + /* hdr->seq = long_be(t->snd_nxt); / * XXX disabled this to not to mess with seq nrs of ACKs anymore * / */ + } else { + tcp_dbg("%s: non-pure ACK with len=0, fl:%04x\n", __FUNCTION__, hdr->flags); + } + + pico_network_send(f); + return 0; +} + +int pico_tcp_push(struct pico_protocol *self, struct pico_frame *data); + +/* Interface: protocol definition */ +struct pico_protocol pico_proto_tcp = { + .name = "tcp", + .proto_number = PICO_PROTO_TCP, + .layer = PICO_LAYER_TRANSPORT, + .process_in = pico_transport_process_in, + .process_out = pico_tcp_process_out, + .push = pico_tcp_push, + .q_in = &tcp_in, + .q_out = &tcp_out, +}; + +static uint32_t pico_paws(void) +{ + static uint32_t _paws = 0; + _paws = pico_rand(); + return long_be(_paws); +} + +static inline void tcp_add_sack_option(struct pico_socket_tcp *ts, struct pico_frame *f, uint16_t flags, uint32_t *ii) +{ + if (flags & PICO_TCP_ACK) { + struct tcp_sack_block *sb; + uint32_t len_off; + + if (ts->sack_ok && ts->sacks) { + f->start[(*ii)++] = PICO_TCP_OPTION_SACK; + len_off = *ii; + f->start[(*ii)++] = PICO_TCPOPTLEN_SACK; + while(ts->sacks) { + sb = ts->sacks; + ts->sacks = sb->next; + memcpy(f->start + *ii, sb, 2 * sizeof(uint32_t)); + *ii += (2 * (uint32_t)sizeof(uint32_t)); + f->start[len_off] = (uint8_t)(f->start[len_off] + (2 * sizeof(uint32_t))); + PICO_FREE(sb); + } + } + } +} + +static void tcp_add_options(struct pico_socket_tcp *ts, struct pico_frame *f, uint16_t flags, uint16_t optsiz) +{ + uint32_t tsval = long_be((uint32_t)TCP_TIME); + uint32_t tsecr = long_be(ts->ts_nxt); + uint32_t i = 0; + f->start = f->transport_hdr + PICO_SIZE_TCPHDR; + + memset(f->start, PICO_TCP_OPTION_NOOP, optsiz); /* fill blanks with noop */ + + if (flags & PICO_TCP_SYN) { + f->start[i++] = PICO_TCP_OPTION_MSS; + f->start[i++] = PICO_TCPOPTLEN_MSS; + f->start[i++] = (uint8_t)((ts->mss >> 8) & 0xFF); + f->start[i++] = (uint8_t)(ts->mss & 0xFF); + f->start[i++] = PICO_TCP_OPTION_SACK_OK; + f->start[i++] = PICO_TCPOPTLEN_SACK_OK; + } + + f->start[i++] = PICO_TCP_OPTION_WS; + f->start[i++] = PICO_TCPOPTLEN_WS; + f->start[i++] = (uint8_t)(ts->wnd_scale); + + if ((flags & PICO_TCP_SYN) || ts->ts_ok) { + f->start[i++] = PICO_TCP_OPTION_TIMESTAMP; + f->start[i++] = PICO_TCPOPTLEN_TIMESTAMP; + memcpy(f->start + i, &tsval, 4); + i += 4; + memcpy(f->start + i, &tsecr, 4); + i += 4; + } + + tcp_add_sack_option(ts, f, flags, &i); + + if (i < optsiz) + f->start[ optsiz - 1 ] = PICO_TCP_OPTION_END; +} + +static uint16_t tcp_options_size_frame(struct pico_frame *f) +{ + uint16_t size = 0; + + /* Always update window scale. */ + size = (uint16_t)(size + PICO_TCPOPTLEN_WS); + if (f->transport_flags_saved) + size = (uint16_t)(size + PICO_TCPOPTLEN_TIMESTAMP); + + size = (uint16_t)(size + PICO_TCPOPTLEN_END); + size = (uint16_t)(((uint16_t)(size + 3u) >> 2u) << 2u); + return size; +} + +static void tcp_add_options_frame(struct pico_socket_tcp *ts, struct pico_frame *f) +{ + uint32_t tsval = long_be((uint32_t)TCP_TIME); + uint32_t tsecr = long_be(ts->ts_nxt); + uint32_t i = 0; + uint16_t optsiz = tcp_options_size_frame(f); + + f->start = f->transport_hdr + PICO_SIZE_TCPHDR; + + memset(f->start, PICO_TCP_OPTION_NOOP, optsiz); /* fill blanks with noop */ + + + f->start[i++] = PICO_TCP_OPTION_WS; + f->start[i++] = PICO_TCPOPTLEN_WS; + f->start[i++] = (uint8_t)(ts->wnd_scale); + + if (f->transport_flags_saved) { + f->start[i++] = PICO_TCP_OPTION_TIMESTAMP; + f->start[i++] = PICO_TCPOPTLEN_TIMESTAMP; + memcpy(f->start + i, &tsval, 4); + i += 4; + memcpy(f->start + i, &tsecr, 4); + i += 4; + } + + if (i < optsiz) + f->start[ optsiz - 1 ] = PICO_TCP_OPTION_END; +} + +static void tcp_send_ack(struct pico_socket_tcp *t); +#define tcp_send_windowUpdate(t) (tcp_send_ack(t)) + +static inline void tcp_set_space_check_winupdate(struct pico_socket_tcp *t, int32_t space, uint32_t shift) +{ + if (((uint32_t)space != t->wnd) || (shift != t->wnd_scale) || ((space - t->wnd) > (int32_t)((uint32_t)space >> 2u))) { + t->wnd = (uint16_t)space; + t->wnd_scale = (uint16_t)shift; + + if(t->wnd == 0) /* mark the entering to zero window state */ + t->localZeroWindow = 1u; + else if(t->localZeroWindow) + { + t->localZeroWindow = 0u; + tcp_send_windowUpdate(t); + } + } +} + +static void tcp_set_space(struct pico_socket_tcp *t) +{ + int32_t space; + uint32_t shift = 0; + + if (t->tcpq_in.max_size == 0) { + space = ONE_GIGABYTE; + } else { + space = (int32_t)(t->tcpq_in.max_size - t->tcpq_in.size); + } + + if (space < 0) + space = 0; + + while(space > 0xFFFF) { + space = (int32_t)(((uint32_t)space >> 1u)); + shift++; + } + tcp_set_space_check_winupdate(t, space, shift); +} + +/* Return 32-bit aligned option size */ +static uint16_t tcp_options_size(struct pico_socket_tcp *t, uint16_t flags) +{ + uint16_t size = 0; + struct tcp_sack_block *sb = t->sacks; + + if (flags & PICO_TCP_SYN) { /* Full options */ + size = PICO_TCPOPTLEN_MSS + PICO_TCP_OPTION_SACK_OK + PICO_TCPOPTLEN_WS + PICO_TCPOPTLEN_TIMESTAMP; + } else { + + /* Always update window scale. */ + size = (uint16_t)(size + PICO_TCPOPTLEN_WS); + + if (t->ts_ok) + size = (uint16_t)(size + PICO_TCPOPTLEN_TIMESTAMP); + + size = (uint16_t)(size + PICO_TCPOPTLEN_END); + } + + if ((flags & PICO_TCP_ACK) && (t->sack_ok && sb)) { + size = (uint16_t)(size + 2); + while(sb) { + size = (uint16_t)(size + (2 * sizeof(uint32_t))); + sb = sb->next; + } + } + + size = (uint16_t)(((size + 3u) >> 2u) << 2u); + return size; +} + +uint16_t pico_tcp_overhead(struct pico_socket *s) +{ + if (!s) + return 0; + + return (uint16_t)(PICO_SIZE_TCPHDR + tcp_options_size((struct pico_socket_tcp *)s, (uint16_t)0)); /* hdr + Options size for data pkt */ + +} + +static inline int tcp_sack_marker(struct pico_frame *f, uint32_t start, uint32_t end, uint16_t *count) +{ + int cmp; + cmp = pico_seq_compare(SEQN(f), start); + if (cmp > 0) + return 0; + + if (cmp == 0) { + cmp = pico_seq_compare(SEQN(f) + f->payload_len, end); + if (cmp > 0) { + tcp_dbg("Invalid SACK: ignoring.\n"); + } + + tcp_dbg("Marking (by SACK) segment %08x BLK:[%08x::%08x]\n", SEQN(f), start, end); + f->flags |= PICO_FRAME_FLAG_SACKED; + (*count)++; + } + + return cmp; +} + +static void tcp_process_sack(struct pico_socket_tcp *t, uint32_t start, uint32_t end) +{ + struct pico_frame *f; + struct pico_tree_node *index, *temp; + uint16_t count = 0; + + pico_tree_foreach_safe(index, &t->tcpq_out.pool, temp){ + f = index->keyValue; + if (tcp_sack_marker(f, start, end, &count) == 0) + goto done; + } + +done: + if (t->x_mode > PICO_TCP_LOOKAHEAD) { + if (t->in_flight > (count)) + t->in_flight -= (count); + else + t->in_flight = 0; + } +} + +inline static void tcp_add_header(struct pico_socket_tcp *t, struct pico_frame *f) +{ + struct pico_tcp_hdr *hdr = (struct pico_tcp_hdr *)f->transport_hdr; + f->timestamp = TCP_TIME; + tcp_add_options(t, f, 0, (uint16_t)(f->transport_len - f->payload_len - (uint16_t)PICO_SIZE_TCPHDR)); + hdr->rwnd = short_be(t->wnd); + hdr->flags |= PICO_TCP_PSH | PICO_TCP_ACK; + hdr->ack = long_be(t->rcv_nxt); + hdr->crc = 0; + hdr->crc = short_be(pico_tcp_checksum(f)); +} + +static void tcp_rcv_sack(struct pico_socket_tcp *t, uint8_t *opt, int len) +{ + uint32_t start, end; + int i = 0; + if (len % 8) { + tcp_dbg("SACK: Invalid len.\n"); + return; + } + + while (i < len) { + start = long_from(opt + i); + i += 4; + end = long_from(opt + i); + i += 4; + tcp_process_sack(t, long_be(start), long_be(end)); + } +} + +static int tcpopt_len_check(uint32_t *idx, uint8_t len, uint8_t expected) +{ + if (len != expected) { + *idx = *idx + len - 2; + return -1; + } + + return 0; +} + +static inline void tcp_parse_option_ws(struct pico_socket_tcp *t, uint8_t len, uint8_t *opt, uint32_t *idx) +{ + if (tcpopt_len_check(idx, len, PICO_TCPOPTLEN_WS) < 0) + return; + + t->recv_wnd_scale = opt[(*idx)++]; + tcp_dbg_options("TCP Window scale: received %d\n", t->recv_wnd_scale); + +} + +static inline void tcp_parse_option_sack_ok(struct pico_socket_tcp *t, struct pico_frame *f, uint8_t len, uint32_t *idx) +{ + if (tcpopt_len_check(idx, len, PICO_TCPOPTLEN_SACK_OK) < 0) + return; + + if(((struct pico_tcp_hdr *)(f->transport_hdr))->flags & PICO_TCP_SYN ) + t->sack_ok = 1; +} + +static inline void tcp_parse_option_mss(struct pico_socket_tcp *t, uint8_t len, uint8_t *opt, uint32_t *idx) +{ + uint16_t mss; + if (tcpopt_len_check(idx, len, PICO_TCPOPTLEN_MSS) < 0) + return; + + t->mss_ok = 1; + mss = short_from(opt + *idx); + *idx += (uint32_t)sizeof(uint16_t); + if (t->mss > short_be(mss)) + t->mss = short_be(mss); +} + +static inline void tcp_parse_option_timestamp(struct pico_socket_tcp *t, struct pico_frame *f, uint8_t len, uint8_t *opt, uint32_t *idx) +{ + uint32_t tsval, tsecr; + if (tcpopt_len_check(idx, len, PICO_TCPOPTLEN_TIMESTAMP) < 0) + return; + + t->ts_ok = 1; + tsval = long_from(opt + *idx); + *idx += (uint32_t)sizeof(uint32_t); + tsecr = long_from(opt + *idx); + f->timestamp = long_be(tsecr); + *idx += (uint32_t)sizeof(uint32_t); + t->ts_nxt = long_be(tsval); +} + +static void tcp_parse_options(struct pico_frame *f) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)f->sock; + uint8_t *opt = f->transport_hdr + PICO_SIZE_TCPHDR; + uint32_t i = 0; + f->timestamp = 0; + while (i < (f->transport_len - PICO_SIZE_TCPHDR)) { + uint8_t type = opt[i++]; + uint8_t len; + if(i < (f->transport_len - PICO_SIZE_TCPHDR) && (type > 1)) + len = opt[i++]; + else + len = 1; + + if (f->payload && ((opt + i) > f->payload)) + break; + + tcp_dbg_options("Received option '%d', len = %d \n", type, len); + switch (type) { + case PICO_TCP_OPTION_NOOP: + case PICO_TCP_OPTION_END: + break; + case PICO_TCP_OPTION_WS: + tcp_parse_option_ws(t, len, opt, &i); + break; + case PICO_TCP_OPTION_SACK_OK: + tcp_parse_option_sack_ok(t, f, len, &i); + break; + case PICO_TCP_OPTION_MSS: + tcp_parse_option_mss(t, len, opt, &i); + break; + case PICO_TCP_OPTION_TIMESTAMP: + tcp_parse_option_timestamp(t, f, len, opt, &i); + break; + + case PICO_TCP_OPTION_SACK: + tcp_rcv_sack(t, opt + i, len - 2); + i = i + len - 2; + break; + default: + tcp_dbg_options("TCP: received unsupported option %u\n", type); + i = i + len - 2; + } + } +} + +static inline void tcp_send_add_tcpflags(struct pico_socket_tcp *ts, struct pico_frame *f) +{ + struct pico_tcp_hdr *hdr = (struct pico_tcp_hdr *) f->transport_hdr; + if (ts->rcv_nxt != 0) { + if ((ts->rcv_ackd == 0) || (pico_seq_compare(ts->rcv_ackd, ts->rcv_nxt) != 0) || (hdr->flags & PICO_TCP_ACK)) { + hdr->flags |= PICO_TCP_ACK; + hdr->ack = long_be(ts->rcv_nxt); + ts->rcv_ackd = ts->rcv_nxt; + } + } + + if (hdr->flags & PICO_TCP_SYN) { + ts->snd_nxt++; + } + + if (f->payload_len > 0) { + hdr->flags |= PICO_TCP_PSH | PICO_TCP_ACK; + hdr->ack = long_be(ts->rcv_nxt); + ts->rcv_ackd = ts->rcv_nxt; + } +} + +static inline int tcp_send_try_enqueue(struct pico_socket_tcp *ts, struct pico_frame *f) +{ + struct pico_tcp_hdr *hdr = (struct pico_tcp_hdr *) f->transport_hdr; + struct pico_frame *cpy; + (void)hdr; + + /* TCP: ENQUEUE to PROTO ( Transmit ) */ + cpy = pico_frame_copy(f); + if (!cpy) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + if ((pico_enqueue(&tcp_out, cpy) > 0)) { + if (f->payload_len > 0) { + ts->in_flight++; + ts->snd_nxt += f->payload_len; /* update next pointer here to prevent sending same segment twice when called twice in same tick */ + } + + tcp_dbg("DBG> [tcp output] state: %02x --> local port:%u remote port: %u seq: %08x ack: %08x flags: %02x = t_len: %u, hdr: %u payload: %d\n", + TCPSTATE(&ts->sock) >> 8, short_be(hdr->trans.sport), short_be(hdr->trans.dport), SEQN(f), ACKN(f), hdr->flags, f->transport_len, (hdr->len & 0xf0) >> 2, f->payload_len ); + } else { + pico_frame_discard(cpy); + } + + return 0; + +} + +static int tcp_send(struct pico_socket_tcp *ts, struct pico_frame *f) +{ + struct pico_tcp_hdr *hdr = (struct pico_tcp_hdr *) f->transport_hdr; + hdr->trans.sport = ts->sock.local_port; + hdr->trans.dport = ts->sock.remote_port; + if (!hdr->seq) + hdr->seq = long_be(ts->snd_nxt); + + tcp_send_add_tcpflags(ts, f); + + f->start = f->transport_hdr + PICO_SIZE_TCPHDR; + hdr->rwnd = short_be(ts->wnd); + hdr->crc = 0; + hdr->crc = short_be(pico_tcp_checksum(f)); + + return tcp_send_try_enqueue(ts, f); + +} + +/* #define PICO_TCP_SUPPORT_SOCKET_STATS */ + +#ifdef PICO_TCP_SUPPORT_SOCKET_STATS +static void sock_stats(uint32_t when, void *arg) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)arg; + tcp_dbg("STATISTIC> [%lu] socket state: %02x --> local port:%d remote port: %d queue size: %d snd_una: %08x snd_nxt: %08x cwnd: %d\n", + when, t->sock.state, short_be(t->sock.local_port), short_be(t->sock.remote_port), t->tcpq_out.size, SEQN((struct pico_frame *)first_segment(&t->tcpq_out)), t->snd_nxt, t->cwnd); + pico_timer_add(2000, sock_stats, t); +} +#endif + +static void tcp_send_probe(struct pico_socket_tcp *t); + +static void pico_tcp_keepalive(pico_time now, void *arg) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)arg; + if (((t->sock.state & PICO_SOCKET_STATE_TCP) == PICO_SOCKET_STATE_TCP_ESTABLISHED) && (t->ka_time > 0)) { + if (t->ka_time < (now - t->ack_timestamp)) { + if (t->ka_retries_count == 0) { + /* First probe */ + tcp_send_probe(t); + t->ka_retries_count++; + } + if (t->ka_retries_count > t->ka_probes) { + if (t->sock.wakeup) + { + pico_err = PICO_ERR_ECONNRESET; + t->sock.wakeup(PICO_SOCK_EV_ERR, &t->sock); + } + } + if (((t->ka_retries_count * t->ka_intvl) + t->ka_time) < (now - t->ack_timestamp)) { + /* Next probe */ + tcp_send_probe(t); + t->ka_retries_count++; + } + } else { + t->ka_retries_count = 0; + } + } + t->keepalive_tmr = pico_timer_add(1000, pico_tcp_keepalive, t); +} + +static inline void rto_set(struct pico_socket_tcp *t, uint32_t rto) +{ + if (rto < PICO_TCP_RTO_MIN) + rto = PICO_TCP_RTO_MIN; + + if (rto > PICO_TCP_RTO_MAX) + rto = PICO_TCP_RTO_MAX; + + t->rto = rto; +} + + +struct pico_socket *pico_tcp_open(uint16_t family) +{ + struct pico_socket_tcp *t = PICO_ZALLOC(sizeof(struct pico_socket_tcp)); + if (!t) + return NULL; + + t->sock.timestamp = TCP_TIME; + pico_socket_set_family(&t->sock, family); + t->mss = (uint16_t)(pico_socket_get_mss(&t->sock) - PICO_SIZE_TCPHDR); + t->tcpq_in.pool.root = t->tcpq_hold.pool.root = t->tcpq_out.pool.root = &LEAF; + t->tcpq_hold.pool.compare = t->tcpq_out.pool.compare = segment_compare; + t->tcpq_in.pool.compare = input_segment_compare; + t->tcpq_in.max_size = PICO_DEFAULT_SOCKETQ; + t->tcpq_out.max_size = PICO_DEFAULT_SOCKETQ; + t->tcpq_hold.max_size = 2u * t->mss; + rto_set(t, PICO_TCP_RTO_MIN); + + /* Uncomment next line and disable Nagle by default */ + t->sock.opt_flags |= (1 << PICO_SOCKET_OPT_TCPNODELAY); + + /* Uncomment next line and Nagle is enabled by default */ + /* t->sock.opt_flags &= (uint16_t) ~(1 << PICO_SOCKET_OPT_TCPNODELAY); */ + + /* Set default linger for the socket */ + t->linger_timeout = PICO_SOCKET_LINGER_TIMEOUT; + + +#ifdef PICO_TCP_SUPPORT_SOCKET_STATS + pico_timer_add(2000, sock_stats, t); +#endif + + t->keepalive_tmr = pico_timer_add(1000, pico_tcp_keepalive, t); + tcp_set_space(t); + + return &t->sock; +} + +static uint32_t tcp_read_finish(struct pico_socket *s, uint32_t tot_rd_len) +{ + struct pico_socket_tcp *t = TCP_SOCK(s); + tcp_set_space(t); + if (t->tcpq_in.size == 0) { + s->ev_pending &= (uint16_t)(~PICO_SOCK_EV_RD); + } + + if (t->remote_closed) { + s->ev_pending |= (uint16_t)(PICO_SOCK_EV_CLOSE); + s->state &= 0x00FFU; + s->state |= PICO_SOCKET_STATE_TCP_CLOSE_WAIT; + /* set SHUT_REMOTE */ + s->state |= PICO_SOCKET_STATE_SHUT_REMOTE; + if (s->wakeup) { + s->wakeup(PICO_SOCK_EV_CLOSE, s); + } + } + + return tot_rd_len; +} + +static inline uint32_t tcp_read_in_frame_len(struct tcp_input_segment *f, int32_t in_frame_off, uint32_t tot_rd_len, uint32_t read_op_len) +{ + uint32_t in_frame_len = 0; + if (in_frame_off > 0) + { + if ((uint32_t)in_frame_off > f->payload_len) { + tcp_dbg("FATAL TCP ERR: in_frame_off > f->payload_len\n"); + } + + in_frame_len = f->payload_len - (uint32_t)in_frame_off; + } else { /* in_frame_off == 0 */ + in_frame_len = f->payload_len; + } + + if ((in_frame_len + tot_rd_len) > (uint32_t)read_op_len) { + in_frame_len = read_op_len - tot_rd_len; + } + + return in_frame_len; + +} + +static inline void tcp_read_check_segment_done(struct pico_socket_tcp *t, struct tcp_input_segment *f, uint32_t in_frame_len) +{ + if ((in_frame_len == 0u) || (in_frame_len == (uint32_t)f->payload_len)) { + pico_discard_segment(&t->tcpq_in, f); + } +} + +uint32_t pico_tcp_read(struct pico_socket *s, void *buf, uint32_t len) +{ + struct pico_socket_tcp *t = TCP_SOCK(s); + struct tcp_input_segment *f; + int32_t in_frame_off; + uint32_t in_frame_len; + uint32_t tot_rd_len = 0; + + while (tot_rd_len < len) { + /* To be sure we don't have garbage at the beginning */ + release_until(&t->tcpq_in, t->rcv_processed); + f = first_segment(&t->tcpq_in); + if (!f) + return tcp_read_finish(s, tot_rd_len); + + in_frame_off = pico_seq_compare(t->rcv_processed, f->seq); + /* Check for hole at the beginning of data, awaiting retransmissions. */ + if (in_frame_off < 0) { + tcp_dbg("TCP> read hole beginning of data, %08x - %08x. rcv_nxt is %08x\n", t->rcv_processed, f->seq, t->rcv_nxt); + return tcp_read_finish(s, tot_rd_len); + } + + in_frame_len = tcp_read_in_frame_len(f, in_frame_off, tot_rd_len, len); + + + memcpy((uint8_t *)buf + tot_rd_len, f->payload + in_frame_off, in_frame_len); + tot_rd_len += in_frame_len; + t->rcv_processed += in_frame_len; + + tcp_read_check_segment_done(t, f, in_frame_len); + + } + return tcp_read_finish(s, tot_rd_len); +} + +int pico_tcp_initconn(struct pico_socket *s); +static void initconn_retry(pico_time when, void *arg) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)arg; + IGNORE_PARAMETER(when); + if (TCPSTATE(&t->sock) != PICO_SOCKET_STATE_TCP_ESTABLISHED) + { + if (t->backoff > PICO_TCP_MAX_CONNECT_RETRIES) { + tcp_dbg("TCP> Connection timeout. \n"); + if (t->sock.wakeup) + { + pico_err = PICO_ERR_ECONNREFUSED; + t->sock.wakeup(PICO_SOCK_EV_ERR, &t->sock); + } + pico_socket_del(&t->sock); + return; + } + + tcp_dbg("TCP> SYN retry %d...\n", t->backoff); + t->backoff++; + pico_tcp_initconn(&t->sock); + } else { + tcp_dbg("TCP> Connection is already established: no retry needed. good.\n"); + } +} + +int pico_tcp_initconn(struct pico_socket *s) +{ + struct pico_socket_tcp *ts = TCP_SOCK(s); + struct pico_frame *syn; + struct pico_tcp_hdr *hdr; + uint16_t mtu, opt_len = tcp_options_size(ts, PICO_TCP_SYN); + + syn = s->net->alloc(s->net, (uint16_t)(PICO_SIZE_TCPHDR + opt_len)); + if (!syn) + return -1; + + hdr = (struct pico_tcp_hdr *) syn->transport_hdr; + + if (!ts->snd_nxt) + ts->snd_nxt = long_be(pico_paws()); + + ts->snd_last = ts->snd_nxt; + ts->cwnd = PICO_TCP_IW; + mtu = (uint16_t)pico_socket_get_mss(s); + ts->mss = (uint16_t)(mtu - PICO_SIZE_TCPHDR); + ts->ssthresh = (uint16_t)((uint16_t)(PICO_DEFAULT_SOCKETQ / ts->mss) - (((uint16_t)(PICO_DEFAULT_SOCKETQ / ts->mss)) >> 3u)); + syn->sock = s; + hdr->seq = long_be(ts->snd_nxt); + hdr->len = (uint8_t)((PICO_SIZE_TCPHDR + opt_len) << 2 | ts->jumbo); + hdr->flags = PICO_TCP_SYN; + tcp_set_space(ts); + hdr->rwnd = short_be(ts->wnd); + tcp_add_options(ts, syn, PICO_TCP_SYN, opt_len); + hdr->trans.sport = ts->sock.local_port; + hdr->trans.dport = ts->sock.remote_port; + + hdr->crc = 0; + hdr->crc = short_be(pico_tcp_checksum(syn)); + + /* TCP: ENQUEUE to PROTO ( SYN ) */ + tcp_dbg("Sending SYN... (ports: %d - %d) size: %d\n", short_be(ts->sock.local_port), short_be(ts->sock.remote_port), syn->buffer_len); + pico_enqueue(&tcp_out, syn); + ts->retrans_tmr = pico_timer_add(PICO_TCP_SYN_TO << ts->backoff, initconn_retry, ts); + return 0; +} + +static int tcp_send_synack(struct pico_socket *s) +{ + struct pico_socket_tcp *ts = TCP_SOCK(s); + struct pico_frame *synack; + struct pico_tcp_hdr *hdr; + uint16_t opt_len = tcp_options_size(ts, PICO_TCP_SYN | PICO_TCP_ACK); + + synack = s->net->alloc(s->net, (uint16_t)(PICO_SIZE_TCPHDR + opt_len)); + if (!synack) + return -1; + + hdr = (struct pico_tcp_hdr *) synack->transport_hdr; + + synack->sock = s; + hdr->len = (uint8_t)((PICO_SIZE_TCPHDR + opt_len) << 2 | ts->jumbo); + hdr->flags = PICO_TCP_SYN | PICO_TCP_ACK; + hdr->rwnd = short_be(ts->wnd); + hdr->seq = long_be(ts->snd_nxt); + ts->rcv_processed = long_be(hdr->seq); + ts->snd_last = ts->snd_nxt; + tcp_set_space(ts); + tcp_add_options(ts, synack, hdr->flags, opt_len); + synack->payload_len = 0; + synack->timestamp = TCP_TIME; + tcp_send(ts, synack); + pico_frame_discard(synack); + return 0; +} + +static void tcp_send_empty(struct pico_socket_tcp *t, uint16_t flags, int is_keepalive) +{ + struct pico_frame *f; + struct pico_tcp_hdr *hdr; + uint16_t opt_len = tcp_options_size(t, flags); + f = t->sock.net->alloc(t->sock.net, (uint16_t)(PICO_SIZE_TCPHDR + opt_len)); + if (!f) { + return; + } + + f->sock = &t->sock; + hdr = (struct pico_tcp_hdr *) f->transport_hdr; + hdr->len = (uint8_t)((PICO_SIZE_TCPHDR + opt_len) << 2 | t->jumbo); + hdr->flags = (uint8_t)flags; + hdr->rwnd = short_be(t->wnd); + tcp_set_space(t); + tcp_add_options(t, f, flags, opt_len); + hdr->trans.sport = t->sock.local_port; + hdr->trans.dport = t->sock.remote_port; + hdr->seq = long_be(t->snd_nxt); + if ((flags & PICO_TCP_ACK) != 0) { + hdr->ack = long_be(t->rcv_nxt); + } + + if (is_keepalive) + hdr->seq = long_be(t->snd_nxt - 1); + + t->rcv_ackd = t->rcv_nxt; + + f->start = f->transport_hdr + PICO_SIZE_TCPHDR; + hdr->rwnd = short_be(t->wnd); + hdr->crc = 0; + hdr->crc = short_be(pico_tcp_checksum(f)); + + /* TCP: ENQUEUE to PROTO */ + pico_enqueue(&tcp_out, f); +} + +static void tcp_send_ack(struct pico_socket_tcp *t) +{ + tcp_send_empty(t, PICO_TCP_ACK, 0); +} + +static void tcp_send_probe(struct pico_socket_tcp *t) +{ + /* tcp_dbg("Sending probe\n"); */ + tcp_send_empty(t, PICO_TCP_PSHACK, 1); +} + +static int tcp_do_send_rst(struct pico_socket *s, uint32_t seq) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *) s; + uint16_t opt_len = tcp_options_size(t, PICO_TCP_RST); + struct pico_frame *f; + struct pico_tcp_hdr *hdr; + f = t->sock.net->alloc(t->sock.net, (uint16_t)(PICO_SIZE_TCPHDR + opt_len)); + if (!f) { + return -1; + } + f->sock = &t->sock; + tcp_dbg("TCP SEND_RST >>>>>>>>>>>>>>> START\n"); + + hdr = (struct pico_tcp_hdr *) f->transport_hdr; + hdr->len = (uint8_t)((PICO_SIZE_TCPHDR + opt_len) << 2 | t->jumbo); + hdr->flags = PICO_TCP_RST; + hdr->rwnd = short_be(t->wnd); + tcp_set_space(t); + tcp_add_options(t, f, PICO_TCP_RST, opt_len); + hdr->trans.sport = t->sock.local_port; + hdr->trans.dport = t->sock.remote_port; + hdr->seq = seq; + hdr->ack = long_be(t->rcv_nxt); + t->rcv_ackd = t->rcv_nxt; + f->start = f->transport_hdr + PICO_SIZE_TCPHDR; + hdr->rwnd = short_be(t->wnd); + hdr->crc = 0; + hdr->crc = short_be(pico_tcp_checksum(f)); + + /* TCP: ENQUEUE to PROTO */ + pico_enqueue(&tcp_out, f); + tcp_dbg("TCP SEND_RST >>>>>>>>>>>>>>> DONE\n"); + return 0; +} + +static int tcp_send_rst(struct pico_socket *s, struct pico_frame *fr) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *) s; + struct pico_tcp_hdr *hdr_rcv; + int ret; + + if (fr && ((s->state & PICO_SOCKET_STATE_TCP) > PICO_SOCKET_STATE_TCP_SYN_RECV)) { + /* in synchronized state: send RST with seq = ack from previous segment */ + hdr_rcv = (struct pico_tcp_hdr *) fr->transport_hdr; + ret = tcp_do_send_rst(s, hdr_rcv->ack); + } else { + /* non-synchronized state */ + /* go to CLOSED here to prevent timer callback to go on after timeout */ + (t->sock).state &= 0x00FFU; + (t->sock).state |= PICO_SOCKET_STATE_TCP_CLOSED; + ret = tcp_do_send_rst(s, long_be(t->snd_nxt)); + + /* Set generic socket state to CLOSED, too */ + (t->sock).state &= 0xFF00U; + (t->sock).state |= PICO_SOCKET_STATE_CLOSED; + + /* call EV_FIN wakeup before deleting */ + if ((t->sock).wakeup) + (t->sock).wakeup(PICO_SOCK_EV_FIN, &(t->sock)); + + /* delete socket */ + pico_socket_del(&t->sock); + } + return ret; +} + +static inline void tcp_fill_rst_payload(struct pico_frame *fr, struct pico_frame *f) +{ + /* fill in IP data from original frame */ + if (IS_IPV4(fr)) { + memcpy(f->net_hdr, fr->net_hdr, sizeof(struct pico_ipv4_hdr)); + ((struct pico_ipv4_hdr *)(f->net_hdr))->dst.addr = ((struct pico_ipv4_hdr *)(fr->net_hdr))->src.addr; + ((struct pico_ipv4_hdr *)(f->net_hdr))->src.addr = ((struct pico_ipv4_hdr *)(fr->net_hdr))->dst.addr; + tcp_dbg("Making IPv4 reset frame...\n"); + + } else { + memcpy(f->net_hdr, fr->net_hdr, sizeof(struct pico_ipv6_hdr)); + ((struct pico_ipv6_hdr *)(f->net_hdr))->dst = ((struct pico_ipv6_hdr *)(fr->net_hdr))->src; + ((struct pico_ipv6_hdr *)(f->net_hdr))->src = ((struct pico_ipv6_hdr *)(fr->net_hdr))->dst; + } + + /* fill in TCP data from original frame */ + ((struct pico_tcp_hdr *)(f->transport_hdr))->trans.dport = ((struct pico_tcp_hdr *)(fr->transport_hdr))->trans.sport; + ((struct pico_tcp_hdr *)(f->transport_hdr))->trans.sport = ((struct pico_tcp_hdr *)(fr->transport_hdr))->trans.dport; + +} + + +static inline void tcp_fill_rst_header(struct pico_frame *fr, struct pico_tcp_hdr *hdr1, struct pico_frame *f, struct pico_tcp_hdr *hdr) +{ + if(!(hdr1->flags & PICO_TCP_ACK)) + hdr->flags |= PICO_TCP_ACK; + + hdr->rwnd = 0; + if (((struct pico_tcp_hdr *)(fr->transport_hdr))->flags & PICO_TCP_ACK) { + hdr->seq = ((struct pico_tcp_hdr *)(fr->transport_hdr))->ack; + } else { + hdr->seq = 0U; + } + + hdr->ack = 0; + if(!(hdr1->flags & PICO_TCP_ACK)) + hdr->ack = long_be(long_be(((struct pico_tcp_hdr *)(fr->transport_hdr))->seq) + fr->payload_len); + + hdr->crc = short_be(pico_tcp_checksum(f)); +} + +int pico_tcp_reply_rst(struct pico_frame *fr) +{ + struct pico_tcp_hdr *hdr, *hdr1; + struct pico_frame *f; + uint16_t size = PICO_SIZE_TCPHDR; + + + hdr1 = (struct pico_tcp_hdr *) (fr->transport_hdr); + if ((hdr1->flags & PICO_TCP_RST) != 0) + return -1; + + tcp_dbg("TCP> sending RST ... \n"); + + f = fr->sock->net->alloc(fr->sock->net, size); + if (!f) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + tcp_fill_rst_payload(fr, f); + + hdr = (struct pico_tcp_hdr *) f->transport_hdr; + hdr->len = (uint8_t)(size << 2); + hdr->flags = PICO_TCP_RST; + + tcp_fill_rst_header(fr, hdr1, f, hdr); + + if (0) { +#ifdef PICO_SUPPORT_IPV4 + } else if (IS_IPV4(f)) { + tcp_dbg("Pushing IPv4 reset frame...\n"); + pico_ipv4_frame_push(f, &(((struct pico_ipv4_hdr *)(f->net_hdr))->dst), PICO_PROTO_TCP); +#endif +#ifdef PICO_SUPPORT_IPV6 + } else { + pico_ipv6_frame_push(f, NULL, &(((struct pico_ipv6_hdr *)(f->net_hdr))->dst), PICO_PROTO_TCP, 0); +#endif + } + + + return 0; +} + +static int tcp_nosync_rst(struct pico_socket *s, struct pico_frame *fr) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *) s; + struct pico_frame *f; + struct pico_tcp_hdr *hdr, *hdr_rcv; + uint16_t opt_len = tcp_options_size(t, PICO_TCP_RST | PICO_TCP_ACK); + hdr_rcv = (struct pico_tcp_hdr *) fr->transport_hdr; + + tcp_dbg("TCP SEND RST (NON-SYNC) >>>>>>>>>>>>>>>>>> state %x\n", (s->state & PICO_SOCKET_STATE_TCP)); + if (((s->state & PICO_SOCKET_STATE_TCP) == PICO_SOCKET_STATE_TCP_LISTEN)) { + if ((fr->flags & PICO_TCP_RST) != 0) + return 0; + + return pico_tcp_reply_rst(fr); + } + + /***************************************************************************/ + /* sending RST */ + f = t->sock.net->alloc(t->sock.net, (uint16_t)(PICO_SIZE_TCPHDR + opt_len)); + + if (!f) { + return -1; + } + + + f->sock = &t->sock; + hdr = (struct pico_tcp_hdr *) f->transport_hdr; + hdr->len = (uint8_t)((PICO_SIZE_TCPHDR + opt_len) << 2 | t->jumbo); + hdr->flags = PICO_TCP_RST | PICO_TCP_ACK; + hdr->rwnd = short_be(t->wnd); + tcp_set_space(t); + tcp_add_options(t, f, PICO_TCP_RST | PICO_TCP_ACK, opt_len); + hdr->trans.sport = t->sock.local_port; + hdr->trans.dport = t->sock.remote_port; + + /* non-synchronized state */ + if (hdr_rcv->flags & PICO_TCP_ACK) { + hdr->seq = hdr_rcv->ack; + } else { + hdr->seq = 0U; + } + + hdr->ack = long_be(SEQN(fr) + fr->payload_len); + + t->rcv_ackd = t->rcv_nxt; + f->start = f->transport_hdr + PICO_SIZE_TCPHDR; + hdr->rwnd = short_be(t->wnd); + hdr->crc = 0; + hdr->crc = short_be(pico_tcp_checksum(f)); + + /* TCP: ENQUEUE to PROTO */ + pico_enqueue(&tcp_out, f); + + /***************************************************************************/ + + tcp_dbg("TCP SEND_RST (NON_SYNC) >>>>>>>>>>>>>>> DONE, ...\n"); + + return 0; +} + +static void tcp_deltcb(pico_time when, void *arg); + +static void tcp_linger(struct pico_socket_tcp *t) +{ + pico_timer_cancel(t->fin_tmr); + t->fin_tmr = pico_timer_add(t->linger_timeout, tcp_deltcb, t); +} + +static void tcp_send_fin(struct pico_socket_tcp *t) +{ + struct pico_frame *f; + struct pico_tcp_hdr *hdr; + uint16_t opt_len = tcp_options_size(t, PICO_TCP_FIN); + f = t->sock.net->alloc(t->sock.net, (uint16_t)(PICO_SIZE_TCPHDR + opt_len)); + if (!f) { + return; + } + + f->sock = &t->sock; + hdr = (struct pico_tcp_hdr *) f->transport_hdr; + hdr->len = (uint8_t)((PICO_SIZE_TCPHDR + opt_len) << 2 | t->jumbo); + hdr->flags = PICO_TCP_FIN | PICO_TCP_ACK; + hdr->ack = long_be(t->rcv_nxt); + t->rcv_ackd = t->rcv_nxt; + hdr->rwnd = short_be(t->wnd); + tcp_set_space(t); + tcp_add_options(t, f, PICO_TCP_FIN, opt_len); + hdr->trans.sport = t->sock.local_port; + hdr->trans.dport = t->sock.remote_port; + hdr->seq = long_be(t->snd_nxt); + + f->start = f->transport_hdr + PICO_SIZE_TCPHDR; + hdr->rwnd = short_be(t->wnd); + hdr->crc = 0; + hdr->crc = short_be(pico_tcp_checksum(f)); + /* tcp_dbg("SENDING FIN...\n"); */ + if (t->linger_timeout > 0) { + pico_enqueue(&tcp_out, f); + t->snd_nxt++; + } else { + pico_frame_discard(f); + } + tcp_linger(t); +} + +static void tcp_sack_prepare(struct pico_socket_tcp *t) +{ + struct tcp_input_segment *pkt; + uint32_t left = 0, right = 0; + struct tcp_sack_block *sb; + int n = 0; + if (t->sacks) /* previous sacks are pending */ + return; + + pkt = first_segment(&t->tcpq_in); + while(n < 3) { + if (!pkt) { + if(left) { + sb = PICO_ZALLOC(sizeof(struct tcp_sack_block)); + if (!sb) + break; + + sb->left = long_be(left); + sb->right = long_be(right); + n++; + sb->next = t->sacks; + t->sacks = sb; + left = 0; + right = 0; + } + + break; + } + + if (pkt->seq < t->rcv_nxt) { + pkt = next_segment(&t->tcpq_in, pkt); + continue; + } + + if (!left) { + left = pkt->seq; + right = pkt->seq + pkt->payload_len; + pkt = next_segment(&t->tcpq_in, pkt); + continue; + } + + if(pkt->seq == right) { + right += pkt->payload_len; + pkt = next_segment(&t->tcpq_in, pkt); + continue; + } else { + sb = PICO_ZALLOC(sizeof(struct tcp_sack_block)); + if (!sb) + break; + + sb->left = long_be(left); + sb->right = long_be(right); + n++; + sb->next = t->sacks; + t->sacks = sb; + left = 0; + right = 0; + pkt = next_segment(&t->tcpq_in, pkt); + } + } +} + +static inline int tcp_data_in_expected(struct pico_socket_tcp *t, struct pico_frame *f) +{ + struct tcp_input_segment *nxt; + if (pico_seq_compare(SEQN(f), t->rcv_nxt) == 0) { /* Exactly what we expected */ + /* Create new segment and enqueue it */ + struct tcp_input_segment *input = segment_from_frame(f); + if (!input) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + if(pico_enqueue_segment(&t->tcpq_in, input) <= 0) + { + /* failed to enqueue, destroy segment */ + PICO_FREE(input->payload); + PICO_FREE(input); + return -1; + } else { + t->rcv_nxt = SEQN(f) + f->payload_len; + nxt = peek_segment(&t->tcpq_in, t->rcv_nxt); + while(nxt) { + tcp_dbg("scrolling rcv_nxt...%08x\n", t->rcv_nxt); + t->rcv_nxt += nxt->payload_len; + nxt = peek_segment(&t->tcpq_in, t->rcv_nxt); + } + t->sock.ev_pending |= PICO_SOCK_EV_RD; + } + } else { + tcp_dbg("TCP> lo segment. Uninteresting retransmission. (exp: %x got: %x)\n", t->rcv_nxt, SEQN(f)); + } + + return 0; +} + +static inline int tcp_data_in_high_segment(struct pico_socket_tcp *t, struct pico_frame *f) +{ + tcp_dbg("TCP> hi segment. Possible packet loss. I'll dupack this. (exp: %x got: %x)\n", t->rcv_nxt, SEQN(f)); + if (t->sack_ok) { + struct tcp_input_segment *input = segment_from_frame(f); + if (!input) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + if(pico_enqueue_segment(&t->tcpq_in, input) <= 0) { + /* failed to enqueue, destroy segment */ + PICO_FREE(input->payload); + PICO_FREE(input); + return -1; + } + + tcp_sack_prepare(t); + } + + return 0; +} + +static inline void tcp_data_in_send_ack(struct pico_socket_tcp *t, struct pico_frame *f) +{ + struct pico_tcp_hdr *hdr = (struct pico_tcp_hdr *) f->transport_hdr; + /* In either case, ack til recv_nxt, unless received data raises a RST flag. */ + if (((t->sock.state & PICO_SOCKET_STATE_TCP) != PICO_SOCKET_STATE_TCP_CLOSE_WAIT) && + ((t->sock.state & PICO_SOCKET_STATE_TCP) != PICO_SOCKET_STATE_TCP_SYN_SENT) && + ((t->sock.state & PICO_SOCKET_STATE_TCP) != PICO_SOCKET_STATE_TCP_SYN_RECV) && + ((hdr->flags & PICO_TCP_RST) == 0)) + tcp_send_ack(t); +} + +static int tcp_data_in(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + struct pico_tcp_hdr *hdr = (struct pico_tcp_hdr *) f->transport_hdr; + uint16_t payload_len = (uint16_t)(f->transport_len - ((hdr->len & 0xf0u) >> 2u)); + int ret = 0; + (void)hdr; + + if (((hdr->len & 0xf0u) >> 2u) <= f->transport_len) { + tcp_parse_options(f); + f->payload = f->transport_hdr + ((hdr->len & 0xf0u) >> 2u); + f->payload_len = payload_len; + tcp_dbg("TCP> Received segment. (exp: %x got: %x)\n", t->rcv_nxt, SEQN(f)); + + if (pico_seq_compare(SEQN(f), t->rcv_nxt) <= 0) { + ret = tcp_data_in_expected(t, f); + } else { + ret = tcp_data_in_high_segment(t, f); + } + + tcp_data_in_send_ack(t, f); + return ret; + } else { + tcp_dbg("TCP: invalid data in pkt len, exp: %d, got %d\n", (hdr->len & 0xf0) >> 2, f->transport_len); + return -1; + } +} + +static int tcp_ack_advance_una(struct pico_socket_tcp *t, struct pico_frame *f, pico_time *timestamp) +{ + int ret = release_all_until(&t->tcpq_out, ACKN(f), timestamp); + if (ret > 0) { + t->sock.ev_pending |= PICO_SOCK_EV_WR; + } + + return ret; +} + +static uint16_t time_diff(pico_time a, pico_time b) +{ + if (a >= b) + return (uint16_t)(a - b); + else + return (uint16_t)(b - a); +} + +static void tcp_rtt(struct pico_socket_tcp *t, uint32_t rtt) +{ + + uint32_t avg = t->avg_rtt; + uint32_t rvar = t->rttvar; + if (!avg) { + /* This follows RFC2988 + * (2.2) When the first RTT measurement R is made, the host MUST set + * + * SRTT <- R + * RTTVAR <- R/2 + * RTO <- SRTT + max (G, K*RTTVAR) + */ + t->avg_rtt = rtt; + t->rttvar = rtt >> 1; + rto_set(t, t->avg_rtt + (t->rttvar << 2)); + } else { + int32_t var = (int32_t)t->avg_rtt - (int32_t)rtt; + if (var < 0) + var = 0 - var; + + /* RFC2988, section (2.3). Alpha and beta are the ones suggested. */ + + /* First, evaluate a new value for the rttvar */ + t->rttvar <<= 2; + t->rttvar -= rvar; + t->rttvar += (uint32_t)var; + t->rttvar >>= 2; + + /* Then, calculate the new avg_rtt */ + t->avg_rtt <<= 3; + t->avg_rtt -= avg; + t->avg_rtt += rtt; + t->avg_rtt >>= 3; + + /* Finally, assign a new value for the RTO, as specified in the RFC, with K=4 */ + rto_set(t, t->avg_rtt + (t->rttvar << 2)); + } + + tcp_dbg(" -----=============== RTT CUR: %u AVG: %u RTTVAR: %u RTO: %u ======================----\n", rtt, t->avg_rtt, t->rttvar, t->rto); +} + +static void tcp_congestion_control(struct pico_socket_tcp *t) +{ + if (t->x_mode > PICO_TCP_LOOKAHEAD) + return; + + tcp_dbg("Doing congestion control\n"); + if (t->cwnd < t->ssthresh) { + t->cwnd++; + } else { + t->cwnd_counter++; + if (t->cwnd_counter >= t->cwnd) { + t->cwnd++; + t->cwnd_counter = 0; + } + } + + tcp_dbg("TCP_CWND, %lu, %u, %u, %u\n", TCP_TIME, t->cwnd, t->ssthresh, t->in_flight); +} + +static void add_retransmission_timer(struct pico_socket_tcp *t, pico_time next_ts); + + +/* Retransmission time out (RTO). */ + +static void tcp_first_timeout(struct pico_socket_tcp *t) +{ + t->x_mode = PICO_TCP_BLACKOUT; + t->cwnd = PICO_TCP_IW; + t->in_flight = 0; +} + +static int tcp_rto_xmit(struct pico_socket_tcp *t, struct pico_frame *f) +{ + struct pico_frame *cpy; + /* TCP: ENQUEUE to PROTO ( retransmit )*/ + cpy = pico_frame_copy(f); + if (!cpy) { + add_retransmission_timer(t, (t->rto << t->backoff) + TCP_TIME); + return -1; + } + + if (pico_enqueue(&tcp_out, cpy) > 0) { + t->snd_last_out = SEQN(cpy); + add_retransmission_timer(t, (t->rto << (++t->backoff)) + TCP_TIME); + tcp_dbg("TCP_CWND, %lu, %u, %u, %u\n", TCP_TIME, t->cwnd, t->ssthresh, t->in_flight); + tcp_dbg("Sending RTO!\n"); + return 1; + } else { + tcp_dbg("RTO fail, retry!\n"); + add_retransmission_timer(t, (t->rto << t->backoff) + TCP_TIME); + pico_frame_discard(cpy); + return 0; + } +} + +static void tcp_next_zerowindow_probe(struct pico_socket_tcp *t) +{ + tcp_dbg("Sending probe!\n"); + tcp_send_probe(t); + add_retransmission_timer(t, (t->rto << ++t->backoff) + TCP_TIME); +} + +static int tcp_is_allowed_to_send(struct pico_socket_tcp *t) +{ + return t->sock.net && + ( + ((t->sock.state & 0xFF00) == PICO_SOCKET_STATE_TCP_ESTABLISHED) || + ((t->sock.state & 0xFF00) == PICO_SOCKET_STATE_TCP_CLOSE_WAIT) + ) && + ((t->backoff < PICO_TCP_MAX_RETRANS)); +} + +static inline int tcp_retrans_timeout_check_queue(struct pico_socket_tcp *t) +{ + struct pico_frame *f = NULL; + f = first_segment(&t->tcpq_out); + while (f) { + tcp_dbg("Checking frame in queue \n"); + if (t->x_mode == PICO_TCP_WINDOW_FULL) { + tcp_dbg("TCP BLACKOUT> TIMED OUT (output) frame %08x, len= %d rto=%d Win full: %d frame flags: %04x\n", SEQN(f), f->payload_len, t->rto, t->x_mode == PICO_TCP_WINDOW_FULL, f->flags); + tcp_next_zerowindow_probe(t); + return -1; + } + + if (t->x_mode != PICO_TCP_BLACKOUT) + tcp_first_timeout(t); + + tcp_add_header(t, f); + if (tcp_rto_xmit(t, f) > 0) /* A segment has been rexmit'd */ + return -1; + + f = next_segment(&t->tcpq_out, f); + } + if (t->tcpq_out.size < t->tcpq_out.max_size) + t->sock.ev_pending |= PICO_SOCK_EV_WR; + + return 0; + + + +} + +static void tcp_retrans_timeout(pico_time val, void *sock) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *) sock; + + t->retrans_tmr = 0; + + if (t->retrans_tmr_due == 0ull) { + return; + } + + if (t->retrans_tmr_due > val) { + /* Timer was postponed... */ + add_retransmission_timer(t, t->retrans_tmr_due); + return; + } + + tcp_dbg("TIMEOUT! backoff = %d, rto: %d\n", t->backoff, t->rto); + t->retrans_tmr_due = 0ull; + + if (tcp_is_allowed_to_send(t)) { + if (tcp_retrans_timeout_check_queue(t) < 0) + return; + } + else if(t->backoff >= PICO_TCP_MAX_RETRANS && (t->sock.state & 0xFF00) == PICO_SOCKET_STATE_TCP_ESTABLISHED ) + { + tcp_dbg("Connection timeout!\n"); + /* the retransmission timer, failed to get an ack for a frame, gives up on the connection */ + tcp_discard_all_segments(&t->tcpq_out); + if(t->sock.wakeup) + t->sock.wakeup(PICO_SOCK_EV_FIN, &t->sock); + + /* delete socket */ + pico_socket_del(&t->sock); + return; + } else { + tcp_dbg("Retransmission not allowed, rescheduling\n"); + } +} + +static void add_retransmission_timer(struct pico_socket_tcp *t, pico_time next_ts) +{ + struct pico_tree_node *index; + pico_time now = TCP_TIME; + pico_time val = 0; + + + if (next_ts == 0) { + struct pico_frame *f; + + pico_tree_foreach(index, &t->tcpq_out.pool){ + f = index->keyValue; + if ((next_ts == 0) || ((f->timestamp < next_ts) && (f->timestamp > 0))) { + next_ts = f->timestamp; + val = next_ts + (t->rto << t->backoff); + } + } + } else { + val = next_ts; + } + + if ((val > 0) || (val > now)) { + t->retrans_tmr_due = val; + } else { + t->retrans_tmr_due = now + 1; + } + + if (!t->retrans_tmr) { + t->retrans_tmr = pico_timer_add(t->retrans_tmr_due - now, tcp_retrans_timeout, t); + tcp_dbg("Next timeout in %u msec\n", (uint32_t) (t->retrans_tmr_due - now)); + } +} + +static int tcp_retrans(struct pico_socket_tcp *t, struct pico_frame *f) +{ + struct pico_frame *cpy; + if (f) { + tcp_dbg("TCP> RETRANS (by dupack) frame %08x, len= %d\n", SEQN(f), f->payload_len); + tcp_add_header(t, f); + /* TCP: ENQUEUE to PROTO ( retransmit )*/ + cpy = pico_frame_copy(f); + if (!cpy) { + return -1; + } + + if (pico_enqueue(&tcp_out, cpy) > 0) { + t->in_flight++; + t->snd_last_out = SEQN(cpy); + } else { + pico_frame_discard(cpy); + } + + add_retransmission_timer(t, TCP_TIME + t->rto); + return(f->payload_len); + } + + return 0; +} + +#ifdef TCP_ACK_DBG +static void tcp_ack_dbg(struct pico_socket *s, struct pico_frame *f) +{ + uint32_t una, nxt, ack, cur; + struct pico_frame *una_f = NULL, *cur_f; + struct pico_tree_node *idx; + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + char info[64]; + char tmp[64]; + ack = ACKN(f); + nxt = t->snd_nxt; + tcp_dbg("===================================\n"); + tcp_dbg("Queue out (%d/%d). ACKED=%08x\n", t->tcpq_out.size, t->tcpq_out.max_size, ack); + + pico_tree_foreach(idx, &t->tcpq_out.pool) { + info[0] = 0; + cur_f = idx->keyValue; + cur = SEQN(cur_f); + if (!una_f) { + una_f = cur_f; + una = SEQN(una_f); + } + + if (cur == nxt) { + strncpy(tmp, info, strlen(info)); + snprintf(info, 64, "%s SND_NXT", tmp); + } + + if (cur == ack) { + strncpy(tmp, info, strlen(info)); + snprintf(info, 64, "%s ACK", tmp); + } + + if (cur == una) { + strncpy(tmp, info, strlen(info)); + snprintf(info, 64, "%s SND_UNA", tmp); + } + + if (cur == t->snd_last) { + strncpy(tmp, info, strlen(info)); + snprintf(info, 64, "%s SND_LAST", tmp); + } + + tcp_dbg("%08x %d%s\n", cur, cur_f->payload_len, info); + + } + tcp_dbg("SND_NXT is %08x, snd_LAST is %08x\n", nxt, t->snd_last); + tcp_dbg("===================================\n"); + tcp_dbg("\n\n"); +} +#endif + +static int tcp_ack(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_frame *f_new; /* use with Nagle to push to out queue */ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + struct pico_tcp_hdr *hdr = (struct pico_tcp_hdr *) f->transport_hdr; + uint32_t rtt = 0; + uint16_t acked = 0; + pico_time acked_timestamp = 0; + + struct pico_frame *una = NULL; + if ((hdr->flags & PICO_TCP_ACK) == 0) + return -1; + +#ifdef TCP_ACK_DBG + tcp_ack_dbg(s, f); +#endif + + tcp_parse_options(f); + t->recv_wnd = short_be(hdr->rwnd); + + acked = (uint16_t)tcp_ack_advance_una(t, f, &acked_timestamp); + una = first_segment(&t->tcpq_out); + t->ack_timestamp = TCP_TIME; + + if ((t->x_mode == PICO_TCP_BLACKOUT) || + ((t->x_mode == PICO_TCP_WINDOW_FULL) && ((t->recv_wnd << t->recv_wnd_scale) > t->mss))) { + int prev_mode = t->x_mode; + tcp_dbg("Re-entering look-ahead...\n\n\n"); + t->x_mode = PICO_TCP_LOOKAHEAD; + t->backoff = 0; + + if((prev_mode == PICO_TCP_BLACKOUT) && (acked > 0) && una) + { + t->snd_nxt = SEQN(una); + /* restart the retrans timer */ + if (t->retrans_tmr) { + t->retrans_tmr_due = 0ull; + } + } + } + + /* One should be acked. */ + if ((acked == 0) && (f->payload_len == 0) && (t->in_flight > 0)) + t->in_flight--; + + if (!una || acked > 0) { + t->x_mode = PICO_TCP_LOOKAHEAD; + tcp_dbg("Mode: Look-ahead. In flight: %d/%d buf: %d\n", t->in_flight, t->cwnd, t->tcpq_out.frames); + t->backoff = 0; + + /* Do rtt/rttvar/rto calculations */ + /* First, try with timestamps, using the value from options */ + if(f && (f->timestamp != 0)) { + rtt = time_diff(TCP_TIME, f->timestamp); + if (rtt) + tcp_rtt(t, rtt); + } else if(acked_timestamp) { + /* If no timestamps are there, use conservative estimation on the una */ + rtt = time_diff(TCP_TIME, acked_timestamp); + if (rtt) + tcp_rtt(t, rtt); + } + + tcp_dbg("TCP ACK> FRESH ACK %08x (acked %d) Queue size: %u/%u frames: %u cwnd: %u in_flight: %u snd_una: %u\n", ACKN(f), acked, t->tcpq_out.size, t->tcpq_out.max_size, t->tcpq_out.frames, t->cwnd, t->in_flight, SEQN(una)); + if (acked > t->in_flight) { + tcp_dbg("WARNING: in flight < 0\n"); + t->in_flight = 0; + } else + t->in_flight -= (acked); + + } else if ((t->snd_old_ack == ACKN(f)) && /* We've just seen this ack, and... */ + ((0 == (hdr->flags & (PICO_TCP_PSH | PICO_TCP_SYN))) && + (f->payload_len == 0)) && /* This is a pure ack, and... */ + (ACKN(f) != t->snd_nxt)) /* There is something in flight awaiting to be acked... */ + { + /* Process incoming duplicate ack. */ + if (t->x_mode < PICO_TCP_RECOVER) { + t->x_mode++; + tcp_dbg("Mode: DUPACK %d, due to PURE ACK %0x, len = %d\n", t->x_mode, SEQN(f), f->payload_len); + /* tcp_dbg("ACK: %x - QUEUE: %x\n", ACKN(f), SEQN(first_segment(&t->tcpq_out))); */ + if (t->x_mode == PICO_TCP_RECOVER) { /* Switching mode */ + if (t->in_flight > PICO_TCP_IW) + t->cwnd = (uint16_t)t->in_flight; + else + t->cwnd = PICO_TCP_IW; + + t->snd_retry = SEQN((struct pico_frame *)first_segment(&t->tcpq_out)); + if (t->ssthresh > t->cwnd) + t->ssthresh >>= 2; + else + t->ssthresh = (t->cwnd >> 1); + + if (t->ssthresh < 2) + t->ssthresh = 2; + } + } else if (t->x_mode == PICO_TCP_RECOVER) { + /* tcp_dbg("TCP RECOVER> DUPACK! snd_una: %08x, snd_nxt: %08x, acked now: %08x\n", SEQN(first_segment(&t->tcpq_out)), t->snd_nxt, ACKN(f)); */ + if (t->in_flight <= t->cwnd) { + struct pico_frame *nxt = peek_segment(&t->tcpq_out, t->snd_retry); + if (!nxt) + nxt = first_segment(&t->tcpq_out); + + while (nxt && (nxt->flags & PICO_FRAME_FLAG_SACKED) && (nxt != first_segment(&t->tcpq_out))) { + tcp_dbg("Skipping %08x because it is sacked.\n", SEQN(nxt)); + nxt = next_segment(&t->tcpq_out, nxt); + } + if (nxt && (pico_seq_compare(SEQN(nxt), t->snd_nxt)) > 0) + nxt = NULL; + + if (nxt && (pico_seq_compare(SEQN(nxt), SEQN((struct pico_frame *)first_segment(&t->tcpq_out))) > (int)(t->recv_wnd << t->recv_wnd_scale))) + nxt = NULL; + + if(!nxt) + nxt = first_segment(&t->tcpq_out); + + if (nxt) { + tcp_retrans(t, peek_segment(&t->tcpq_out, t->snd_retry)); + t->snd_retry = SEQN(nxt); + } + } + + if (++t->cwnd_counter > 1) { + t->cwnd--; + if (t->cwnd < 2) + t->cwnd = 2; + + t->cwnd_counter = 0; + } + } else { + tcp_dbg("DUPACK in mode %d \n", t->x_mode); + + } + } /* End case duplicate ack detection */ + + /* Linux very special zero-window probe detection (see bug #107) */ + if ((0 == (hdr->flags & (PICO_TCP_PSH | PICO_TCP_SYN))) && /* This is a pure ack, and... */ + (ACKN(f) == t->snd_nxt) && /* it's acking our snd_nxt, and... */ + (pico_seq_compare(SEQN(f), t->rcv_nxt) < 0)) /* Has an old seq number */ + { + tcp_send_ack(t); + } + + + /* Do congestion control */ + tcp_congestion_control(t); + if ((acked > 0) && t->sock.wakeup) { + if (t->tcpq_out.size < t->tcpq_out.max_size) + t->sock.wakeup(PICO_SOCK_EV_WR, &(t->sock)); + + /* t->sock.ev_pending |= PICO_SOCK_EV_WR; */ + } + + /* if Nagle enabled, check if no unack'ed data and fill out queue (till window) */ + if (IS_NAGLE_ENABLED((&(t->sock)))) { + while (!IS_TCP_HOLDQ_EMPTY(t) && ((t->tcpq_out.max_size - t->tcpq_out.size) >= t->mss)) { + tcp_dbg_nagle("TCP_ACK - NAGLE add new segment\n"); + f_new = pico_hold_segment_make(t); + if (f_new == NULL) + break; /* XXX corrupt !!! (or no memory) */ + + if (pico_enqueue_segment(&t->tcpq_out, f_new) <= 0) + /* handle error */ + tcp_dbg_nagle("TCP_ACK - NAGLE FAILED to enqueue in out\n"); + } + } + + /* If some space was created, put a few segments out. */ + tcp_dbg("TCP_CWND, %lu, %u, %u, %u\n", TCP_TIME, t->cwnd, t->ssthresh, t->in_flight); + if (t->x_mode == PICO_TCP_LOOKAHEAD) { + if ((t->cwnd >= t->in_flight) && (t->snd_nxt > t->snd_last_out)) { + pico_tcp_output(&t->sock, (int)t->cwnd - (int)t->in_flight); + } + } + + add_retransmission_timer(t, 0); + t->snd_old_ack = ACKN(f); + return 0; +} + +static int tcp_finwaitack(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + tcp_dbg("RECEIVED ACK IN FIN_WAIT1\n"); + + /* acking part */ + tcp_ack(s, f); + + + tcp_dbg("FIN_WAIT1: ack is %08x - snd_nxt is %08x\n", ACKN(f), t->snd_nxt); + if (ACKN(f) == (t->snd_nxt - 1u)) { + /* update TCP state */ + s->state &= 0x00FFU; + s->state |= PICO_SOCKET_STATE_TCP_FIN_WAIT2; + tcp_dbg("TCP> IN STATE FIN_WAIT2\n"); + } + return 0; +} + +static void tcp_deltcb(pico_time when, void *arg) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)arg; + IGNORE_PARAMETER(when); + + /* send RST if not yet in TIME_WAIT */ + if ( (((t->sock).state & PICO_SOCKET_STATE_TCP) != PICO_SOCKET_STATE_TCP_TIME_WAIT) + && (((t->sock).state & PICO_SOCKET_STATE_TCP) != PICO_SOCKET_STATE_TCP_CLOSING) ) { + tcp_dbg("Called deltcb in state = %04x (sending reset!)\n", (t->sock).state); + tcp_do_send_rst(&t->sock, long_be(t->snd_nxt)); + } else { + tcp_dbg("Called deltcb in state = %04x\n", (t->sock).state); + } + + /* update state */ + (t->sock).state &= 0x00FFU; + (t->sock).state |= PICO_SOCKET_STATE_TCP_CLOSED; + (t->sock).state &= 0xFF00U; + (t->sock).state |= PICO_SOCKET_STATE_CLOSED; + /* call EV_FIN wakeup before deleting */ + if (t->sock.wakeup) { + (t->sock).wakeup(PICO_SOCK_EV_FIN, &(t->sock)); + } + + /* delete socket */ + pico_socket_del(&t->sock); +} + +static int tcp_finwaitfin(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + struct pico_tcp_hdr *hdr = (struct pico_tcp_hdr *) (f->transport_hdr); + tcp_dbg("TCP> received fin in FIN_WAIT2\n"); + /* received FIN, increase ACK nr */ + t->rcv_nxt = long_be(hdr->seq) + 1; + s->state &= 0x00FFU; + s->state |= PICO_SOCKET_STATE_TCP_TIME_WAIT; + /* set SHUT_REMOTE */ + s->state |= PICO_SOCKET_STATE_SHUT_REMOTE; + if (s->wakeup) + s->wakeup(PICO_SOCK_EV_CLOSE, s); + + if (f->payload_len > 0) /* needed?? */ + tcp_data_in(s, f); + + /* send ACK */ + tcp_send_ack(t); + /* linger */ + tcp_linger(t); + return 0; +} + +static int tcp_closing_ack(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + tcp_dbg("TCP> received ack in CLOSING\n"); + /* acking part */ + tcp_ack(s, f); + + /* update TCP state DLA TODO: Only if FIN is acked! */ + tcp_dbg("CLOSING: ack is %08x - snd_nxt is %08x\n", ACKN(f), t->snd_nxt); + if (ACKN(f) == t->snd_nxt) { + s->state &= 0x00FFU; + s->state |= PICO_SOCKET_STATE_TCP_TIME_WAIT; + /* set timer */ + tcp_linger(t); + } + return 0; +} + +static int tcp_lastackwait(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + tcp_dbg("LAST_ACK: ack is %08x - snd_nxt is %08x\n", ACKN(f), t->snd_nxt); + if (ACKN(f) == t->snd_nxt) { + s->state &= 0x00FFU; + s->state |= PICO_SOCKET_STATE_TCP_CLOSED; + s->state &= 0xFF00U; + s->state |= PICO_SOCKET_STATE_CLOSED; + /* call socket wakeup with EV_FIN */ + if (s->wakeup) + s->wakeup(PICO_SOCK_EV_FIN, s); + + /* delete socket */ + pico_socket_del(s); + } + return 0; +} + +static int tcp_syn(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_socket_tcp *new = NULL; + struct pico_tcp_hdr *hdr = NULL; + uint16_t mtu; + if(s->number_of_pending_conn >= s->max_backlog) + return -1; + + new = (struct pico_socket_tcp *)pico_socket_clone(s); + hdr = (struct pico_tcp_hdr *)f->transport_hdr; + if (!new) + return -1; + +#ifdef PICO_TCP_SUPPORT_SOCKET_STATS + pico_timer_add(2000, sock_stats, s); +#endif + + new->sock.remote_port = ((struct pico_trans *)f->transport_hdr)->sport; +#ifdef PICO_SUPPORT_IPV4 + if (IS_IPV4(f)) { + new->sock.remote_addr.ip4.addr = ((struct pico_ipv4_hdr *)(f->net_hdr))->src.addr; + new->sock.local_addr.ip4.addr = ((struct pico_ipv4_hdr *)(f->net_hdr))->dst.addr; + } + +#endif +#ifdef PICO_SUPPORT_IPV6 + if (IS_IPV6(f)) { + new->sock.remote_addr.ip6 = ((struct pico_ipv6_hdr *)(f->net_hdr))->src; + new->sock.local_addr.ip6 = ((struct pico_ipv6_hdr *)(f->net_hdr))->dst; + } + +#endif + f->sock = &new->sock; + tcp_parse_options(f); + mtu = (uint16_t)pico_socket_get_mss(&new->sock); + new->mss = (uint16_t)(mtu - PICO_SIZE_TCPHDR); + new->tcpq_in.max_size = PICO_DEFAULT_SOCKETQ; + new->tcpq_out.max_size = PICO_DEFAULT_SOCKETQ; + new->tcpq_hold.max_size = 2u * mtu; + new->rcv_nxt = long_be(hdr->seq) + 1; + new->snd_nxt = long_be(pico_paws()); + new->snd_last = new->snd_nxt; + new->cwnd = PICO_TCP_IW; + new->ssthresh = (uint16_t)((uint16_t)(PICO_DEFAULT_SOCKETQ / new->mss) - (((uint16_t)(PICO_DEFAULT_SOCKETQ / new->mss)) >> 3u)); + new->recv_wnd = short_be(hdr->rwnd); + new->jumbo = hdr->len & 0x07; + new->linger_timeout = PICO_SOCKET_LINGER_TIMEOUT; + s->number_of_pending_conn++; + new->sock.parent = s; + new->sock.wakeup = s->wakeup; + rto_set(new, PICO_TCP_RTO_MIN); + /* Initialize timestamp values */ + new->sock.state = PICO_SOCKET_STATE_BOUND | PICO_SOCKET_STATE_CONNECTED | PICO_SOCKET_STATE_TCP_SYN_RECV; + pico_socket_add(&new->sock); + tcp_send_synack(&new->sock); + tcp_dbg("SYNACK sent, socket added. snd_nxt is %08x\n", new->snd_nxt); + return 0; +} + +static int tcp_synrecv_syn(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_tcp_hdr *hdr = NULL; + struct pico_socket_tcp *t = TCP_SOCK(s); + hdr = (struct pico_tcp_hdr *)f->transport_hdr; + if (t->rcv_nxt == long_be(hdr->seq) + 1u) { + /* take back our own SEQ number to its original value, + * so the synack retransmitted is identical to the original. + */ + t->snd_nxt--; + tcp_send_synack(s); + } else { + tcp_send_rst(s, f); + return -1; + } + + return 0; +} + +static void tcp_set_init_point(struct pico_socket *s) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + t->rcv_processed = t->rcv_nxt; +} + + +uint16_t pico_tcp_get_socket_mss(struct pico_socket *s) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *) s; + if (t->mss > 0) + return (uint16_t)(t->mss + PICO_SIZE_TCPHDR); + else + return (uint16_t)pico_socket_get_mss(s); +} + +static int tcp_synack(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *) s; + struct pico_tcp_hdr *hdr = (struct pico_tcp_hdr *)f->transport_hdr; + + if (ACKN(f) == (1u + t->snd_nxt)) { + /* Get rid of initconn retry */ + pico_timer_cancel(t->retrans_tmr); + t->retrans_tmr = 0; + + t->rcv_nxt = long_be(hdr->seq); + t->rcv_processed = t->rcv_nxt + 1; + tcp_ack(s, f); + + s->state &= 0x00FFU; + s->state |= PICO_SOCKET_STATE_TCP_ESTABLISHED; + tcp_dbg("TCP> Established. State: %x\n", s->state); + + if (s->wakeup) + s->wakeup(PICO_SOCK_EV_CONN, s); + + s->ev_pending |= PICO_SOCK_EV_WR; + + t->rcv_nxt++; + t->snd_nxt++; + tcp_send_ack(t); /* return ACK */ + + return 0; + + } else if ((hdr->flags & PICO_TCP_RST) == 0) { + tcp_dbg("TCP> Not established, RST sent.\n"); + tcp_nosync_rst(s, f); + return 0; + } else { + /* The segment has the reset flag on: Ignore! */ + return 0; + } +} + +static int tcp_first_ack(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + struct pico_tcp_hdr *hdr = (struct pico_tcp_hdr *)f->transport_hdr; + tcp_dbg("ACK in SYN_RECV: expecting %08x got %08x\n", t->snd_nxt, ACKN(f)); + if (t->snd_nxt == ACKN(f)) { + tcp_set_init_point(s); + tcp_ack(s, f); + s->state &= 0x00FFU; + s->state |= PICO_SOCKET_STATE_TCP_ESTABLISHED; + tcp_dbg("TCP: Established. State now: %04x\n", s->state); + if( !s->parent && s->wakeup) { /* If the socket has no parent, -> sending socket that has a sim_open */ + tcp_dbg("FIRST ACK - No parent found -> sending socket\n"); + s->wakeup(PICO_SOCK_EV_CONN, s); + } + + if (s->parent && s->parent->wakeup) { + tcp_dbg("FIRST ACK - Parent found -> listening socket\n"); + s->wakeup = s->parent->wakeup; + s->parent->wakeup(PICO_SOCK_EV_CONN, s->parent); + } + + s->ev_pending |= PICO_SOCK_EV_WR; + tcp_dbg("%s: snd_nxt is now %08x\n", __FUNCTION__, t->snd_nxt); + return 0; + } else if ((hdr->flags & PICO_TCP_RST) == 0) { + tcp_nosync_rst(s, f); + return 0; + } else { + /* The segment has the reset flag on: Ignore! */ + return 0; + } +} + +static void tcp_attempt_closewait(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + struct pico_tcp_hdr *hdr = (struct pico_tcp_hdr *) (f->transport_hdr); + if (pico_seq_compare(SEQN(f), t->rcv_nxt) == 0) { + /* received FIN, increase ACK nr */ + t->rcv_nxt = long_be(hdr->seq) + 1; + if (pico_seq_compare(SEQN(f), t->rcv_processed) == 0) { + if ((s->state & PICO_SOCKET_STATE_TCP) == PICO_SOCKET_STATE_TCP_ESTABLISHED) { + tcp_dbg("Changing state to CLOSE_WAIT\n"); + s->state &= 0x00FFU; + s->state |= PICO_SOCKET_STATE_TCP_CLOSE_WAIT; + } + + /* set SHUT_REMOTE */ + s->state |= PICO_SOCKET_STATE_SHUT_REMOTE; + tcp_dbg("TCP> Close-wait\n"); + if (s->wakeup) { + s->wakeup(PICO_SOCK_EV_CLOSE, s); + } + } else { + t->remote_closed = 1; + } + } + + +} + +static int tcp_closewait(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + struct pico_tcp_hdr *hdr = (struct pico_tcp_hdr *) (f->transport_hdr); + + if (f->payload_len > 0) + tcp_data_in(s, f); + + if (hdr->flags & PICO_TCP_ACK) + tcp_ack(s, f); + + tcp_dbg("called close_wait (%p), in state %08x, f->flags: 0x%02x, hdr->flags: 0x%02x\n", tcp_closewait, s->state, f->flags, hdr->flags); + tcp_attempt_closewait(s, f); + + /* Ensure that the notification given to the socket + * did not put us in LAST_ACK state before sending the ACK: i.e. if + * pico_socket_close() has been called in the socket callback, we don't need to send + * an ACK here. + * + */ + if (((s->state & PICO_SOCKET_STATE_TCP) == PICO_SOCKET_STATE_TCP_CLOSE_WAIT) || + ((s->state & PICO_SOCKET_STATE_TCP) == PICO_SOCKET_STATE_TCP_ESTABLISHED)) + { + tcp_dbg("In closewait: Sending ack! (state is %08x)\n", s->state); + tcp_send_ack(t); + } + + return 0; +} + +static int tcp_rcvfin(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + IGNORE_PARAMETER(f); + tcp_dbg("TCP> Received FIN in FIN_WAIT1\n"); + s->state &= 0x00FFU; + s->state |= PICO_SOCKET_STATE_TCP_CLOSING; + t->rcv_processed = t->rcv_nxt + 1; + t->rcv_nxt++; + /* send ACK */ + tcp_send_ack(t); + return 0; +} + +static int tcp_finack(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + IGNORE_PARAMETER(f); + + tcp_dbg("TCP> ENTERED finack\n"); + t->rcv_nxt++; + /* send ACK */ + tcp_send_ack(t); + + /* call socket wakeup with EV_FIN */ + if (s->wakeup) + s->wakeup(PICO_SOCK_EV_FIN, s); + + s->state &= 0x00FFU; + s->state |= PICO_SOCKET_STATE_TCP_TIME_WAIT; + /* set SHUT_REMOTE */ + s->state |= PICO_SOCKET_STATE_SHUT_REMOTE; + + tcp_linger(t); + + return 0; +} + +static void tcp_force_closed(struct pico_socket *s) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *) s; + /* update state */ + (t->sock).state &= 0x00FFU; + (t->sock).state |= PICO_SOCKET_STATE_TCP_CLOSED; + (t->sock).state &= 0xFF00U; + (t->sock).state |= PICO_SOCKET_STATE_CLOSED; + /* call EV_ERR wakeup before deleting */ + if (((s->state & PICO_SOCKET_STATE_TCP) == PICO_SOCKET_STATE_TCP_ESTABLISHED)) { + if ((t->sock).wakeup) + (t->sock).wakeup(PICO_SOCK_EV_FIN, &(t->sock)); + } else { + pico_err = PICO_ERR_ECONNRESET; + if ((t->sock).wakeup) + (t->sock).wakeup(PICO_SOCK_EV_ERR, &(t->sock)); + + /* delete socket */ + pico_socket_del(&t->sock); + } +} + +static void tcp_wakeup_pending(struct pico_socket *s, uint16_t ev) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *) s; + if ((t->sock).wakeup) + (t->sock).wakeup(ev, &(t->sock)); +} + +static int tcp_rst(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *) s; + struct pico_tcp_hdr *hdr = (struct pico_tcp_hdr *) (f->transport_hdr); + + tcp_dbg("TCP >>>>>>>>>>>>>> received RST <<<<<<<<<<<<<<<<<<<<\n"); + if ((s->state & PICO_SOCKET_STATE_TCP) == PICO_SOCKET_STATE_TCP_SYN_SENT) { + /* the RST is acceptable if the ACK field acknowledges the SYN */ + if ((t->snd_nxt + 1u) == ACKN(f)) { /* valid, got to closed state */ + tcp_force_closed(s); + } else { /* not valid, ignore */ + tcp_dbg("TCP RST> IGNORE\n"); + return 0; + } + } else { /* all other states */ + /* all reset (RST) segments are validated by checking their SEQ-fields, + a reset is valid if its sequence number is in the window */ + uint32_t this_seq = long_be(hdr->seq); + if ((this_seq >= t->rcv_ackd) && (this_seq <= ((uint32_t)(short_be(hdr->rwnd) << (t->wnd_scale)) + t->rcv_ackd))) { + tcp_force_closed(s); + } else { /* not valid, ignore */ + tcp_dbg("TCP RST> IGNORE\n"); + return 0; + } + } + + return 0; +} +static int tcp_halfopencon(struct pico_socket *s, struct pico_frame *fr) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *) s; + IGNORE_PARAMETER(fr); + tcp_send_ack(t); + return 0; +} + +static int tcp_closeconn(struct pico_socket *s, struct pico_frame *fr) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *) s; + struct pico_tcp_hdr *hdr = (struct pico_tcp_hdr *) (fr->transport_hdr); + + if (pico_seq_compare(SEQN(fr), t->rcv_nxt) == 0) { + /* received FIN, increase ACK nr */ + t->rcv_nxt = long_be(hdr->seq) + 1; + s->state &= 0x00FFU; + s->state |= PICO_SOCKET_STATE_TCP_CLOSE_WAIT; + /* set SHUT_LOCAL */ + s->state |= PICO_SOCKET_STATE_SHUT_LOCAL; + pico_socket_close(s); + return 1; + } + + return 0; +} + +struct tcp_action_entry { + uint16_t tcpstate; + int (*syn)(struct pico_socket *s, struct pico_frame *f); + int (*synack)(struct pico_socket *s, struct pico_frame *f); + int (*ack)(struct pico_socket *s, struct pico_frame *f); + int (*data)(struct pico_socket *s, struct pico_frame *f); + int (*fin)(struct pico_socket *s, struct pico_frame *f); + int (*finack)(struct pico_socket *s, struct pico_frame *f); + int (*rst)(struct pico_socket *s, struct pico_frame *f); +}; + +static const struct tcp_action_entry tcp_fsm[] = { + /* State syn synack ack data fin finack rst*/ + { PICO_SOCKET_STATE_TCP_UNDEF, NULL, NULL, NULL, NULL, NULL, NULL, NULL }, + { PICO_SOCKET_STATE_TCP_CLOSED, NULL, NULL, NULL, NULL, NULL, NULL, NULL }, + { PICO_SOCKET_STATE_TCP_LISTEN, &tcp_syn, NULL, NULL, NULL, NULL, NULL, NULL }, + { PICO_SOCKET_STATE_TCP_SYN_SENT, NULL, &tcp_synack, NULL, NULL, NULL, NULL, &tcp_rst }, + { PICO_SOCKET_STATE_TCP_SYN_RECV, &tcp_synrecv_syn, NULL, &tcp_first_ack, &tcp_data_in, NULL, &tcp_closeconn, &tcp_rst }, + { PICO_SOCKET_STATE_TCP_ESTABLISHED, &tcp_halfopencon, &tcp_ack, &tcp_ack, &tcp_data_in, &tcp_closewait, &tcp_closewait, &tcp_rst }, + { PICO_SOCKET_STATE_TCP_CLOSE_WAIT, NULL, &tcp_ack, &tcp_ack, &tcp_send_rst, &tcp_closewait, &tcp_closewait, &tcp_rst }, + { PICO_SOCKET_STATE_TCP_LAST_ACK, NULL, &tcp_ack, &tcp_lastackwait, &tcp_send_rst, &tcp_send_rst, &tcp_send_rst, &tcp_rst }, + { PICO_SOCKET_STATE_TCP_FIN_WAIT1, NULL, &tcp_ack, &tcp_finwaitack, &tcp_data_in, &tcp_rcvfin, &tcp_finack, &tcp_rst }, + { PICO_SOCKET_STATE_TCP_FIN_WAIT2, NULL, &tcp_ack, &tcp_ack, &tcp_data_in, &tcp_finwaitfin, &tcp_finack, &tcp_rst }, + { PICO_SOCKET_STATE_TCP_CLOSING, NULL, &tcp_ack, &tcp_closing_ack, &tcp_send_rst, &tcp_send_rst, &tcp_send_rst, &tcp_rst }, + { PICO_SOCKET_STATE_TCP_TIME_WAIT, NULL, NULL, NULL, &tcp_send_rst, NULL, NULL, NULL} +}; + +#define MAX_VALID_FLAGS 10 /* Maximum number of valid flag combinations */ +static uint8_t invalid_flags(struct pico_socket *s, uint8_t flags) +{ + uint8_t i; + static const uint8_t valid_flags[PICO_SOCKET_STATE_TCP_ARRAYSIZ][MAX_VALID_FLAGS] = { + { /* PICO_SOCKET_STATE_TCP_UNDEF */ 0, }, + { /* PICO_SOCKET_STATE_TCP_CLOSED */ 0, }, + { /* PICO_SOCKET_STATE_TCP_LISTEN */ PICO_TCP_SYN }, + { /* PICO_SOCKET_STATE_TCP_SYN_SENT */ PICO_TCP_SYNACK, PICO_TCP_RST, PICO_TCP_RSTACK}, + { /* PICO_SOCKET_STATE_TCP_SYN_RECV */ PICO_TCP_SYN, PICO_TCP_ACK, PICO_TCP_PSH, PICO_TCP_PSHACK, PICO_TCP_FINACK, PICO_TCP_FINPSHACK, PICO_TCP_RST}, + { /* PICO_SOCKET_STATE_TCP_ESTABLISHED*/ PICO_TCP_SYN, PICO_TCP_SYNACK, PICO_TCP_ACK, PICO_TCP_PSH, PICO_TCP_PSHACK, PICO_TCP_FIN, PICO_TCP_FINACK, PICO_TCP_FINPSHACK, PICO_TCP_RST, PICO_TCP_RSTACK}, + { /* PICO_SOCKET_STATE_TCP_CLOSE_WAIT */ PICO_TCP_SYNACK, PICO_TCP_ACK, PICO_TCP_PSH, PICO_TCP_PSHACK, PICO_TCP_FIN, PICO_TCP_FINACK, PICO_TCP_FINPSHACK, PICO_TCP_RST}, + { /* PICO_SOCKET_STATE_TCP_LAST_ACK */ PICO_TCP_SYNACK, PICO_TCP_ACK, PICO_TCP_PSH, PICO_TCP_PSHACK, PICO_TCP_FIN, PICO_TCP_FINACK, PICO_TCP_FINPSHACK, PICO_TCP_RST}, + { /* PICO_SOCKET_STATE_TCP_FIN_WAIT1 */ PICO_TCP_SYNACK, PICO_TCP_ACK, PICO_TCP_PSH, PICO_TCP_PSHACK, PICO_TCP_FIN, PICO_TCP_FINACK, PICO_TCP_FINPSHACK, PICO_TCP_RST}, + { /* PICO_SOCKET_STATE_TCP_FIN_WAIT2 */ PICO_TCP_SYNACK, PICO_TCP_ACK, PICO_TCP_PSH, PICO_TCP_PSHACK, PICO_TCP_FIN, PICO_TCP_FINACK, PICO_TCP_FINPSHACK, PICO_TCP_RST}, + { /* PICO_SOCKET_STATE_TCP_CLOSING */ PICO_TCP_SYNACK, PICO_TCP_ACK, PICO_TCP_PSH, PICO_TCP_PSHACK, PICO_TCP_FIN, PICO_TCP_FINACK, PICO_TCP_FINPSHACK, PICO_TCP_RST}, + { /* PICO_SOCKET_STATE_TCP_TIME_WAIT */ PICO_TCP_SYNACK, PICO_TCP_ACK, PICO_TCP_PSH, PICO_TCP_PSHACK, PICO_TCP_FIN, PICO_TCP_FINACK, PICO_TCP_FINPSHACK, PICO_TCP_RST}, + }; + if(!flags) + return 1; + + for(i = 0; i < MAX_VALID_FLAGS; i++) { + if(valid_flags[s->state >> 8u][i] == flags) + return 0; + } + return 1; +} + +static void tcp_action_call(int (*call)(struct pico_socket *s, struct pico_frame *f), struct pico_socket *s, struct pico_frame *f ) +{ + if (call) + call(s, f); +} + +static int tcp_action_by_flags(const struct tcp_action_entry *action, struct pico_socket *s, struct pico_frame *f, uint8_t flags) +{ + int ret = 0; + + if ((flags == PICO_TCP_ACK) || (flags == (PICO_TCP_ACK | PICO_TCP_PSH))) { + tcp_action_call(action->ack, s, f); + } + + if ((f->payload_len > 0 || (flags & PICO_TCP_PSH)) && + !(s->state & PICO_SOCKET_STATE_CLOSED) && !TCP_IS_STATE(s, PICO_SOCKET_STATE_TCP_LISTEN)) + { + ret = f->payload_len; + tcp_action_call(action->data, s, f); + } + + if (flags == PICO_TCP_FIN) { + tcp_action_call(action->fin, s, f); + } + + if ((flags == (PICO_TCP_FIN | PICO_TCP_ACK)) || (flags == (PICO_TCP_FIN | PICO_TCP_ACK | PICO_TCP_PSH))) { + tcp_action_call(action->finack, s, f); + } + + if (flags & PICO_TCP_RST) { + tcp_action_call(action->rst, s, f); + } + + return ret; +} + +int pico_tcp_input(struct pico_socket *s, struct pico_frame *f) +{ + struct pico_tcp_hdr *hdr = (struct pico_tcp_hdr *) (f->transport_hdr); + int ret = 0; + uint8_t flags = hdr->flags; + const struct tcp_action_entry *action = &tcp_fsm[s->state >> 8]; + + f->payload = (f->transport_hdr + ((hdr->len & 0xf0u) >> 2u)); + f->payload_len = (uint16_t)(f->transport_len - ((hdr->len & 0xf0u) >> 2u)); + + tcp_dbg("[sam] TCP> [tcp input] t_len: %u\n", f->transport_len); + tcp_dbg("[sam] TCP> flags = 0x%02x\n", hdr->flags); + tcp_dbg("[sam] TCP> s->state >> 8 = %u\n", s->state >> 8); + tcp_dbg("[sam] TCP> [tcp input] socket: %p state: %d <-- local port:%u remote port: %u seq: 0x%08x ack: 0x%08x flags: 0x%02x t_len: %u, hdr: %u payload: %d\n", s, s->state >> 8, short_be(hdr->trans.dport), short_be(hdr->trans.sport), SEQN(f), ACKN(f), hdr->flags, f->transport_len, (hdr->len & 0xf0) >> 2, f->payload_len ); + + /* This copy of the frame has the current socket as owner */ + f->sock = s; + s->timestamp = TCP_TIME; + /* Those are not supported at this time. */ + /* flags &= (uint8_t) ~(PICO_TCP_CWR | PICO_TCP_URG | PICO_TCP_ECN); */ + if(invalid_flags(s, flags)) { + pico_tcp_reply_rst(f); + } + else if (flags == PICO_TCP_SYN) { + tcp_action_call(action->syn, s, f); + } else if (flags == (PICO_TCP_SYN | PICO_TCP_ACK)) { + tcp_action_call(action->synack, s, f); + } else { + ret = tcp_action_by_flags(action, s, f, flags); + } + + if (s->ev_pending) + tcp_wakeup_pending(s, s->ev_pending); + +/* discard: */ + pico_frame_discard(f); + return ret; +} + + +inline static int checkLocalClosing(struct pico_socket *s); +inline static int checkRemoteClosing(struct pico_socket *s); + +static struct pico_frame *tcp_split_segment(struct pico_socket_tcp *t, struct pico_frame *f, uint16_t size) +{ + struct pico_frame *f1, *f2; + uint16_t size1, size2, size_f; + uint16_t overhead; + struct pico_tcp_hdr *hdr1, *hdr2, *hdr = (struct pico_tcp_hdr *)f->transport_hdr; + overhead = pico_tcp_overhead(&t->sock); + size_f = f->payload_len; + + + if (size >= size_f) + return f; /* no need to split! */ + + size1 = size; + size2 = (uint16_t)(size_f - size); + + f1 = pico_socket_frame_alloc(&t->sock, (uint16_t) (size1 + overhead)); + f2 = pico_socket_frame_alloc(&t->sock, (uint16_t) (size2 + overhead)); + + if (!f1 || !f2) { + pico_err = PICO_ERR_ENOMEM; + return NULL; + } + + /* Advance payload pointer to the beginning of segment data */ + f1->payload += overhead; + f1->payload_len = (uint16_t)(f1->payload_len - overhead); + f2->payload += overhead; + f2->payload_len = (uint16_t)(f2->payload_len - overhead); + + hdr1 = (struct pico_tcp_hdr *)f1->transport_hdr; + hdr2 = (struct pico_tcp_hdr *)f2->transport_hdr; + + /* Copy payload */ + memcpy(f1->payload, f->payload, size1); + memcpy(f2->payload, f->payload + size1, size2); + + /* Copy tcp hdr */ + memcpy(hdr1, hdr, sizeof(struct pico_tcp_hdr)); + memcpy(hdr2, hdr, sizeof(struct pico_tcp_hdr)); + + /* Adjust f2's sequence number */ + hdr2->seq = long_be(SEQN(f) + size1); + + /* Add TCP options */ + pico_tcp_flags_update(f1, &t->sock); + pico_tcp_flags_update(f2, &t->sock); + tcp_add_options_frame(t, f1); + tcp_add_options_frame(t, f2); + + /* Get rid of the full frame */ + pico_discard_segment(&t->tcpq_out, f); + + /* Enqueue f2 for later send... */ + pico_enqueue_segment(&t->tcpq_out, f2); + + /* Return the partial frame */ + return f1; +} + + +int pico_tcp_output(struct pico_socket *s, int loop_score) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + struct pico_frame *f, *una; + int sent = 0; + int data_sent = 0; + int32_t seq_diff = 0; + + una = first_segment(&t->tcpq_out); + f = peek_segment(&t->tcpq_out, t->snd_nxt); + + while((f) && (t->cwnd >= t->in_flight)) { + f->timestamp = TCP_TIME; + add_retransmission_timer(t, t->rto + TCP_TIME); + tcp_add_options_frame(t, f); + seq_diff = pico_seq_compare(SEQN(f), SEQN(una)); + if (seq_diff < 0) { + tcp_dbg(">>> FATAL: seq diff is negative!\n"); + break; + } + + /* Check if advertised window is full */ + if ((uint32_t)seq_diff >= (uint32_t)(t->recv_wnd << t->recv_wnd_scale)) { + if (t->x_mode != PICO_TCP_WINDOW_FULL) { + tcp_dbg("TCP> RIGHT SIZING (rwnd: %d, frame len: %d\n", t->recv_wnd << t->recv_wnd_scale, f->payload_len); + tcp_dbg("In window full...\n"); + t->snd_nxt = SEQN(una); + t->snd_retry = SEQN(una); + t->x_mode = PICO_TCP_WINDOW_FULL; + } + + break; + } + + /* Check if the advertised window is too small to receive the current frame */ + if ((uint32_t)(seq_diff + f->payload_len) > (uint32_t)(t->recv_wnd << t->recv_wnd_scale)) { + f = tcp_split_segment(t, f, (uint16_t)(t->recv_wnd << t->recv_wnd_scale)); + if (!f) + break; + + /* Limit sending window to packets in flight (right sizing) */ + t->cwnd = (uint16_t)t->in_flight; + if (t->cwnd < 1) + t->cwnd = 1; + } + + tcp_dbg("TCP> DEQUEUED (for output) frame %08x, acks %08x len= %d, remaining frames %d\n", SEQN(f), ACKN(f), f->payload_len, t->tcpq_out.frames); + tcp_send(t, f); + sent++; + loop_score--; + t->snd_last_out = SEQN(f); + if (loop_score < 1) + break; + + if (f->payload_len > 0) { + data_sent++; + f = next_segment(&t->tcpq_out, f); + } else { + f = NULL; + } + } + if ((sent > 0 && data_sent > 0)) { + rto_set(t, t->rto); + } else { + /* Nothing to transmit. */ + } + + if ((t->tcpq_out.frames == 0) && (s->state & PICO_SOCKET_STATE_SHUT_LOCAL)) { /* if no more packets in queue, XXX replaced !f by tcpq check */ + if(!checkLocalClosing(&t->sock)) /* check if local closing started and send fin */ + { + checkRemoteClosing(&t->sock); /* check if remote closing started and send fin */ + } + } + + return loop_score; +} + +/* function to make new segment from hold queue with specific size (mss) */ +static struct pico_frame *pico_hold_segment_make(struct pico_socket_tcp *t) +{ + struct pico_frame *f_temp, *f_new; + struct pico_socket *s = (struct pico_socket *) &t->sock; + struct pico_tcp_hdr *hdr; + uint16_t total_len = 0, total_payload_len = 0; + uint16_t off = 0, test = 0; + + off = pico_tcp_overhead(s); + + /* init with first frame in hold queue */ + f_temp = first_segment(&t->tcpq_hold); + total_len = f_temp->payload_len; + f_temp = next_segment(&t->tcpq_hold, f_temp); + + /* check till total_len <= MSS */ + while ((f_temp != NULL) && ((total_len + f_temp->payload_len) <= t->mss)) { + total_len = (uint16_t)(total_len + f_temp->payload_len); + f_temp = next_segment(&t->tcpq_hold, f_temp); + if (f_temp == NULL) + break; + } + /* alloc new frame with payload size = off + total_len */ + f_new = pico_socket_frame_alloc(s, (uint16_t)(off + total_len)); + if (!f_new) { + pico_err = PICO_ERR_ENOMEM; + return f_new; + } + + pico_tcp_flags_update(f_new, &t->sock); + hdr = (struct pico_tcp_hdr *) f_new->transport_hdr; + /* init new frame */ + f_new->payload += off; + f_new->payload_len = (uint16_t)(f_new->payload_len - off); + f_new->sock = s; + + f_temp = first_segment(&t->tcpq_hold); + hdr->seq = ((struct pico_tcp_hdr *)(f_temp->transport_hdr))->seq; /* get sequence number of first frame */ + hdr->trans.sport = t->sock.local_port; + hdr->trans.dport = t->sock.remote_port; + + /* check till total_payload_len <= MSS */ + while ((f_temp != NULL) && ((total_payload_len + f_temp->payload_len) <= t->mss)) { + /* cpy data and discard frame */ + test++; + memcpy(f_new->payload + total_payload_len, f_temp->payload, f_temp->payload_len); + total_payload_len = (uint16_t)(total_payload_len + f_temp->payload_len); + pico_discard_segment(&t->tcpq_hold, f_temp); + f_temp = first_segment(&t->tcpq_hold); + } + hdr->len = (uint8_t)((f_new->payload - f_new->transport_hdr) << 2u | (int8_t)t->jumbo); + + tcp_dbg_nagle("NAGLE make - joined %d segments, len %d bytes\n", test, total_payload_len); + tcp_add_options_frame(t, f_new); + + return f_new; +} + + + +static int pico_tcp_push_nagle_enqueue(struct pico_socket_tcp *t, struct pico_frame *f) +{ + if (pico_enqueue_segment(&t->tcpq_out, f) > 0) { + tcp_dbg_nagle("TCP_PUSH - NAGLE - Pushing segment %08x, len %08x to socket %p\n", t->snd_last + 1, f->payload_len, t); + t->snd_last += f->payload_len; + return f->payload_len; + } else { + tcp_dbg("Enqueue failed.\n"); + return 0; + } +} + +static int pico_tcp_push_nagle_hold(struct pico_socket_tcp *t, struct pico_frame *f) +{ + struct pico_frame *f_new; + uint32_t total_len = 0; + total_len = f->payload_len + t->tcpq_hold.size; + if ((total_len >= t->mss) && ((t->tcpq_out.max_size - t->tcpq_out.size) >= t->mss)) { + /* IF enough data in hold (>mss) AND space in out queue (>mss) */ + /* add current frame in hold and make new segment */ + if (pico_enqueue_segment(&t->tcpq_hold, f) > 0 ) { + tcp_dbg_nagle("TCP_PUSH - NAGLE - Pushed into hold, make new (enqueued frames out %d)\n", t->tcpq_out.frames); + t->snd_last += f->payload_len; /* XXX WATCH OUT */ + f_new = pico_hold_segment_make(t); + } else { + tcp_dbg_nagle("TCP_PUSH - NAGLE - enqueue hold failed 1\n"); + return 0; + } + + /* and put new frame in out queue */ + if ((f_new != NULL) && (pico_enqueue_segment(&t->tcpq_out, f_new) > 0)) { + return f_new->payload_len; + } else { + tcp_dbg_nagle("TCP_PUSH - NAGLE - enqueue out failed, f_new = %p\n", f_new); + return -1; /* XXX something seriously wrong */ + } + } else { + /* ELSE put frame in hold queue */ + if (pico_enqueue_segment(&t->tcpq_hold, f) > 0) { + tcp_dbg_nagle("TCP_PUSH - NAGLE - Pushed into hold (enqueued frames out %d)\n", t->tcpq_out.frames); + t->snd_last += f->payload_len; /* XXX WATCH OUT */ + return f->payload_len; + } else { + pico_err = PICO_ERR_EAGAIN; + tcp_dbg_nagle("TCP_PUSH - NAGLE - enqueue hold failed 2\n"); + } + } + + return 0; +} + + +static int pico_tcp_push_nagle_on(struct pico_socket_tcp *t, struct pico_frame *f) +{ + /* Nagle's algorithm enabled, check if ready to send, or put frame in hold queue */ + if (IS_TCP_IDLE(t) && IS_TCP_HOLDQ_EMPTY(t)) + return pico_tcp_push_nagle_enqueue(t, f); + + return pico_tcp_push_nagle_hold(t, f); +} + + + +/* original behavior kept when Nagle disabled; + Nagle algorithm added here, keeping hold frame queue instead of eg linked list of data */ +int pico_tcp_push(struct pico_protocol *self, struct pico_frame *f) +{ + struct pico_tcp_hdr *hdr = (struct pico_tcp_hdr *)f->transport_hdr; + struct pico_socket_tcp *t = (struct pico_socket_tcp *) f->sock; + IGNORE_PARAMETER(self); + pico_err = PICO_ERR_NOERR; + hdr->trans.sport = t->sock.local_port; + hdr->trans.dport = t->sock.remote_port; + hdr->seq = long_be(t->snd_last + 1); + hdr->len = (uint8_t)((f->payload - f->transport_hdr) << 2u | (int8_t)t->jumbo); + + if ((uint32_t)f->payload_len > (uint32_t)(t->tcpq_out.max_size - t->tcpq_out.size)) + t->sock.ev_pending &= (uint16_t)(~PICO_SOCK_EV_WR); + + /***************************************************************************/ + + if (!IS_NAGLE_ENABLED((&(t->sock)))) { + /* TCP_NODELAY enabled, original behavior */ + if (pico_enqueue_segment(&t->tcpq_out, f) > 0) { + tcp_dbg_nagle("TCP_PUSH - NO NAGLE - Pushing segment %08x, len %08x to socket %p\n", t->snd_last + 1, f->payload_len, t); + t->snd_last += f->payload_len; + return f->payload_len; + } else { + tcp_dbg("Enqueue failed.\n"); + return 0; + } + } else { + return pico_tcp_push_nagle_on(t, f); + } + +} + +inline static void tcp_discard_all_segments(struct pico_tcp_queue *tq) +{ + struct pico_tree_node *index = NULL, *index_safe = NULL; + PICOTCP_MUTEX_LOCK(Mutex); + pico_tree_foreach_safe(index, &tq->pool, index_safe) + { + void *f = index->keyValue; + if(!f) + break; + + pico_tree_delete(&tq->pool, f); + if(IS_INPUT_QUEUE(tq)) + { + struct tcp_input_segment *inp = (struct tcp_input_segment *)f; + PICO_FREE(inp->payload); + PICO_FREE(inp); + } + else + pico_frame_discard(f); + } + tq->frames = 0; + tq->size = 0; + PICOTCP_MUTEX_UNLOCK(Mutex); +} + +void pico_tcp_cleanup_queues(struct pico_socket *sck) +{ + struct pico_socket_tcp *tcp = (struct pico_socket_tcp *)sck; + pico_timer_cancel(tcp->retrans_tmr); + pico_timer_cancel(tcp->keepalive_tmr); + pico_timer_cancel(tcp->fin_tmr); + + tcp->retrans_tmr = 0; + tcp->keepalive_tmr = 0; + tcp->fin_tmr = 0; + + tcp_discard_all_segments(&tcp->tcpq_in); + tcp_discard_all_segments(&tcp->tcpq_out); + tcp_discard_all_segments(&tcp->tcpq_hold); +} + +static int checkLocalClosing(struct pico_socket *s) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + if ((s->state & PICO_SOCKET_STATE_TCP) == PICO_SOCKET_STATE_TCP_ESTABLISHED) { + tcp_dbg("TCP> buffer empty, shutdown established ...\n"); + /* send fin if queue empty and in state shut local (write) */ + tcp_send_fin(t); + /* change tcp state to FIN_WAIT1 */ + s->state &= 0x00FFU; + s->state |= PICO_SOCKET_STATE_TCP_FIN_WAIT1; + return 1; + } + + return 0; +} + +static int checkRemoteClosing(struct pico_socket *s) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + if ((s->state & PICO_SOCKET_STATE_TCP) == PICO_SOCKET_STATE_TCP_CLOSE_WAIT) { + /* send fin if queue empty and in state shut local (write) */ + tcp_send_fin(t); + /* change tcp state to LAST_ACK */ + s->state &= 0x00FFU; + s->state |= PICO_SOCKET_STATE_TCP_LAST_ACK; + tcp_dbg("TCP> STATE: LAST_ACK.\n"); + return 1; + } + + return 0; +} + +void pico_tcp_notify_closing(struct pico_socket *sck) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)sck; + if(t->tcpq_out.frames == 0) + { + if(!checkLocalClosing(sck)) + checkRemoteClosing(sck); + } +} + + +int pico_tcp_check_listen_close(struct pico_socket *s) +{ + if (TCP_IS_STATE(s, PICO_SOCKET_STATE_TCP_LISTEN)) { + pico_socket_del(s); + return 0; + } + + return -1; +} + +void pico_tcp_flags_update(struct pico_frame *f, struct pico_socket *s) +{ + f->transport_flags_saved = ((struct pico_socket_tcp *)s)->ts_ok; +} + +int pico_tcp_set_bufsize_in(struct pico_socket *s, uint32_t value) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + t->tcpq_in.max_size = value; + return 0; +} + +int pico_tcp_set_bufsize_out(struct pico_socket *s, uint32_t value) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + t->tcpq_out.max_size = value; + return 0; +} + +int pico_tcp_get_bufsize_in(struct pico_socket *s, uint32_t *value) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + *value = t->tcpq_in.max_size; + return 0; +} + +int pico_tcp_get_bufsize_out(struct pico_socket *s, uint32_t *value) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + *value = t->tcpq_out.max_size; + return 0; +} + +int pico_tcp_set_keepalive_probes(struct pico_socket *s, uint32_t value) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + t->ka_probes = value; + return 0; +} + +int pico_tcp_set_keepalive_intvl(struct pico_socket *s, uint32_t value) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + t->ka_intvl = value; + return 0; +} + +int pico_tcp_set_keepalive_time(struct pico_socket *s, uint32_t value) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + t->ka_time = value; + return 0; +} + +int pico_tcp_set_linger(struct pico_socket *s, uint32_t value) +{ + struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; + t->linger_timeout = value; + return 0; +} + +#endif /* PICO_SUPPORT_TCP */ diff --git a/ext/picotcp/modules/pico_tcp.h b/ext/picotcp/modules/pico_tcp.h new file mode 100644 index 0000000..ece38bb --- /dev/null +++ b/ext/picotcp/modules/pico_tcp.h @@ -0,0 +1,106 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + . + + *********************************************************************/ +#ifndef INCLUDE_PICO_TCP +#define INCLUDE_PICO_TCP +#include "pico_addressing.h" +#include "pico_protocol.h" +#include "pico_socket.h" + +extern struct pico_protocol pico_proto_tcp; + +PACKED_STRUCT_DEF pico_tcp_hdr { + struct pico_trans trans; + uint32_t seq; + uint32_t ack; + uint8_t len; + uint8_t flags; + uint16_t rwnd; + uint16_t crc; + uint16_t urgent; +}; + +PACKED_STRUCT_DEF tcp_pseudo_hdr_ipv4 +{ + struct pico_ip4 src; + struct pico_ip4 dst; + uint16_t tcp_len; + uint8_t res; + uint8_t proto; +}; + +#define PICO_TCPHDR_SIZE 20 +#define PICO_SIZE_TCPOPT_SYN 20 +#define PICO_SIZE_TCPHDR (uint32_t)(sizeof(struct pico_tcp_hdr)) + +/* TCP options */ +#define PICO_TCP_OPTION_END 0x00 +#define PICO_TCPOPTLEN_END 1u +#define PICO_TCP_OPTION_NOOP 0x01 +#define PICO_TCPOPTLEN_NOOP 1 +#define PICO_TCP_OPTION_MSS 0x02 +#define PICO_TCPOPTLEN_MSS 4 +#define PICO_TCP_OPTION_WS 0x03 +#define PICO_TCPOPTLEN_WS 3u +#define PICO_TCP_OPTION_SACK_OK 0x04 +#define PICO_TCPOPTLEN_SACK_OK 2 +#define PICO_TCP_OPTION_SACK 0x05 +#define PICO_TCPOPTLEN_SACK 2 /* Plus the block */ +#define PICO_TCP_OPTION_TIMESTAMP 0x08 +#define PICO_TCPOPTLEN_TIMESTAMP 10u + +/* TCP flags */ +#define PICO_TCP_FIN 0x01u +#define PICO_TCP_SYN 0x02u +#define PICO_TCP_RST 0x04u +#define PICO_TCP_PSH 0x08u +#define PICO_TCP_ACK 0x10u +#define PICO_TCP_URG 0x20u +#define PICO_TCP_ECN 0x40u +#define PICO_TCP_CWR 0x80u + +#define PICO_TCP_SYNACK (PICO_TCP_SYN | PICO_TCP_ACK) +#define PICO_TCP_PSHACK (PICO_TCP_PSH | PICO_TCP_ACK) +#define PICO_TCP_FINACK (PICO_TCP_FIN | PICO_TCP_ACK) +#define PICO_TCP_FINPSHACK (PICO_TCP_FIN | PICO_TCP_PSH | PICO_TCP_ACK) +#define PICO_TCP_RSTACK (PICO_TCP_RST | PICO_TCP_ACK) + + +PACKED_STRUCT_DEF pico_tcp_option +{ + uint8_t kind; + uint8_t len; +}; + +struct pico_socket *pico_tcp_open(uint16_t family); +uint32_t pico_tcp_read(struct pico_socket *s, void *buf, uint32_t len); +int pico_tcp_initconn(struct pico_socket *s); +int pico_tcp_input(struct pico_socket *s, struct pico_frame *f); +uint16_t pico_tcp_checksum(struct pico_frame *f); +uint16_t pico_tcp_checksum_ipv4(struct pico_frame *f); +#ifdef PICO_SUPPORT_IPV6 +uint16_t pico_tcp_checksum_ipv6(struct pico_frame *f); +#endif +uint16_t pico_tcp_overhead(struct pico_socket *s); +int pico_tcp_output(struct pico_socket *s, int loop_score); +int pico_tcp_queue_in_is_empty(struct pico_socket *s); +int pico_tcp_reply_rst(struct pico_frame *f); +void pico_tcp_cleanup_queues(struct pico_socket *sck); +void pico_tcp_notify_closing(struct pico_socket *sck); +void pico_tcp_flags_update(struct pico_frame *f, struct pico_socket *s); +int pico_tcp_set_bufsize_in(struct pico_socket *s, uint32_t value); +int pico_tcp_set_bufsize_out(struct pico_socket *s, uint32_t value); +int pico_tcp_get_bufsize_in(struct pico_socket *s, uint32_t *value); +int pico_tcp_get_bufsize_out(struct pico_socket *s, uint32_t *value); +int pico_tcp_set_keepalive_probes(struct pico_socket *s, uint32_t value); +int pico_tcp_set_keepalive_intvl(struct pico_socket *s, uint32_t value); +int pico_tcp_set_keepalive_time(struct pico_socket *s, uint32_t value); +int pico_tcp_set_linger(struct pico_socket *s, uint32_t value); +uint16_t pico_tcp_get_socket_mss(struct pico_socket *s); +int pico_tcp_check_listen_close(struct pico_socket *s); + +#endif diff --git a/ext/picotcp/modules/pico_tftp.c b/ext/picotcp/modules/pico_tftp.c new file mode 100644 index 0000000..83c5c6f --- /dev/null +++ b/ext/picotcp/modules/pico_tftp.c @@ -0,0 +1,1300 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + . + + Author: Daniele Lacamera + *********************************************************************/ + +#include +#include +#include +#include +#include + +/* a zero value means adaptative timeout! (2, 4, 8) */ +#define PICO_TFTP_TIMEOUT 2000U + +#define TFTP_MAX_RETRY 3 + +#define TFTP_STATE_READ_REQUESTED 0 +#define TFTP_STATE_RX 1 +#define TFTP_STATE_LAST_ACK_SENT 2 +#define TFTP_STATE_WRITE_REQUESTED 3 +#define TFTP_STATE_TX 4 +#define TFTP_STATE_WAIT_OPT_CONFIRM 5 +#define TFTP_STATE_WAIT_LAST_ACK 6 +#define TFTP_STATE_CLOSING 7 + +#define AUTOMA_STATES (TFTP_STATE_CLOSING + 1) + +/* MAX_OPTIONS_SIZE: "timeout" 255 "tsize" filesize => 8 + 4 + 6 + 11 */ +#define MAX_OPTIONS_SIZE 29 + +/* RRQ and WRQ packets (opcodes 1 and 2 respectively) */ +PACKED_STRUCT_DEF pico_tftp_hdr +{ + uint16_t opcode; +}; + +/* DATA or ACK (opcodes 3 and 4 respectively)*/ +PACKED_STRUCT_DEF pico_tftp_data_hdr +{ + uint16_t opcode; + uint16_t block; +}; + +/* ERROR (opcode 5) */ +PACKED_STRUCT_DEF pico_tftp_err_hdr +{ + uint16_t opcode; + uint16_t error_code; +}; + +#define PICO_TFTP_TOTAL_BLOCK_SIZE (PICO_TFTP_PAYLOAD_SIZE + (int32_t)sizeof(struct pico_tftp_data_hdr)) +#define tftp_payload(p) (((uint8_t *)(p)) + sizeof(struct pico_tftp_data_hdr)) + +/* STATUS FLAGS */ +#define SESSION_STATUS_CLOSED 1 +#define SESSION_STATUS_APP_PENDING 2 +#define SESSION_STATUS_IN_CALLBACK 4 +#define SESSION_STATUS_APP_ACK 64 + +struct pico_tftp_session { + int state; + int status; + int options; + int retry; + uint16_t packet_counter; + /* Current connection */ + struct pico_socket *socket; + union pico_address remote_address; + uint16_t remote_port; + uint16_t localport; + pico_time wallclock_timeout; + pico_time bigger_wallclock; + struct pico_tftp_session *next; + uint32_t timer; + unsigned int active_timers; + void *argument; + int (*callback)(struct pico_tftp_session *session, uint16_t event, uint8_t *block, int32_t len, void *arg); + int32_t file_size; + int32_t len; + uint8_t option_timeout; + uint8_t tftp_block[PICO_TFTP_TOTAL_BLOCK_SIZE]; + int32_t block_len; +}; + +struct server_t { + void (*listen_callback)(union pico_address *addr, uint16_t port, uint16_t opcode, char *filename, int32_t len); + struct pico_socket *listen_socket; + uint8_t tftp_block[PICO_TFTP_TOTAL_BLOCK_SIZE]; +}; + +struct automa_events { + void (*ack)(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port); + void (*data)(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port); + void (*error)(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port); + void (*oack)(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port); + void (*timeout)(struct pico_tftp_session *session, pico_time t); +}; + +static struct server_t server; + +static struct pico_tftp_session *tftp_sessions = NULL; + +static inline void session_status_set(struct pico_tftp_session *session, int status) +{ + session->status |= status; +} + +static inline void session_status_clear(struct pico_tftp_session *session, int status) +{ + session->status &= ~status; +} + +static char *extract_arg_pointer(char *arg, char *end_arg, char **value) +{ + char *pos; + + pos = get_string_terminator_position(arg, (size_t)(end_arg - arg)); + if (!pos) + return NULL; + + if (end_arg == ++pos) + return NULL; + + arg = get_string_terminator_position(pos, (size_t)(end_arg - pos)); + + if (!arg) + return NULL; + + *value = pos; + return arg + 1; +} + +static int extract_value(char *str, uint32_t *value, uint32_t max) +{ + char *endptr; + unsigned long num; + + num = strtoul(str, &endptr, 10); + + if (endptr == str || *endptr || num > max) + return -1; + + *value = (uint32_t)num; + return 0; +} + +static int parse_optional_arguments(char *option_string, int32_t len, int *options, uint8_t *timeout, int32_t *filesize) +{ + char *pos; + char *end_args = option_string + len; + char *current_option; + int ret; + uint32_t value; + + *options = 0; + + while (option_string < end_args) { + current_option = option_string; + option_string = extract_arg_pointer(option_string, end_args, &pos); + if (!option_string) + return 0; + + if (!pico_strncasecmp("timeout", current_option, (size_t)(pos - current_option))) { + ret = extract_value(pos, &value, PICO_TFTP_MAX_TIMEOUT); + if (ret) + return -1; + + *timeout = (uint8_t)value; + *options |= PICO_TFTP_OPTION_TIME; + } else { + if (!pico_strncasecmp("tsize", current_option, (size_t)(pos - current_option))) { + ret = extract_value(pos, (uint32_t *)filesize, PICO_TFTP_MAX_FILESIZE); + if (ret) + return -1; + + if (*filesize < 0) + return -1; + + *options |= PICO_TFTP_OPTION_FILE; + } + } + } + return 0; +} + +static inline struct pico_tftp_session *pico_tftp_session_create(struct pico_socket *sock, union pico_address *remote_addr) +{ + struct pico_tftp_session *session; + + session = (struct pico_tftp_session *) PICO_ZALLOC(sizeof (struct pico_tftp_session)); + + if (!session) + pico_err = PICO_ERR_ENOMEM; + else { + session->state = 0; + session->status = 0; + session->options = 0; + session->packet_counter = 0u; + session->socket = sock; + session->wallclock_timeout = 0; + session->bigger_wallclock = 0; + session->active_timers = 0; + session->next = NULL; + session->localport = 0; + session->callback = NULL; + session->argument = NULL; + memcpy(&session->remote_address, remote_addr, sizeof(union pico_address)); + session->remote_port = 0; + session->len = 0; + } + + return session; +} + +static struct pico_tftp_session *find_session_by_socket(struct pico_socket *tftp_socket) +{ + struct pico_tftp_session *pos = tftp_sessions; + + for (; pos; pos = pos->next) + if (pos->socket == tftp_socket) + return pos; + + return NULL; +} + +/* **************** for future use... + static struct pico_tftp_session * find_session_by_localport(uint16_t localport) + { + struct pico_tftp_session *idx = tftp_sessions; + + for (; idx; idx = idx->next) + if (idx->localport == localport) + return idx; + + return NULL; + } *********************/ + +static void add_session(struct pico_tftp_session *idx) +{ + struct pico_tftp_session *prev = NULL; + struct pico_tftp_session *pos; + + for (pos = tftp_sessions; pos; prev = pos, pos = pos->next) + if (pos->localport > idx->localport) + break; + + if (prev) { + idx->next = prev->next; + prev->next = idx; + } else { + idx->next = tftp_sessions; + tftp_sessions = idx; + } +} + +/* Returns 0 if OK and -1 in case of errors */ +static int del_session(struct pico_tftp_session *idx) +{ + struct pico_tftp_session *prev = NULL; + struct pico_tftp_session *pos; + + for (pos = tftp_sessions; pos; pos = pos->next) { + if (pos == idx) { + if (pos == tftp_sessions) + tftp_sessions = tftp_sessions->next; + else + prev->next = pos->next; + + PICO_FREE(idx); + return 0; + } + + prev = pos; + } + return -1; +} + +static inline int do_callback(struct pico_tftp_session *session, uint16_t err, uint8_t *data, int32_t len) +{ + int ret; + + session_status_set(session, SESSION_STATUS_IN_CALLBACK); + ret = session->callback(session, err, data, len, session->argument); + session_status_clear(session, SESSION_STATUS_IN_CALLBACK); + + return ret; +} + +static void timer_callback(pico_time now, void *arg); + +static void tftp_schedule_timeout(struct pico_tftp_session *session, pico_time interval) +{ + pico_time new_timeout = PICO_TIME_MS() + interval; + + if (session->active_timers) { + if (session->bigger_wallclock > new_timeout) { + session->timer = pico_timer_add(interval + 1, timer_callback, session); + session->active_timers++; + } + } else { + session->timer = pico_timer_add(interval + 1, timer_callback, session); + session->active_timers++; + session->bigger_wallclock = new_timeout; + } + + session->wallclock_timeout = new_timeout; +} + +static void tftp_finish(struct pico_tftp_session *session) +{ + if (session->state != TFTP_STATE_CLOSING) { + pico_socket_close(session->socket); + session->state = TFTP_STATE_CLOSING; + if (session->active_timers) { + pico_timer_cancel(session->timer); + --session->active_timers; + } + + session->wallclock_timeout = 0; + tftp_schedule_timeout(session, 5); + } +} + +static void tftp_send(struct pico_tftp_session *session, int len) +{ + if (len) + session->len = len; + else + len = session->len; + + pico_socket_sendto(session->socket, session->tftp_block, session->len, &session->remote_address, session->remote_port); +} + +static void tftp_send_ack(struct pico_tftp_session *session) +{ + struct pico_tftp_data_hdr *dh; + + dh = PICO_ZALLOC(sizeof(struct pico_tftp_data_hdr)); + if (!dh) + return; + + dh->opcode = short_be(PICO_TFTP_ACK); + dh->block = short_be(session->packet_counter); + + if (session->socket) { + pico_socket_sendto(session->socket, dh, (int) sizeof(struct pico_tftp_err_hdr), + &session->remote_address, session->remote_port); + tftp_schedule_timeout(session, PICO_TFTP_TIMEOUT); + } + + PICO_FREE(dh); +} + +static size_t prepare_options_string(struct pico_tftp_session *session, char *str_options, int32_t filesize) +{ + size_t len = 0; + int res; + + if (session->options & PICO_TFTP_OPTION_TIME) { + strcpy(str_options, "timeout"); + len += 8; + res = num2string(session->option_timeout, &str_options[len], 4); + if (res < 0) + return 0; + + len += (size_t)res; + } + + if (session->options & PICO_TFTP_OPTION_FILE) { + strcpy(&str_options[len], "tsize"); + len += 6; + res = num2string(filesize, &str_options[len], 11); + if (res < 0) + return 0; + + len += (size_t)res; + } + + return len; +} + +static void tftp_send_oack(struct pico_tftp_session *session) +{ + struct pico_tftp_hdr *hdr; + size_t options_size; + size_t options_pos = sizeof(struct pico_tftp_hdr); + uint8_t *buf; + char str_options[MAX_OPTIONS_SIZE] = { 0 }; + + options_size = prepare_options_string(session, str_options, session->file_size); + + buf = PICO_ZALLOC(options_pos + options_size); + if (!buf) { + strcpy((char *)session->tftp_block, "Out of memory"); + do_callback(session, PICO_TFTP_EV_ERR_LOCAL, session->tftp_block, 0); + tftp_finish(session); + return; + } + + hdr = (struct pico_tftp_hdr *)buf; + hdr->opcode = short_be(PICO_TFTP_OACK); + memcpy(buf + options_pos, str_options, options_size); + (void)pico_socket_sendto(session->socket, buf, (int)(options_pos + options_size), &session->remote_address, session->remote_port); + PICO_FREE(buf); +} + +static void tftp_send_req(struct pico_tftp_session *session, union pico_address *a, uint16_t port, const char *filename, uint16_t opcode) +{ +#define OCTET_STRSIZ 7U + static const char octet[OCTET_STRSIZ] = { + 0, 'o', 'c', 't', 'e', 't', 0 + }; + struct pico_tftp_hdr *hdr; + size_t len; + size_t options_size; + size_t options_pos; + uint8_t *buf; + char str_options[MAX_OPTIONS_SIZE] = { 0 }; + + if (!filename) { + return; + } + + len = strlen(filename); + + options_size = prepare_options_string(session, str_options, (opcode == PICO_TFTP_WRQ) ? (session->file_size) : (0)); + + options_pos = sizeof(struct pico_tftp_hdr) + OCTET_STRSIZ + len; + buf = PICO_ZALLOC(options_pos + options_size); + if (!buf) { + strcpy((char *)session->tftp_block, "Out of memory"); + do_callback(session, PICO_TFTP_EV_ERR_LOCAL, session->tftp_block, 0); + tftp_finish(session); + return; + } + + hdr = (struct pico_tftp_hdr *)buf; + hdr->opcode = short_be(opcode); + memcpy(buf + sizeof(struct pico_tftp_hdr), filename, len); + memcpy(buf + sizeof(struct pico_tftp_hdr) + len, octet, OCTET_STRSIZ); + memcpy(buf + options_pos, str_options, options_size); + (void)pico_socket_sendto(session->socket, buf, (int)(options_pos + options_size), a, port); + PICO_FREE(buf); +} + +static void tftp_send_rx_req(struct pico_tftp_session *session, union pico_address *a, uint16_t port, const char *filename) +{ + tftp_send_req(session, a, port, filename, PICO_TFTP_RRQ); + session->state = TFTP_STATE_READ_REQUESTED; + tftp_schedule_timeout(session, PICO_TFTP_TIMEOUT); +} + +static void tftp_send_tx_req(struct pico_tftp_session *session, union pico_address *a, uint16_t port, const char *filename) +{ + tftp_send_req(session, a, port, filename, PICO_TFTP_WRQ); + session->state = TFTP_STATE_WRITE_REQUESTED; + tftp_schedule_timeout(session, PICO_TFTP_TIMEOUT); +} + +static int send_error(uint8_t *buf, struct pico_socket *sock, union pico_address *a, uint16_t port, uint16_t errcode, const char *errmsg) +{ + struct pico_tftp_err_hdr *eh; + int32_t len; + int32_t maxlen = PICO_TFTP_TOTAL_BLOCK_SIZE - sizeof(struct pico_tftp_err_hdr); + + if (!errmsg) + len = 0; + else + len = (int32_t)strlen(errmsg); + + eh = (struct pico_tftp_err_hdr *) buf; + eh->opcode = short_be(PICO_TFTP_ERROR); + eh->error_code = short_be(errcode); + if (len + 1 > maxlen) + len = maxlen; + + if (len) + memcpy(tftp_payload(eh), errmsg, (size_t)len); + + tftp_payload(eh)[len++] = (char)0; + + return pico_socket_sendto(sock, eh, (int)(len + (int32_t)sizeof(struct pico_tftp_err_hdr)), a, port); +} + +static void tftp_send_error(struct pico_tftp_session *session, union pico_address *a, uint16_t port, uint16_t errcode, const char *errmsg) +{ + struct pico_tftp_err_hdr *eh; + int32_t len; + int32_t maxlen = PICO_TFTP_TOTAL_BLOCK_SIZE - sizeof(struct pico_tftp_err_hdr); + + if (!errmsg) + len = 0; + else + len = (int32_t)strlen(errmsg); + + if (!a) { + a = &session->remote_address; + port = session->remote_port; + } + + eh = (struct pico_tftp_err_hdr *) (session ? (session->tftp_block) : (server.tftp_block)); + eh->opcode = short_be(PICO_TFTP_ERROR); + eh->error_code = short_be(errcode); + if (len + 1 > maxlen) + len = maxlen; + + if (len) + memcpy(tftp_payload(eh), errmsg, (size_t)len); + + tftp_payload(eh)[len++] = (char)0; + if (session) { + (void)pico_socket_sendto(session->socket, eh, (int) (len + (int32_t)sizeof(struct pico_tftp_err_hdr)), a, port); + tftp_finish(session); + } else + (void)pico_socket_sendto(server.listen_socket, eh, (int) (len + (int32_t)sizeof(struct pico_tftp_err_hdr)), a, port); +} + +static void tftp_send_data(struct pico_tftp_session *session, const uint8_t *data, int32_t len) +{ + struct pico_tftp_data_hdr *dh; + + dh = (struct pico_tftp_data_hdr *) session->tftp_block; + dh->opcode = short_be(PICO_TFTP_DATA); + dh->block = short_be(session->packet_counter++); + + if (len < PICO_TFTP_PAYLOAD_SIZE) + session->state = TFTP_STATE_WAIT_LAST_ACK; + else + session->state = TFTP_STATE_TX; + + memcpy(session->tftp_block + sizeof(struct pico_tftp_data_hdr), data, (size_t)len); + pico_socket_sendto(session->socket, session->tftp_block, (int)(len + (int32_t)sizeof(struct pico_tftp_data_hdr)), + &session->remote_address, session->remote_port); + tftp_schedule_timeout(session, PICO_TFTP_TIMEOUT); +} + +static inline void tftp_eval_finish(struct pico_tftp_session *session, int32_t len) +{ + if (len < PICO_TFTP_PAYLOAD_SIZE) { + pico_socket_close(session->socket); + session->state = TFTP_STATE_CLOSING; + } +} + +static inline int tftp_data_prepare(struct pico_tftp_session *session, union pico_address *a, uint16_t port) +{ + if (!session->socket) + return -1; + + if (pico_address_compare(a, &session->remote_address, session->socket->net->proto_number) != 0) { + tftp_send_error(session, a, port, TFTP_ERR_EXCEEDED, "TFTP busy, try again later."); + return -1; + } + + return 0; +} + +static void tftp_req(uint8_t *block, int32_t len, union pico_address *a, uint16_t port) +{ + struct pico_tftp_hdr *hdr = (struct pico_tftp_hdr *)block; + char *filename; + char *pos; + char *mode; + int ret; + + switch (short_be(hdr->opcode)) { + case PICO_TFTP_RRQ: + case PICO_TFTP_WRQ: + filename = (char *)(block + sizeof(struct pico_tftp_hdr)); + len -= (int32_t)sizeof(struct pico_tftp_hdr); + + pos = extract_arg_pointer(filename, filename + len, &mode); + if (!pos) { + send_error(block, server.listen_socket, a, port, TFTP_ERR_EILL, "Invalid argument in request"); + return; + } + + ret = strcmp("octet", mode); + if (ret) { + send_error(block, server.listen_socket, a, port, TFTP_ERR_EILL, "Unsupported mode"); + return; + } + + /*ret = parse_optional_arguments((char *)(block + sizeof(struct pico_tftp_hdr)), len - sizeof(struct pico_tftp_hdr), &new_options, &new_timeout, &new_filesize); + if (ret) { + tftp_send_error(NULL, a, port, TFTP_ERR_EILL, "Bad request"); + return; + } */ + + if (server.listen_callback) { + server.listen_callback(a, port, short_be(hdr->opcode), filename, len); + } + + break; + default: + send_error(block, server.listen_socket, a, port, TFTP_ERR_EILL, "Illegal opcode"); + } +} + +static int event_ack_base(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port) +{ + struct pico_tftp_data_hdr *dh; + uint16_t block_n; + const char *wrong_address = "Wrong address"; + const char *wrong_block = "Wrong packet number"; + + (void)len; + if (pico_address_compare(a, &session->remote_address, session->socket->net->proto_number) != 0) { + strcpy((char *)session->tftp_block, wrong_address); + do_callback(session, PICO_TFTP_EV_ERR_PEER, session->tftp_block, len); + tftp_send_error(session, a, port, TFTP_ERR_EXCEEDED, wrong_address); + return -1; + } + + dh = (struct pico_tftp_data_hdr *)session->tftp_block; + block_n = short_be(dh->block); + if (block_n != (session->packet_counter - 1U)) { + strcpy((char *)session->tftp_block, wrong_block); + do_callback(session, PICO_TFTP_EV_ERR_PEER, session->tftp_block, len); + tftp_send_error(session, a, port, TFTP_ERR_EILL, wrong_block); + return -1; + } + + return 0; +} + +static inline int event_ack0_check(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port) +{ + struct pico_tftp_data_hdr *dh; + uint16_t block_n; + + (void)len; + if (pico_address_compare(a, &session->remote_address, session->socket->net->proto_number) != 0) { + tftp_send_error(session, a, port, TFTP_ERR_EXCEEDED, "TFTP busy, try again later."); + return -1; + } + + dh = (struct pico_tftp_data_hdr *)session->tftp_block; + block_n = short_be(dh->block); + if (block_n != 0) { + tftp_send_error(session, a, port, TFTP_ERR_EILL, "TFTP connection broken!"); + return -1; + } + + return 0; +} + +static void event_ack0_wr(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port) +{ + if (!event_ack0_check(session, len, a, port)) { + session->remote_port = port; + do_callback(session, PICO_TFTP_EV_OK, session->tftp_block, 0); + } +} + +static void event_ack0_woc(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port) +{ + if (!event_ack0_check(session, len, a, port)) + do_callback(session, PICO_TFTP_EV_OPT, session->tftp_block, 0); +} + +static void event_ack(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port) +{ + if (!event_ack_base(session, len, a, port)) + do_callback(session, PICO_TFTP_EV_OK, session->tftp_block, 0); +} + +static void event_ack_last(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port) +{ + if (!event_ack_base(session, len, a, port)) + tftp_finish(session); +} + +static void event_data(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port) +{ + struct pico_tftp_data_hdr *dh; + int32_t payload_len = len - (int32_t)sizeof(struct pico_tftp_data_hdr); + + if (tftp_data_prepare(session, a, port)) + return; + + dh = (struct pico_tftp_data_hdr *)session->tftp_block; + if (short_be(dh->block) > (session->packet_counter + 1U)) { + strcpy((char *)session->tftp_block, "Wrong/unexpected sequence number"); + do_callback(session, PICO_TFTP_EV_ERR_LOCAL, session->tftp_block, 0); + tftp_send_error(session, a, port, TFTP_ERR_EILL, "TFTP connection broken!"); + return; + } + + if (short_be(dh->block) == (session->packet_counter + 1U)) { + session->packet_counter++; + if (do_callback(session, PICO_TFTP_EV_OK, tftp_payload(session->tftp_block), payload_len) >= 0) { + if (!(session->status & SESSION_STATUS_APP_ACK)) + tftp_send_ack(session); + } + + if (!(session->status & SESSION_STATUS_APP_ACK)) + tftp_eval_finish(session, len); + } +} + +static void event_data_rdr(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port) +{ + if (tftp_data_prepare(session, a, port)) + return; + + session->remote_port = port; + session->state = TFTP_STATE_RX; + event_data(session, len, a, port); +} + +static void event_data_rpl(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port) +{ + struct pico_tftp_data_hdr *dh; + + (void)len; + if (tftp_data_prepare(session, a, port)) + return; + + dh = (struct pico_tftp_data_hdr *)session->tftp_block; + + if (short_be(dh->block) == session->packet_counter) + tftp_send_ack(session); +} + +static void event_err(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port) +{ + (void)a; + (void)port; + do_callback(session, PICO_TFTP_EV_ERR_PEER, session->tftp_block, len); + tftp_finish(session); +} + +static inline void event_oack(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port) +{ + char *option_string = (char *)session->tftp_block + sizeof(struct pico_tftp_hdr); + int ret; + int proposed_options = session->options; + + (void)a; + + session->remote_port = port; + + ret = parse_optional_arguments(option_string, len - (int32_t)sizeof(struct pico_tftp_hdr), &session->options, &session->option_timeout, &session->file_size); + if (ret || (session->options & ~proposed_options)) { + do_callback(session, PICO_TFTP_EV_ERR_PEER, session->tftp_block, len); + tftp_send_error(session, a, port, TFTP_ERR_EOPT, "Invalid option"); + return; + } + + do_callback(session, PICO_TFTP_EV_OPT, session->tftp_block, len); +} + +static void event_oack_rr(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port) +{ + event_oack(session, len, a, port); + tftp_send_ack(session); + session->state = TFTP_STATE_RX; +} + +static void event_oack_wr(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port) +{ + event_oack(session, len, a, port); + session->state = TFTP_STATE_TX; +} + +static void event_timeout(struct pico_tftp_session *session, pico_time t) +{ + pico_time new_timeout; + int factor; + + (void)t; + if (++session->retry == TFTP_MAX_RETRY) { + strcpy((char *)session->tftp_block, "Network timeout"); + do_callback(session, PICO_TFTP_EV_ERR_PEER, session->tftp_block, 0); + tftp_finish(session); + return; + } + + tftp_send(session, 0); + if (session->options & PICO_TFTP_OPTION_TIME) + new_timeout = session->option_timeout * 1000U; + else { + new_timeout = PICO_TFTP_TIMEOUT; + for (factor = session->retry; factor; --factor) + new_timeout *= 2; + } + + tftp_schedule_timeout(session, new_timeout); +} + +static void event_timeout_closing(struct pico_tftp_session *session, pico_time t) +{ + (void)t; + if (session->active_timers == 0) + del_session(session); +} + +static void event_timeout_final(struct pico_tftp_session *session, pico_time t) +{ + (void)t; + + tftp_finish(session); +} + +static void unexpected(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port) +{ + (void)len; + tftp_send_error(session, a, port, TFTP_ERR_EILL, "Unexpected message"); +} + +static void null(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port) +{ + (void)session; + (void)len; + (void)a; + (void)port; +} + +static struct automa_events fsm[AUTOMA_STATES] = { + /* STATE * ACK DATA ERROR OACK TIMEOUT */ + /* ***************************************************************************************************************** */ + { /* TFTP_STATE_READ_REQUESTED */ unexpected, event_data_rdr, event_err, event_oack_rr, event_timeout}, + { /* TFTP_STATE_RX */ unexpected, event_data, event_err, unexpected, event_timeout}, + { /* TFTP_STATE_LAST_ACK_SENT */ unexpected, event_data_rpl, null, unexpected, event_timeout_final}, + { /* TFTP_STATE_WRITE_REQUESTED */ event_ack0_wr, unexpected, event_err, event_oack_wr, event_timeout}, + { /* TFTP_STATE_TX */ event_ack, unexpected, event_err, unexpected, event_timeout}, + { /* TFTP_STATE_WAIT_OPT_CONFIRM */ event_ack0_woc, unexpected, event_err, unexpected, event_timeout}, + { /* TFTP_STATE_WAIT_LAST_ACK */ event_ack_last, unexpected, event_err, unexpected, event_timeout}, + { /* TFTP_STATE_CLOSING */ null, null, null, null, event_timeout_closing} +}; + +static void tftp_message_received(struct pico_tftp_session *session, uint8_t *block, int32_t len, union pico_address *a, uint16_t port) +{ + struct pico_tftp_hdr *th = (struct pico_tftp_hdr *) block; + + if (!session->callback) + return; + + session->wallclock_timeout = 0; + + switch (short_be(th->opcode)) { + case PICO_TFTP_RRQ: + case PICO_TFTP_WRQ: + unexpected(session, len, a, port); + break; + case PICO_TFTP_DATA: + fsm[session->state].data(session, len, a, port); + break; + case PICO_TFTP_ACK: + fsm[session->state].ack(session, len, a, port); + break; + case PICO_TFTP_ERROR: + fsm[session->state].error(session, len, a, port); + break; + case PICO_TFTP_OACK: + fsm[session->state].oack(session, len, a, port); + break; + default: + tftp_send_error(session, NULL, 0, TFTP_ERR_EILL, "Illegal opcode"); + } +} + +static void tftp_cb(uint16_t ev, struct pico_socket *s) +{ + int r; + struct pico_tftp_session *session; + union pico_address ep; + uint16_t port = 0; + + session = find_session_by_socket(s); + if (session) { + if (ev == PICO_SOCK_EV_ERR) { + strcpy((char *)session->tftp_block, "Socket Error"); + do_callback(session, PICO_TFTP_EV_ERR_LOCAL, session->tftp_block, (int32_t)strlen((char *)session->tftp_block)); + tftp_finish(session); + return; + } + + r = pico_socket_recvfrom(s, session->tftp_block, PICO_TFTP_TOTAL_BLOCK_SIZE, &ep, &port); + if (r < (int)sizeof(struct pico_tftp_hdr)) + return; + + tftp_message_received(session, session->tftp_block, r, &ep, port); + } else { + if (!server.listen_socket || s != server.listen_socket) { + return; + } + + r = pico_socket_recvfrom(s, server.tftp_block, PICO_TFTP_TOTAL_BLOCK_SIZE, &ep, &port); + if (r < (int)sizeof(struct pico_tftp_hdr)) + return; + + tftp_req(server.tftp_block, r, &ep, port); + } +} + +static int application_rx_cb(struct pico_tftp_session *session, uint16_t event, uint8_t *block, int32_t len, void *arg) +{ + int *flag = (int *)arg; + + (void)block; + + switch (event) { + case PICO_TFTP_EV_ERR_PEER: + case PICO_TFTP_EV_ERR_LOCAL: + *flag = 0 - event; + break; + case PICO_TFTP_EV_OK: + session->len = len; + *flag = 1; + break; + case PICO_TFTP_EV_OPT: + break; + } + return 0; +} + +static int application_tx_cb(struct pico_tftp_session *session, uint16_t event, uint8_t *block, int32_t len, void *arg) +{ + (void)session; + (void)block; + (void)len; + + *(int*)arg = ((event == PICO_TFTP_EV_OK) || (event == PICO_TFTP_EV_OPT)) ? (1) : (0 - event); + return 0; +} + +static void timer_callback(pico_time now, void *arg) +{ + struct pico_tftp_session *session = (struct pico_tftp_session *)arg; + + --session->active_timers; + if (session->wallclock_timeout == 0) { + /* Timer is cancelled. */ + return; + } + + if (now >= session->wallclock_timeout) { + session->wallclock_timeout = 0ULL; + fsm[session->state].timeout(session, now); + } else { + tftp_schedule_timeout(session, session->wallclock_timeout - now); + } +} + +static struct pico_socket *tftp_socket_open(uint16_t family, uint16_t localport) +{ + struct pico_socket *sock; + union pico_address local_address; + + sock = pico_socket_open(family, PICO_PROTO_UDP, tftp_cb); + if (!sock) + return NULL; + + localport = short_be(localport); + + memset(&local_address, 0, sizeof(union pico_address)); + if (pico_socket_bind(sock, &local_address, &localport) < 0) { + pico_socket_close(sock); + return NULL; + } + + return sock; +} + +static inline int tftp_start_check(struct pico_tftp_session *session, uint16_t port, const char *filename, + int (*user_cb)(struct pico_tftp_session *session, uint16_t err, uint8_t *block, int32_t len, void *arg)) +{ + if (!session) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + if ((!server.listen_socket) && (port != short_be(PICO_TFTP_PORT))) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + if (!filename) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + if (!user_cb) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + return 0; +} + +/* *** EXPORTED FUNCTIONS *** */ + +struct pico_tftp_session *pico_tftp_session_setup(union pico_address *a, uint16_t family) +{ + struct pico_socket *sock; + + sock = tftp_socket_open(family, 0); + if (!sock) + return NULL; + + return pico_tftp_session_create(sock, a); +} + +int pico_tftp_get_option(struct pico_tftp_session *session, uint8_t type, int32_t *value) +{ + if (!session) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + switch (type) { + case PICO_TFTP_OPTION_FILE: + if (session->options & PICO_TFTP_OPTION_FILE) + *value = session->file_size; + else { + pico_err = PICO_ERR_ENOENT; + return -1; + } + + break; + case PICO_TFTP_OPTION_TIME: + if (session->options & PICO_TFTP_OPTION_TIME) + *value = session->option_timeout; + else { + pico_err = PICO_ERR_ENOENT; + return -1; + } + + break; + default: + pico_err = PICO_ERR_EINVAL; + return -1; + } + + return 0; +} + +int pico_tftp_set_option(struct pico_tftp_session *session, uint8_t type, int32_t value) +{ + if (!session) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + switch (type) { + case PICO_TFTP_OPTION_FILE: + if (value < 0) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + session->file_size = value; + session->options |= PICO_TFTP_OPTION_FILE; + break; + case PICO_TFTP_OPTION_TIME: + if (value > PICO_TFTP_MAX_TIMEOUT) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + session->option_timeout = (uint8_t)(value & 0xFF); + if (value) { + session->options |= PICO_TFTP_OPTION_TIME; + } else { + session->options &= ~PICO_TFTP_OPTION_TIME; + } + + break; + default: + pico_err = PICO_ERR_EINVAL; + return -1; + } + + return 0; +} + +/* Active RX request from PicoTCP */ +int pico_tftp_start_rx(struct pico_tftp_session *session, uint16_t port, const char *filename, + int (*user_cb)(struct pico_tftp_session *session, uint16_t event, uint8_t *block, int32_t len, void *arg), void *arg) +{ + if (tftp_start_check(session, port, filename, user_cb)) + return -1; + + session->callback = user_cb; + session->packet_counter = 0u; + session->argument = arg; + + add_session(session); + + if (port != short_be(PICO_TFTP_PORT)) { + session->remote_port = port; + session->state = TFTP_STATE_RX; + if (session->options & (PICO_TFTP_OPTION_FILE | PICO_TFTP_OPTION_TIME)) + tftp_send_oack(session); + else + tftp_send_ack(session); + } else { + tftp_send_rx_req(session, &session->remote_address, port, filename); + } + + return 0; +} + +int pico_tftp_start_tx(struct pico_tftp_session *session, uint16_t port, const char *filename, + int (*user_cb)(struct pico_tftp_session *session, uint16_t event, uint8_t *block, int32_t len, void *arg), void *arg) +{ + if (tftp_start_check(session, port, filename, user_cb)) + return -1; + + session->callback = user_cb; + session->packet_counter = 1u; + session->argument = arg; + + add_session(session); + + if (port != short_be(PICO_TFTP_PORT)) { + session->remote_port = port; + if (session->options) { + tftp_send_oack(session); + session->state = TFTP_STATE_WAIT_OPT_CONFIRM; + } else { + do_callback(session, PICO_TFTP_EV_OK, NULL, 0); + } + } else + tftp_send_tx_req(session, &session->remote_address, port, filename); + + return 0; +} + +int pico_tftp_reject_request(union pico_address*addr, uint16_t port, uint16_t error_code, const char*error_message) +{ + return send_error(server.tftp_block, server.listen_socket, addr, port, error_code, error_message); +} + +int32_t pico_tftp_send(struct pico_tftp_session *session, const uint8_t *data, int32_t len) +{ + int32_t size; + + + if (len < 0) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + size = len; + + if (size > PICO_TFTP_PAYLOAD_SIZE) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + tftp_send_data(session, data, size); + + return len; +} + +int pico_tftp_listen(uint16_t family, void (*cb)(union pico_address *addr, uint16_t port, uint16_t opcode, char *filename, int32_t len)) +{ + struct pico_socket *sock; + + if (server.listen_socket) { + pico_err = PICO_ERR_EEXIST; + return -1; + } + + sock = tftp_socket_open(family, PICO_TFTP_PORT); + if (!sock) + return -1; + + server.listen_socket = sock; + server.listen_callback = cb; + + return 0; +} + +int pico_tftp_parse_request_args(char *args, int32_t len, int *options, uint8_t *timeout, int32_t *filesize) +{ + char *pos; + char *end_args = args + len; + + args = extract_arg_pointer(args, end_args, &pos); + + return parse_optional_arguments(args, (int32_t)(end_args - args), options, timeout, filesize); +} + +int pico_tftp_abort(struct pico_tftp_session *session, uint16_t error, const char *reason) +{ + if (!session) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + if (!find_session_by_socket(session->socket)) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + tftp_send_error(session, NULL, 0, error, reason); + + return 0; +} + +int pico_tftp_close_server(void) +{ + if (!server.listen_socket) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + pico_socket_close(server.listen_socket); + server.listen_socket = NULL; + return 0; +} + +int pico_tftp_get_file_size(struct pico_tftp_session *session, int32_t *file_size) +{ + return pico_tftp_get_option(session, PICO_TFTP_OPTION_FILE, file_size); +} + +struct pico_tftp_session *pico_tftp_app_setup(union pico_address *a, uint16_t port, uint16_t family, int *synchro) +{ + struct pico_tftp_session *session; + + if (!synchro) { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + + session = pico_tftp_session_setup(a, family); + if (!session) + return NULL; + + session->remote_port = port; + session->status |= SESSION_STATUS_APP_ACK; + session->argument = synchro; + + *synchro = 0; + + return session; +} + +int pico_tftp_app_start_rx(struct pico_tftp_session *session, const char *filename) +{ + return pico_tftp_start_rx(session, session->remote_port, filename, application_rx_cb, session->argument); +} + +int pico_tftp_app_start_tx(struct pico_tftp_session *session, const char *filename) +{ + return pico_tftp_start_tx(session, session->remote_port, filename, application_tx_cb, session->argument); +} + +int32_t pico_tftp_get(struct pico_tftp_session *session, uint8_t *data, int32_t len) +{ + int synchro; + + if (!session || len < session->len ) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + synchro = *(int*)session->argument; + *(int*)session->argument = 0; + if ((session->state != TFTP_STATE_RX) && (session->state != TFTP_STATE_READ_REQUESTED)) + return -1; + + if (synchro < 0) + return synchro; + + memcpy(data, tftp_payload(session->tftp_block), (size_t)session->len); + len = session->len; + + tftp_send_ack(session); + tftp_eval_finish(session, len); + return len; +} + +int32_t pico_tftp_put(struct pico_tftp_session *session, uint8_t *data, int32_t len) +{ + int synchro; + + if ((!session) || (!data) || (len < 0)) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + synchro = *(int*)session->argument; + *(int*)session->argument = 0; + if (synchro < 0) + return synchro; + + if (len > PICO_TFTP_PAYLOAD_SIZE) + len = PICO_TFTP_PAYLOAD_SIZE; + + pico_tftp_send(session, data, len); + return len; +} diff --git a/ext/picotcp/modules/pico_tftp.h b/ext/picotcp/modules/pico_tftp.h new file mode 100644 index 0000000..bcbb648 --- /dev/null +++ b/ext/picotcp/modules/pico_tftp.h @@ -0,0 +1,83 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + . + *********************************************************************/ +#ifndef PICO_TFTP_H +#define PICO_TFTP_H + +#include +#include + +#define PICO_TFTP_PORT (69) +#define PICO_TFTP_PAYLOAD_SIZE (512) + +#define PICO_TFTP_NONE 0 +#define PICO_TFTP_RRQ 1 +#define PICO_TFTP_WRQ 2 +#define PICO_TFTP_DATA 3 +#define PICO_TFTP_ACK 4 +#define PICO_TFTP_ERROR 5 +#define PICO_TFTP_OACK 6 + +/* Callback user events */ +#define PICO_TFTP_EV_OK 0 +#define PICO_TFTP_EV_OPT 1 +#define PICO_TFTP_EV_ERR_PEER 2 +#define PICO_TFTP_EV_ERR_LOCAL 3 + +/* TFTP ERROR CODES */ +#define TFTP_ERR_UNDEF 0 +#define TFTP_ERR_ENOENT 1 +#define TFTP_ERR_EACC 2 +#define TFTP_ERR_EXCEEDED 3 +#define TFTP_ERR_EILL 4 +#define TFTP_ERR_ETID 5 +#define TFTP_ERR_EEXIST 6 +#define TFTP_ERR_EUSR 7 +#define TFTP_ERR_EOPT 8 + +/* Session options */ +#define PICO_TFTP_OPTION_FILE 1 + +/* timeout: 0 -> adaptative, 1-255 -> fixed */ +#define PICO_TFTP_OPTION_TIME 2 + + +#define PICO_TFTP_MAX_TIMEOUT 255 +#define PICO_TFTP_MAX_FILESIZE (65535 * 512 - 1) + +struct pico_tftp_session; + +struct pico_tftp_session *pico_tftp_session_setup(union pico_address *a, uint16_t family); +int pico_tftp_set_option(struct pico_tftp_session *session, uint8_t type, int32_t value); +int pico_tftp_get_option(struct pico_tftp_session *session, uint8_t type, int32_t *value); + +int pico_tftp_start_rx(struct pico_tftp_session *session, uint16_t port, const char *filename, + int (*user_cb)(struct pico_tftp_session *session, uint16_t event, uint8_t *block, int32_t len, void *arg), void *arg); +int pico_tftp_start_tx(struct pico_tftp_session *session, uint16_t port, const char *filename, + int (*user_cb)(struct pico_tftp_session *session, uint16_t event, uint8_t *block, int32_t len, void *arg), void *arg); + +int pico_tftp_reject_request(union pico_address *addr, uint16_t port, uint16_t error_code, const char *error_message); +int32_t pico_tftp_send(struct pico_tftp_session *session, const uint8_t *data, int32_t len); + +int pico_tftp_listen(uint16_t family, void (*cb)(union pico_address *addr, uint16_t port, uint16_t opcode, char *filename, int32_t len)); + +int pico_tftp_parse_request_args(char *args, int32_t len, int *options, uint8_t *timeout, int32_t *filesize); + +int pico_tftp_abort(struct pico_tftp_session *session, uint16_t error, const char *reason); +int pico_tftp_close_server(void); + +int pico_tftp_get_file_size(struct pico_tftp_session *session, int32_t *file_size); + +/* SPECIFIC APPLICATION DRIVEN FUNCTIONS */ +struct pico_tftp_session *pico_tftp_app_setup(union pico_address *a, uint16_t port, uint16_t family, int *synchro); + +int pico_tftp_app_start_rx(struct pico_tftp_session *session, const char *filename); +int pico_tftp_app_start_tx(struct pico_tftp_session *session, const char *filename); + +int32_t pico_tftp_get(struct pico_tftp_session *session, uint8_t *data, int32_t len); +int32_t pico_tftp_put(struct pico_tftp_session *session, uint8_t *data, int32_t len); + +#endif diff --git a/ext/picotcp/modules/pico_udp.c b/ext/picotcp/modules/pico_udp.c new file mode 100644 index 0000000..9545781 --- /dev/null +++ b/ext/picotcp/modules/pico_udp.c @@ -0,0 +1,217 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + . + + Authors: Daniele Lacamera + *********************************************************************/ + + +#include "pico_udp.h" +#include "pico_config.h" +#include "pico_eth.h" +#include "pico_socket.h" +#include "pico_stack.h" + +#define UDP_FRAME_OVERHEAD (sizeof(struct pico_frame)) +#define udp_dbg(...) do {} while(0) + +/* Queues */ +static struct pico_queue udp_in = { + 0 +}; +static struct pico_queue udp_out = { + 0 +}; + + +/* Functions */ + +uint16_t pico_udp_checksum_ipv4(struct pico_frame *f) +{ + struct pico_ipv4_hdr *hdr = (struct pico_ipv4_hdr *) f->net_hdr; + struct pico_udp_hdr *udp_hdr = (struct pico_udp_hdr *) f->transport_hdr; + struct pico_socket *s = f->sock; + struct pico_ipv4_pseudo_hdr pseudo; + + if (s) { + /* Case of outgoing frame */ + udp_dbg("UDP CRC: on outgoing frame\n"); + pseudo.src.addr = s->local_addr.ip4.addr; + pseudo.dst.addr = s->remote_addr.ip4.addr; + } else { + /* Case of incomming frame */ + udp_dbg("UDP CRC: on incomming frame\n"); + pseudo.src.addr = hdr->src.addr; + pseudo.dst.addr = hdr->dst.addr; + } + + pseudo.zeros = 0; + pseudo.proto = PICO_PROTO_UDP; + pseudo.len = short_be(f->transport_len); + + return pico_dualbuffer_checksum(&pseudo, sizeof(struct pico_ipv4_pseudo_hdr), udp_hdr, f->transport_len); +} + +#ifdef PICO_SUPPORT_IPV6 +uint16_t pico_udp_checksum_ipv6(struct pico_frame *f) +{ + struct pico_ipv6_hdr *ipv6_hdr = (struct pico_ipv6_hdr *)f->net_hdr; + struct pico_udp_hdr *udp_hdr = (struct pico_udp_hdr *)f->transport_hdr; + struct pico_ipv6_pseudo_hdr pseudo = { + .src = {{0}}, .dst = {{0}}, .len = 0, .zero = {0}, .nxthdr = 0 + }; + struct pico_socket *s = f->sock; + struct pico_remote_endpoint *remote_endpoint = (struct pico_remote_endpoint *)f->info; + + /* XXX If the IPv6 packet contains a Routing header, the Destination + * Address used in the pseudo-header is that of the final destination */ + if (s) { + /* Case of outgoing frame */ + pseudo.src = s->local_addr.ip6; + if (remote_endpoint) + pseudo.dst = remote_endpoint->remote_addr.ip6; + else + pseudo.dst = s->remote_addr.ip6; + } else { + /* Case of incomming frame */ + pseudo.src = ipv6_hdr->src; + pseudo.dst = ipv6_hdr->dst; + } + + pseudo.len = long_be(f->transport_len); + pseudo.nxthdr = PICO_PROTO_UDP; + + return pico_dualbuffer_checksum(&pseudo, sizeof(struct pico_ipv6_pseudo_hdr), udp_hdr, f->transport_len); +} +#endif + + + +static int pico_udp_process_out(struct pico_protocol *self, struct pico_frame *f) +{ + IGNORE_PARAMETER(self); + return (int)pico_network_send(f); +} + +static int pico_udp_push(struct pico_protocol *self, struct pico_frame *f) +{ + struct pico_udp_hdr *hdr = (struct pico_udp_hdr *) f->transport_hdr; + struct pico_remote_endpoint *remote_endpoint = (struct pico_remote_endpoint *) f->info; + + /* this (fragmented) frame should contain a transport header */ + if (f->transport_hdr != f->payload) { + hdr->trans.sport = f->sock->local_port; + if (remote_endpoint) { + hdr->trans.dport = remote_endpoint->remote_port; + } else { + hdr->trans.dport = f->sock->remote_port; + } + + hdr->len = short_be(f->transport_len); + + /* do not perform CRC validation. If you want to, a system needs to be + implemented to calculate the CRC over the total payload of a + fragmented payload + */ + hdr->crc = 0; + } + + if (pico_enqueue(self->q_out, f) > 0) { + return f->payload_len; + } else { + return 0; + } +} + +/* Interface: protocol definition */ +struct pico_protocol pico_proto_udp = { + .name = "udp", + .proto_number = PICO_PROTO_UDP, + .layer = PICO_LAYER_TRANSPORT, + .process_in = pico_transport_process_in, + .process_out = pico_udp_process_out, + .push = pico_udp_push, + .q_in = &udp_in, + .q_out = &udp_out, +}; + + + +struct pico_socket *pico_udp_open(void) +{ + struct pico_socket_udp *u = PICO_ZALLOC(sizeof(struct pico_socket_udp)); + if (!u) + return NULL; + + u->mode = PICO_UDP_MODE_UNICAST; + +#ifdef PICO_SUPPORT_MCAST + u->mc_ttl = PICO_IP_DEFAULT_MULTICAST_TTL; + /* enable multicast loopback by default */ + u->sock.opt_flags |= (1 << PICO_SOCKET_OPT_MULTICAST_LOOP); +#endif + + return &u->sock; +} + +static void pico_udp_get_msginfo(struct pico_frame *f, struct pico_msginfo *msginfo) +{ + if (!msginfo || !f->net_hdr) + return; + + msginfo->dev = f->dev; + + if (IS_IPV4(f)) { /* IPV4 */ +#ifdef PICO_SUPPORT_IPV4 + struct pico_ipv4_hdr *hdr = (struct pico_ipv4_hdr *)(f->net_hdr); + msginfo->ttl = hdr->ttl; + msginfo->tos = hdr->tos; +#endif + } else { +#ifdef PICO_SUPPORT_IPV6 + struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)(f->net_hdr); + msginfo->ttl = hdr->hop; + msginfo->tos = (hdr->vtf >> 20) & 0xFF; /* IPv6 traffic class */ +#endif + } +} + +uint16_t pico_udp_recv(struct pico_socket *s, void *buf, uint16_t len, void *src, uint16_t *port, struct pico_msginfo *msginfo) +{ + struct pico_frame *f = pico_queue_peek(&s->q_in); + if (f) { + if(!f->payload_len) { + f->payload = f->transport_hdr + sizeof(struct pico_udp_hdr); + f->payload_len = (uint16_t)(f->transport_len - sizeof(struct pico_udp_hdr)); + } + + udp_dbg("expected: %d, got: %d\n", len, f->payload_len); + if (src) + pico_store_network_origin(src, f); + + if (port) { + struct pico_trans *hdr = (struct pico_trans *)f->transport_hdr; + *port = hdr->sport; + } + + if (msginfo) { + pico_udp_get_msginfo(f, msginfo); + } + + if (f->payload_len > len) { + memcpy(buf, f->payload, len); + f->payload += len; + f->payload_len = (uint16_t)(f->payload_len - len); + return len; + } else { + uint16_t ret = f->payload_len; + memcpy(buf, f->payload, f->payload_len); + f = pico_dequeue(&s->q_in); + pico_frame_discard(f); + return ret; + } + } else return 0; +} + diff --git a/ext/picotcp/modules/pico_udp.h b/ext/picotcp/modules/pico_udp.h new file mode 100644 index 0000000..80b97a9 --- /dev/null +++ b/ext/picotcp/modules/pico_udp.h @@ -0,0 +1,45 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + . + + *********************************************************************/ +#ifndef INCLUDE_PICO_UDP +#define INCLUDE_PICO_UDP +#include "pico_addressing.h" +#include "pico_protocol.h" +#include "pico_socket.h" +#define PICO_UDP_MODE_UNICAST 0x01 +#define PICO_UDP_MODE_MULTICAST 0x02 +#define PICO_UDP_MODE_BROADCAST 0xFF + +struct pico_socket_udp +{ + struct pico_socket sock; + int mode; + uint8_t mc_ttl; /* Multicasting TTL */ +}; + + +extern struct pico_protocol pico_proto_udp; + +PACKED_STRUCT_DEF pico_udp_hdr { + struct pico_trans trans; + uint16_t len; + uint16_t crc; +}; +#define PICO_UDPHDR_SIZE 8 + +struct pico_socket *pico_udp_open(void); +uint16_t pico_udp_recv(struct pico_socket *s, void *buf, uint16_t len, void *src, uint16_t *port, struct pico_msginfo *msginfo); +uint16_t pico_udp_checksum_ipv4(struct pico_frame *f); + +#ifdef PICO_SUPPORT_IPV6 +uint16_t pico_udp_checksum_ipv6(struct pico_frame *f); +#endif + + +int pico_udp_setsockopt(struct pico_socket *s, int option, void *value); + +#endif diff --git a/ext/picotcp/rules/crc.mk b/ext/picotcp/rules/crc.mk new file mode 100644 index 0000000..8ec9344 --- /dev/null +++ b/ext/picotcp/rules/crc.mk @@ -0,0 +1 @@ +OPTIONS+=-DPICO_SUPPORT_CRC diff --git a/ext/picotcp/rules/cyassl.mk b/ext/picotcp/rules/cyassl.mk new file mode 100644 index 0000000..5903b8e --- /dev/null +++ b/ext/picotcp/rules/cyassl.mk @@ -0,0 +1,3 @@ +OPTIONS+=-DPICO_SUPPORT_CYASSL +LDFLAGS+=-lcyassl + diff --git a/ext/picotcp/rules/devloop.mk b/ext/picotcp/rules/devloop.mk new file mode 100644 index 0000000..066a281 --- /dev/null +++ b/ext/picotcp/rules/devloop.mk @@ -0,0 +1,2 @@ +OPTIONS+=-DPICO_SUPPORT_DEVLOOP +MOD_OBJ+=$(LIBBASE)modules/pico_dev_loop.o diff --git a/ext/picotcp/rules/dhcp_client.mk b/ext/picotcp/rules/dhcp_client.mk new file mode 100644 index 0000000..94a1507 --- /dev/null +++ b/ext/picotcp/rules/dhcp_client.mk @@ -0,0 +1,2 @@ +OPTIONS+=-DPICO_SUPPORT_DHCPC +MOD_OBJ+=$(LIBBASE)modules/pico_dhcp_client.o $(LIBBASE)modules/pico_dhcp_common.o diff --git a/ext/picotcp/rules/dhcp_server.mk b/ext/picotcp/rules/dhcp_server.mk new file mode 100644 index 0000000..c6d2b59 --- /dev/null +++ b/ext/picotcp/rules/dhcp_server.mk @@ -0,0 +1,2 @@ +OPTIONS+=-DPICO_SUPPORT_DHCPD +MOD_OBJ+=$(LIBBASE)modules/pico_dhcp_server.o $(LIBBASE)modules/pico_dhcp_common.o diff --git a/ext/picotcp/rules/dns_client.mk b/ext/picotcp/rules/dns_client.mk new file mode 100644 index 0000000..f5e14c7 --- /dev/null +++ b/ext/picotcp/rules/dns_client.mk @@ -0,0 +1,2 @@ +OPTIONS+=-DPICO_SUPPORT_DNS_CLIENT +MOD_OBJ+=$(LIBBASE)modules/pico_dns_client.o $(LIBBASE)modules/pico_dns_common.o diff --git a/ext/picotcp/rules/dns_sd.mk b/ext/picotcp/rules/dns_sd.mk new file mode 100644 index 0000000..62d05cc --- /dev/null +++ b/ext/picotcp/rules/dns_sd.mk @@ -0,0 +1,2 @@ +OPTIONS+=-DPICO_SUPPORT_DNS_SD +MOD_OBJ+=$(LIBBASE)modules/pico_dns_sd.o $(LIBBASE)modules/pico_mdns.o $(LIBBASE)modules/pico_dns_common.o \ No newline at end of file diff --git a/ext/picotcp/rules/eth.mk b/ext/picotcp/rules/eth.mk new file mode 100644 index 0000000..f955bd8 --- /dev/null +++ b/ext/picotcp/rules/eth.mk @@ -0,0 +1,2 @@ +OPTIONS+=-DPICO_SUPPORT_ETH +MOD_OBJ+=$(LIBBASE)modules/pico_arp.o diff --git a/ext/picotcp/rules/icmp4.mk b/ext/picotcp/rules/icmp4.mk new file mode 100644 index 0000000..826e0e0 --- /dev/null +++ b/ext/picotcp/rules/icmp4.mk @@ -0,0 +1,5 @@ +OPTIONS+=-DPICO_SUPPORT_ICMP4 +MOD_OBJ+=$(LIBBASE)modules/pico_icmp4.o +ifneq ($(PING),0) + OPTIONS+=-DPICO_SUPPORT_PING +endif diff --git a/ext/picotcp/rules/igmp.mk b/ext/picotcp/rules/igmp.mk new file mode 100644 index 0000000..804079b --- /dev/null +++ b/ext/picotcp/rules/igmp.mk @@ -0,0 +1,3 @@ +OPTIONS+=-DPICO_SUPPORT_IGMP +MOD_OBJ+=$(LIBBASE)modules/pico_igmp.o + diff --git a/ext/picotcp/rules/ipfilter.mk b/ext/picotcp/rules/ipfilter.mk new file mode 100644 index 0000000..3044eb6 --- /dev/null +++ b/ext/picotcp/rules/ipfilter.mk @@ -0,0 +1,2 @@ +OPTIONS+=-DPICO_SUPPORT_IPFILTER +MOD_OBJ+=$(LIBBASE)modules/pico_ipfilter.o diff --git a/ext/picotcp/rules/ipv4.mk b/ext/picotcp/rules/ipv4.mk new file mode 100644 index 0000000..34d2108 --- /dev/null +++ b/ext/picotcp/rules/ipv4.mk @@ -0,0 +1,2 @@ +OPTIONS+=-DPICO_SUPPORT_IPV4 +MOD_OBJ+=$(LIBBASE)modules/pico_ipv4.o diff --git a/ext/picotcp/rules/ipv4frag.mk b/ext/picotcp/rules/ipv4frag.mk new file mode 100644 index 0000000..a79ccb3 --- /dev/null +++ b/ext/picotcp/rules/ipv4frag.mk @@ -0,0 +1,2 @@ +OPTIONS+=-DPICO_SUPPORT_IPV4FRAG +MOD_OBJ+=$(LIBBASE)modules/pico_fragments.o diff --git a/ext/picotcp/rules/ipv6.mk b/ext/picotcp/rules/ipv6.mk new file mode 100644 index 0000000..f313cbf --- /dev/null +++ b/ext/picotcp/rules/ipv6.mk @@ -0,0 +1,3 @@ +OPTIONS+=-DPICO_SUPPORT_IPV6 -DPICO_SUPPORT_ICMP6 +MOD_OBJ+=$(LIBBASE)modules/pico_ipv6.o $(LIBBASE)modules/pico_ipv6_nd.o $(LIBBASE)modules/pico_icmp6.o +include rules/ipv6frag.mk diff --git a/ext/picotcp/rules/ipv6frag.mk b/ext/picotcp/rules/ipv6frag.mk new file mode 100644 index 0000000..da6435b --- /dev/null +++ b/ext/picotcp/rules/ipv6frag.mk @@ -0,0 +1,2 @@ +OPTIONS+=-DPICO_SUPPORT_IPV6FRAG +MOD_OBJ+=$(LIBBASE)modules/pico_fragments.o diff --git a/ext/picotcp/rules/mcast.mk b/ext/picotcp/rules/mcast.mk new file mode 100644 index 0000000..037305b --- /dev/null +++ b/ext/picotcp/rules/mcast.mk @@ -0,0 +1 @@ +OPTIONS+=-DPICO_SUPPORT_MCAST diff --git a/ext/picotcp/rules/mdns.mk b/ext/picotcp/rules/mdns.mk new file mode 100644 index 0000000..b28aeeb --- /dev/null +++ b/ext/picotcp/rules/mdns.mk @@ -0,0 +1,2 @@ +OPTIONS+=-DPICO_SUPPORT_MDNS +MOD_OBJ+=$(LIBBASE)modules/pico_mdns.o $(LIBBASE)modules/pico_dns_common.o diff --git a/ext/picotcp/rules/memory_manager.mk b/ext/picotcp/rules/memory_manager.mk new file mode 100644 index 0000000..7b0d923 --- /dev/null +++ b/ext/picotcp/rules/memory_manager.mk @@ -0,0 +1,2 @@ +OPTIONS+=-DPICO_SUPPORT_MM +MOD_OBJ+=$(LIBBASE)modules/pico_mm.o diff --git a/ext/picotcp/rules/mld.mk b/ext/picotcp/rules/mld.mk new file mode 100644 index 0000000..3302e7e --- /dev/null +++ b/ext/picotcp/rules/mld.mk @@ -0,0 +1,3 @@ +OPTIONS+=-DPICO_SUPPORT_MLD +MOD_OBJ+=$(LIBBASE)modules/pico_mld.o + diff --git a/ext/picotcp/rules/nat.mk b/ext/picotcp/rules/nat.mk new file mode 100644 index 0000000..2bd67b5 --- /dev/null +++ b/ext/picotcp/rules/nat.mk @@ -0,0 +1,2 @@ +OPTIONS+=-DPICO_SUPPORT_NAT +MOD_OBJ+=$(LIBBASE)modules/pico_nat.o diff --git a/ext/picotcp/rules/olsr.mk b/ext/picotcp/rules/olsr.mk new file mode 100644 index 0000000..3cf4443 --- /dev/null +++ b/ext/picotcp/rules/olsr.mk @@ -0,0 +1,2 @@ +OPTIONS+=-DPICO_SUPPORT_OLSR +MOD_OBJ+=$(LIBBASE)modules/pico_olsr.o diff --git a/ext/picotcp/rules/pcap.mk b/ext/picotcp/rules/pcap.mk new file mode 100644 index 0000000..9c67277 --- /dev/null +++ b/ext/picotcp/rules/pcap.mk @@ -0,0 +1 @@ +MOD_OBJ+=$(LIBBASE)modules/pico_dev_pcap.o diff --git a/ext/picotcp/rules/polarssl.mk b/ext/picotcp/rules/polarssl.mk new file mode 100644 index 0000000..e5df9d9 --- /dev/null +++ b/ext/picotcp/rules/polarssl.mk @@ -0,0 +1,2 @@ +OPTIONS += -DPICO_SUPPORT_POLARSSL +LDFLAGS += -lpolarssl diff --git a/ext/picotcp/rules/ppp.mk b/ext/picotcp/rules/ppp.mk new file mode 100644 index 0000000..f47c083 --- /dev/null +++ b/ext/picotcp/rules/ppp.mk @@ -0,0 +1,3 @@ +OPTIONS+=-DPICO_SUPPORT_PPP +MOD_OBJ+=$(LIBBASE)modules/pico_dev_ppp.o + diff --git a/ext/picotcp/rules/slaacv4.mk b/ext/picotcp/rules/slaacv4.mk new file mode 100644 index 0000000..2073a2a --- /dev/null +++ b/ext/picotcp/rules/slaacv4.mk @@ -0,0 +1,2 @@ +OPTIONS+=-DPICO_SUPPORT_SLAACV4 +MOD_OBJ+=$(LIBBASE)modules/pico_slaacv4.o $(LIBBASE)modules/pico_hotplug_detection.o diff --git a/ext/picotcp/rules/sntp_client.mk b/ext/picotcp/rules/sntp_client.mk new file mode 100644 index 0000000..423583b --- /dev/null +++ b/ext/picotcp/rules/sntp_client.mk @@ -0,0 +1,2 @@ +OPTIONS+=-DPICO_SUPPORT_SNTP_CLIENT +MOD_OBJ+=$(LIBBASE)modules/pico_sntp_client.o diff --git a/ext/picotcp/rules/tap.mk b/ext/picotcp/rules/tap.mk new file mode 100644 index 0000000..2f3e149 --- /dev/null +++ b/ext/picotcp/rules/tap.mk @@ -0,0 +1 @@ +MOD_OBJ+=$(LIBBASE)modules/pico_dev_tap.o diff --git a/ext/picotcp/rules/tcp.mk b/ext/picotcp/rules/tcp.mk new file mode 100644 index 0000000..ae53034 --- /dev/null +++ b/ext/picotcp/rules/tcp.mk @@ -0,0 +1,3 @@ +OPTIONS+=-DPICO_SUPPORT_TCP +MOD_OBJ+=$(LIBBASE)modules/pico_tcp.o +MOD_OBJ+=$(LIBBASE)modules/pico_socket_tcp.o diff --git a/ext/picotcp/rules/tun.mk b/ext/picotcp/rules/tun.mk new file mode 100644 index 0000000..a5b93fe --- /dev/null +++ b/ext/picotcp/rules/tun.mk @@ -0,0 +1 @@ +MOD_OBJ+=$(LIBBASE)modules/pico_dev_tun.o diff --git a/ext/picotcp/rules/udp.mk b/ext/picotcp/rules/udp.mk new file mode 100644 index 0000000..ec9157c --- /dev/null +++ b/ext/picotcp/rules/udp.mk @@ -0,0 +1,3 @@ +OPTIONS+=-DPICO_SUPPORT_UDP +MOD_OBJ+=$(LIBBASE)modules/pico_udp.o +MOD_OBJ+=$(LIBBASE)modules/pico_socket_udp.o diff --git a/ext/picotcp/rules/wolfssl.mk b/ext/picotcp/rules/wolfssl.mk new file mode 100644 index 0000000..f66d114 --- /dev/null +++ b/ext/picotcp/rules/wolfssl.mk @@ -0,0 +1,3 @@ +OPTIONS+=-DPICO_SUPPORT_CYASSL -DPICO_SUPPORT_WOLFSSL +LDFLAGS+=-lwolfssl + diff --git a/ext/picotcp/stack/pico_device.c b/ext/picotcp/stack/pico_device.c new file mode 100644 index 0000000..b76d4f3 --- /dev/null +++ b/ext/picotcp/stack/pico_device.c @@ -0,0 +1,408 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + . + + Authors: Daniele Lacamera + *********************************************************************/ + +#include "pico_config.h" +#include "pico_device.h" +#include "pico_stack.h" +#include "pico_protocol.h" +#include "pico_tree.h" +#include "pico_ipv6.h" +#include "pico_ipv4.h" +#include "pico_icmp6.h" +#include "pico_eth.h" +#define PICO_DEVICE_DEFAULT_MTU (1500) + +struct pico_devices_rr_info { + struct pico_tree_node *node_in, *node_out; +}; + +static struct pico_devices_rr_info Devices_rr_info = { + NULL, NULL +}; + +static int pico_dev_cmp(void *ka, void *kb) +{ + struct pico_device *a = ka, *b = kb; + if (a->hash < b->hash) + return -1; + + if (a->hash > b->hash) + return 1; + + return 0; +} + +PICO_TREE_DECLARE(Device_tree, pico_dev_cmp); + +#ifdef PICO_SUPPORT_IPV6 +static void device_init_ipv6_final(struct pico_device *dev, struct pico_ip6 *linklocal) +{ + dev->hostvars.basetime = PICO_ND_REACHABLE_TIME; + /* RFC 4861 $6.3.2 value between 0.5 and 1.5 times basetime */ + dev->hostvars.reachabletime = ((5 + (pico_rand() % 10)) * PICO_ND_REACHABLE_TIME) / 10; + dev->hostvars.retranstime = PICO_ND_RETRANS_TIMER; + pico_icmp6_router_solicitation(dev, linklocal); + dev->hostvars.hoplimit = PICO_IPV6_DEFAULT_HOP; +} + +struct pico_ipv6_link *pico_ipv6_link_add_local(struct pico_device *dev, const struct pico_ip6 *prefix) +{ + struct pico_ip6 newaddr; + struct pico_ip6 netmask64 = {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; + struct pico_ipv6_link *link; + memcpy(newaddr.addr, prefix->addr, PICO_SIZE_IP6); + /* modified EUI-64 + invert universal/local bit */ + newaddr.addr[8] = (dev->eth->mac.addr[0] ^ 0x02); + newaddr.addr[9] = dev->eth->mac.addr[1]; + newaddr.addr[10] = dev->eth->mac.addr[2]; + newaddr.addr[11] = 0xff; + newaddr.addr[12] = 0xfe; + newaddr.addr[13] = dev->eth->mac.addr[3]; + newaddr.addr[14] = dev->eth->mac.addr[4]; + newaddr.addr[15] = dev->eth->mac.addr[5]; + link = pico_ipv6_link_add(dev, newaddr, netmask64); + if (link) { + device_init_ipv6_final(dev, &newaddr); + } + + return link; +} +#endif + +static int device_init_mac(struct pico_device *dev, uint8_t *mac) +{ + #ifdef PICO_SUPPORT_IPV6 + struct pico_ip6 linklocal = {{0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xff, 0xfe, 0xaa, 0xaa, 0xaa}}; + #endif + dev->eth = PICO_ZALLOC(sizeof(struct pico_ethdev)); + if (dev->eth) { + memcpy(dev->eth->mac.addr, mac, PICO_SIZE_ETH); + #ifdef PICO_SUPPORT_IPV6 + if (pico_ipv6_link_add_local(dev, &linklocal) == NULL) { + PICO_FREE(dev->q_in); + PICO_FREE(dev->q_out); + PICO_FREE(dev->eth); + return -1; + } + + #endif + } else { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + return 0; +} + +int pico_device_ipv6_random_ll(struct pico_device *dev) +{ + #ifdef PICO_SUPPORT_IPV6 + struct pico_ip6 linklocal = {{0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xff, 0xfe, 0xaa, 0xaa, 0xaa}}; + struct pico_ip6 netmask6 = {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; + uint32_t len = (uint32_t)strlen(dev->name); + if (strcmp(dev->name, "loop")) { + do { + /* privacy extension + unset universal/local and individual/group bit */ + len = pico_rand(); + linklocal.addr[8] = (uint8_t)((len & 0xffu) & (uint8_t)(~0x03)); + linklocal.addr[9] = (uint8_t)(len >> 8); + linklocal.addr[10] = (uint8_t)(len >> 16); + linklocal.addr[11] = (uint8_t)(len >> 24); + len = pico_rand(); + linklocal.addr[12] = (uint8_t)len; + linklocal.addr[13] = (uint8_t)(len >> 8); + linklocal.addr[14] = (uint8_t)(len >> 16); + linklocal.addr[15] = (uint8_t)(len >> 24); + pico_rand_feed(dev->hash); + } while (pico_ipv6_link_get(&linklocal)); + + if (pico_ipv6_link_add(dev, linklocal, netmask6) == NULL) { + return -1; + } + } + + #endif + return 0; +} + +static int device_init_nomac(struct pico_device *dev) +{ + if (pico_device_ipv6_random_ll(dev) < 0) { + PICO_FREE(dev->q_in); + PICO_FREE(dev->q_out); + return -1; + } + + dev->eth = NULL; + return 0; +} + +extern int pico_device_init(struct pico_device *dev, const char *name, uint8_t *mac) +{ + uint32_t len = (uint32_t)strlen(name); + int ret = 0; + if(len > MAX_DEVICE_NAME) + len = MAX_DEVICE_NAME; + + memcpy(dev->name, name, len); + dev->hash = pico_hash(dev->name, len); + + Devices_rr_info.node_in = NULL; + Devices_rr_info.node_out = NULL; + dev->q_in = PICO_ZALLOC(sizeof(struct pico_queue)); + if (!dev->q_in) + return -1; + + dev->q_out = PICO_ZALLOC(sizeof(struct pico_queue)); + if (!dev->q_out) { + PICO_FREE(dev->q_in); + return -1; + } + + pico_tree_insert(&Device_tree, dev); + if (!dev->mtu) + dev->mtu = PICO_DEVICE_DEFAULT_MTU; + + if (mac) { + ret = device_init_mac(dev, mac); + } else { + ret = device_init_nomac(dev); + } + + return ret; +} + +static void pico_queue_destroy(struct pico_queue *q) +{ + if (q) { + pico_queue_empty(q); + PICO_FREE(q); + } +} + +void pico_device_destroy(struct pico_device *dev) +{ + + pico_queue_destroy(dev->q_in); + pico_queue_destroy(dev->q_out); + + if (dev->eth) + PICO_FREE(dev->eth); + +#ifdef PICO_SUPPORT_IPV4 + pico_ipv4_cleanup_links(dev); +#endif +#ifdef PICO_SUPPORT_IPV6 + pico_ipv6_cleanup_links(dev); +#endif + pico_tree_delete(&Device_tree, dev); + + if (dev->destroy) + dev->destroy(dev); + + Devices_rr_info.node_in = NULL; + Devices_rr_info.node_out = NULL; + PICO_FREE(dev); +} + +static int check_dev_serve_interrupt(struct pico_device *dev, int loop_score) +{ + if ((dev->__serving_interrupt) && (dev->dsr)) { + /* call dsr routine */ + loop_score = dev->dsr(dev, loop_score); + } + + return loop_score; +} + +static int check_dev_serve_polling(struct pico_device *dev, int loop_score) +{ + if (dev->poll) { + loop_score = dev->poll(dev, loop_score); + } + + return loop_score; +} + +static int devloop_in(struct pico_device *dev, int loop_score) +{ + struct pico_frame *f; + while(loop_score > 0) { + if (dev->q_in->frames == 0) + break; + + /* Receive */ + f = pico_dequeue(dev->q_in); + if (f) { + if (dev->eth) { + f->datalink_hdr = f->buffer; + (void)pico_ethernet_receive(f); + } else { + f->net_hdr = f->buffer; + pico_network_receive(f); + } + + loop_score--; + } + } + return loop_score; +} + +static int devloop_sendto_dev(struct pico_device *dev, struct pico_frame *f) +{ + + if (dev->eth) { + /* Ethernet: pass management of the frame to the pico_ethernet_send() rdv function */ + return pico_ethernet_send(f); + } else { + /* non-ethernet: no post-processing needed */ + return (dev->send(dev, f->start, (int)f->len) <= 0); /* Return 0 upon success, which is dev->send() > 0 */ + } +} + +static int devloop_out(struct pico_device *dev, int loop_score) +{ + struct pico_frame *f; + while(loop_score > 0) { + if (dev->q_out->frames == 0) + break; + + /* Device dequeue + send */ + f = pico_queue_peek(dev->q_out); + if (!f) + break; + + if (devloop_sendto_dev(dev, f) == 0) { /* success. */ + f = pico_dequeue(dev->q_out); + pico_frame_discard(f); /* SINGLE POINT OF DISCARD for OUTGOING FRAMES */ + loop_score--; + } else + break; /* Don't discard */ + + } + return loop_score; +} + +static int devloop(struct pico_device *dev, int loop_score, int direction) +{ + /* If device supports interrupts, read the value of the condition and trigger the dsr */ + loop_score = check_dev_serve_interrupt(dev, loop_score); + + /* If device supports polling, give control. Loop score is managed internally, + * remaining loop points are returned. */ + loop_score = check_dev_serve_polling(dev, loop_score); + + if (direction == PICO_LOOP_DIR_OUT) + loop_score = devloop_out(dev, loop_score); + else + loop_score = devloop_in(dev, loop_score); + + return loop_score; +} + + +static struct pico_tree_node *pico_dev_roundrobin_start(int direction) +{ + if (Devices_rr_info.node_in == NULL) + Devices_rr_info.node_in = pico_tree_firstNode(Device_tree.root); + + if (Devices_rr_info.node_out == NULL) + Devices_rr_info.node_out = pico_tree_firstNode(Device_tree.root); + + if (direction == PICO_LOOP_DIR_IN) + return Devices_rr_info.node_in; + else + return Devices_rr_info.node_out; +} + +static void pico_dev_roundrobin_end(int direction, struct pico_tree_node *last) +{ + if (direction == PICO_LOOP_DIR_IN) + Devices_rr_info.node_in = last; + else + Devices_rr_info.node_out = last; +} + +#define DEV_LOOP_MIN 16 + +int pico_devices_loop(int loop_score, int direction) +{ + struct pico_device *start, *next; + struct pico_tree_node *next_node = pico_dev_roundrobin_start(direction); + + if (!next_node) + return loop_score; + + next = next_node->keyValue; + start = next; + + /* round-robin all devices, break if traversed all devices */ + while ((loop_score > DEV_LOOP_MIN) && (next != NULL)) { + loop_score = devloop(next, loop_score, direction); + next_node = pico_tree_next(next_node); + next = next_node->keyValue; + if (next == NULL) + { + next_node = pico_tree_firstNode(Device_tree.root); + next = next_node->keyValue; + } + + if (next == start) + break; + } + pico_dev_roundrobin_end(direction, next_node); + return loop_score; +} + +struct pico_device *pico_get_device(const char*name) +{ + struct pico_device *dev; + struct pico_tree_node *index; + pico_tree_foreach(index, &Device_tree){ + dev = index->keyValue; + if(strcmp(name, dev->name) == 0) + return dev; + } + return NULL; +} + +int32_t pico_device_broadcast(struct pico_frame *f) +{ + struct pico_tree_node *index; + int32_t ret = -1; + + pico_tree_foreach(index, &Device_tree) + { + struct pico_device *dev = index->keyValue; + if(dev != f->dev) + { + struct pico_frame *copy = pico_frame_copy(f); + + if(!copy) + break; + + copy->dev = dev; + copy->dev->send(copy->dev, copy->start, (int)copy->len); + pico_frame_discard(copy); + } + else + { + ret = f->dev->send(f->dev, f->start, (int)f->len); + } + } + return ret; +} + +int pico_device_link_state(struct pico_device *dev) +{ + if (!dev->link_state) + return 1; /* Not supported, assuming link is always up */ + + return dev->link_state(dev); +} diff --git a/ext/picotcp/stack/pico_frame.c b/ext/picotcp/stack/pico_frame.c new file mode 100644 index 0000000..ef5f8d7 --- /dev/null +++ b/ext/picotcp/stack/pico_frame.c @@ -0,0 +1,286 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + . + + Authors: Daniele Lacamera + *********************************************************************/ + + +#include "pico_config.h" +#include "pico_frame.h" +#include "pico_protocol.h" +#include "pico_stack.h" + +#ifdef PICO_SUPPORT_DEBUG_MEMORY +static int n_frames_allocated; +#endif + +/** frame alloc/dealloc/copy **/ +void pico_frame_discard(struct pico_frame *f) +{ + if (!f) + return; + + (*f->usage_count)--; + if (*f->usage_count == 0) { + if (f->flags & PICO_FRAME_FLAG_EXT_USAGE_COUNTER) + PICO_FREE(f->usage_count); + +#ifdef PICO_SUPPORT_DEBUG_MEMORY + dbg("Discarded buffer @%p, caller: %p\n", f->buffer, __builtin_return_address(3)); + dbg("DEBUG MEMORY: %d frames in use.\n", --n_frames_allocated); +#endif + if (!(f->flags & PICO_FRAME_FLAG_EXT_BUFFER)) + PICO_FREE(f->buffer); + else if (f->notify_free) + f->notify_free(f->buffer); + + if (f->info) + PICO_FREE(f->info); + } + +#ifdef PICO_SUPPORT_DEBUG_MEMORY + else { + dbg("Removed frame @%p(copy), usage count now: %d\n", f, *f->usage_count); + } +#endif + PICO_FREE(f); +} + +struct pico_frame *pico_frame_copy(struct pico_frame *f) +{ + struct pico_frame *new = PICO_ZALLOC(sizeof(struct pico_frame)); + if (!new) + return NULL; + + memcpy(new, f, sizeof(struct pico_frame)); + *(new->usage_count) += 1; +#ifdef PICO_SUPPORT_DEBUG_MEMORY + dbg("Copied frame @%p, into %p, usage count now: %d\n", f, new, *new->usage_count); +#endif + new->next = NULL; + return new; +} + + +static struct pico_frame *pico_frame_do_alloc(uint32_t size, int zerocopy, int ext_buffer) +{ + struct pico_frame *p = PICO_ZALLOC(sizeof(struct pico_frame)); + uint32_t frame_buffer_size = size; + if (!p) + return NULL; + + if (ext_buffer && !zerocopy) { + /* external buffer implies zerocopy flag! */ + PICO_FREE(p); + return NULL; + } + + if (!zerocopy) { + unsigned int align = size % sizeof(uint32_t); + /* Ensure that usage_count starts on an aligned address */ + if (align) { + frame_buffer_size += (uint32_t)sizeof(uint32_t) - align; + } + + p->buffer = PICO_ZALLOC(frame_buffer_size + sizeof(uint32_t)); + if (!p->buffer) { + PICO_FREE(p); + return NULL; + } + + p->usage_count = (uint32_t *)((void *)(((uint8_t*)p->buffer) + frame_buffer_size)); + } else { + p->buffer = NULL; + p->flags |= PICO_FRAME_FLAG_EXT_USAGE_COUNTER; + p->usage_count = PICO_ZALLOC(sizeof(uint32_t)); + if (!p->usage_count) { + PICO_FREE(p); + return NULL; + } + } + + + p->buffer_len = size; + + /* By default, frame content is the full buffer. */ + p->start = p->buffer; + p->len = p->buffer_len; + *p->usage_count = 1; + + if (ext_buffer) + p->flags |= PICO_FRAME_FLAG_EXT_BUFFER; + +#ifdef PICO_SUPPORT_DEBUG_MEMORY + dbg("Allocated buffer @%p, len= %d caller: %p\n", p->buffer, p->buffer_len, __builtin_return_address(2)); + dbg("DEBUG MEMORY: %d frames in use.\n", ++n_frames_allocated); +#endif + return p; +} + +struct pico_frame *pico_frame_alloc(uint32_t size) +{ + return pico_frame_do_alloc(size, 0, 0); +} + +int pico_frame_grow(struct pico_frame *f, uint32_t size) +{ + uint8_t *oldbuf; + uint32_t usage_count, *p_old_usage; + uint32_t frame_buffer_size; + uint32_t oldsize; + unsigned int align; + int addr_diff = 0; + + if (!f || (size < f->buffer_len)) { + return -1; + } + + align = size % sizeof(uint32_t); + frame_buffer_size = size; + if (align) { + frame_buffer_size += (uint32_t)sizeof(uint32_t) - align; + } + + oldbuf = f->buffer; + oldsize = f->buffer_len; + usage_count = *(f->usage_count); + p_old_usage = f->usage_count; + f->buffer = PICO_ZALLOC(frame_buffer_size + sizeof(uint32_t)); + if (!f->buffer) { + f->buffer = oldbuf; + return -1; + } + + f->usage_count = (uint32_t *)((void *)(((uint8_t*)f->buffer) + frame_buffer_size)); + *f->usage_count = usage_count; + f->buffer_len = size; + memcpy(f->buffer, oldbuf, oldsize); + + /* Update hdr fields to new buffer*/ + addr_diff = (int)(f->buffer - oldbuf); + f->net_hdr += addr_diff; + f->datalink_hdr += addr_diff; + f->transport_hdr += addr_diff; + f->app_hdr += addr_diff; + f->start += addr_diff; + f->payload += addr_diff; + + if (f->flags & PICO_FRAME_FLAG_EXT_USAGE_COUNTER) + PICO_FREE(p_old_usage); + + if (!(f->flags & PICO_FRAME_FLAG_EXT_BUFFER)) + PICO_FREE(oldbuf); + else if (f->notify_free) + f->notify_free(oldbuf); + + f->flags = 0; + /* Now, the frame is not zerocopy anymore, and the usage counter has been moved within it */ + return 0; +} + +struct pico_frame *pico_frame_alloc_skeleton(uint32_t size, int ext_buffer) +{ + return pico_frame_do_alloc(size, 1, ext_buffer); +} + +int pico_frame_skeleton_set_buffer(struct pico_frame *f, void *buf) +{ + if (!buf) + return -1; + + f->buffer = (uint8_t *) buf; + f->start = f->buffer; + return 0; +} + +struct pico_frame *pico_frame_deepcopy(struct pico_frame *f) +{ + struct pico_frame *new = pico_frame_alloc(f->buffer_len); + int addr_diff; + unsigned char *buf; + uint32_t *uc; + if (!new) + return NULL; + + /* Save the two key pointers... */ + buf = new->buffer; + uc = new->usage_count; + + /* Overwrite all fields with originals */ + memcpy(new, f, sizeof(struct pico_frame)); + + /* ...restore the two key pointers */ + new->buffer = buf; + new->usage_count = uc; + + /* Update in-buffer pointers with offset */ + addr_diff = (int)(new->buffer - f->buffer); + new->datalink_hdr += addr_diff; + new->net_hdr += addr_diff; + new->transport_hdr += addr_diff; + new->app_hdr += addr_diff; + new->start += addr_diff; + new->payload += addr_diff; + +#ifdef PICO_SUPPORT_DEBUG_MEMORY + dbg("Deep-Copied frame @%p, into %p, usage count now: %d\n", f, new, *new->usage_count); +#endif + new->next = NULL; + return new; +} + + +static inline uint32_t pico_checksum_adder(uint32_t sum, void *data, uint32_t len) +{ + uint16_t *buf = (uint16_t *)data; + uint16_t *stop; + + if (len & 0x01) { + --len; +#ifdef PICO_BIGENDIAN + sum += (((uint8_t *)data)[len]) << 8; +#else + sum += ((uint8_t *)data)[len]; +#endif + } + + stop = (uint16_t *)((void *)(((uint8_t *)data) + len)); + + while (buf < stop) { + sum += *buf++; + } + return sum; +} + +static inline uint16_t pico_checksum_finalize(uint32_t sum) +{ + while (sum >> 16) { /* a second carry is possible! */ + sum = (sum & 0x0000FFFF) + (sum >> 16); + } + return short_be((uint16_t) ~sum); +} + +/** + * Calculate checksum of a given string + */ +uint16_t pico_checksum(void *inbuf, uint32_t len) +{ + uint32_t sum; + + sum = pico_checksum_adder(0, inbuf, len); + return pico_checksum_finalize(sum); +} + +/* WARNING: len1 MUST be an EVEN number */ +uint16_t pico_dualbuffer_checksum(void *inbuf1, uint32_t len1, void *inbuf2, uint32_t len2) +{ + uint32_t sum; + + sum = pico_checksum_adder(0, inbuf1, len1); + sum = pico_checksum_adder(sum, inbuf2, len2); + return pico_checksum_finalize(sum); +} + diff --git a/ext/picotcp/stack/pico_md5.c b/ext/picotcp/stack/pico_md5.c new file mode 100644 index 0000000..d612bce --- /dev/null +++ b/ext/picotcp/stack/pico_md5.c @@ -0,0 +1,43 @@ +/********************************************************************* + * PicoTCP. Copyright (c) 2015 Altran Intelligent Systems. Some rights reserved. + * See LICENSE and COPYING for usage. + * + * Authors: Daniele Lacamera + * *********************************************************************/ + + +#include + +#if defined (PICO_SUPPORT_CYASSL) +#include + +void pico_md5sum(uint8_t *dst, const uint8_t *src, size_t len) +{ + Md5 md5; + InitMd5(&md5); + Md5Update(&md5, src, len); + Md5Final(&md5, dst); +} + +#elif defined (PICO_SUPPORT_POLARSSL) +#include + +void pico_md5sum(uint8_t *dst, const uint8_t *src, size_t len) +{ + md5(src, len, dst); +} + +#else +static void (*do_pico_md5sum)(uint8_t *dst, const uint8_t *src, size_t len); +void pico_md5sum(uint8_t *dst, const uint8_t *src, size_t len) +{ + if (do_pico_md5sum) { + do_pico_md5sum(dst, src, len); + } +} + +void pico_register_md5sum(void (*md5)(uint8_t *, const uint8_t *, size_t)) +{ + do_pico_md5sum = md5; +} +#endif diff --git a/ext/picotcp/stack/pico_protocol.c b/ext/picotcp/stack/pico_protocol.c new file mode 100644 index 0000000..b8afec2 --- /dev/null +++ b/ext/picotcp/stack/pico_protocol.c @@ -0,0 +1,214 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + . + + Authors: Daniele Lacamera + *********************************************************************/ + + +#include "pico_protocol.h" +#include "pico_tree.h" + +struct pico_proto_rr +{ + struct pico_tree *t; + struct pico_tree_node *node_in, *node_out; +}; + + +static int pico_proto_cmp(void *ka, void *kb) +{ + struct pico_protocol *a = ka, *b = kb; + if (a->hash < b->hash) + return -1; + + if (a->hash > b->hash) + return 1; + + return 0; +} + +PICO_TREE_DECLARE(Datalink_proto_tree, pico_proto_cmp); +PICO_TREE_DECLARE(Network_proto_tree, pico_proto_cmp); +PICO_TREE_DECLARE(Transport_proto_tree, pico_proto_cmp); +PICO_TREE_DECLARE(Socket_proto_tree, pico_proto_cmp); + +/* Static variables to keep track of the round robin loop */ +static struct pico_proto_rr proto_rr_datalink = { + &Datalink_proto_tree, NULL, NULL +}; +static struct pico_proto_rr proto_rr_network = { + &Network_proto_tree, NULL, NULL +}; +static struct pico_proto_rr proto_rr_transport = { + &Transport_proto_tree, NULL, NULL +}; +static struct pico_proto_rr proto_rr_socket = { + &Socket_proto_tree, NULL, NULL +}; + +static int proto_loop_in(struct pico_protocol *proto, int loop_score) +{ + struct pico_frame *f; + while(loop_score > 0) { + if (proto->q_in->frames == 0) + break; + + f = pico_dequeue(proto->q_in); + if ((f) && (proto->process_in(proto, f) > 0)) { + loop_score--; + } + } + return loop_score; +} + +static int proto_loop_out(struct pico_protocol *proto, int loop_score) +{ + struct pico_frame *f; + while(loop_score > 0) { + if (proto->q_out->frames == 0) + break; + + f = pico_dequeue(proto->q_out); + if ((f) && (proto->process_out(proto, f) > 0)) { + loop_score--; + } + } + return loop_score; +} + +static int proto_loop(struct pico_protocol *proto, int loop_score, int direction) +{ + + if (direction == PICO_LOOP_DIR_IN) + loop_score = proto_loop_in(proto, loop_score); + else if (direction == PICO_LOOP_DIR_OUT) + loop_score = proto_loop_out(proto, loop_score); + + return loop_score; +} + +static struct pico_tree_node *roundrobin_init(struct pico_proto_rr *rr, int direction) +{ + struct pico_tree_node *next_node = NULL; + /* Initialization (takes place only once) */ + if (rr->node_in == NULL) + rr->node_in = pico_tree_firstNode(rr->t->root); + + if (rr->node_out == NULL) + rr->node_out = pico_tree_firstNode(rr->t->root); + + if (direction == PICO_LOOP_DIR_IN) + next_node = rr->node_in; + else + next_node = rr->node_out; + + return next_node; +} + +static void roundrobin_end(struct pico_proto_rr *rr, int direction, struct pico_tree_node *last) +{ + if (direction == PICO_LOOP_DIR_IN) + rr->node_in = last; + else + rr->node_out = last; +} + +static int pico_protocol_generic_loop(struct pico_proto_rr *rr, int loop_score, int direction) +{ + struct pico_protocol *start, *next; + struct pico_tree_node *next_node = roundrobin_init(rr, direction); + + if (!next_node) + return loop_score; + + next = next_node->keyValue; + + /* init start node */ + start = next; + + /* round-robin all layer protocols, break if traversed all protocols */ + while (loop_score > 1 && next != NULL) { + loop_score = proto_loop(next, loop_score, direction); + next_node = pico_tree_next(next_node); + next = next_node->keyValue; + if (next == NULL) + { + next_node = pico_tree_firstNode(rr->t->root); + next = next_node->keyValue; + } + + if (next == start) + break; + } + roundrobin_end(rr, direction, next_node); + return loop_score; +} + +int pico_protocol_datalink_loop(int loop_score, int direction) +{ + return pico_protocol_generic_loop(&proto_rr_datalink, loop_score, direction); +} + +int pico_protocol_network_loop(int loop_score, int direction) +{ + return pico_protocol_generic_loop(&proto_rr_network, loop_score, direction); +} + +int pico_protocol_transport_loop(int loop_score, int direction) +{ + return pico_protocol_generic_loop(&proto_rr_transport, loop_score, direction); +} + +int pico_protocol_socket_loop(int loop_score, int direction) +{ + return pico_protocol_generic_loop(&proto_rr_socket, loop_score, direction); +} + +int pico_protocols_loop(int loop_score) +{ +/* + loop_score = pico_protocol_datalink_loop(loop_score); + loop_score = pico_protocol_network_loop(loop_score); + loop_score = pico_protocol_transport_loop(loop_score); + loop_score = pico_protocol_socket_loop(loop_score); + */ + return loop_score; +} + +static void proto_layer_rr_reset(struct pico_proto_rr *rr) +{ + rr->node_in = NULL; + rr->node_out = NULL; +} + +void pico_protocol_init(struct pico_protocol *p) +{ + if (!p) + return; + + p->hash = pico_hash(p->name, (uint32_t)strlen(p->name)); + switch (p->layer) { + case PICO_LAYER_DATALINK: + pico_tree_insert(&Datalink_proto_tree, p); + proto_layer_rr_reset(&proto_rr_datalink); + break; + case PICO_LAYER_NETWORK: + pico_tree_insert(&Network_proto_tree, p); + proto_layer_rr_reset(&proto_rr_network); + break; + case PICO_LAYER_TRANSPORT: + pico_tree_insert(&Transport_proto_tree, p); + proto_layer_rr_reset(&proto_rr_transport); + break; + case PICO_LAYER_SOCKET: + pico_tree_insert(&Socket_proto_tree, p); + proto_layer_rr_reset(&proto_rr_socket); + break; + } + //dbg("Protocol %s registered (layer: %d).\n", p->name, p->layer); + +} + diff --git a/ext/picotcp/stack/pico_socket.c b/ext/picotcp/stack/pico_socket.c new file mode 100644 index 0000000..a9df66d --- /dev/null +++ b/ext/picotcp/stack/pico_socket.c @@ -0,0 +1,2231 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + + Authors: Daniele Lacamera + *********************************************************************/ + + +#include "pico_config.h" +#include "pico_queue.h" +#include "pico_socket.h" +#include "pico_ipv4.h" +#include "pico_ipv6.h" +#include "pico_udp.h" +#include "pico_tcp.h" +#include "pico_stack.h" +#include "pico_icmp4.h" +#include "pico_nat.h" +#include "pico_tree.h" +#include "pico_device.h" +#include "pico_socket_multicast.h" +#include "pico_socket_tcp.h" +#include "pico_socket_udp.h" + +#if defined (PICO_SUPPORT_IPV4) || defined (PICO_SUPPORT_IPV6) +#if defined (PICO_SUPPORT_TCP) || defined (PICO_SUPPORT_UDP) + + +#define PROTO(s) ((s)->proto->proto_number) +#define PICO_MIN_MSS (1280) +#define TCP_STATE(s) (s->state & PICO_SOCKET_STATE_TCP) + +#ifdef PICO_SUPPORT_MUTEX +static void *Mutex = NULL; +#endif + + +#define PROTO(s) ((s)->proto->proto_number) + +#define PICO_SOCKET_MTU 1480 /* Ethernet MTU(1500) - IP header size(20) */ + +# define frag_dbg(...) do {} while(0) + + +static struct pico_sockport *sp_udp = NULL, *sp_tcp = NULL; + +struct pico_frame *pico_socket_frame_alloc(struct pico_socket *s, uint16_t len); + +static int socket_cmp_family(struct pico_socket *a, struct pico_socket *b) +{ + uint32_t a_is_ip6 = is_sock_ipv6(a); + uint32_t b_is_ip6 = is_sock_ipv6(b); + (void)a; + (void)b; + if (a_is_ip6 < b_is_ip6) + return -1; + + if (a_is_ip6 > b_is_ip6) + return 1; + + return 0; +} + + +static int socket_cmp_ipv6(struct pico_socket *a, struct pico_socket *b) +{ + int ret = 0; + (void)a; + (void)b; +#ifdef PICO_SUPPORT_IPV6 + if (!is_sock_ipv6(a) || !is_sock_ipv6(b)) + return 0; + + if ((memcmp(a->local_addr.ip6.addr, PICO_IP6_ANY, PICO_SIZE_IP6) == 0) || (memcmp(b->local_addr.ip6.addr, PICO_IP6_ANY, PICO_SIZE_IP6) == 0)) + ret = 0; + else + ret = memcmp(a->local_addr.ip6.addr, b->local_addr.ip6.addr, PICO_SIZE_IP6); + +#endif + return ret; +} + +static int socket_cmp_ipv4(struct pico_socket *a, struct pico_socket *b) +{ + int ret = 0; + (void)a; + (void)b; + if (!is_sock_ipv4(a) || !is_sock_ipv4(b)) + return 0; + +#ifdef PICO_SUPPORT_IPV4 + if ((a->local_addr.ip4.addr == PICO_IP4_ANY) || (b->local_addr.ip4.addr == PICO_IP4_ANY)) + ret = 0; + else + ret = (int)(a->local_addr.ip4.addr - b->local_addr.ip4.addr); + +#endif + return ret; +} + +static int socket_cmp_remotehost(struct pico_socket *a, struct pico_socket *b) +{ + int ret = 0; + if (is_sock_ipv6(a)) + ret = memcmp(a->remote_addr.ip6.addr, b->remote_addr.ip6.addr, PICO_SIZE_IP6); + else + ret = (int)(a->remote_addr.ip4.addr - b->remote_addr.ip4.addr); + + return ret; +} + +static int socket_cmp_addresses(struct pico_socket *a, struct pico_socket *b) +{ + int ret = 0; + /* At this point, sort by local host */ + ret = socket_cmp_ipv6(a, b); + + if (ret == 0) + ret = socket_cmp_ipv4(a, b); + + /* Sort by remote host */ + if (ret == 0) + ret = socket_cmp_remotehost(a, b); + + return 0; +} + +static int socket_cmp(void *ka, void *kb) +{ + struct pico_socket *a = ka, *b = kb; + int ret = 0; + + /* First, order by network family */ + ret = socket_cmp_family(a, b); + + /* Then, compare by source/destination addresses */ + if (ret == 0) + ret = socket_cmp_addresses(a, b); + + /* And finally by remote port. The two sockets are coincident if the quad is the same. */ + if (ret == 0) + ret = b->remote_port - a->remote_port; + + return ret; +} + + +#define INIT_SOCKPORT { {&LEAF, socket_cmp}, 0, 0 } + +static int sockport_cmp(void *ka, void *kb) +{ + struct pico_sockport *a = ka, *b = kb; + if (a->number < b->number) + return -1; + + if (a->number > b->number) + return 1; + + return 0; +} + +PICO_TREE_DECLARE(UDPTable, sockport_cmp); +PICO_TREE_DECLARE(TCPTable, sockport_cmp); + +struct pico_sockport *pico_get_sockport(uint16_t proto, uint16_t port) +{ + struct pico_sockport test = INIT_SOCKPORT; + test.number = port; + + if (proto == PICO_PROTO_UDP){ + return pico_tree_findKey(&UDPTable, &test); + } + + else if (proto == PICO_PROTO_TCP){ + return pico_tree_findKey(&TCPTable, &test); + } + + else + { return NULL; + } +} + +#ifdef PICO_SUPPORT_IPV4 + +static int pico_port_in_use_by_nat(uint16_t proto, uint16_t port) +{ + int ret = 0; + (void) proto; + (void) port; +#ifdef PICO_SUPPORT_NAT + if (pico_ipv4_nat_find(port, NULL, 0, (uint8_t)proto)) { + dbg("In use by nat....\n"); + ret = 1; + } + +#endif + return ret; +} + +static int pico_port_in_use_with_this_ipv4_address(struct pico_sockport *sp, struct pico_ip4 ip) +{ + if (sp) { + struct pico_ip4 *s_local; + struct pico_tree_node *idx; + struct pico_socket *s; + pico_tree_foreach(idx, &sp->socks) { + s = idx->keyValue; + if (s->net == &pico_proto_ipv4) { + s_local = (struct pico_ip4*) &s->local_addr; + if ((s_local->addr == PICO_IPV4_INADDR_ANY) || (s_local->addr == ip.addr)) { + return 1; + } + } + } + } + + return 0; +} + + +static int pico_port_in_use_ipv4(struct pico_sockport *sp, void *addr) +{ + struct pico_ip4 ip; + /* IPv4 */ + if (addr) + ip.addr = ((struct pico_ip4 *)addr)->addr; + else + ip.addr = PICO_IPV4_INADDR_ANY; + + if (ip.addr == PICO_IPV4_INADDR_ANY) { + if (!sp) + return 0; + else { + dbg("In use, and asked for ANY\n"); + return 1; + } + } + + return pico_port_in_use_with_this_ipv4_address(sp, ip); +} +#endif + +#ifdef PICO_SUPPORT_IPV6 +static int pico_port_in_use_with_this_ipv6_address(struct pico_sockport *sp, struct pico_ip6 ip) +{ + if (sp) { + struct pico_ip6 *s_local; + struct pico_tree_node *idx; + struct pico_socket *s; + pico_tree_foreach(idx, &sp->socks) { + s = idx->keyValue; + if (s->net == &pico_proto_ipv6) { + s_local = (struct pico_ip6*) &s->local_addr; + if ((pico_ipv6_is_unspecified(s_local->addr)) || (!memcmp(s_local->addr, ip.addr, PICO_SIZE_IP6))) { + return 1; + } + } + } + } + + return 0; +} + +static int pico_port_in_use_ipv6(struct pico_sockport *sp, void *addr) +{ + struct pico_ip6 ip; + /* IPv6 */ + if (addr) + memcpy(ip.addr, ((struct pico_ip6 *)addr)->addr, sizeof(struct pico_ip6)); + else + memcpy(ip.addr, PICO_IP6_ANY, sizeof(struct pico_ip6)); + + if (memcmp(ip.addr, PICO_IP6_ANY, sizeof(struct pico_ip6)) == 0) { + if (!sp) + return 0; + else { + dbg("In use, and asked for ANY\n"); + return 1; + } + } + + return pico_port_in_use_with_this_ipv6_address(sp, ip); +} +#endif + + + +static int pico_generic_port_in_use(uint16_t proto, uint16_t port, struct pico_sockport *sp, void *addr, void *net) +{ +#ifdef PICO_SUPPORT_IPV4 + if (net == &pico_proto_ipv4) + { + if (pico_port_in_use_by_nat(proto, port)) { + return 1; + } + + if (pico_port_in_use_ipv4(sp, addr)) { + return 1; + } + } + +#endif + +#ifdef PICO_SUPPORT_IPV6 + if (net == &pico_proto_ipv6) + { + if (pico_port_in_use_ipv6(sp, addr)) { + return 1; + } + } + +#endif + + return 0; +} + +int pico_is_port_free(uint16_t proto, uint16_t port, void *addr, void *net) +{ + struct pico_sockport *sp; + sp = pico_get_sockport(proto, port); + + if (pico_generic_port_in_use(proto, port, sp, addr, net)) + return 0; + + return 1; +} + +static int pico_check_socket(struct pico_socket *s) +{ + struct pico_sockport *test; + struct pico_socket *found; + struct pico_tree_node *index; + + test = pico_get_sockport(PROTO(s), s->local_port); + + if (!test) { + return -1; + } + + pico_tree_foreach(index, &test->socks){ + found = index->keyValue; + if (s == found) { + return 0; + } + } + return -1; +} + +struct pico_socket *pico_sockets_find(uint16_t local, uint16_t remote) +{ + struct pico_socket *sock = NULL; + struct pico_tree_node *index = NULL; + struct pico_sockport *sp = NULL; + + sp = pico_get_sockport(PICO_PROTO_TCP, local); + if(sp) + { + pico_tree_foreach(index, &sp->socks) + { + if(((struct pico_socket *)index->keyValue)->remote_port == remote) + { + sock = (struct pico_socket *)index->keyValue; + break; + } + } + } + return sock; +} + + +int8_t pico_socket_add(struct pico_socket *s) +{ + struct pico_sockport *sp = pico_get_sockport(PROTO(s), s->local_port); + PICOTCP_MUTEX_LOCK(Mutex); + if (!sp) { + /* dbg("Creating sockport..%04x\n", s->local_port); / * In comment due to spam during test * / */ + sp = PICO_ZALLOC(sizeof(struct pico_sockport)); + + if (!sp) { + pico_err = PICO_ERR_ENOMEM; + PICOTCP_MUTEX_UNLOCK(Mutex); + return -1; + } + + sp->proto = PROTO(s); + sp->number = s->local_port; + sp->socks.root = &LEAF; + sp->socks.compare = socket_cmp; + + if (PROTO(s) == PICO_PROTO_UDP) + { + pico_tree_insert(&UDPTable, sp); + } + else if (PROTO(s) == PICO_PROTO_TCP) + { + pico_tree_insert(&TCPTable, sp); + } + } + + pico_tree_insert(&sp->socks, s); + s->state |= PICO_SOCKET_STATE_BOUND; + PICOTCP_MUTEX_UNLOCK(Mutex); +#ifdef DEBUG_SOCKET_TREE + { + struct pico_tree_node *index; + /* RB_FOREACH(s, socket_tree, &sp->socks) { */ + pico_tree_foreach(index, &sp->socks){ + s = index->keyValue; + dbg(">>>> List Socket lc=%hu rm=%hu\n", short_be(s->local_port), short_be(s->remote_port)); + } + + } +#endif + return 0; +} + + +static void socket_clean_queues(struct pico_socket *sock) +{ + struct pico_frame *f_in = pico_dequeue(&sock->q_in); + struct pico_frame *f_out = pico_dequeue(&sock->q_out); + while(f_in || f_out) + { + if(f_in) + { + pico_frame_discard(f_in); + f_in = pico_dequeue(&sock->q_in); + } + + if(f_out) + { + pico_frame_discard(f_out); + f_out = pico_dequeue(&sock->q_out); + } + } + pico_queue_deinit(&sock->q_in); + pico_queue_deinit(&sock->q_out); + pico_socket_tcp_cleanup(sock); +} + +static void socket_garbage_collect(pico_time now, void *arg) +{ + struct pico_socket *s = (struct pico_socket *) arg; + IGNORE_PARAMETER(now); + + socket_clean_queues(s); + PICO_FREE(s); +} + + +static void pico_socket_check_empty_sockport(struct pico_socket *s, struct pico_sockport *sp) +{ + if(pico_tree_empty(&sp->socks)) { + if (PROTO(s) == PICO_PROTO_UDP) + { + pico_tree_delete(&UDPTable, sp); + } + else if (PROTO(s) == PICO_PROTO_TCP) + { + pico_tree_delete(&TCPTable, sp); + } + + if(sp_tcp == sp) + sp_tcp = NULL; + + if(sp_udp == sp) + sp_udp = NULL; + + PICO_FREE(sp); + } +} + +int8_t pico_socket_del(struct pico_socket *s) +{ + struct pico_sockport *sp = pico_get_sockport(PROTO(s), s->local_port); + if (!sp) { + pico_err = PICO_ERR_ENXIO; + return -1; + } + + PICOTCP_MUTEX_LOCK(Mutex); + pico_tree_delete(&sp->socks, s); + pico_socket_check_empty_sockport(s, sp); + pico_multicast_delete(s); + pico_socket_tcp_delete(s); + s->state = PICO_SOCKET_STATE_CLOSED; + pico_timer_add((pico_time)10, socket_garbage_collect, s); + PICOTCP_MUTEX_UNLOCK(Mutex); + return 0; +} + +static void pico_socket_update_tcp_state(struct pico_socket *s, uint16_t tcp_state) +{ + if (tcp_state) { + s->state &= 0x00FF; + s->state |= tcp_state; + } +} + +static int8_t pico_socket_alter_state(struct pico_socket *s, uint16_t more_states, uint16_t less_states, uint16_t tcp_state) +{ + struct pico_sockport *sp; + if (more_states & PICO_SOCKET_STATE_BOUND) + return pico_socket_add(s); + + if (less_states & PICO_SOCKET_STATE_BOUND) + return pico_socket_del(s); + + sp = pico_get_sockport(PROTO(s), s->local_port); + if (!sp) { + pico_err = PICO_ERR_ENXIO; + return -1; + } + + s->state |= more_states; + s->state = (uint16_t)(s->state & (~less_states)); + pico_socket_update_tcp_state(s, tcp_state); + return 0; +} + + +static int pico_socket_transport_deliver(struct pico_protocol *p, struct pico_sockport *sp, struct pico_frame *f) +{ +#ifdef PICO_SUPPORT_TCP + if (p->proto_number == PICO_PROTO_TCP) + return pico_socket_tcp_deliver(sp, f); + +#endif + +#ifdef PICO_SUPPORT_UDP + if (p->proto_number == PICO_PROTO_UDP) + return pico_socket_udp_deliver(sp, f); + +#endif + + return -1; +} + + +static int pico_socket_deliver(struct pico_protocol *p, struct pico_frame *f, uint16_t localport) +{ + struct pico_sockport *sp = NULL; + struct pico_trans *tr = (struct pico_trans *) f->transport_hdr; + + if (!tr) + return -1; + + sp = pico_get_sockport(p->proto_number, localport); + if (!sp) { + //dbg("No such port %d\n", short_be(localport)); + return -1; + } + + return pico_socket_transport_deliver(p, sp, f); +} + +int pico_socket_set_family(struct pico_socket *s, uint16_t family) +{ + (void) family; + + #ifdef PICO_SUPPORT_IPV4 + if (family == PICO_PROTO_IPV4) + s->net = &pico_proto_ipv4; + + #endif + + #ifdef PICO_SUPPORT_IPV6 + if (family == PICO_PROTO_IPV6) + s->net = &pico_proto_ipv6; + + #endif + + if (s->net == NULL) + return -1; + + return 0; +} + +static struct pico_socket *pico_socket_transport_open(uint16_t proto, uint16_t family) +{ + struct pico_socket *s = NULL; + (void)family; +#ifdef PICO_SUPPORT_UDP + if (proto == PICO_PROTO_UDP) + s = pico_socket_udp_open(); + +#endif + +#ifdef PICO_SUPPORT_TCP + if (proto == PICO_PROTO_TCP) + s = pico_socket_tcp_open(family); + +#endif + + return s; + +} + +extern struct pico_socket *pico_socket_open(uint16_t net, uint16_t proto, void (*wakeup)(uint16_t ev, struct pico_socket *)) +{ + + struct pico_socket *s = NULL; + + s = pico_socket_transport_open(proto, net); + + if (!s) { + pico_err = PICO_ERR_EPROTONOSUPPORT; + return NULL; + } + + if (pico_socket_set_family(s, net) != 0) { + PICO_FREE(s); + pico_err = PICO_ERR_ENETUNREACH; + return NULL; + } + + s->q_in.max_size = PICO_DEFAULT_SOCKETQ; + s->q_out.max_size = PICO_DEFAULT_SOCKETQ; + + s->wakeup = wakeup; + return s; +} + + +static void pico_socket_clone_assign_address(struct pico_socket *s, struct pico_socket *facsimile) +{ + +#ifdef PICO_SUPPORT_IPV4 + if (facsimile->net == &pico_proto_ipv4) { + s->net = &pico_proto_ipv4; + memcpy(&s->local_addr, &facsimile->local_addr, sizeof(struct pico_ip4)); + memcpy(&s->remote_addr, &facsimile->remote_addr, sizeof(struct pico_ip4)); + } + +#endif + +#ifdef PICO_SUPPORT_IPV6 + if (facsimile->net == &pico_proto_ipv6) { + s->net = &pico_proto_ipv6; + memcpy(&s->local_addr, &facsimile->local_addr, sizeof(struct pico_ip6)); + memcpy(&s->remote_addr, &facsimile->remote_addr, sizeof(struct pico_ip6)); + } + +#endif + +} + +struct pico_socket *pico_socket_clone(struct pico_socket *facsimile) +{ + struct pico_socket *s = NULL; + + s = pico_socket_transport_open(facsimile->proto->proto_number, facsimile->net->proto_number); + if (!s) { + pico_err = PICO_ERR_EPROTONOSUPPORT; + return NULL; + } + + s->local_port = facsimile->local_port; + s->remote_port = facsimile->remote_port; + s->state = facsimile->state; + pico_socket_clone_assign_address(s, facsimile); + if (!s->net) { + PICO_FREE(s); + pico_err = PICO_ERR_ENETUNREACH; + return NULL; + } + + s->q_in.max_size = PICO_DEFAULT_SOCKETQ; + s->q_out.max_size = PICO_DEFAULT_SOCKETQ; + s->wakeup = NULL; + return s; +} + +static int pico_socket_transport_read(struct pico_socket *s, void *buf, int len) +{ + if (PROTO(s) == PICO_PROTO_UDP) + { + /* make sure cast to uint16_t doesn't give unexpected results */ + if(len > 0xFFFF) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + return pico_socket_udp_recv(s, buf, (uint16_t)len, NULL, NULL); + } + else if (PROTO(s) == PICO_PROTO_TCP) + return pico_socket_tcp_read(s, buf, (uint32_t)len); + else return 0; +} +#include + +int pico_socket_read(struct pico_socket *s, void *buf, int len) +{ + if (!s || buf == NULL) { + pico_err = PICO_ERR_EINVAL; + return -1; + } else { + /* check if exists in tree */ + /* See task #178 */ + if (pico_check_socket(s) != 0) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + } + + if ((s->state & PICO_SOCKET_STATE_BOUND) == 0) { + pico_err = PICO_ERR_EIO; + return -1; + } + + return pico_socket_transport_read(s, buf, len); +} + +static int pico_socket_write_check_state(struct pico_socket *s) +{ + if ((s->state & PICO_SOCKET_STATE_BOUND) == 0) { + pico_err = PICO_ERR_EIO; + return -1; + } + + if ((s->state & PICO_SOCKET_STATE_CONNECTED) == 0) { + pico_err = PICO_ERR_ENOTCONN; + return -1; + } + + if (s->state & PICO_SOCKET_STATE_SHUT_LOCAL) { /* check if in shutdown state */ + pico_err = PICO_ERR_ESHUTDOWN; + return -1; + } + + return 0; +} + +static int pico_socket_write_attempt(struct pico_socket *s, const void *buf, int len) +{ + if (pico_socket_write_check_state(s) < 0) { + return -1; + } else { + return pico_socket_sendto(s, buf, len, &s->remote_addr, s->remote_port); + } +} + +extern int pico_socket_write(struct pico_socket *s, const void *buf, int len) +{ + if (!s || buf == NULL) { + pico_err = PICO_ERR_EINVAL; + return -1; + } else { + /* check if exists in tree */ + /* See task #178 */ + if (pico_check_socket(s) != 0) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + } + + return pico_socket_write_attempt(s, buf, len); +} + +static uint16_t pico_socket_high_port(uint16_t proto) +{ + uint16_t port; + if (0 || +#ifdef PICO_SUPPORT_TCP + (proto == PICO_PROTO_TCP) || +#endif +#ifdef PICO_SUPPORT_UDP + (proto == PICO_PROTO_UDP) || +#endif + 0) { + do { + uint32_t rand = pico_rand(); + port = (uint16_t) (rand & 0xFFFFU); + port = (uint16_t)((port % (65535 - 1024)) + 1024U); + if (pico_is_port_free(proto, port, NULL, NULL)) { + return short_be(port); + } + } while(1); + } + else return 0U; +} + +static void *pico_socket_sendto_get_ip4_src(struct pico_socket *s, struct pico_ip4 *dst) +{ + struct pico_ip4 *src4 = NULL; + +#ifdef PICO_SUPPORT_IPV4 + /* Check if socket is connected: destination address MUST match the + * current connected endpoint + */ + if ((s->state & PICO_SOCKET_STATE_CONNECTED)) { + src4 = &s->local_addr.ip4; + if (s->remote_addr.ip4.addr != ((struct pico_ip4 *)dst)->addr ) { + pico_err = PICO_ERR_EADDRNOTAVAIL; + return NULL; + } + } else { + + src4 = pico_ipv4_source_find(dst); + if (!src4) { + pico_err = PICO_ERR_EHOSTUNREACH; + return NULL; + } + + } + + if (src4->addr != PICO_IPV4_INADDR_ANY) + s->local_addr.ip4.addr = src4->addr; + +#else + pico_err = PICO_ERR_EPROTONOSUPPORT; +#endif + return src4; +} + +static void *pico_socket_sendto_get_ip6_src(struct pico_socket *s, struct pico_ip6 *dst) +{ + struct pico_ip6 *src6 = NULL; + (void)s; + (void)dst; + +#ifdef PICO_SUPPORT_IPV6 + + /* Check if socket is connected: destination address MUST match the + * current connected endpoint + */ + if ((s->state & PICO_SOCKET_STATE_CONNECTED)) { + src6 = &s->local_addr.ip6; + if (memcmp(&s->remote_addr, dst, PICO_SIZE_IP6)) { + pico_err = PICO_ERR_EADDRNOTAVAIL; + return NULL; + } + } else { + src6 = pico_ipv6_source_find(dst); + if (!src6) { + pico_err = PICO_ERR_EHOSTUNREACH; + return NULL; + } + + if (!pico_ipv6_is_unspecified(src6->addr)) + s->local_addr.ip6 = *src6; + } + +#else + pico_err = PICO_ERR_EPROTONOSUPPORT; +#endif + return src6; +} + + +static int pico_socket_sendto_dest_check(struct pico_socket *s, void *dst, uint16_t port) +{ + + /* For the sendto call to be valid, + * dst and remote_port should be always populated. + */ + if (!dst || !port) { + pico_err = PICO_ERR_EADDRNOTAVAIL; + return -1; + } + + /* When coming from pico_socket_send (or _write), + * the destination is automatically assigned to the currently connected endpoint. + * This check will ensure that there is no mismatch when sendto() is called directly + * on a connected socket + */ + if ((s->state & PICO_SOCKET_STATE_CONNECTED) != 0) { + if (port != s->remote_port) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + } + + return 0; +} + +static int pico_socket_sendto_initial_checks(struct pico_socket *s, const void *buf, const int len, void *dst, uint16_t remote_port) +{ + if (len < 0) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + if (buf == NULL || s == NULL) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + return pico_socket_sendto_dest_check(s, dst, remote_port); +} + +static void *pico_socket_sendto_get_src(struct pico_socket *s, void *dst) +{ + void *src = NULL; + if (is_sock_ipv4(s)) + src = pico_socket_sendto_get_ip4_src(s, (struct pico_ip4 *)dst); + + if (is_sock_ipv6(s)) + src = pico_socket_sendto_get_ip6_src(s, (struct pico_ip6 *)dst); + + return src; +} + +static struct pico_remote_endpoint *pico_socket_sendto_destination_ipv4(struct pico_socket *s, struct pico_ip4 *dst, uint16_t port) +{ + struct pico_remote_endpoint *ep = NULL; + (void)s; + ep = PICO_ZALLOC(sizeof(struct pico_remote_endpoint)); + if (!ep) { + pico_err = PICO_ERR_ENOMEM; + return NULL; + } + + ep->remote_addr.ip4.addr = ((struct pico_ip4 *)dst)->addr; + ep->remote_port = port; + return ep; +} + +static void pico_endpoint_free(struct pico_remote_endpoint *ep) +{ + if (ep) + PICO_FREE(ep); +} + +static struct pico_remote_endpoint *pico_socket_sendto_destination_ipv6(struct pico_socket *s, struct pico_ip6 *dst, uint16_t port) +{ + struct pico_remote_endpoint *ep = NULL; + (void)s; + (void)dst; + (void)port; +#ifdef PICO_SUPPORT_IPV6 + ep = PICO_ZALLOC(sizeof(struct pico_remote_endpoint)); + if (!ep) { + pico_err = PICO_ERR_ENOMEM; + return NULL; + } + + memcpy(&ep->remote_addr.ip6, dst, sizeof(struct pico_ip6)); + ep->remote_port = port; +#endif + return ep; +} + + +static struct pico_remote_endpoint *pico_socket_sendto_destination(struct pico_socket *s, void *dst, uint16_t port) +{ + struct pico_remote_endpoint *ep = NULL; + (void)pico_socket_sendto_destination_ipv6; + /* socket remote info could change in a consecutive call, make persistent */ +# ifdef PICO_SUPPORT_UDP + if (PROTO(s) == PICO_PROTO_UDP) { +# ifdef PICO_SUPPORT_IPV6 + if (is_sock_ipv6(s)) + ep = pico_socket_sendto_destination_ipv6(s, (struct pico_ip6 *)dst, port); + +# endif +# ifdef PICO_SUPPORT_IPV4 + if (is_sock_ipv4(s)) + ep = pico_socket_sendto_destination_ipv4(s, (struct pico_ip4 *)dst, port); + +# endif + } + +# endif + return ep; +} + +static int32_t pico_socket_sendto_set_localport(struct pico_socket *s) +{ + + if ((s->state & PICO_SOCKET_STATE_BOUND) == 0) { + s->local_port = pico_socket_high_port(s->proto->proto_number); + if (s->local_port == 0) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + pico_socket_alter_state(s, PICO_SOCKET_STATE_BOUND, 0, 0); + } + + return s->local_port; +} + +static int pico_socket_sendto_transport_offset(struct pico_socket *s) +{ + int header_offset = -1; + #ifdef PICO_SUPPORT_TCP + if (PROTO(s) == PICO_PROTO_TCP) + header_offset = pico_tcp_overhead(s); + + #endif + + #ifdef PICO_SUPPORT_UDP + if (PROTO(s) == PICO_PROTO_UDP) + header_offset = sizeof(struct pico_udp_hdr); + + #endif + return header_offset; +} + + +static struct pico_remote_endpoint *pico_socket_set_info(struct pico_remote_endpoint *ep) +{ + struct pico_remote_endpoint *info; + info = PICO_ZALLOC(sizeof(struct pico_remote_endpoint)); + if (!info) { + pico_err = PICO_ERR_ENOMEM; + return NULL; + } + + memcpy(info, ep, sizeof(struct pico_remote_endpoint)); + return info; +} + +static void pico_xmit_frame_set_nofrag(struct pico_frame *f) +{ +#ifdef PICO_SUPPORT_IPV4FRAG + f->frag = PICO_IPV4_DONTFRAG; +#else + (void)f; +#endif +} + +static int pico_socket_final_xmit(struct pico_socket *s, struct pico_frame *f) +{ + if (s->proto->push(s->proto, f) > 0) { + return f->payload_len; + } else { + pico_frame_discard(f); + return 0; + } +} + +static int pico_socket_xmit_one(struct pico_socket *s, const void *buf, const int len, void *src, + struct pico_remote_endpoint *ep, struct pico_msginfo *msginfo) +{ + struct pico_frame *f; + uint16_t hdr_offset = (uint16_t)pico_socket_sendto_transport_offset(s); + int ret = 0; + (void)src; + + f = pico_socket_frame_alloc(s, (uint16_t)(len + hdr_offset)); + if (!f) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + f->payload += hdr_offset; + f->payload_len = (uint16_t)(len); + f->sock = s; + transport_flags_update(f, s); + pico_xmit_frame_set_nofrag(f); + if (ep && !f->info) { + f->info = pico_socket_set_info(ep); + if (!f->info) { + pico_frame_discard(f); + return -1; + } + } + + if (msginfo) { + f->send_ttl = (uint8_t)msginfo->ttl; + f->send_tos = (uint8_t)msginfo->tos; + f->dev = msginfo->dev; + } +#ifdef PICO_SUPPORT_IPV6 + if(IS_SOCK_IPV6(s) && ep && pico_ipv6_is_multicast(&ep->remote_addr.ip6.addr[0])) { + f->dev = pico_ipv6_link_find(src); + if(!f->dev) { + return -1; + } + } +#endif + memcpy(f->payload, (const uint8_t *)buf, f->payload_len); + /* dbg("Pushing segment, hdr len: %d, payload_len: %d\n", header_offset, f->payload_len); */ + ret = pico_socket_final_xmit(s, f); + return ret; +} + +static int pico_socket_xmit_avail_space(struct pico_socket *s); + +#ifdef PICO_SUPPORT_IPV4FRAG +static void pico_socket_xmit_first_fragment_setup(struct pico_frame *f, int space, int hdr_offset) +{ + frag_dbg("FRAG: first fragmented frame %p | len = %u offset = 0\n", f, f->payload_len); + /* transport header length field contains total length + header length */ + f->transport_len = (uint16_t)(space); + f->frag = PICO_IPV4_MOREFRAG; + f->payload += hdr_offset; +} + +static void pico_socket_xmit_next_fragment_setup(struct pico_frame *f, int hdr_offset, int total_payload_written, int len) +{ + /* no transport header in fragmented IP */ + f->payload = f->transport_hdr; + /* set offset in octets */ + f->frag = (uint16_t)((total_payload_written + (uint16_t)hdr_offset) >> 3u); /* first fragment had a header offset */ + if (total_payload_written + f->payload_len < len) { + frag_dbg("FRAG: intermediate fragmented frame %p | len = %u offset = %u\n", f, f->payload_len, short_be(f->frag)); + f->frag |= PICO_IPV4_MOREFRAG; + } else { + frag_dbg("FRAG: last fragmented frame %p | len = %u offset = %u\n", f, f->payload_len, short_be(f->frag)); + f->frag &= PICO_IPV4_FRAG_MASK; + } +} +#endif + +/* Implies ep discarding! */ +static int pico_socket_xmit_fragments(struct pico_socket *s, const void *buf, const int len, + void *src, struct pico_remote_endpoint *ep, struct pico_msginfo *msginfo) +{ + int space = pico_socket_xmit_avail_space(s); + int hdr_offset = pico_socket_sendto_transport_offset(s); + int total_payload_written = 0; + int retval = 0; + struct pico_frame *f = NULL; + + if (space > len) { + retval = pico_socket_xmit_one(s, buf, len, src, ep, msginfo); + pico_endpoint_free(ep); + return retval; + } + +#ifdef PICO_SUPPORT_IPV6 + /* Can't fragment IPv6 */ + if (is_sock_ipv6(s)) { + retval = pico_socket_xmit_one(s, buf, space, src, ep, msginfo); + pico_endpoint_free(ep); + return retval; + } + +#endif + +#ifdef PICO_SUPPORT_IPV4FRAG + while(total_payload_written < len) { + /* Always allocate the max space available: space + offset */ + if (len < space) + space = len; + + if (space > len - total_payload_written) /* update space for last fragment */ + space = len - total_payload_written; + + f = pico_socket_frame_alloc(s, (uint16_t)(space + hdr_offset)); + if (!f) { + pico_err = PICO_ERR_ENOMEM; + pico_endpoint_free(ep); + return -1; + } + + f->sock = s; + if (ep) { + f->info = pico_socket_set_info(ep); + if (!f->info) { + pico_frame_discard(f); + pico_endpoint_free(ep); + return -1; + } + } + + f->payload_len = (uint16_t) space; + if (total_payload_written == 0) { + /* First fragment: no payload written yet! */ + pico_socket_xmit_first_fragment_setup(f, space, hdr_offset); + space += hdr_offset; /* only first fragments contains transport header */ + hdr_offset = 0; + } else { + /* Next fragment */ + pico_socket_xmit_next_fragment_setup(f, pico_socket_sendto_transport_offset(s), total_payload_written, len); + } + + memcpy(f->payload, (const uint8_t *)buf + total_payload_written, f->payload_len); + transport_flags_update(f, s); + if (s->proto->push(s->proto, f) > 0) { + total_payload_written += f->payload_len; + } else { + pico_frame_discard(f); + break; + } + } /* while() */ + pico_endpoint_free(ep); + return total_payload_written; + +#else + /* Careful with that axe, Eugene! + * + * cropping down datagrams to the MTU value. + */ + (void) f; + (void) hdr_offset; + (void) total_payload_written; + retval = pico_socket_xmit_one(s, buf, space, src, ep, msginfo); + pico_endpoint_free(ep); + return retval; + +#endif +} + +static void get_sock_dev(struct pico_socket *s) +{ + if (0) {} + +#ifdef PICO_SUPPORT_IPV6 + else if (is_sock_ipv6(s)) + s->dev = pico_ipv6_source_dev_find(&s->remote_addr.ip6); +#endif +#ifdef PICO_SUPPORT_IPV4 + else if (is_sock_ipv4(s)) + s->dev = pico_ipv4_source_dev_find(&s->remote_addr.ip4); +#endif + +} + + +static uint32_t pico_socket_adapt_mss_to_proto(struct pico_socket *s, uint32_t mss) +{ +#ifdef PICO_SUPPORT_IPV6 + if (is_sock_ipv6(s)) + mss -= PICO_SIZE_IP6HDR; + else +#endif + mss -= PICO_SIZE_IP4HDR; + return mss; +} + +uint32_t pico_socket_get_mss(struct pico_socket *s) +{ + uint32_t mss = PICO_MIN_MSS; + if (!s) + return mss; + + if (!s->dev) + get_sock_dev(s); + + if (!s->dev) { + mss = PICO_MIN_MSS; + } else { + mss = s->dev->mtu; + } + + return pico_socket_adapt_mss_to_proto(s, mss); +} + + +static int pico_socket_xmit_avail_space(struct pico_socket *s) +{ + int transport_len; + int header_offset; + +#ifdef PICO_SUPPORT_TCP + if (PROTO(s) == PICO_PROTO_TCP) { + transport_len = (uint16_t)pico_tcp_get_socket_mss(s); + } else +#endif + transport_len = (uint16_t)pico_socket_get_mss(s); + header_offset = pico_socket_sendto_transport_offset(s); + if (header_offset < 0) { + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; + } + + transport_len -= pico_socket_sendto_transport_offset(s); + return transport_len; +} + + +static int pico_socket_xmit(struct pico_socket *s, const void *buf, const int len, void *src, + struct pico_remote_endpoint *ep, struct pico_msginfo *msginfo) +{ + int space = pico_socket_xmit_avail_space(s); + int total_payload_written = 0; + + if (space < 0) { + pico_err = PICO_ERR_EPROTONOSUPPORT; + pico_endpoint_free(ep); + return -1; + } + + if ((PROTO(s) == PICO_PROTO_UDP) && (len > space)) { + total_payload_written = pico_socket_xmit_fragments(s, buf, len, src, ep, msginfo); + /* Implies ep discarding */ + return total_payload_written; + } + + while (total_payload_written < len) { + int w, chunk_len = len - total_payload_written; + if (chunk_len > space) + chunk_len = space; + + w = pico_socket_xmit_one(s, (const void *)((const uint8_t *)buf + total_payload_written), chunk_len, src, ep, msginfo); + if (w <= 0) { + break; + } + + total_payload_written += w; + if (PROTO(s) == PICO_PROTO_UDP) { + /* Break after the first datagram sent with at most MTU bytes. */ + break; + } + } + pico_endpoint_free(ep); + return total_payload_written; +} + +static void pico_socket_sendto_set_dport(struct pico_socket *s, uint16_t port) +{ + if ((s->state & PICO_SOCKET_STATE_CONNECTED) == 0) { + s->remote_port = port; + } +} + + +int MOCKABLE pico_socket_sendto_extended(struct pico_socket *s, const void *buf, const int len, + void *dst, uint16_t remote_port, struct pico_msginfo *msginfo) +{ + struct pico_remote_endpoint *remote_endpoint = NULL; + void *src = NULL; + + if(len == 0) + return 0; + + if (pico_socket_sendto_initial_checks(s, buf, len, dst, remote_port) < 0) + return -1; + + + src = pico_socket_sendto_get_src(s, dst); + if (!src) { +#ifdef PICO_SUPPORT_IPV6 + if((s->net->proto_number == PICO_PROTO_IPV6) + && msginfo && msginfo->dev + && pico_ipv6_is_multicast(((struct pico_ip6 *)dst)->addr)) + { + src = &(pico_ipv6_linklocal_get(msginfo->dev)->address); + } + else +#endif + return -1; + } + + remote_endpoint = pico_socket_sendto_destination(s, dst, remote_port); + if (pico_socket_sendto_set_localport(s) < 0) { + pico_endpoint_free(remote_endpoint); + return -1; + } + + pico_socket_sendto_set_dport(s, remote_port); + return pico_socket_xmit(s, buf, len, src, remote_endpoint, msginfo); /* Implies discarding the endpoint */ +} + +int MOCKABLE pico_socket_sendto(struct pico_socket *s, const void *buf, const int len, void *dst, uint16_t remote_port) +{ + return pico_socket_sendto_extended(s, buf, len, dst, remote_port, NULL); +} + +int pico_socket_send(struct pico_socket *s, const void *buf, int len) +{ + if (!s || buf == NULL) { + pico_err = PICO_ERR_EINVAL; + return -1; + } else { + /* check if exists in tree */ + /* See task #178 */ + if (pico_check_socket(s) != 0) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + } + + if ((s->state & PICO_SOCKET_STATE_CONNECTED) == 0) { + pico_err = PICO_ERR_ENOTCONN; + return -1; + } + + return pico_socket_sendto(s, buf, len, &s->remote_addr, s->remote_port); +} + +int pico_socket_recvfrom_extended(struct pico_socket *s, void *buf, int len, void *orig, + uint16_t *remote_port, struct pico_msginfo *msginfo) +{ + if (!s || buf == NULL) { /* / || orig == NULL || remote_port == NULL) { */ + pico_err = PICO_ERR_EINVAL; + return -1; + } else { + /* check if exists in tree */ + if (pico_check_socket(s) != 0) { + pico_err = PICO_ERR_EINVAL; + /* See task #178 */ + return -1; + } + } + + if ((s->state & PICO_SOCKET_STATE_BOUND) == 0) { + pico_err = PICO_ERR_EADDRNOTAVAIL; + return -1; + } + +#ifdef PICO_SUPPORT_UDP + if (PROTO(s) == PICO_PROTO_UDP) { + /* make sure cast to uint16_t doesn't give unexpected results */ + if(len > 0xFFFF) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + return pico_udp_recv(s, buf, (uint16_t)len, orig, remote_port, msginfo); + } + +#endif +#ifdef PICO_SUPPORT_TCP + if (PROTO(s) == PICO_PROTO_TCP) { + /* check if in shutdown state and if tcpq_in empty */ + if ((s->state & PICO_SOCKET_STATE_SHUT_REMOTE) && pico_tcp_queue_in_is_empty(s)) { + pico_err = PICO_ERR_ESHUTDOWN; + return -1; + } else { + /* dbg("socket tcp recv\n"); */ + return (int)pico_tcp_read(s, buf, (uint32_t)len); + } + } + +#endif + /* dbg("socket return 0\n"); */ + return 0; +} + +extern int pico_socket_recvfrom(struct pico_socket *s, void *buf, int len, void *orig, + uint16_t *remote_port) +{ + return pico_socket_recvfrom_extended(s, buf, len, orig, remote_port, NULL); + +} + +int pico_socket_recv(struct pico_socket *s, void *buf, int len) +{ + return pico_socket_recvfrom(s, buf, len, NULL, NULL); +} + + +int pico_socket_getname(struct pico_socket *s, void *local_addr, uint16_t *port, uint16_t *proto) +{ + + if (!s || !local_addr || !port || !proto) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + if (is_sock_ipv4(s)) { + #ifdef PICO_SUPPORT_IPV4 + struct pico_ip4 *ip = (struct pico_ip4 *)local_addr; + ip->addr = s->local_addr.ip4.addr; + *proto = PICO_PROTO_IPV4; + #endif + } else if (is_sock_ipv6(s)) { + #ifdef PICO_SUPPORT_IPV6 + struct pico_ip6 *ip = (struct pico_ip6 *)local_addr; + memcpy(ip->addr, s->local_addr.ip6.addr, PICO_SIZE_IP6); + *proto = PICO_PROTO_IPV6; + #endif + } else { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + *port = s->local_port; + return 0; +} + +int pico_socket_getpeername(struct pico_socket *s, void *remote_addr, uint16_t *port, uint16_t *proto) +{ + if (!s || !remote_addr || !port || !proto) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + if ((s->state & PICO_SOCKET_STATE_CONNECTED) == 0) { + pico_err = PICO_ERR_ENOTCONN; + return -1; + } + + if (is_sock_ipv4(s)) { + #ifdef PICO_SUPPORT_IPV4 + struct pico_ip4 *ip = (struct pico_ip4 *)remote_addr; + ip->addr = s->remote_addr.ip4.addr; + *proto = PICO_PROTO_IPV4; + #endif + } else if (is_sock_ipv6(s)) { + #ifdef PICO_SUPPORT_IPV6 + struct pico_ip6 *ip = (struct pico_ip6 *)remote_addr; + memcpy(ip->addr, s->remote_addr.ip6.addr, PICO_SIZE_IP6); + *proto = PICO_PROTO_IPV6; + #endif + } else { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + *port = s->remote_port; + return 0; + +} + +int pico_socket_bind(struct pico_socket *s, void *local_addr, uint16_t *port) +{ + if (!s || !local_addr || !port) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + if (is_sock_ipv4(s)) { + #ifdef PICO_SUPPORT_IPV4 + struct pico_ip4 *ip = (struct pico_ip4 *)local_addr; + if (ip->addr != PICO_IPV4_INADDR_ANY) { + if (!pico_ipv4_link_find(local_addr)) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + } + + #endif + } else if (is_sock_ipv6(s)) { + #ifdef PICO_SUPPORT_IPV6 + struct pico_ip6 *ip = (struct pico_ip6 *)local_addr; + if (!pico_ipv6_is_unspecified(ip->addr)) { + if (!pico_ipv6_link_find(local_addr)) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + } + + #endif + } else { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* When given port = 0, get a random high port to bind to. */ + if (*port == 0) { + *port = pico_socket_high_port(PROTO(s)); + if (*port == 0) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + } + + if (pico_is_port_free(PROTO(s), *port, local_addr, s->net) == 0) { + pico_err = PICO_ERR_EADDRINUSE; + return -1; + } + + s->local_port = *port; + + if (is_sock_ipv4(s)) { + #ifdef PICO_SUPPORT_IPV4 + struct pico_ip4 *ip = (struct pico_ip4 *)local_addr; + s->local_addr.ip4 = *ip; + #endif + } else if (is_sock_ipv6(s)) { + #ifdef PICO_SUPPORT_IPV6 + struct pico_ip6 *ip = (struct pico_ip6 *)local_addr; + s->local_addr.ip6 = *ip; + #endif + } else { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + return pico_socket_alter_state(s, PICO_SOCKET_STATE_BOUND, 0, 0); +} + + +int pico_socket_connect(struct pico_socket *s, const void *remote_addr, uint16_t remote_port) +{ + int ret = -1; + pico_err = PICO_ERR_EPROTONOSUPPORT; + if (!s || remote_addr == NULL || remote_port == 0) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + s->remote_port = remote_port; + + if (s->local_port == 0) { + s->local_port = pico_socket_high_port(PROTO(s)); + if (!s->local_port) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + } + + if (is_sock_ipv4(s)) { + #ifdef PICO_SUPPORT_IPV4 + struct pico_ip4 *local = NULL; + const struct pico_ip4 *ip = (const struct pico_ip4 *)remote_addr; + s->remote_addr.ip4 = *ip; + local = pico_ipv4_source_find(ip); + if (local) { + get_sock_dev(s); + s->local_addr.ip4 = *local; + } else { + pico_err = PICO_ERR_EHOSTUNREACH; + return -1; + } + + #endif + } else if (is_sock_ipv6(s)) { + #ifdef PICO_SUPPORT_IPV6 + struct pico_ip6 *local = NULL; + const struct pico_ip6 *ip = (const struct pico_ip6 *)remote_addr; + s->remote_addr.ip6 = *ip; + local = pico_ipv6_source_find(ip); + if (local) { + get_sock_dev(s); + s->local_addr.ip6 = *local; + } else { + pico_err = PICO_ERR_EHOSTUNREACH; + return -1; + } + + #endif + } else { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + pico_socket_alter_state(s, PICO_SOCKET_STATE_BOUND, 0, 0); + +#ifdef PICO_SUPPORT_UDP + if (PROTO(s) == PICO_PROTO_UDP) { + pico_socket_alter_state(s, PICO_SOCKET_STATE_CONNECTED, 0, 0); + pico_err = PICO_ERR_NOERR; + ret = 0; + } + +#endif + +#ifdef PICO_SUPPORT_TCP + if (PROTO(s) == PICO_PROTO_TCP) { + if (pico_tcp_initconn(s) == 0) { + pico_socket_alter_state(s, PICO_SOCKET_STATE_CONNECTED | PICO_SOCKET_STATE_TCP_SYN_SENT, PICO_SOCKET_STATE_CLOSED, 0); + pico_err = PICO_ERR_NOERR; + ret = 0; + } else { + pico_err = PICO_ERR_EHOSTUNREACH; + } + } + +#endif + + return ret; +} + + +#ifdef PICO_SUPPORT_TCP + +extern int pico_socket_listen(struct pico_socket *s, int backlog) +{ + if (!s || backlog < 1) { + pico_err = PICO_ERR_EINVAL; + return -1; + } else { + /* check if exists in tree */ + /* See task #178 */ + if (pico_check_socket(s) != 0) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + } + + if (PROTO(s) == PICO_PROTO_UDP) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + if ((s->state & PICO_SOCKET_STATE_BOUND) == 0) { + pico_err = PICO_ERR_EISCONN; + return -1; + } + + if (PROTO(s) == PICO_PROTO_TCP) + pico_socket_alter_state(s, PICO_SOCKET_STATE_TCP_SYN_SENT, 0, PICO_SOCKET_STATE_TCP_LISTEN); + + s->max_backlog = (uint16_t)backlog; + + return 0; +} + +struct pico_socket *pico_socket_accept(struct pico_socket *s, void *orig, uint16_t *port) +{ + if (!s || !orig || !port) { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + + pico_err = PICO_ERR_EINVAL; + + if ((s->state & PICO_SOCKET_STATE_BOUND) == 0) { + return NULL; + } + + if (PROTO(s) == PICO_PROTO_UDP) { + return NULL; + } + + if (TCPSTATE(s) == PICO_SOCKET_STATE_TCP_LISTEN) { + struct pico_sockport *sp = pico_get_sockport(PICO_PROTO_TCP, s->local_port); + struct pico_socket *found; + uint32_t socklen = sizeof(struct pico_ip4); + /* If at this point no incoming connection socket is found, + * the accept call is valid, but no connection is established yet. + */ + pico_err = PICO_ERR_EAGAIN; + if (sp) { + struct pico_tree_node *index; + /* RB_FOREACH(found, socket_tree, &sp->socks) { */ + pico_tree_foreach(index, &sp->socks){ + found = index->keyValue; + if ((s == found->parent) && ((found->state & PICO_SOCKET_STATE_TCP) == PICO_SOCKET_STATE_TCP_ESTABLISHED)) { + found->parent = NULL; + pico_err = PICO_ERR_NOERR; + #ifdef PICO_SUPPORT_IPV6 + if (is_sock_ipv6(s)) + socklen = sizeof(struct pico_ip6); + + #endif + memcpy(orig, &found->remote_addr, socklen); + *port = found->remote_port; + s->number_of_pending_conn--; + return found; + } + } + } + } + + return NULL; +} + +#else + +extern int pico_socket_listen(struct pico_socket *s, int backlog) +{ + IGNORE_PARAMETER(s); + IGNORE_PARAMETER(backlog); + pico_err = PICO_ERR_EINVAL; + return -1; +} + +extern struct pico_socket *pico_socket_accept(struct pico_socket *s, void *orig, uint16_t *local_port) +{ + IGNORE_PARAMETER(s); + IGNORE_PARAMETER(orig); + IGNORE_PARAMETER(local_port); + pico_err = PICO_ERR_EINVAL; + return NULL; +} + +#endif + + +int pico_socket_setoption(struct pico_socket *s, int option, void *value) +{ + + if (s == NULL) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + + if (PROTO(s) == PICO_PROTO_TCP) + return pico_setsockopt_tcp(s, option, value); + + if (PROTO(s) == PICO_PROTO_UDP) + return pico_setsockopt_udp(s, option, value); + + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} + + +int pico_socket_getoption(struct pico_socket *s, int option, void *value) +{ + if (s == NULL) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + + if (PROTO(s) == PICO_PROTO_TCP) + return pico_getsockopt_tcp(s, option, value); + + if (PROTO(s) == PICO_PROTO_UDP) + return pico_getsockopt_udp(s, option, value); + + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} + + +int pico_socket_shutdown(struct pico_socket *s, int mode) +{ + if (!s) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* Check if the socket has already been closed */ + if (s->state & PICO_SOCKET_STATE_CLOSED) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + /* unbound sockets can be deleted immediately */ + if (!(s->state & PICO_SOCKET_STATE_BOUND)) + { + socket_garbage_collect((pico_time)10, s); + return 0; + } + +#ifdef PICO_SUPPORT_UDP + if (PROTO(s) == PICO_PROTO_UDP) { + if ((mode & PICO_SHUT_RDWR) == PICO_SHUT_RDWR) + pico_socket_alter_state(s, PICO_SOCKET_STATE_CLOSED, PICO_SOCKET_STATE_CLOSING | PICO_SOCKET_STATE_BOUND | PICO_SOCKET_STATE_CONNECTED, 0); + else if (mode & PICO_SHUT_RD) + pico_socket_alter_state(s, 0, PICO_SOCKET_STATE_BOUND, 0); + } + +#endif +#ifdef PICO_SUPPORT_TCP + if (PROTO(s) == PICO_PROTO_TCP) { + if ((mode & PICO_SHUT_RDWR) == PICO_SHUT_RDWR) + { + pico_socket_alter_state(s, PICO_SOCKET_STATE_SHUT_LOCAL | PICO_SOCKET_STATE_SHUT_REMOTE, 0, 0); + pico_tcp_notify_closing(s); + } + else if (mode & PICO_SHUT_WR) { + pico_socket_alter_state(s, PICO_SOCKET_STATE_SHUT_LOCAL, 0, 0); + pico_tcp_notify_closing(s); + } else if (mode & PICO_SHUT_RD) + pico_socket_alter_state(s, PICO_SOCKET_STATE_SHUT_REMOTE, 0, 0); + + } + +#endif + return 0; +} + +extern int MOCKABLE pico_socket_close(struct pico_socket *s) +{ + if (!s) + return -1; + +#ifdef PICO_SUPPORT_TCP + if (PROTO(s) == PICO_PROTO_TCP) { + if (pico_tcp_check_listen_close(s) == 0) + return 0; + } + +#endif + return pico_socket_shutdown(s, PICO_SHUT_RDWR); +} + +#ifdef PICO_SUPPORT_CRC +static inline int pico_transport_crc_check(struct pico_frame *f) +{ + struct pico_ipv4_hdr *net_hdr = (struct pico_ipv4_hdr *) f->net_hdr; + struct pico_udp_hdr *udp_hdr = NULL; + uint16_t checksum_invalid = 1; + + switch (net_hdr->proto) + { +#ifdef PICO_SUPPORT_TCP + case PICO_PROTO_TCP: + checksum_invalid = short_be(pico_tcp_checksum(f)); + /* dbg("TCP CRC validation == %u\n", checksum_invalid); */ + if (checksum_invalid) { + dbg("TCP CRC: validation failed!\n"); + pico_frame_discard(f); + return 0; + } + + break; +#endif /* PICO_SUPPORT_TCP */ + +#ifdef PICO_SUPPORT_UDP + case PICO_PROTO_UDP: + udp_hdr = (struct pico_udp_hdr *) f->transport_hdr; + if (short_be(udp_hdr->crc)) { +#ifdef PICO_SUPPORT_IPV4 + if (IS_IPV4(f)) + checksum_invalid = short_be(pico_udp_checksum_ipv4(f)); + +#endif +#ifdef PICO_SUPPORT_IPV6 + if (IS_IPV6(f)) + checksum_invalid = short_be(pico_udp_checksum_ipv6(f)); + +#endif + /* dbg("UDP CRC validation == %u\n", checksum_invalid); */ + if (checksum_invalid) { + /* dbg("UDP CRC: validation failed!\n"); */ + pico_frame_discard(f); + return 0; + } + } + + break; +#endif /* PICO_SUPPORT_UDP */ + + default: + /* Do nothing */ + break; + } + return 1; +} +#else +static inline int pico_transport_crc_check(struct pico_frame *f) +{ + IGNORE_PARAMETER(f); + return 1; +} +#endif /* PICO_SUPPORT_CRC */ + +int pico_transport_process_in(struct pico_protocol *self, struct pico_frame *f) +{ + struct pico_trans *hdr = (struct pico_trans *) f->transport_hdr; + int ret = 0; + + if (!hdr) { + pico_err = PICO_ERR_EFAULT; + return -1; + } + + ret = pico_transport_crc_check(f); + if (ret < 1) + return ret; + else + ret = 0; + + if ((hdr) && (pico_socket_deliver(self, f, hdr->dport) == 0)) + return ret; + + if (!IS_BCAST(f)) { + dbg("Socket not found... \n"); + pico_notify_socket_unreachable(f); + ret = -1; + pico_err = PICO_ERR_ENOENT; + } + + pico_frame_discard(f); + return ret; +} + +#define SL_LOOP_MIN 1 + +#ifdef PICO_SUPPORT_TCP +static int check_socket_sanity(struct pico_socket *s) +{ + + /* checking for pending connections */ + if(TCP_STATE(s) == PICO_SOCKET_STATE_TCP_SYN_RECV) { + if((PICO_TIME_MS() - s->timestamp) >= PICO_SOCKET_BOUND_TIMEOUT) + return -1; + } + return 0; +} +#endif + + +static int pico_sockets_loop_udp(int loop_score) +{ + +#ifdef PICO_SUPPORT_UDP + static struct pico_tree_node *index_udp; + struct pico_sockport *start; + struct pico_socket *s; + struct pico_frame *f; + + if (sp_udp == NULL) + { + index_udp = pico_tree_firstNode(UDPTable.root); + sp_udp = index_udp->keyValue; + } + + /* init start node */ + start = sp_udp; + + /* round-robin all transport protocols, break if traversed all protocols */ + while (loop_score > SL_LOOP_MIN && sp_udp != NULL) { + struct pico_tree_node *index; + + pico_tree_foreach(index, &sp_udp->socks){ + s = index->keyValue; + f = pico_dequeue(&s->q_out); + while (f && (loop_score > 0)) { + pico_proto_udp.push(&pico_proto_udp, f); + loop_score -= 1; + if (loop_score > 0) /* only dequeue if there is still loop_score, otherwise f might get lost */ + f = pico_dequeue(&s->q_out); + } + } + + index_udp = pico_tree_next(index_udp); + sp_udp = index_udp->keyValue; + + if (sp_udp == NULL) + { + index_udp = pico_tree_firstNode(UDPTable.root); + sp_udp = index_udp->keyValue; + } + + if (sp_udp == start) + break; + } +#endif + return loop_score; +} + +static int pico_sockets_loop_tcp(int loop_score) +{ +#ifdef PICO_SUPPORT_TCP + struct pico_sockport *start; + struct pico_socket *s; + static struct pico_tree_node *index_tcp; + if (sp_tcp == NULL) + { + index_tcp = pico_tree_firstNode(TCPTable.root); + sp_tcp = index_tcp->keyValue; + } + + /* init start node */ + start = sp_tcp; + + while (loop_score > SL_LOOP_MIN && sp_tcp != NULL) { + struct pico_tree_node *index = NULL, *safe_index = NULL; + pico_tree_foreach_safe(index, &sp_tcp->socks, safe_index){ + s = index->keyValue; + loop_score = pico_tcp_output(s, loop_score); + if ((s->ev_pending) && s->wakeup) { + s->wakeup(s->ev_pending, s); + if(!s->parent) + s->ev_pending = 0; + } + + if (loop_score <= 0) { + loop_score = 0; + break; + } + + if(check_socket_sanity(s) < 0) + { + pico_socket_del(s); + index_tcp = NULL; /* forcing the restart of loop */ + sp_tcp = NULL; + break; + } + } + + /* check if RB_FOREACH ended, if not, break to keep the cur sp_tcp */ + if (!index_tcp || (index && index->keyValue)) + break; + + index_tcp = pico_tree_next(index_tcp); + sp_tcp = index_tcp->keyValue; + + if (sp_tcp == NULL) + { + index_tcp = pico_tree_firstNode(TCPTable.root); + sp_tcp = index_tcp->keyValue; + } + + if (sp_tcp == start) + break; + } +#endif + return loop_score; + + +} + +int pico_sockets_loop(int loop_score) +{ + loop_score = pico_sockets_loop_udp(loop_score); + loop_score = pico_sockets_loop_tcp(loop_score); + return loop_score; +} + +int pico_count_sockets(uint8_t proto) +{ + struct pico_sockport *sp; + struct pico_tree_node *idx_sp, *idx_s; + int count = 0; + + if ((proto == 0) || (proto == PICO_PROTO_TCP)) { + pico_tree_foreach(idx_sp, &TCPTable) { + sp = idx_sp->keyValue; + if (sp) { + pico_tree_foreach(idx_s, &sp->socks) + count++; + } + } + } + + if ((proto == 0) || (proto == PICO_PROTO_UDP)) { + pico_tree_foreach(idx_sp, &UDPTable) { + sp = idx_sp->keyValue; + if (sp) { + pico_tree_foreach(idx_s, &sp->socks) + count++; + } + } + } + + return count; +} + + +struct pico_frame *pico_socket_frame_alloc(struct pico_socket *s, uint16_t len) +{ + struct pico_frame *f = NULL; + +#ifdef PICO_SUPPORT_IPV6 + if (is_sock_ipv6(s)) + f = pico_proto_ipv6.alloc(&pico_proto_ipv6, len); + +#endif + +#ifdef PICO_SUPPORT_IPV4 + if (is_sock_ipv4(s)) + f = pico_proto_ipv4.alloc(&pico_proto_ipv4, len); + +#endif + if (!f) { + pico_err = PICO_ERR_ENOMEM; + return f; + } + + f->payload = f->transport_hdr; + f->payload_len = len; + f->sock = s; + return f; +} + +static void pico_transport_error_set_picoerr(int code) +{ + /* dbg("SOCKET ERROR FROM ICMP NOTIFICATION. (icmp code= %d)\n\n", code); */ + switch(code) { + case PICO_ICMP_UNREACH_NET: + pico_err = PICO_ERR_ENETUNREACH; + break; + + case PICO_ICMP_UNREACH_HOST: + pico_err = PICO_ERR_EHOSTUNREACH; + break; + + case PICO_ICMP_UNREACH_PROTOCOL: + pico_err = PICO_ERR_ENOPROTOOPT; + break; + + case PICO_ICMP_UNREACH_PORT: + pico_err = PICO_ERR_ECONNREFUSED; + break; + + case PICO_ICMP_UNREACH_NET_UNKNOWN: + pico_err = PICO_ERR_ENETUNREACH; + break; + + case PICO_ICMP_UNREACH_HOST_UNKNOWN: + pico_err = PICO_ERR_EHOSTDOWN; + break; + + case PICO_ICMP_UNREACH_ISOLATED: + pico_err = PICO_ERR_ENONET; + break; + + case PICO_ICMP_UNREACH_NET_PROHIB: + case PICO_ICMP_UNREACH_HOST_PROHIB: + pico_err = PICO_ERR_EHOSTUNREACH; + break; + + default: + pico_err = PICO_ERR_EOPNOTSUPP; + } +} + +int pico_transport_error(struct pico_frame *f, uint8_t proto, int code) +{ + int ret = -1; + struct pico_trans *trans = (struct pico_trans*) f->transport_hdr; + struct pico_sockport *port = NULL; + struct pico_socket *s = NULL; + switch (proto) { + + +#ifdef PICO_SUPPORT_UDP + case PICO_PROTO_UDP: + port = pico_get_sockport(proto, trans->sport); + break; +#endif + +#ifdef PICO_SUPPORT_TCP + case PICO_PROTO_TCP: + port = pico_get_sockport(proto, trans->sport); + break; +#endif + + default: + /* Protocol not available */ + ret = -1; + } + if (port) { + struct pico_tree_node *index; + ret = 0; + + pico_tree_foreach(index, &port->socks) { + s = index->keyValue; + if (trans->dport == s->remote_port) { + if (s->wakeup) { + pico_transport_error_set_picoerr(code); + s->state |= PICO_SOCKET_STATE_SHUT_REMOTE; + s->wakeup(PICO_SOCK_EV_ERR, s); + } + + break; + } + } + } + + pico_frame_discard(f); + return ret; +} +#endif +#endif diff --git a/ext/picotcp/stack/pico_socket_multicast.c b/ext/picotcp/stack/pico_socket_multicast.c new file mode 100644 index 0000000..132881c --- /dev/null +++ b/ext/picotcp/stack/pico_socket_multicast.c @@ -0,0 +1,1340 @@ +#include "pico_config.h" +#include "pico_stack.h" +#include "pico_socket.h" +#include "pico_socket_multicast.h" +#include "pico_tree.h" +#include "pico_ipv4.h" +#include "pico_ipv6.h" +#include "pico_udp.h" + +#ifdef PICO_SUPPORT_MCAST +# define so_mcast_dbg(...) do { }while(0) /* ip_mcast_dbg in pico_ipv4.c */ +/* #define so_mcast_dbg dbg */ + +/* socket +* | +* MCASTListen +* | | | +* ------------ | ------------ +* | | | +* MCASTSources MCASTSources MCASTSources +* | | | | | | | | | | | | +* S S S S S S S S S S S S +* +* MCASTListen: RBTree(mcast_link, mcast_group) +* MCASTSources: RBTree(source) +*/ +struct pico_mcast_listen +{ + uint8_t filter_mode; + union pico_address mcast_link; + union pico_address mcast_group; + struct pico_tree MCASTSources; + struct pico_tree MCASTSources_ipv6; + uint16_t proto; +}; +//Parameters +struct pico_mcast +{ + struct pico_socket *s; + struct pico_ip_mreq *mreq; + struct pico_ip_mreq_source *mreq_s; + union pico_address *address; + union pico_link *mcast_link; + struct pico_mcast_listen *listen; +}; +static int mcast_listen_link_cmp(struct pico_mcast_listen *a, struct pico_mcast_listen *b) +{ + + if (a->proto < b->proto) + return -1; + + if (a->proto > b->proto) + return 1; + + return pico_address_compare(&a->mcast_link, &b->mcast_link, a->proto); +} + +static int mcast_listen_grp_cmp(struct pico_mcast_listen *a, struct pico_mcast_listen *b) +{ + if (a->mcast_group.ip4.addr < b->mcast_group.ip4.addr) + return -1; + + if (a->mcast_group.ip4.addr > b->mcast_group.ip4.addr) + return 1; + + return mcast_listen_link_cmp(a, b); +} +#ifdef PICO_SUPPORT_IPV6 +static int mcast_listen_grp_cmp_ipv6(struct pico_mcast_listen *a, struct pico_mcast_listen *b) +{ + int tmp = memcmp(&a->mcast_group.ip6, &b->mcast_group.ip6, sizeof(struct pico_ip6)); + if(!tmp) + return mcast_listen_link_cmp(a, b); + return tmp; +} +#endif + +static int mcast_listen_cmp(void *ka, void *kb) +{ + struct pico_mcast_listen *a = ka, *b = kb; + if (a->proto < b->proto) + return -1; + + if (a->proto > b->proto) + return 1; + + return mcast_listen_grp_cmp(a, b); +} +#ifdef PICO_SUPPORT_IPV6 +static int mcast_listen_cmp_ipv6(void *ka, void *kb) +{ + struct pico_mcast_listen *a = ka, *b = kb; + if (a->proto < b->proto) + return -1; + + if (a->proto > b->proto) + return 1; + + return mcast_listen_grp_cmp_ipv6(a, b); +} +#endif +static int mcast_sources_cmp(void *ka, void *kb) +{ + union pico_address *a = ka, *b = kb; + if (a->ip4.addr < b->ip4.addr) + return -1; + + if (a->ip4.addr > b->ip4.addr) + return 1; + + return 0; +} +#ifdef PICO_SUPPORT_IPV6 +static int mcast_sources_cmp_ipv6(void *ka, void *kb) +{ + union pico_address *a = ka, *b = kb; + return memcmp(&a->ip6, &b->ip6, sizeof(struct pico_ip6)); +} +#endif +static int mcast_socket_cmp(void *ka, void *kb) +{ + struct pico_socket *a = ka, *b = kb; + if (a < b) + return -1; + + if (a > b) + return 1; + + return 0; +} + +/* gather all multicast sockets to hasten filter aggregation */ +PICO_TREE_DECLARE(MCASTSockets, mcast_socket_cmp); + +static int mcast_filter_cmp(void *ka, void *kb) +{ + union pico_address *a = ka, *b = kb; + if (a->ip4.addr < b->ip4.addr) + return -1; + + if (a->ip4.addr > b->ip4.addr) + return 1; + + return 0; +} +/* gather sources to be filtered */ +PICO_TREE_DECLARE(MCASTFilter, mcast_filter_cmp); + +static int mcast_filter_cmp_ipv6(void *ka, void *kb) +{ + union pico_address *a = ka, *b = kb; + return memcmp(&a->ip6, &b->ip6, sizeof(struct pico_ip6)); +} +/* gather sources to be filtered */ +PICO_TREE_DECLARE(MCASTFilter_ipv6, mcast_filter_cmp_ipv6); + +inline static struct pico_tree *mcast_get_src_tree(struct pico_socket *s,struct pico_mcast *mcast) { + if( IS_SOCK_IPV4(s)) { + mcast->listen->MCASTSources.compare = mcast_sources_cmp; + return &mcast->listen->MCASTSources; + } +#ifdef PICO_SUPPORT_IPV6 + else if( IS_SOCK_IPV6(s) ) { + mcast->listen->MCASTSources_ipv6.compare = mcast_sources_cmp_ipv6; + return &mcast->listen->MCASTSources_ipv6; + } +#endif + return NULL; +} +inline static struct pico_tree *mcast_get_listen_tree(struct pico_socket *s) { + if( IS_SOCK_IPV4(s)) + return s->MCASTListen; +#ifdef PICO_SUPPORT_IPV6 + else if( IS_SOCK_IPV6(s) ) + return s->MCASTListen_ipv6; +#endif + return NULL; +} +inline static void mcast_set_listen_tree_p_null(struct pico_socket *s) { + if( IS_SOCK_IPV4(s)) + s->MCASTListen = NULL; +#ifdef PICO_SUPPORT_IPV6 + else if( IS_SOCK_IPV6(s) ) + s->MCASTListen_ipv6 = NULL; +#endif +} +static struct pico_mcast_listen *listen_find(struct pico_socket *s, union pico_address *lnk, union pico_address *grp) +{ + struct pico_mcast_listen ltest = { + 0 + }; + ltest.mcast_link = *lnk; + ltest.mcast_group = *grp; + + if(IS_SOCK_IPV4(s)) + return pico_tree_findKey(s->MCASTListen, <est); +#ifdef PICO_SUPPORT_IPV6 + else if(IS_SOCK_IPV6(s) ) { + ltest.proto = PICO_PROTO_IPV6; + return pico_tree_findKey(s->MCASTListen_ipv6, <est); + } +#endif + return NULL; +} +static union pico_address *pico_mcast_get_link_address(struct pico_socket *s, union pico_link *mcast_link) { + if( IS_SOCK_IPV4(s) ) + return (union pico_address *) &mcast_link->ipv4.address; +#ifdef PICO_SUPPORT_IPV6 + if( IS_SOCK_IPV6(s)) + return (union pico_address *) &mcast_link->ipv6.address; +#endif + return NULL; +} +static uint8_t pico_mcast_filter_excl_excl(struct pico_mcast_listen *listen) +{ + /* filter = intersection of EXCLUDEs */ + /* any record with filter mode EXCLUDE, causes the interface mode to be EXCLUDE */ + /* remove from the interface EXCLUDE filter any source not in the socket EXCLUDE filter */ + struct pico_tree_node *index = NULL, *_tmp = NULL; + union pico_address *source = NULL; + if(!pico_tree_empty(&MCASTFilter)) { + pico_tree_foreach_safe(index, &MCASTFilter, _tmp) + { + source = pico_tree_findKey(&listen->MCASTSources, index->keyValue); + if (!source) + pico_tree_delete(&MCASTFilter, index->keyValue); + } + } +#ifdef PICO_SUPPORT_IPV6 + if(!pico_tree_empty(&MCASTFilter_ipv6)) { + pico_tree_foreach_safe(index, &MCASTFilter_ipv6, _tmp) + { + source = pico_tree_findKey(&listen->MCASTSources_ipv6, index->keyValue); + if (!source) + pico_tree_delete(&MCASTFilter_ipv6, index->keyValue); + } + } +#endif + return PICO_IP_MULTICAST_EXCLUDE; +} + +static uint8_t pico_mcast_filter_excl_incl(struct pico_mcast_listen *listen) +{ + /* filter = EXCLUDE - INCLUDE */ + /* any record with filter mode EXCLUDE, causes the interface mode to be EXCLUDE */ + /* remove from the interface EXCLUDE filter any source in the socket INCLUDE filter */ + struct pico_tree_node *index = NULL, *_tmp = NULL; + union pico_address *source = NULL; + if(!pico_tree_empty(&listen->MCASTSources)) { + pico_tree_foreach_safe(index, &listen->MCASTSources, _tmp) + { + source = pico_tree_findKey(&MCASTFilter, index->keyValue); + if (source) + pico_tree_delete(&MCASTFilter, source); + } + } +#ifdef PICO_SUPPORT_IPV6 + if(!pico_tree_empty(&listen->MCASTSources_ipv6)) { + pico_tree_foreach_safe(index, &listen->MCASTSources_ipv6, _tmp) + { + source = pico_tree_findKey(&MCASTFilter_ipv6, index->keyValue); + if (source) + pico_tree_delete(&MCASTFilter_ipv6, source); + } + } +#endif + return PICO_IP_MULTICAST_EXCLUDE; +} + +static uint8_t pico_mcast_filter_incl_excl(struct pico_mcast_listen *listen) +{ + /* filter = EXCLUDE - INCLUDE */ + /* delete from the interface INCLUDE filter any source NOT in the socket EXCLUDE filter */ + struct pico_tree_node *index = NULL, *_tmp = NULL; + union pico_address *source = NULL; + if(!pico_tree_empty(&listen->MCASTSources)) { + pico_tree_foreach_safe(index, &MCASTFilter, _tmp) + { + source = pico_tree_findKey(&listen->MCASTSources, index->keyValue); + if (!source) + pico_tree_delete(&MCASTFilter, index->keyValue); + } + } +#ifdef PICO_SUPPORT_IPV6 + if(!pico_tree_empty(&listen->MCASTSources_ipv6)) { + pico_tree_foreach_safe(index, &MCASTFilter_ipv6, _tmp) + { + source = pico_tree_findKey(&listen->MCASTSources_ipv6, index->keyValue); + if (!source) + pico_tree_delete(&MCASTFilter_ipv6, index->keyValue); + } + } +#endif + /* any record with filter mode EXCLUDE, causes the interface mode to be EXCLUDE */ + + /* add to the interface EXCLUDE filter any socket source NOT in the former interface INCLUDE filter */ + if(!pico_tree_empty(&listen->MCASTSources)) { + pico_tree_foreach_safe(index, &listen->MCASTSources, _tmp) + { + source = pico_tree_insert(&MCASTFilter, index->keyValue); + if (source) + pico_tree_delete(&MCASTFilter, source); + } + } +#ifdef PICO_SUPPORT_IPV6 + if(!pico_tree_empty(&listen->MCASTSources_ipv6)) { + pico_tree_foreach_safe(index, &listen->MCASTSources_ipv6, _tmp) + { + source = pico_tree_insert(&MCASTFilter_ipv6, index->keyValue); + if (source) + pico_tree_delete(&MCASTFilter_ipv6, source); + } + } +#endif + return PICO_IP_MULTICAST_EXCLUDE; +} + +static uint8_t pico_mcast_filter_incl_incl(struct pico_mcast_listen *listen) +{ + /* filter = summation of INCLUDEs */ + /* mode stays INCLUDE, add all sources to filter */ + struct pico_tree_node *index = NULL, *_tmp = NULL; + union pico_address *source = NULL; + + if( !pico_tree_empty(&listen->MCASTSources)) { + pico_tree_foreach_safe(index, &listen->MCASTSources, _tmp) + { + source = index->keyValue; + pico_tree_insert(&MCASTFilter, source); + } + } +#ifdef PICO_SUPPORT_IPV6 + if( !pico_tree_empty(&listen->MCASTSources_ipv6)) { + pico_tree_foreach_safe(index, &listen->MCASTSources_ipv6, _tmp) + { + source = index->keyValue; + pico_tree_insert(&MCASTFilter_ipv6, source); + } + } +#endif + return PICO_IP_MULTICAST_INCLUDE; +} + +struct pico_mcast_filter_aggregation +{ + uint8_t (*call)(struct pico_mcast_listen *); +}; + +static const struct pico_mcast_filter_aggregation mcast_filter_aggr_call[2][2] = +{ + { + /* EXCL + EXCL */ {.call = pico_mcast_filter_excl_excl}, + /* EXCL + INCL */ {.call = pico_mcast_filter_excl_incl} + }, + + { + /* INCL + EXCL */ {.call = pico_mcast_filter_incl_excl}, + /* INCL + INCL */ {.call = pico_mcast_filter_incl_incl} + } +}; + +static int mcast_aggr_validate(uint8_t fm, struct pico_mcast_listen *l) +{ + if (!l) + return -1; + + if (fm > 1) + return -1; + + if (l->filter_mode > 1) + return -1; + + return 0; +} + + +/* MCASTFilter will be empty if no socket is listening on mcast_group on mcast_link anymore */ +static int pico_socket_aggregate_mcastfilters(union pico_address *mcast_link, union pico_address *mcast_group) +{ + uint8_t filter_mode = PICO_IP_MULTICAST_INCLUDE; + struct pico_mcast_listen *listen = NULL; + struct pico_socket *mcast_sock = NULL; + struct pico_tree_node *index = NULL, *_tmp = NULL; + + /* cleanup old filter */ + if(!pico_tree_empty(&MCASTFilter)) { + pico_tree_foreach_safe(index, &MCASTFilter, _tmp) + { + pico_tree_delete(&MCASTFilter, index->keyValue); + } + } +#ifdef PICO_SUPPORT_IPV6 + if(!pico_tree_empty(&MCASTFilter_ipv6)) { + pico_tree_foreach_safe(index, &MCASTFilter_ipv6, _tmp) + { + pico_tree_delete(&MCASTFilter_ipv6, index->keyValue); + } + } +#endif + /* construct new filter */ + pico_tree_foreach_safe(index, &MCASTSockets, _tmp) + { + mcast_sock = index->keyValue; + listen = listen_find(mcast_sock, mcast_link, mcast_group); + if (listen) { + if (mcast_aggr_validate(filter_mode, listen) < 0) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + if (mcast_filter_aggr_call[filter_mode][listen->filter_mode].call) { + filter_mode = mcast_filter_aggr_call[filter_mode][listen->filter_mode].call(listen); + if (filter_mode > 1) + return -1; + } + } + } + return filter_mode; +} + +static int pico_socket_mcast_filter_include(struct pico_mcast_listen *listen, union pico_address *src) +{ + struct pico_tree_node *index = NULL; +#ifdef PICO_DEBUG_MCAST + char tmp_string[PICO_IPV6_STRING]; +#endif + if(!pico_tree_empty(&listen->MCASTSources)) { + pico_tree_foreach(index, &listen->MCASTSources) + { + if (src->ip4.addr == ((union pico_address *)index->keyValue)->ip4.addr) { + so_mcast_dbg("MCAST: IP %08X in included socket source list\n", src->ip4.addr); + return 0; + } + } + } +#ifdef PICO_SUPPORT_IPV6 + if(!pico_tree_empty(&listen->MCASTSources_ipv6)) { + pico_tree_foreach(index, &listen->MCASTSources_ipv6) + { + if (memcmp(&src->ip6 , &((union pico_address *)index->keyValue)->ip6, sizeof(struct pico_ip6))) { +#ifdef PICO_DEBUG_MCAST + pico_ipv6_to_string(tmp_string, src->ip6.addr); + so_mcast_dbg("MCAST: IP %s in included socket source list\n", tmp_string); +#endif + return 0; + } + } + } +#endif + /* XXX IPV6 ADDRESS */ + so_mcast_dbg("MCAST: IP %08X NOT in included socket source list\n", src->ip4.addr); + return -1; + +} + +static int pico_socket_mcast_filter_exclude(struct pico_mcast_listen *listen, union pico_address *src) +{ + struct pico_tree_node *index = NULL; +#ifdef PICO_DEBUG_MCAST + char tmp_string[PICO_IPV6_STRING]; +#endif + if(!pico_tree_empty(&listen->MCASTSources)) { + pico_tree_foreach(index, &listen->MCASTSources) + { + if (src->ip4.addr == ((union pico_address *)index->keyValue)->ip4.addr) { + so_mcast_dbg("MCAST: IP %08X in excluded socket source list\n", src->ip4.addr); + return -1; + } + } + } +#ifdef PICO_SUPPORT_IPV6 + if(!pico_tree_empty(&listen->MCASTSources_ipv6)) { + pico_tree_foreach(index, &listen->MCASTSources_ipv6) + { + if (memcmp(&src->ip6 , &((union pico_address *)index->keyValue)->ip6, sizeof(struct pico_ip6))) { +#ifdef PICO_DEBUG_MCAST + pico_ipv6_to_string(tmp_string, src->ip6.addr); + so_mcast_dbg("MCAST: IP %s in excluded socket source list\n", tmp_string); +#endif + return 0; + } + } + } +#endif + /* XXX IPV6 ADDRESS */ + so_mcast_dbg("MCAST: IP %08X NOT in excluded socket source list\n", src->ip4.addr); + return 0; +} + +static int pico_socket_mcast_source_filtering(struct pico_mcast_listen *listen, union pico_address *src) +{ + /* perform source filtering */ + if (listen->filter_mode == PICO_IP_MULTICAST_INCLUDE) + return pico_socket_mcast_filter_include(listen, src); + + if (listen->filter_mode == PICO_IP_MULTICAST_EXCLUDE) + return pico_socket_mcast_filter_exclude(listen, src); + + return -1; +} + +static void *pico_socket_mcast_filter_link_get(struct pico_socket *s) +{ + /* check if no multicast enabled on socket */ + if (!s->MCASTListen) + return NULL; + if( IS_SOCK_IPV4(s) ) { + if (!s->local_addr.ip4.addr) + return pico_ipv4_get_default_mcastlink(); + + return pico_ipv4_link_get(&s->local_addr.ip4); + } +#ifdef PICO_SUPPORT_IPV6 + else if( IS_SOCK_IPV6(s)) { + if (pico_ipv6_is_null_address(&s->local_addr.ip6)) + return pico_ipv6_get_default_mcastlink(); + + return pico_ipv6_link_get(&s->local_addr.ip6); + } +#endif + return NULL; +} + +int pico_socket_mcast_filter(struct pico_socket *s, union pico_address *mcast_group, union pico_address *src) +{ + void *mcast_link = NULL; + struct pico_mcast_listen *listen = NULL; + mcast_link = pico_socket_mcast_filter_link_get(s); + if (!mcast_link) + return -1; + if(IS_SOCK_IPV4(s)) + listen = listen_find(s,(union pico_address *) &((struct pico_ipv4_link*)(mcast_link))->address, mcast_group); +#ifdef PICO_SUPPORT_IPV6 + else if(IS_SOCK_IPV6(s)) + listen = listen_find(s, (union pico_address *)&((struct pico_ipv6_link*)(mcast_link))->address, mcast_group); +#endif + if (!listen) + return -1; + + return pico_socket_mcast_source_filtering(listen, src); +} + + +static struct pico_ipv4_link *get_mcast_link(union pico_address *a) { + if (!a->ip4.addr) + return pico_ipv4_get_default_mcastlink(); + return pico_ipv4_link_get(&a->ip4); +} +#ifdef PICO_SUPPORT_IPV6 +static struct pico_ipv6_link *get_mcast_link_ipv6(union pico_address *a) { + + if (pico_ipv6_is_null_address(&a->ip6)) { + return pico_ipv6_get_default_mcastlink(); + } + return pico_ipv6_link_get(&a->ip6); +} +#endif + +static int pico_socket_setoption_pre_validation(struct pico_ip_mreq *mreq) + { + if (!mreq) + return -1; + + if (!mreq->mcast_group_addr.ip4.addr) + return -1; + + return 0; +} +#ifdef PICO_SUPPORT_IPV6 +static int pico_socket_setoption_pre_validation_ipv6(struct pico_ip_mreq *mreq) +{ + if (!mreq) + return -1; + + if (pico_ipv6_is_null_address((struct pico_ip6*)&mreq->mcast_group_addr)) + return -1; + + return 0; +} +#endif + +static struct pico_ipv4_link *pico_socket_setoption_validate_mreq(struct pico_ip_mreq *mreq) +{ + if (pico_socket_setoption_pre_validation(mreq) < 0) + return NULL; + + if (pico_ipv4_is_unicast(mreq->mcast_group_addr.ip4.addr)) + return NULL; + + return get_mcast_link((union pico_address *)&mreq->mcast_link_addr); +} + +#ifdef PICO_SUPPORT_IPV6 +static struct pico_ipv6_link *pico_socket_setoption_validate_mreq_ipv6(struct pico_ip_mreq *mreq) +{ + if (pico_socket_setoption_pre_validation_ipv6(mreq) < 0) + return NULL; + + if (pico_ipv6_is_unicast((struct pico_ip6 *)&mreq->mcast_group_addr)) + return NULL; + return get_mcast_link_ipv6((union pico_address *)&mreq->mcast_link_addr); +} +#endif + +static int pico_socket_setoption_pre_validation_s(struct pico_ip_mreq_source *mreq) +{ + if (!mreq) + return -1; + + if (!mreq->mcast_group_addr.ip4.addr) + return -1; + + return 0; +} +#ifdef PICO_SUPPORT_IPV6 +static int pico_socket_setoption_pre_validation_s_ipv6(struct pico_ip_mreq_source *mreq) +{ + if (!mreq) + return -1; + + if (pico_ipv6_is_null_address((struct pico_ip6 *)&mreq->mcast_group_addr)) + return -1; + + return 0; +} +#endif + +static struct pico_ipv4_link *pico_socket_setoption_validate_s_mreq(struct pico_ip_mreq_source *mreq) +{ + if (pico_socket_setoption_pre_validation_s(mreq) < 0) + return NULL; + + if (pico_ipv4_is_unicast(mreq->mcast_group_addr.ip4.addr)) + return NULL; + + if (!pico_ipv4_is_unicast(mreq->mcast_source_addr.ip4.addr)) + return NULL; + + return get_mcast_link((union pico_address *)&mreq->mcast_link_addr); +} +#ifdef PICO_SUPPORT_IPV6 +static struct pico_ipv6_link *pico_socket_setoption_validate_s_mreq_ipv6(struct pico_ip_mreq_source *mreq) +{ + if (pico_socket_setoption_pre_validation_s_ipv6(mreq) < 0) { + return NULL; + } + if (pico_ipv6_is_unicast((struct pico_ip6 *)&mreq->mcast_group_addr)){ + return NULL; + } + if (!pico_ipv6_is_unicast((struct pico_ip6 *)&mreq->mcast_source_addr)){ + return NULL; + } + + return get_mcast_link_ipv6(&mreq->mcast_link_addr); +} +#endif + +static struct pico_ipv4_link *setop_multicast_link_search(void *value, int bysource) +{ + + struct pico_ip_mreq *mreq = NULL; + struct pico_ipv4_link *mcast_link = NULL; + struct pico_ip_mreq_source *mreq_src = NULL; + if (!bysource) { + mreq = (struct pico_ip_mreq *)value; + mcast_link = pico_socket_setoption_validate_mreq(mreq); + if (!mcast_link) + return NULL; + if (!mreq->mcast_link_addr.ip4.addr) + mreq->mcast_link_addr.ip4.addr = mcast_link->address.addr; + } else { + mreq_src = (struct pico_ip_mreq_source *)value; + if (!mreq_src) { + return NULL; + } + + mcast_link = pico_socket_setoption_validate_s_mreq(mreq_src); + if (!mcast_link) { + return NULL; + } + + if (!mreq_src->mcast_link_addr.ip4.addr) + mreq_src->mcast_link_addr.ip4 = mcast_link->address; + } + + return mcast_link; +} +#ifdef PICO_SUPPORT_IPV6 +static struct pico_ipv6_link *setop_multicast_link_search_ipv6(void *value, int bysource) +{ + struct pico_ip_mreq *mreq = NULL; + struct pico_ipv6_link *mcast_link = NULL; + struct pico_ip_mreq_source *mreq_src = NULL; + if (!bysource) { + mreq = (struct pico_ip_mreq *)value; + mcast_link = pico_socket_setoption_validate_mreq_ipv6(mreq); + if (!mcast_link) { + return NULL; + } + if (pico_ipv6_is_null_address(&mreq->mcast_link_addr.ip6)) + mreq->mcast_link_addr.ip6 = mcast_link->address; + } else { + mreq_src = (struct pico_ip_mreq_source *)value; + if (!mreq_src) { + return NULL; + } + + mcast_link = pico_socket_setoption_validate_s_mreq_ipv6(mreq_src); + if (!mcast_link) { + return NULL; + } + if (pico_ipv6_is_null_address(&mreq_src->mcast_link_addr.ip6)) + mreq_src->mcast_link_addr.ip6 = mcast_link->address; + } + return mcast_link; +} +#endif +static int setop_verify_listen_tree(struct pico_socket *s, int alloc) +{ + if(!alloc) + return -1; + + if( IS_SOCK_IPV4(s) ) { + + s->MCASTListen = PICO_ZALLOC(sizeof(struct pico_tree)); + if (!s->MCASTListen) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + s->MCASTListen->root = &LEAF; + s->MCASTListen->compare = mcast_listen_cmp; + return 0; + } +#ifdef PICO_SUPPORT_IPV6 + else if( IS_SOCK_IPV6(s)){ + s->MCASTListen_ipv6 = PICO_ZALLOC(sizeof(struct pico_tree)); + if (!s->MCASTListen_ipv6) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + s->MCASTListen_ipv6->root = &LEAF; + s->MCASTListen_ipv6->compare = mcast_listen_cmp_ipv6; + return 0; + + } +#endif + return -1; +} + + +static void *setopt_multicast_check(struct pico_socket *s, void *value, int alloc, int bysource) +{ + void *mcast_link = NULL; + struct pico_tree *listen_tree = mcast_get_listen_tree(s); + if (!value) { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + if(IS_SOCK_IPV4(s)) + mcast_link = setop_multicast_link_search(value, bysource); +#ifdef PICO_SUPPORT_IPV6 + else if(IS_SOCK_IPV6(s)) + mcast_link = setop_multicast_link_search_ipv6(value, bysource); +#endif + if (!mcast_link) { + pico_err = PICO_ERR_EINVAL; + return NULL; + } + if (!listen_tree) { /* No RBTree allocated yet */ + if (setop_verify_listen_tree(s, alloc) < 0) { + return NULL; + } + } + return mcast_link; +} + +void pico_multicast_delete(struct pico_socket *s) +{ + int filter_mode; + struct pico_tree_node *index = NULL, *_tmp = NULL, *index2 = NULL, *_tmp2 = NULL; + struct pico_mcast_listen *listen = NULL; + union pico_address *source = NULL; + struct pico_tree *tree, *listen_tree; + struct pico_mcast mcast; + listen_tree = mcast_get_listen_tree(s); + if(listen_tree) { + pico_tree_delete(&MCASTSockets, s); + pico_tree_foreach_safe(index, listen_tree, _tmp) + { + listen = index->keyValue; + mcast.listen = listen; + tree = mcast_get_src_tree(s, &mcast); + if (tree) { + pico_tree_foreach_safe(index2, tree, _tmp2) + { + source = index->keyValue; + pico_tree_delete(tree, source); + PICO_FREE(source); + } + } + filter_mode = pico_socket_aggregate_mcastfilters((union pico_address *)&listen->mcast_link, (union pico_address *)&listen->mcast_group); + if (filter_mode >= 0) { + if(IS_SOCK_IPV4(s)) + pico_ipv4_mcast_leave(&listen->mcast_link.ip4, &listen->mcast_group.ip4, 1, (uint8_t)filter_mode, &MCASTFilter); +#ifdef PICO_SUPPORT_IPV6 + else if(IS_SOCK_IPV6(s)) + pico_ipv6_mcast_leave(&listen->mcast_link.ip6, &listen->mcast_group.ip6, 1, (uint8_t)filter_mode, &MCASTFilter_ipv6); +#endif + } + pico_tree_delete(listen_tree, listen); + PICO_FREE(listen); + } + PICO_FREE(listen_tree); + mcast_set_listen_tree_p_null(s); + } +} + + +int pico_getsockopt_mcast(struct pico_socket *s, int option, void *value) +{ + switch(option) { + case PICO_IP_MULTICAST_IF: + pico_err = PICO_ERR_EOPNOTSUPP; + return -1; + + case PICO_IP_MULTICAST_TTL: + if (s->proto->proto_number == PICO_PROTO_UDP) { + pico_udp_get_mc_ttl(s, (uint8_t *) value); + } else { + *(uint8_t *)value = 0; + pico_err = PICO_ERR_EINVAL; + return -1; + } + + break; + + case PICO_IP_MULTICAST_LOOP: + if (s->proto->proto_number == PICO_PROTO_UDP) { + *(uint8_t *)value = (uint8_t)PICO_SOCKET_GETOPT(s, PICO_SOCKET_OPT_MULTICAST_LOOP); + } else { + *(uint8_t *)value = 0; + pico_err = PICO_ERR_EINVAL; + return -1; + } + + break; + default: + pico_err = PICO_ERR_EINVAL; + return -1; + } + + return 0; +} + +static int mcast_so_loop(struct pico_socket *s, void *value) +{ + uint8_t val = (*(uint8_t *)value); + if (val == 0u) { + PICO_SOCKET_SETOPT_DIS(s, PICO_SOCKET_OPT_MULTICAST_LOOP); + return 0; + } else if (val == 1u) { + PICO_SOCKET_SETOPT_EN(s, PICO_SOCKET_OPT_MULTICAST_LOOP); + return 0; + } + + pico_err = PICO_ERR_EINVAL; + return -1; +} +static int mcast_get_param(struct pico_mcast *mcast, struct pico_socket *s, void *value,int alloc, int by_source) { + if(by_source) + mcast->mreq_s = (struct pico_ip_mreq_source *)value; + else + mcast->mreq = (struct pico_ip_mreq *)value; + mcast->mcast_link = setopt_multicast_check(s, value, alloc, by_source); + if (!mcast->mcast_link) + return -1; + mcast->address = pico_mcast_get_link_address(s, mcast->mcast_link); + if(by_source) + mcast->listen = listen_find(s, &(mcast->mreq_s)->mcast_link_addr, &mcast->mreq_s->mcast_group_addr); + else + mcast->listen = listen_find(s, &(mcast->mreq)->mcast_link_addr, &mcast->mreq->mcast_group_addr); + return 0; +} +static int mcast_so_addm(struct pico_socket *s, void *value) +{ + int filter_mode = 0; + struct pico_mcast mcast; + struct pico_tree *tree, *listen_tree; + if(mcast_get_param(&mcast, s, value, 1,0) < 0) + return -1; + + if (mcast.listen) { + if (mcast.listen->filter_mode != PICO_IP_MULTICAST_EXCLUDE) { + so_mcast_dbg("pico_socket_setoption: ERROR any-source multicast (exclude) on source-specific multicast (include)\n"); + } else { + so_mcast_dbg("pico_socket_setoption: ERROR duplicate PICO_IP_ADD_MEMBERSHIP\n"); + } + pico_err = PICO_ERR_EINVAL; + return -1; + } + mcast.listen = PICO_ZALLOC(sizeof(struct pico_mcast_listen)); + if (!mcast.listen) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + mcast.listen->filter_mode = PICO_IP_MULTICAST_EXCLUDE; + mcast.listen->mcast_link = mcast.mreq->mcast_link_addr; + mcast.listen->mcast_group = mcast.mreq->mcast_group_addr; + mcast.listen->proto = s->net->proto_number; + + tree = mcast_get_src_tree(s, &mcast); + listen_tree = mcast_get_listen_tree(s); +#ifdef PICO_SUPPORT_IPV6 + if( IS_SOCK_IPV6(s)) + mcast.listen->proto = PICO_PROTO_IPV6; +#endif + tree->root = &LEAF; + pico_tree_insert(listen_tree, mcast.listen); + + pico_tree_insert(&MCASTSockets, s); + filter_mode = pico_socket_aggregate_mcastfilters(mcast.address, &mcast.mreq->mcast_group_addr); + if (filter_mode < 0) + return -1; + so_mcast_dbg("PICO_IP_ADD_MEMBERSHIP - success, added %p\n", s); + if(IS_SOCK_IPV4(s)) + return pico_ipv4_mcast_join((struct pico_ip4*)&mcast.mreq->mcast_link_addr,(struct pico_ip4*) &mcast.mreq->mcast_group_addr, 1, (uint8_t)filter_mode, &MCASTFilter); +#ifdef PICO_SUPPORT_IPV6 + else if(IS_SOCK_IPV6(s)) { + return pico_ipv6_mcast_join((struct pico_ip6*)&mcast.mreq->mcast_link_addr,(struct pico_ip6*)&mcast.mreq->mcast_group_addr, 1, (uint8_t)filter_mode, &MCASTFilter_ipv6); + } +#endif + return -1; +} + +static int mcast_so_dropm(struct pico_socket *s, void *value) +{ + int filter_mode = 0; + union pico_address *source = NULL; + struct pico_tree_node *_tmp,*index; + struct pico_mcast mcast; + struct pico_tree *listen_tree,*tree; + if(mcast_get_param(&mcast, s, value, 0,0) < 0) + return -1; + if (!mcast.listen) { + so_mcast_dbg("pico_socket_setoption: ERROR PICO_IP_DROP_MEMBERSHIP before PICO_IP_ADD_MEMBERSHIP/SOURCE_MEMBERSHIP\n"); + pico_err = PICO_ERR_EADDRNOTAVAIL; + return -1; + } + tree = mcast_get_src_tree(s,&mcast); + listen_tree = mcast_get_listen_tree(s); + + pico_tree_foreach_safe(index, tree, _tmp) + { + source = index->keyValue; + pico_tree_delete(tree, source); + } + pico_tree_delete(listen_tree, mcast.listen); + PICO_FREE(mcast.listen); + if (pico_tree_empty(listen_tree)) { + PICO_FREE(listen_tree); + mcast_set_listen_tree_p_null(s); + pico_tree_delete(&MCASTSockets, s); + } + + filter_mode = pico_socket_aggregate_mcastfilters(mcast.address, &mcast.mreq->mcast_group_addr); + if (filter_mode < 0) + return -1; + if(IS_SOCK_IPV4(s)) + return pico_ipv4_mcast_leave((struct pico_ip4*) &mcast.mreq->mcast_link_addr,(struct pico_ip4 *) &mcast.mreq->mcast_group_addr, 1, (uint8_t)filter_mode, &MCASTFilter); +#ifdef PICO_SUPPORT_IPV6 + else if(IS_SOCK_IPV6(s)) { } + return pico_ipv6_mcast_leave((struct pico_ip6*)&mcast.mreq->mcast_link_addr,(struct pico_ip6*)&mcast.mreq->mcast_group_addr, 1, (uint8_t)filter_mode, &MCASTFilter_ipv6); +#endif + return -1; +} + +static int mcast_so_unblock_src(struct pico_socket *s, void *value) +{ + int filter_mode = 0; + union pico_address stest, *source = NULL; + struct pico_mcast mcast; + if(mcast_get_param(&mcast, s, value, 0,1) < 0) + return -1; + + memset(&stest, 0, sizeof(union pico_address)); + if (!mcast.listen) { + so_mcast_dbg("pico_socket_setoption: ERROR PICO_IP_UNBLOCK_SOURCE before PICO_IP_ADD_MEMBERSHIP\n"); + pico_err = PICO_ERR_EINVAL; + return -1; + } + if (mcast.listen->filter_mode != PICO_IP_MULTICAST_EXCLUDE) { + so_mcast_dbg("pico_socket_setoption: ERROR any-source multicast (exclude) on source-specific multicast (include)\n"); + pico_err = PICO_ERR_EINVAL; + return -1; + } + + stest = mcast.mreq_s->mcast_source_addr; + if( IS_SOCK_IPV4(s)) + source = pico_tree_findKey(&mcast.listen->MCASTSources, &stest); +#ifdef PICO_SUPPORT_IPV6 + else if( IS_SOCK_IPV6(s)) + source = pico_tree_findKey(&mcast.listen->MCASTSources_ipv6, &stest); +#endif + if (!source) { + so_mcast_dbg("pico_socket_setoption: ERROR address to unblock not in source list\n"); + pico_err = PICO_ERR_EADDRNOTAVAIL; + return -1; + } + if( IS_SOCK_IPV4(s) ) + pico_tree_delete(&mcast.listen->MCASTSources, source); +#ifdef PICO_SUPPORT_IPV6 + else if( IS_SOCK_IPV6(s) ) + pico_tree_delete(&mcast.listen->MCASTSources_ipv6, source); +#endif + + filter_mode = pico_socket_aggregate_mcastfilters(mcast.address, &mcast.mreq_s->mcast_group_addr); + if (filter_mode < 0) + return -1; + if(IS_SOCK_IPV4(s)) + return pico_ipv4_mcast_leave((struct pico_ip4 *)&mcast.mreq_s->mcast_link_addr,(struct pico_ip4*) &mcast.mreq_s->mcast_group_addr, 0, (uint8_t)filter_mode, &MCASTFilter); +#ifdef PICO_SUPPORT_IPV6 + else if(IS_SOCK_IPV6(s)) { } + return pico_ipv6_mcast_leave((struct pico_ip6*)&mcast.mreq_s->mcast_link_addr,(struct pico_ip6*)&mcast.mreq_s->mcast_group_addr, 0, (uint8_t)filter_mode, &MCASTFilter_ipv6); +#endif + return -1; +} + +static int mcast_so_block_src(struct pico_socket *s, void *value) +{ + int filter_mode = 0; + union pico_address stest, *source = NULL; + struct pico_mcast mcast; + if(mcast_get_param(&mcast, s, value, 0,1) < 0) + return -1; + + memset(&stest, 0, sizeof(union pico_address)); + if (!mcast.listen) { + dbg("pico_socket_setoption: ERROR PICO_IP_BLOCK_SOURCE before PICO_IP_ADD_MEMBERSHIP\n"); + pico_err = PICO_ERR_EINVAL; + return -1; + } + if (mcast.listen->filter_mode != PICO_IP_MULTICAST_EXCLUDE) { + so_mcast_dbg("pico_socket_setoption: ERROR any-source multicast (exclude) on source-specific multicast (include)\n"); + pico_err = PICO_ERR_EINVAL; + return -1; + } + stest = mcast.mreq_s->mcast_source_addr; + if( IS_SOCK_IPV4(s)) + source = pico_tree_findKey(&mcast.listen->MCASTSources, &stest); +#ifdef PICO_SUPPORT_IPV6 + else if( IS_SOCK_IPV6(s)) + source = pico_tree_findKey(&mcast.listen->MCASTSources_ipv6, &stest); +#endif + if (source) { + so_mcast_dbg("pico_socket_setoption: ERROR address to block already in source list\n"); + pico_err = PICO_ERR_ENOMEM; + return -1; + } + source = PICO_ZALLOC(sizeof(union pico_address)); + if (!source) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + *source = mcast.mreq_s->mcast_source_addr; + if( IS_SOCK_IPV4(s) ) + pico_tree_insert(&mcast.listen->MCASTSources, source); +#ifdef PICO_SUPPORT_IPV6 + else if( IS_SOCK_IPV6(s) ) + pico_tree_insert(&mcast.listen->MCASTSources_ipv6, source); +#endif + + filter_mode = pico_socket_aggregate_mcastfilters(mcast.address, &mcast.mreq_s->mcast_group_addr); + if (filter_mode < 0) + return -1; + if(IS_SOCK_IPV4(s)) + return pico_ipv4_mcast_join((struct pico_ip4 *) &mcast.mreq_s->mcast_link_addr, (struct pico_ip4*)&mcast.mreq_s->mcast_group_addr, 0, (uint8_t)filter_mode, &MCASTFilter); +#ifdef PICO_SUPPORT_IPV6 + else if(IS_SOCK_IPV6(s)) { } + return pico_ipv6_mcast_join((struct pico_ip6 *)&mcast.mreq_s->mcast_link_addr,(struct pico_ip6*)&mcast.mreq_s->mcast_group_addr, 0, (uint8_t)filter_mode, &MCASTFilter_ipv6); +#endif + return -1; +} + +static int mcast_so_addsrcm(struct pico_socket *s, void *value) +{ + int filter_mode = 0, reference_count = 0; + union pico_address stest, *source = NULL; + struct pico_mcast mcast; + struct pico_tree *tree,*listen_tree; + if(mcast_get_param(&mcast, s, value, 1,1) < 0) + return -1; + + memset(&stest, 0, sizeof(union pico_address)); + listen_tree = mcast_get_listen_tree(s); + if (mcast.listen) { + tree = mcast_get_src_tree(s,&mcast); + if (mcast.listen->filter_mode != PICO_IP_MULTICAST_INCLUDE) { + so_mcast_dbg("pico_socket_setoption: ERROR source-specific multicast (include) on any-source multicast (exclude)\n"); + pico_err = PICO_ERR_EINVAL; + return -1; + } + stest = mcast.mreq_s->mcast_source_addr; + source = pico_tree_findKey(tree, &stest); + if (source) { + so_mcast_dbg("pico_socket_setoption: ERROR source address to allow already in source list\n"); + pico_err = PICO_ERR_ENOMEM; + return -1; + } + source = PICO_ZALLOC(sizeof(union pico_address)); + if (!source) { + pico_err = PICO_ERR_EADDRNOTAVAIL; + return -1; + } + *source = mcast.mreq_s->mcast_source_addr; + pico_tree_insert(tree, source); + + } else { + mcast.listen = PICO_ZALLOC(sizeof(struct pico_mcast_listen)); + if (!mcast.listen) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + tree = mcast_get_src_tree(s,&mcast); + mcast.listen->filter_mode = PICO_IP_MULTICAST_INCLUDE; + mcast.listen->mcast_link = mcast.mreq_s->mcast_link_addr; + mcast.listen->mcast_group = mcast.mreq_s->mcast_group_addr; + tree->root = &LEAF; + source = PICO_ZALLOC(sizeof(union pico_address)); + if (!source) { + PICO_FREE(mcast.listen); + pico_err = PICO_ERR_ENOMEM; + return -1; + } +#ifdef PICO_SUPPORT_IPV6 + if( IS_SOCK_IPV6(s)) + mcast.listen->proto = PICO_PROTO_IPV6; +#endif + *source = mcast.mreq_s->mcast_source_addr; + pico_tree_insert(tree, source); + pico_tree_insert(listen_tree, mcast.listen); + reference_count = 1; + } + pico_tree_insert(&MCASTSockets, s); + filter_mode = pico_socket_aggregate_mcastfilters(mcast.address, &mcast.mreq_s->mcast_group_addr); + if (filter_mode < 0) + return -1; + if(IS_SOCK_IPV4(s)) + return pico_ipv4_mcast_join((struct pico_ip4 *)&mcast.mreq_s->mcast_link_addr, (struct pico_ip4*)&mcast.mreq_s->mcast_group_addr, (uint8_t)reference_count, (uint8_t)filter_mode, &MCASTFilter); +#ifdef PICO_SUPPORT_IPV6 + else if(IS_SOCK_IPV6(s)) { } + return pico_ipv6_mcast_join((struct pico_ip6 *) &mcast.mreq_s->mcast_link_addr,(struct pico_ip6*)&mcast.mreq_s->mcast_group_addr, (uint8_t)reference_count, (uint8_t)filter_mode, &MCASTFilter_ipv6); +#endif + return -1; +} + +static int mcast_so_dropsrcm(struct pico_socket *s, void *value) +{ + int filter_mode = 0, reference_count = 0; + union pico_address stest, *source = NULL; + struct pico_mcast mcast; + struct pico_tree *tree,*listen_tree; + if(mcast_get_param(&mcast, s, value, 0,1) < 0) + return -1; + + memset(&stest, 0, sizeof(union pico_address)); + listen_tree = mcast_get_listen_tree(s); + if (!mcast.listen) { + so_mcast_dbg("pico_socket_setoption: ERROR PICO_IP_DROP_SOURCE_MEMBERSHIP before PICO_IP_ADD_SOURCE_MEMBERSHIP\n"); + pico_err = PICO_ERR_EADDRNOTAVAIL; + return -1; + } + if (mcast.listen->filter_mode != PICO_IP_MULTICAST_INCLUDE) { + so_mcast_dbg("pico_socket_setoption: ERROR source-specific multicast (include) on any-source multicast (exclude)\n"); + pico_err = PICO_ERR_EINVAL; + return -1; + } + tree = mcast_get_src_tree(s, &mcast); + stest = mcast.mreq_s->mcast_source_addr; + source = pico_tree_findKey(tree, &stest); + if (!source) { + so_mcast_dbg("pico_socket_setoption: ERROR address to drop not in source list\n"); + pico_err = PICO_ERR_EADDRNOTAVAIL; + return -1; + } + pico_tree_delete(tree, source); + if (pico_tree_empty(tree)) { /* 1 if empty, 0 otherwise */ + reference_count = 1; + pico_tree_delete(listen_tree, mcast.listen); + PICO_FREE(mcast.listen); + if (pico_tree_empty(listen_tree)) { + PICO_FREE(listen_tree); + mcast_set_listen_tree_p_null(s); + pico_tree_delete(&MCASTSockets, s); + } + } + + filter_mode = pico_socket_aggregate_mcastfilters(mcast.address, &mcast.mreq_s->mcast_group_addr); + if (filter_mode < 0) + return -1; + if(IS_SOCK_IPV4(s)) + return pico_ipv4_mcast_leave((struct pico_ip4 *) &mcast.mreq_s->mcast_link_addr, (struct pico_ip4*)&mcast.mreq_s->mcast_group_addr, (uint8_t)reference_count, (uint8_t)filter_mode, &MCASTFilter); +#ifdef PICO_SUPPORT_IPV6 + else if(IS_SOCK_IPV6(s)) { } + return pico_ipv6_mcast_leave((struct pico_ip6 *)&mcast.mreq_s->mcast_link_addr,(struct pico_ip6*)&mcast.mreq_s->mcast_group_addr, (uint8_t)reference_count, (uint8_t)filter_mode, &MCASTFilter_ipv6); +#endif + return -1; +} + + +struct pico_setsockopt_mcast_call +{ + int option; + int (*call)(struct pico_socket *, void *); +}; + +static const struct pico_setsockopt_mcast_call mcast_so_calls[1 + PICO_IP_DROP_SOURCE_MEMBERSHIP - PICO_IP_MULTICAST_IF] = +{ + { PICO_IP_MULTICAST_IF, NULL }, + { PICO_IP_MULTICAST_TTL, pico_udp_set_mc_ttl }, + { PICO_IP_MULTICAST_LOOP, mcast_so_loop }, + { PICO_IP_ADD_MEMBERSHIP, mcast_so_addm }, + { PICO_IP_DROP_MEMBERSHIP, mcast_so_dropm }, + { PICO_IP_UNBLOCK_SOURCE, mcast_so_unblock_src }, + { PICO_IP_BLOCK_SOURCE, mcast_so_block_src }, + { PICO_IP_ADD_SOURCE_MEMBERSHIP, mcast_so_addsrcm }, + { PICO_IP_DROP_SOURCE_MEMBERSHIP, mcast_so_dropsrcm } +}; + + +static int mcast_so_check_socket(struct pico_socket *s) +{ + pico_err = PICO_ERR_EINVAL; + if (!s) + return -1; + + if (!s->proto) + return -1; + + if (s->proto->proto_number != PICO_PROTO_UDP) + return -1; + + pico_err = PICO_ERR_NOERR; + return 0; +} + +int pico_setsockopt_mcast(struct pico_socket *s, int option, void *value) +{ + int arrayn = option - PICO_IP_MULTICAST_IF; + if (option < PICO_IP_MULTICAST_IF || option > PICO_IP_DROP_SOURCE_MEMBERSHIP) { + pico_err = PICO_ERR_EOPNOTSUPP; + return -1; + } + + if (mcast_so_check_socket(s) < 0) + return -1; + + if (!mcast_so_calls[arrayn].call) { + pico_err = PICO_ERR_EOPNOTSUPP; + return -1; + } + + return (mcast_so_calls[arrayn].call(s, value)); +} + +int pico_udp_set_mc_ttl(struct pico_socket *s, void *_ttl) +{ + struct pico_socket_udp *u; + uint8_t ttl = *(uint8_t *)_ttl; + if(!s) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + u = (struct pico_socket_udp *) s; + u->mc_ttl = ttl; + return 0; +} + +int pico_udp_get_mc_ttl(struct pico_socket *s, uint8_t *ttl) +{ + struct pico_socket_udp *u; + if(!s) + return -1; + + u = (struct pico_socket_udp *) s; + *ttl = u->mc_ttl; + return 0; +} +#else +int pico_udp_set_mc_ttl(struct pico_socket *s, void *_ttl) +{ + IGNORE_PARAMETER(s); + IGNORE_PARAMETER(_ttl); + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} + +int pico_udp_get_mc_ttl(struct pico_socket *s, uint8_t *ttl) +{ + IGNORE_PARAMETER(s); + IGNORE_PARAMETER(ttl); + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} + +int pico_socket_mcast_filter(struct pico_socket *s, union pico_address *mcast_group, union pico_address *src) +{ + IGNORE_PARAMETER(s); + IGNORE_PARAMETER(mcast_group); + IGNORE_PARAMETER(src); + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} + +void pico_multicast_delete(struct pico_socket *s) +{ + (void)s; +} + +int pico_getsockopt_mcast(struct pico_socket *s, int option, void *value) +{ + (void)s; + (void)option; + (void)value; + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; +} + +int pico_setsockopt_mcast(struct pico_socket *s, int option, void *value) +{ + (void)s; + (void)option; + (void)value; + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; + +} +#endif /* PICO_SUPPORT_MCAST */ + diff --git a/ext/picotcp/stack/pico_stack.c b/ext/picotcp/stack/pico_stack.c new file mode 100644 index 0000000..f1871e7 --- /dev/null +++ b/ext/picotcp/stack/pico_stack.c @@ -0,0 +1,1165 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + . + + Authors: Daniele Lacamera + *********************************************************************/ + + +#include "pico_config.h" +#include "pico_frame.h" +#include "pico_device.h" +#include "pico_protocol.h" +#include "pico_stack.h" +#include "pico_addressing.h" +#include "pico_dns_client.h" + +#include "pico_olsr.h" +#include "pico_aodv.h" +#include "pico_eth.h" +#include "pico_arp.h" +#include "pico_ipv4.h" +#include "pico_ipv6.h" +#include "pico_icmp4.h" +#include "pico_icmp6.h" +#include "pico_igmp.h" +#include "pico_udp.h" +#include "pico_tcp.h" +#include "pico_socket.h" +#include "heap.h" + +#define IS_LIMITED_BCAST(f) (((struct pico_ipv4_hdr *) f->net_hdr)->dst.addr == PICO_IP4_BCAST) + +const uint8_t PICO_ETHADDR_ALL[6] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +# define PICO_SIZE_MCAST 3 +const uint8_t PICO_ETHADDR_MCAST[6] = { + 0x01, 0x00, 0x5e, 0x00, 0x00, 0x00 +}; + +#ifdef PICO_SUPPORT_IPV6 +# define PICO_SIZE_MCAST6 2 +const uint8_t PICO_ETHADDR_MCAST6[6] = { + 0x33, 0x33, 0x00, 0x00, 0x00, 0x00 +}; +#endif + + +volatile pico_time pico_tick; +volatile pico_err_t pico_err; + +static uint32_t _rand_seed; + +void WEAK pico_rand_feed(uint32_t feed) +{ + if (!feed) + return; + + _rand_seed *= 1664525; + _rand_seed += 1013904223; + _rand_seed ^= ~(feed); +} + +uint32_t WEAK pico_rand(void) +{ + pico_rand_feed((uint32_t)pico_tick); + return _rand_seed; +} + +void pico_to_lowercase(char *str) +{ + int i = 0; + if (!str) + return; + + while(str[i]) { + if ((str[i] <= 'Z') && (str[i] >= 'A')) + str[i] = (char) (str[i] - (char)('A' - 'a')); + + i++; + } +} + +/* NOTIFICATIONS: distributed notifications for stack internal errors. + */ + +int pico_notify_socket_unreachable(struct pico_frame *f) +{ + if (0) {} + +#ifdef PICO_SUPPORT_ICMP4 + else if (IS_IPV4(f)) { + pico_icmp4_port_unreachable(f); + } +#endif +#ifdef PICO_SUPPORT_ICMP6 + else if (IS_IPV6(f)) { + pico_icmp6_port_unreachable(f); + } +#endif + + return 0; +} + +int pico_notify_proto_unreachable(struct pico_frame *f) +{ + if (0) {} + +#ifdef PICO_SUPPORT_ICMP4 + else if (IS_IPV4(f)) { + pico_icmp4_proto_unreachable(f); + } +#endif +#ifdef PICO_SUPPORT_ICMP6 + else if (IS_IPV6(f)) { + pico_icmp6_proto_unreachable(f); + } +#endif + return 0; +} + +int pico_notify_dest_unreachable(struct pico_frame *f) +{ + if (0) {} + +#ifdef PICO_SUPPORT_ICMP4 + else if (IS_IPV4(f)) { + pico_icmp4_dest_unreachable(f); + } +#endif +#ifdef PICO_SUPPORT_ICMP6 + else if (IS_IPV6(f)) { + pico_icmp6_dest_unreachable(f); + } +#endif + return 0; +} + +int pico_notify_ttl_expired(struct pico_frame *f) +{ + if (0) {} + +#ifdef PICO_SUPPORT_ICMP4 + else if (IS_IPV4(f)) { + pico_icmp4_ttl_expired(f); + } +#endif +#ifdef PICO_SUPPORT_ICMP6 + else if (IS_IPV6(f)) { + pico_icmp6_ttl_expired(f); + } +#endif + return 0; +} + +int pico_notify_frag_expired(struct pico_frame *f) +{ + if (0) {} + +#ifdef PICO_SUPPORT_ICMP4 + else if (IS_IPV4(f)) { + pico_icmp4_frag_expired(f); + } +#endif +#ifdef PICO_SUPPORT_ICMP6 + else if (IS_IPV6(f)) { + pico_icmp6_frag_expired(f); + } +#endif + return 0; +} + +int pico_notify_pkt_too_big(struct pico_frame *f) +{ + if (0) {} + +#ifdef PICO_SUPPORT_ICMP4 + else if (IS_IPV4(f)) { + pico_icmp4_mtu_exceeded(f); + } +#endif +#ifdef PICO_SUPPORT_ICMP6 + else if (IS_IPV6(f)) { + pico_icmp6_pkt_too_big(f); + } +#endif + return 0; +} + + +/* Transport layer */ +MOCKABLE int32_t pico_transport_receive(struct pico_frame *f, uint8_t proto) +{ + int32_t ret = -1; + switch (proto) { + +#ifdef PICO_SUPPORT_ICMP4 + case PICO_PROTO_ICMP4: + ret = pico_enqueue(pico_proto_icmp4.q_in, f); + break; +#endif + +#ifdef PICO_SUPPORT_ICMP6 + case PICO_PROTO_ICMP6: + ret = pico_enqueue(pico_proto_icmp6.q_in, f); + break; +#endif + + +#if defined(PICO_SUPPORT_IGMP) && defined(PICO_SUPPORT_MCAST) + case PICO_PROTO_IGMP: + ret = pico_enqueue(pico_proto_igmp.q_in, f); + break; +#endif + +#ifdef PICO_SUPPORT_UDP + case PICO_PROTO_UDP: + ret = pico_enqueue(pico_proto_udp.q_in, f); + break; +#endif + +#ifdef PICO_SUPPORT_TCP + case PICO_PROTO_TCP: + ret = pico_enqueue(pico_proto_tcp.q_in, f); + break; +#endif + + default: + /* Protocol not available */ + dbg("pkt: no such protocol (%d)\n", proto); + pico_notify_proto_unreachable(f); + pico_frame_discard(f); + ret = -1; + } + return ret; +} + +int32_t pico_network_receive(struct pico_frame *f) +{ + if (0) {} + +#ifdef PICO_SUPPORT_IPV4 + else if (IS_IPV4(f)) { + pico_enqueue(pico_proto_ipv4.q_in, f); + } +#endif +#ifdef PICO_SUPPORT_IPV6 + else if (IS_IPV6(f)) { + pico_enqueue(pico_proto_ipv6.q_in, f); + } +#endif + else { + dbg("Network not found.\n"); + pico_frame_discard(f); + return -1; + } + return (int32_t)f->buffer_len; +} + +/* Network layer: interface towards socket for frame sending */ +int32_t pico_network_send(struct pico_frame *f) +{ + if (!f || !f->sock || !f->sock->net) { + pico_frame_discard(f); + return -1; + } + + return f->sock->net->push(f->sock->net, f); +} + +int pico_source_is_local(struct pico_frame *f) +{ + if (0) { } + +#ifdef PICO_SUPPORT_IPV4 + else if (IS_IPV4(f)) { + struct pico_ipv4_hdr *hdr = (struct pico_ipv4_hdr *)f->net_hdr; + if (hdr->src.addr == PICO_IPV4_INADDR_ANY) + return 1; + + if (pico_ipv4_link_find(&hdr->src)) + return 1; + } +#endif +#ifdef PICO_SUPPORT_IPV6 + else if (IS_IPV6(f)) { + struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)f->net_hdr; + if (pico_ipv6_is_unspecified(hdr->src.addr) || pico_ipv6_link_find(&hdr->src)) + return 1; + } +#endif + return 0; +} + +#ifdef PICO_SUPPORT_ETH +/* DATALINK LEVEL: interface from network to the device + * and vice versa. + */ + +/* The pico_ethernet_receive() function is used by + * those devices supporting ETH in order to push packets up + * into the stack. + */ + +static int destination_is_bcast(struct pico_frame *f) +{ + if (!f) + return 0; + + if (IS_IPV6(f)) + return 0; + +#ifdef PICO_SUPPORT_IPV4 + else { + struct pico_ipv4_hdr *hdr = (struct pico_ipv4_hdr *) f->net_hdr; + return pico_ipv4_is_broadcast(hdr->dst.addr); + } +#else + return 0; +#endif +} + +static int destination_is_mcast(struct pico_frame *f) +{ + int ret = 0; + if (!f) + return 0; + +#ifdef PICO_SUPPORT_IPV6 + if (IS_IPV6(f)) { + struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *) f->net_hdr; + ret = pico_ipv6_is_multicast(hdr->dst.addr); + } + +#endif +#ifdef PICO_SUPPORT_IPV4 + else { + struct pico_ipv4_hdr *hdr = (struct pico_ipv4_hdr *) f->net_hdr; + ret = pico_ipv4_is_multicast(hdr->dst.addr); + } +#endif + + return ret; +} + +#ifdef PICO_SUPPORT_IPV4 +static int32_t pico_ipv4_ethernet_receive(struct pico_frame *f) +{ + if (IS_IPV4(f)) { + pico_enqueue(pico_proto_ipv4.q_in, f); + } else { + (void)pico_icmp4_param_problem(f, 0); + pico_frame_discard(f); + return -1; + } + + return (int32_t)f->buffer_len; +} +#endif + +#ifdef PICO_SUPPORT_IPV6 +static int32_t pico_ipv6_ethernet_receive(struct pico_frame *f) +{ + if (IS_IPV6(f)) { + pico_enqueue(pico_proto_ipv6.q_in, f); + } else { + /* Wrong version for link layer type */ + pico_frame_discard(f); + return -1; + } + + return (int32_t)f->buffer_len; +} +#endif + +static int32_t pico_ll_receive(struct pico_frame *f) +{ + struct pico_eth_hdr *hdr = (struct pico_eth_hdr *) f->datalink_hdr; + f->net_hdr = f->datalink_hdr + sizeof(struct pico_eth_hdr); + +#if (defined PICO_SUPPORT_IPV4) && (defined PICO_SUPPORT_ETH) + if (hdr->proto == PICO_IDETH_ARP) + return pico_arp_receive(f); + +#endif + +#if defined (PICO_SUPPORT_IPV4) + if (hdr->proto == PICO_IDETH_IPV4) + return pico_ipv4_ethernet_receive(f); + +#endif + +#if defined (PICO_SUPPORT_IPV6) + if (hdr->proto == PICO_IDETH_IPV6) + return pico_ipv6_ethernet_receive(f); + +#endif + + pico_frame_discard(f); + return -1; +} + +static void pico_ll_check_bcast(struct pico_frame *f) +{ + struct pico_eth_hdr *hdr = (struct pico_eth_hdr *) f->datalink_hdr; + /* Indicate a link layer broadcast packet */ + if (memcmp(hdr->daddr, PICO_ETHADDR_ALL, PICO_SIZE_ETH) == 0) + f->flags |= PICO_FRAME_FLAG_BCAST; +} + +int32_t pico_ethernet_receive(struct pico_frame *f) +{ + struct pico_eth_hdr *hdr; + if (!f || !f->dev || !f->datalink_hdr) + { + pico_frame_discard(f); + return -1; + } + + hdr = (struct pico_eth_hdr *) f->datalink_hdr; + if ((memcmp(hdr->daddr, f->dev->eth->mac.addr, PICO_SIZE_ETH) != 0) && + (memcmp(hdr->daddr, PICO_ETHADDR_MCAST, PICO_SIZE_MCAST) != 0) && +#ifdef PICO_SUPPORT_IPV6 + (memcmp(hdr->daddr, PICO_ETHADDR_MCAST6, PICO_SIZE_MCAST6) != 0) && +#endif + (memcmp(hdr->daddr, PICO_ETHADDR_ALL, PICO_SIZE_ETH) != 0)) + { + pico_frame_discard(f); + return -1; + } + + pico_ll_check_bcast(f); + return pico_ll_receive(f); +} + +static struct pico_eth *pico_ethernet_mcast_translate(struct pico_frame *f, uint8_t *pico_mcast_mac) +{ + struct pico_ipv4_hdr *hdr = (struct pico_ipv4_hdr *) f->net_hdr; + + /* place 23 lower bits of IP in lower 23 bits of MAC */ + pico_mcast_mac[5] = (long_be(hdr->dst.addr) & 0x000000FFu); + pico_mcast_mac[4] = (uint8_t)((long_be(hdr->dst.addr) & 0x0000FF00u) >> 8u); + pico_mcast_mac[3] = (uint8_t)((long_be(hdr->dst.addr) & 0x007F0000u) >> 16u); + + return (struct pico_eth *)pico_mcast_mac; +} + + +#ifdef PICO_SUPPORT_IPV6 +static struct pico_eth *pico_ethernet_mcast6_translate(struct pico_frame *f, uint8_t *pico_mcast6_mac) +{ + struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)f->net_hdr; + + /* first 2 octets are 0x33, last four are the last four of dst */ + pico_mcast6_mac[5] = hdr->dst.addr[PICO_SIZE_IP6 - 1]; + pico_mcast6_mac[4] = hdr->dst.addr[PICO_SIZE_IP6 - 2]; + pico_mcast6_mac[3] = hdr->dst.addr[PICO_SIZE_IP6 - 3]; + pico_mcast6_mac[2] = hdr->dst.addr[PICO_SIZE_IP6 - 4]; + + return (struct pico_eth *)pico_mcast6_mac; +} +#endif + +static int pico_ethernet_ipv6_dst(struct pico_frame *f, struct pico_eth *const dstmac) +{ + int retval = -1; + if (!dstmac) + return -1; + + #ifdef PICO_SUPPORT_IPV6 + if (destination_is_mcast(f)) { + uint8_t pico_mcast6_mac[6] = { + 0x33, 0x33, 0x00, 0x00, 0x00, 0x00 + }; + pico_ethernet_mcast6_translate(f, pico_mcast6_mac); + memcpy(dstmac, pico_mcast6_mac, PICO_SIZE_ETH); + retval = 0; + } else { + struct pico_eth *neighbor = pico_ipv6_get_neighbor(f); + if (neighbor) + { + memcpy(dstmac, neighbor, PICO_SIZE_ETH); + retval = 0; + } + } + + #else + (void)f; + pico_err = PICO_ERR_EPROTONOSUPPORT; + #endif + return retval; +} + + +/* Ethernet send, first attempt: try our own address. + * Returns 0 if the packet is not for us. + * Returns 1 if the packet is cloned to our own receive queue, so the caller can discard the original frame. + * */ +static int32_t pico_ethsend_local(struct pico_frame *f, struct pico_eth_hdr *hdr) +{ + if (!hdr) + return 0; + + /* Check own mac */ + if(!memcmp(hdr->daddr, hdr->saddr, PICO_SIZE_ETH)) { + struct pico_frame *clone = pico_frame_copy(f); + dbg("sending out packet destined for our own mac\n"); + (void)pico_ethernet_receive(clone); + return 1; + } + + return 0; +} + +/* Ethernet send, second attempt: try bcast. + * Returns 0 if the packet is not bcast, so it will be handled somewhere else. + * Returns 1 if the packet is handled by the pico_device_broadcast() function, so it can be discarded. + * */ +static int32_t pico_ethsend_bcast(struct pico_frame *f) +{ + if (IS_LIMITED_BCAST(f)) { + (void)pico_device_broadcast(f); /* We can discard broadcast even if it's not sent. */ + return 1; + } + + return 0; +} + +/* Ethernet send, third attempt: try unicast. + * If the device driver is busy, we return 0, so the stack won't discard the frame. + * In case of success, we can safely return 1. + */ +static int32_t pico_ethsend_dispatch(struct pico_frame *f) +{ + int ret = f->dev->send(f->dev, f->start, (int) f->len); + if (ret <= 0) + return 0; /* Failure to deliver! */ + else { + return 1; /* Frame is in flight by now. */ + } +} + + + + +/* This function looks for the destination mac address + * in order to send the frame being processed. + */ + +int32_t MOCKABLE pico_ethernet_send(struct pico_frame *f) +{ + struct pico_eth dstmac; + uint8_t dstmac_valid = 0; + uint16_t proto = PICO_IDETH_IPV4; + +#ifdef PICO_SUPPORT_IPV6 + /* Step 1: If the frame has an IPv6 packet, + * destination address is taken from the ND tables + */ + if (IS_IPV6(f)) { + if (pico_ethernet_ipv6_dst(f, &dstmac) < 0) + { + pico_ipv6_nd_postpone(f); + return 0; /* I don't care if frame was actually postponed. If there is no room in the ND table, discard safely. */ + } + + dstmac_valid = 1; + proto = PICO_IDETH_IPV6; + } + else +#endif + + /* In case of broadcast (IPV4 only), dst mac is FF:FF:... */ + if (IS_BCAST(f) || destination_is_bcast(f)) + { + memcpy(&dstmac, PICO_ETHADDR_ALL, PICO_SIZE_ETH); + dstmac_valid = 1; + } + + /* In case of multicast, dst mac is translated from the group address */ + else if (destination_is_mcast(f)) { + uint8_t pico_mcast_mac[6] = { + 0x01, 0x00, 0x5e, 0x00, 0x00, 0x00 + }; + pico_ethernet_mcast_translate(f, pico_mcast_mac); + memcpy(&dstmac, pico_mcast_mac, PICO_SIZE_ETH); + dstmac_valid = 1; + } + +#if (defined PICO_SUPPORT_IPV4) + else { + struct pico_eth *arp_get; + arp_get = pico_arp_get(f); + if (arp_get) { + memcpy(&dstmac, arp_get, PICO_SIZE_ETH); + dstmac_valid = 1; + } else { + /* At this point, ARP will discard the frame in any case. + * It is safe to return without discarding. + */ + pico_arp_postpone(f); + return 0; + /* Same case as for IPv6 ... */ + } + + } +#endif + + /* This sets destination and source address, then pushes the packet to the device. */ + if (dstmac_valid) { + struct pico_eth_hdr *hdr; + hdr = (struct pico_eth_hdr *) f->datalink_hdr; + if ((f->start > f->buffer) && ((f->start - f->buffer) >= PICO_SIZE_ETHHDR)) + { + f->start -= PICO_SIZE_ETHHDR; + f->len += PICO_SIZE_ETHHDR; + f->datalink_hdr = f->start; + hdr = (struct pico_eth_hdr *) f->datalink_hdr; + memcpy(hdr->saddr, f->dev->eth->mac.addr, PICO_SIZE_ETH); + memcpy(hdr->daddr, &dstmac, PICO_SIZE_ETH); + hdr->proto = proto; + } + + if (pico_ethsend_local(f, hdr) || pico_ethsend_bcast(f) || pico_ethsend_dispatch(f)) { + /* one of the above functions has delivered the frame accordingly. (returned != 0) + * It is safe to directly return success. + * */ + return 0; + } + } + + /* Failure: do not dequeue the frame, keep it for later. */ + return -1; +} + +#endif /* PICO_SUPPORT_ETH */ + + +void pico_store_network_origin(void *src, struct pico_frame *f) +{ + #ifdef PICO_SUPPORT_IPV4 + struct pico_ip4 *ip4; + #endif + + #ifdef PICO_SUPPORT_IPV6 + struct pico_ip6 *ip6; + #endif + + #ifdef PICO_SUPPORT_IPV4 + if (IS_IPV4(f)) { + struct pico_ipv4_hdr *hdr; + hdr = (struct pico_ipv4_hdr *) f->net_hdr; + ip4 = (struct pico_ip4 *) src; + ip4->addr = hdr->src.addr; + } + + #endif + #ifdef PICO_SUPPORT_IPV6 + if (IS_IPV6(f)) { + struct pico_ipv6_hdr *hdr; + hdr = (struct pico_ipv6_hdr *) f->net_hdr; + ip6 = (struct pico_ip6 *) src; + memcpy(ip6->addr, hdr->src.addr, PICO_SIZE_IP6); + } + + #endif +} + +int pico_address_compare(union pico_address *a, union pico_address *b, uint16_t proto) +{ + #ifdef PICO_SUPPORT_IPV6 + if (proto == PICO_PROTO_IPV6) { + return pico_ipv6_compare(&a->ip6, &b->ip6); + } + + #endif + #ifdef PICO_SUPPORT_IPV4 + if (proto == PICO_PROTO_IPV4) { + return pico_ipv4_compare(&a->ip4, &b->ip4); + } + + #endif + return 0; + +} + +int pico_frame_dst_is_unicast(struct pico_frame *f) +{ + if (0) { + return 0; + } + +#ifdef PICO_SUPPORT_IPV4 + if (IS_IPV4(f)) { + struct pico_ipv4_hdr *hdr = (struct pico_ipv4_hdr *)f->net_hdr; + if (pico_ipv4_is_multicast(hdr->dst.addr) || pico_ipv4_is_broadcast(hdr->dst.addr)) + return 0; + + return 1; + } + +#endif + +#ifdef PICO_SUPPORT_IPV6 + if (IS_IPV6(f)) { + struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)f->net_hdr; + if (pico_ipv6_is_multicast(hdr->dst.addr) || pico_ipv6_is_unspecified(hdr->dst.addr)) + return 0; + + return 1; + } + +#endif + else return 0; +} + + +/* LOWEST LEVEL: interface towards devices. */ +/* Device driver will call this function which returns immediately. + * Incoming packet will be processed later on in the dev loop. + */ +extern int32_t pico_stack_recv(struct pico_device *dev, uint8_t *buffer, uint32_t len) +{ + struct pico_frame *f; + int32_t ret; + if (len == 0) + return -1; + + f = pico_frame_alloc(len); + if (!f) + { + dbg("Cannot alloc incoming frame!\n"); + return -1; + } + + /* Association to the device that just received the frame. */ + f->dev = dev; + + /* Setup the start pointer, length. */ + f->start = f->buffer; + f->len = f->buffer_len; + if (f->len > 8) { + uint32_t rand, mid_frame = (f->buffer_len >> 2) << 1; + mid_frame -= (mid_frame % 4); + memcpy(&rand, f->buffer + mid_frame, sizeof(uint32_t)); + pico_rand_feed(rand); + } + + memcpy(f->buffer, buffer, len); + ret = pico_enqueue(dev->q_in, f); + if (ret <= 0) { + pico_frame_discard(f); + } + + return ret; +} + +static int32_t _pico_stack_recv_zerocopy(struct pico_device *dev, uint8_t *buffer, uint32_t len, int ext_buffer, void (*notify_free)(uint8_t *)) +{ + struct pico_frame *f; + int ret; + if (len == 0) + return -1; + + f = pico_frame_alloc_skeleton(len, ext_buffer); + if (!f) + { + dbg("Cannot alloc incoming frame!\n"); + return -1; + } + + if (pico_frame_skeleton_set_buffer(f, buffer) < 0) + { + dbg("Invalid zero-copy buffer!\n"); + PICO_FREE(f->usage_count); + PICO_FREE(f); + return -1; + } + + if (notify_free) { + f->notify_free = notify_free; + } + + f->dev = dev; + ret = pico_enqueue(dev->q_in, f); + if (ret <= 0) { + pico_frame_discard(f); + } + + return ret; +} + +int32_t pico_stack_recv_zerocopy(struct pico_device *dev, uint8_t *buffer, uint32_t len) +{ + return _pico_stack_recv_zerocopy(dev, buffer, len, 0, NULL); +} + +int32_t pico_stack_recv_zerocopy_ext_buffer(struct pico_device *dev, uint8_t *buffer, uint32_t len) +{ + return _pico_stack_recv_zerocopy(dev, buffer, len, 1, NULL); +} + +int32_t pico_stack_recv_zerocopy_ext_buffer_notify(struct pico_device *dev, uint8_t *buffer, uint32_t len, void (*notify_free)(uint8_t *buffer)) +{ + return _pico_stack_recv_zerocopy(dev, buffer, len, 1, notify_free); +} + +int32_t pico_sendto_dev(struct pico_frame *f) +{ + if (!f->dev) { + pico_frame_discard(f); + return -1; + } else { + if (f->len > 8) { + uint32_t rand, mid_frame = (f->buffer_len >> 2) << 1; + mid_frame -= (mid_frame % 4); + memcpy(&rand, f->buffer + mid_frame, sizeof(uint32_t)); + pico_rand_feed(rand); + } + + return pico_enqueue(f->dev->q_out, f); + } +} + +struct pico_timer +{ + void *arg; + void (*timer)(pico_time timestamp, void *arg); +}; + + +static uint32_t tmr_id = 0u; +struct pico_timer_ref +{ + pico_time expire; + uint32_t id; + struct pico_timer *tmr; +}; + +typedef struct pico_timer_ref pico_timer_ref; + +DECLARE_HEAP(pico_timer_ref, expire); + +static heap_pico_timer_ref *Timers; + +int32_t pico_seq_compare(uint32_t a, uint32_t b) +{ + uint32_t thresh = ((uint32_t)(-1)) >> 1; + + if (a > b) /* return positive number, if not wrapped */ + { + if ((a - b) > thresh) /* b wrapped */ + return -(int32_t)(b - a); /* b = very small, a = very big */ + else + return (int32_t)(a - b); /* a = biggest, b = a bit smaller */ + + } + + if (a < b) /* return negative number, if not wrapped */ + { + if ((b - a) > thresh) /* a wrapped */ + return (int32_t)(a - b); /* a = very small, b = very big */ + else + return -(int32_t)(b - a); /* b = biggest, a = a bit smaller */ + + } + + return 0; +} + +static void pico_check_timers(void) +{ + struct pico_timer *t; + struct pico_timer_ref tref_unused, *tref = heap_first(Timers); + pico_tick = PICO_TIME_MS(); + while((tref) && (tref->expire < pico_tick)) { + t = tref->tmr; + if (t && t->timer) + t->timer(pico_tick, t->arg); + + if (t) + { + PICO_FREE(t); + } + + t = NULL; + heap_peek(Timers, &tref_unused); + tref = heap_first(Timers); + } +} + +void MOCKABLE pico_timer_cancel(uint32_t id) +{ + uint32_t i; + struct pico_timer_ref *tref = Timers->top; + if (id == 0u) + return; + for (i = 1; i <= Timers->n; i++) { + if (tref[i].id == id) { + PICO_FREE(Timers->top[i].tmr); + Timers->top[i].tmr = NULL; + break; + } + } +} + +#define PROTO_DEF_NR 11 +#define PROTO_DEF_AVG_NR 4 +#define PROTO_DEF_SCORE 32 +#define PROTO_MIN_SCORE 32 +#define PROTO_MAX_SCORE 128 +#define PROTO_LAT_IND 3 /* latency indication 0-3 (lower is better latency performance), x1, x2, x4, x8 */ +#define PROTO_MAX_LOOP (PROTO_MAX_SCORE << PROTO_LAT_IND) /* max global loop score, so per tick */ + +static int calc_score(int *score, int *index, int avg[][PROTO_DEF_AVG_NR], int *ret) +{ + int temp, i, j, sum; + int max_total = PROTO_MAX_LOOP, total = 0; + + /* dbg("USED SCORES> "); */ + + for (i = 0; i < PROTO_DEF_NR; i++) { + + /* if used looped score */ + if (ret[i] < score[i]) { + temp = score[i] - ret[i]; /* remaining loop score */ + + /* dbg("%3d - ",temp); */ + + if (index[i] >= PROTO_DEF_AVG_NR) + index[i] = 0; /* reset index */ + + j = index[i]; + avg[i][j] = temp; + + index[i]++; + + if (ret[i] == 0 && ((score[i] * 2) <= PROTO_MAX_SCORE) && ((total + (score[i] * 2)) < max_total)) { /* used all loop score -> increase next score directly */ + score[i] *= 2; + total += score[i]; + continue; + } + + sum = 0; + for (j = 0; j < PROTO_DEF_AVG_NR; j++) + sum += avg[i][j]; /* calculate sum */ + + sum /= 4; /* divide by 4 to get average used score */ + + /* criterion to increase next loop score */ + if (sum > (score[i] - (score[i] / 4)) && ((score[i] * 2) <= PROTO_MAX_SCORE) && ((total + (score[i] / 2)) < max_total)) { /* > 3/4 */ + score[i] *= 2; /* double loop score */ + total += score[i]; + continue; + } + + /* criterion to decrease next loop score */ + if ((sum < (score[i] / 4)) && ((score[i] / 2) >= PROTO_MIN_SCORE)) { /* < 1/4 */ + score[i] /= 2; /* half loop score */ + total += score[i]; + continue; + } + + /* also add non-changed scores */ + total += score[i]; + } + else if (ret[i] == score[i]) { + /* no used loop score - gradually decrease */ + + /* dbg("%3d - ",0); */ + + if (index[i] >= PROTO_DEF_AVG_NR) + index[i] = 0; /* reset index */ + + j = index[i]; + avg[i][j] = 0; + + index[i]++; + + sum = 0; + for (j = 0; j < PROTO_DEF_AVG_NR; j++) + sum += avg[i][j]; /* calculate sum */ + + sum /= 2; /* divide by 4 to get average used score */ + + if ((sum == 0) && ((score[i] / 2) >= PROTO_MIN_SCORE)) { + score[i] /= 2; /* half loop score */ + total += score[i]; + for (j = 0; j < PROTO_DEF_AVG_NR; j++) + avg[i][j] = score[i]; + } + + } + } + /* dbg("\n"); */ + + return 0; +} + +extern void pico_stack_tick(void) +{ + static int score[PROTO_DEF_NR] = { + PROTO_DEF_SCORE, PROTO_DEF_SCORE, PROTO_DEF_SCORE, PROTO_DEF_SCORE, PROTO_DEF_SCORE, PROTO_DEF_SCORE, PROTO_DEF_SCORE, PROTO_DEF_SCORE, PROTO_DEF_SCORE, PROTO_DEF_SCORE, PROTO_DEF_SCORE + }; + static int index[PROTO_DEF_NR] = { + 0, 0, 0, 0, 0, 0 + }; + static int avg[PROTO_DEF_NR][PROTO_DEF_AVG_NR]; + static int ret[PROTO_DEF_NR] = { + 0 + }; + + pico_check_timers(); + + /* dbg("LOOP_SCORES> %3d - %3d - %3d - %3d - %3d - %3d - %3d - %3d - %3d - %3d - %3d\n",score[0],score[1],score[2],score[3],score[4],score[5],score[6],score[7],score[8],score[9],score[10]); */ + + /* score = pico_protocols_loop(100); */ + + ret[0] = pico_devices_loop(score[0], PICO_LOOP_DIR_IN); + pico_rand_feed((uint32_t)ret[0]); + + ret[1] = pico_protocol_datalink_loop(score[1], PICO_LOOP_DIR_IN); + pico_rand_feed((uint32_t)ret[1]); + + ret[2] = pico_protocol_network_loop(score[2], PICO_LOOP_DIR_IN); + pico_rand_feed((uint32_t)ret[2]); + + ret[3] = pico_protocol_transport_loop(score[3], PICO_LOOP_DIR_IN); + pico_rand_feed((uint32_t)ret[3]); + + + ret[5] = score[5]; +#if defined (PICO_SUPPORT_IPV4) || defined (PICO_SUPPORT_IPV6) +#if defined (PICO_SUPPORT_TCP) || defined (PICO_SUPPORT_UDP) + ret[5] = pico_sockets_loop(score[5]); /* swapped */ + pico_rand_feed((uint32_t)ret[5]); +#endif +#endif + + ret[4] = pico_protocol_socket_loop(score[4], PICO_LOOP_DIR_IN); + pico_rand_feed((uint32_t)ret[4]); + + + ret[6] = pico_protocol_socket_loop(score[6], PICO_LOOP_DIR_OUT); + pico_rand_feed((uint32_t)ret[6]); + + ret[7] = pico_protocol_transport_loop(score[7], PICO_LOOP_DIR_OUT); + pico_rand_feed((uint32_t)ret[7]); + + ret[8] = pico_protocol_network_loop(score[8], PICO_LOOP_DIR_OUT); + pico_rand_feed((uint32_t)ret[8]); + + ret[9] = pico_protocol_datalink_loop(score[9], PICO_LOOP_DIR_OUT); + pico_rand_feed((uint32_t)ret[9]); + + ret[10] = pico_devices_loop(score[10], PICO_LOOP_DIR_OUT); + pico_rand_feed((uint32_t)ret[10]); + + /* calculate new loop scores for next iteration */ + calc_score(score, index, (int (*)[])avg, ret); +} + +void pico_stack_loop(void) +{ + while(1) { + pico_stack_tick(); + PICO_IDLE(); + } +} + +MOCKABLE uint32_t pico_timer_add(pico_time expire, void (*timer)(pico_time, void *), void *arg) +{ + struct pico_timer *t = PICO_ZALLOC(sizeof(struct pico_timer)); + struct pico_timer_ref tref; + + /* zero is guard for timers */ + if (tmr_id == 0u) + tmr_id++; + + if (!t) { + pico_err = PICO_ERR_ENOMEM; + return 0; + } + + tref.expire = PICO_TIME_MS() + expire; + t->arg = arg; + t->timer = timer; + tref.tmr = t; + tref.id = tmr_id++; + heap_insert(Timers, &tref); + if (Timers->n > PICO_MAX_TIMERS) { + dbg("Warning: I have %d timers\n", (int)Timers->n); + } + + return tref.id; +} + +extern int pico_stack_init(void) +{ + +#ifdef PICO_SUPPORT_IPV4 + pico_protocol_init(&pico_proto_ipv4); +#endif + +#ifdef PICO_SUPPORT_IPV6 + pico_protocol_init(&pico_proto_ipv6); +#endif + +#ifdef PICO_SUPPORT_ICMP4 + pico_protocol_init(&pico_proto_icmp4); +#endif + +#ifdef PICO_SUPPORT_ICMP6 + pico_protocol_init(&pico_proto_icmp6); +#endif + +#if defined(PICO_SUPPORT_IGMP) && defined(PICO_SUPPORT_MCAST) + pico_protocol_init(&pico_proto_igmp); +#endif + +#ifdef PICO_SUPPORT_UDP + pico_protocol_init(&pico_proto_udp); +#endif + +#ifdef PICO_SUPPORT_TCP + pico_protocol_init(&pico_proto_tcp); +#endif + +#ifdef PICO_SUPPORT_DNS_CLIENT + pico_dns_client_init(); +#endif + + pico_rand_feed(123456); + + /* Initialize timer heap */ + Timers = heap_init(); + if (!Timers) + return -1; + +#if ((defined PICO_SUPPORT_IPV4) && (defined PICO_SUPPORT_ETH)) + /* Initialize ARP module */ + pico_arp_init(); +#endif + +#ifdef PICO_SUPPORT_IPV6 + /* Initialize Neighbor discovery module */ + pico_ipv6_nd_init(); +#endif + +#ifdef PICO_SUPPORT_OLSR + pico_olsr_init(); +#endif +#ifdef PICO_SUPPORT_AODV + pico_aodv_init(); +#endif + + pico_stack_tick(); + pico_stack_tick(); + pico_stack_tick(); + return 0; +} + diff --git a/ext/picotcp/stack/pico_tree.c b/ext/picotcp/stack/pico_tree.c new file mode 100644 index 0000000..8af9845 --- /dev/null +++ b/ext/picotcp/stack/pico_tree.c @@ -0,0 +1,575 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + Author: Andrei Carp + *********************************************************************/ + +#include "pico_tree.h" +#include "pico_config.h" +#include "pico_protocol.h" +#include "pico_mm.h" + +#define RED 0 +#define BLACK 1 + +/* By default the null leafs are black */ +struct pico_tree_node LEAF = { + NULL, /* key */ + &LEAF, &LEAF, &LEAF, /* parent, left,right */ + BLACK, /* color */ +}; + +#define IS_LEAF(x) (x == &LEAF) +#define IS_NOT_LEAF(x) (x != &LEAF) +#define INIT_LEAF (&LEAF) + +#define AM_I_LEFT_CHILD(x) (x == x->parent->leftChild) +#define AM_I_RIGHT_CHILD(x) (x == x->parent->rightChild) + +#define PARENT(x) (x->parent) +#define GRANPA(x) (x->parent->parent) + +/* + * Local Functions + */ +static struct pico_tree_node *create_node(struct pico_tree *tree, void *key, uint8_t allocator); +static void rotateToLeft(struct pico_tree*tree, struct pico_tree_node*node); +static void rotateToRight(struct pico_tree*root, struct pico_tree_node*node); +static void fix_insert_collisions(struct pico_tree*tree, struct pico_tree_node*node); +static void fix_delete_collisions(struct pico_tree*tree, struct pico_tree_node *node); +static void switchNodes(struct pico_tree*tree, struct pico_tree_node*nodeA, struct pico_tree_node*nodeB); +void *pico_tree_insert_implementation(struct pico_tree *tree, void *key, uint8_t allocator); +void *pico_tree_delete_implementation(struct pico_tree *tree, void *key, uint8_t allocator); + +#ifdef PICO_SUPPORT_MM +/* The memory manager also uses the pico_tree to keep track of all the different slab sizes it has. + * These nodes should be placed in the manager page which is in a different memory region then the nodes + * which are used for the pico stack in general. + * Therefore the following 2 functions are created so that pico_tree can use them to to put these nodes + * into the correct memory regions. + * If pico_tree_insert is called from the memory manager module, then create_node should use + * pico_mem_page0_zalloc to create a node. The same for pico_tree_delete. + */ +extern void*pico_mem_page0_zalloc(size_t len); +extern void pico_mem_page0_free(void*ptr); +#endif /* PICO_SUPPORT_MM */ + +/* + * Exported functions + */ + +struct pico_tree_node *pico_tree_firstNode(struct pico_tree_node *node) +{ + while(IS_NOT_LEAF(node->leftChild)) + node = node->leftChild; + return node; +} + +struct pico_tree_node *pico_tree_lastNode(struct pico_tree_node *node) +{ + while(IS_NOT_LEAF(node->rightChild)) + node = node->rightChild; + return node; +} + +struct pico_tree_node *pico_tree_next(struct pico_tree_node *node) +{ + if (!node) + return NULL; + if(IS_NOT_LEAF(node->rightChild)) + { + node = node->rightChild; + while(IS_NOT_LEAF(node->leftChild)) + node = node->leftChild; + } + else + { + if (IS_NOT_LEAF(node->parent) && AM_I_LEFT_CHILD(node)) + node = node->parent; + else { + while (IS_NOT_LEAF(node->parent) && AM_I_RIGHT_CHILD(node)) + node = node->parent; + node = node->parent; + } + } + + return node; +} + +struct pico_tree_node *pico_tree_prev(struct pico_tree_node *node) +{ + if (IS_NOT_LEAF(node->leftChild)) { + node = node->leftChild; + while (IS_NOT_LEAF(node->rightChild)) + node = node->rightChild; + } else { + if (IS_NOT_LEAF(node->parent) && AM_I_RIGHT_CHILD(node)) + node = node->parent; + else { + while (IS_NOT_LEAF(node) && AM_I_LEFT_CHILD(node)) + node = node->parent; + node = node->parent; + } + } + + return node; +} + +/* The memory manager also uses the pico_tree to keep track of all the different slab sizes it has. + * These nodes should be placed in the manager page which is in a different memory region then the nodes + * which are used for the pico stack in general. + * Therefore the following wrapper for pico_tree_insert is created. + * The actual implementation can be found in pico_tree_insert_implementation. + */ +void *pico_tree_insert(struct pico_tree *tree, void *key) +{ + return pico_tree_insert_implementation(tree, key, USE_PICO_ZALLOC); +} + +void *pico_tree_insert_implementation(struct pico_tree *tree, void *key, uint8_t allocator) +{ + struct pico_tree_node *last_node = INIT_LEAF; + struct pico_tree_node *temp = tree->root; + struct pico_tree_node *insert; + void *LocalKey; + int result = 0; + + LocalKey = (IS_NOT_LEAF(tree->root) ? pico_tree_findKey(tree, key) : NULL); + + /* if node already in, bail out */ + if(LocalKey) { + return LocalKey; + } + else + { + if(allocator == USE_PICO_PAGE0_ZALLOC) + insert = create_node(tree, key, USE_PICO_PAGE0_ZALLOC); + else + insert = create_node(tree, key, USE_PICO_ZALLOC); + + if(!insert) + { + pico_err = PICO_ERR_ENOMEM; + /* to let the user know that it couldn't insert */ + return (void *)&LEAF; + } + } + + /* search for the place to insert the new node */ + while(IS_NOT_LEAF(temp)) + { + last_node = temp; + result = tree->compare(insert->keyValue, temp->keyValue); + + temp = (result < 0) ? (temp->leftChild) : (temp->rightChild); + } + /* make the needed connections */ + insert->parent = last_node; + + if(IS_LEAF(last_node)) + tree->root = insert; + else{ + result = tree->compare(insert->keyValue, last_node->keyValue); + if(result < 0) + last_node->leftChild = insert; + else + last_node->rightChild = insert; + } + + /* fix colour issues */ + fix_insert_collisions(tree, insert); + + return NULL; +} + +struct pico_tree_node *pico_tree_findNode(struct pico_tree *tree, void *key) +{ + struct pico_tree_node *found; + + found = tree->root; + + while(IS_NOT_LEAF(found)) + { + int result; + result = tree->compare(found->keyValue, key); + if(result == 0) + { + return found; + } + else if(result < 0) + found = found->rightChild; + else + found = found->leftChild; + } + return NULL; +} + +void *pico_tree_findKey(struct pico_tree *tree, void *key) +{ + struct pico_tree_node *found; + + + found = tree->root; + while(IS_NOT_LEAF(found)) + { + int result; + + result = tree->compare(found->keyValue, key); + if(result == 0) + return found->keyValue; + else if(result < 0) + found = found->rightChild; + else + found = found->leftChild; + + } + return NULL; +} + +void *pico_tree_first(struct pico_tree *tree) +{ + return pico_tree_firstNode(tree->root)->keyValue; +} + +void *pico_tree_last(struct pico_tree *tree) +{ + return pico_tree_lastNode(tree->root)->keyValue; +} + +static uint8_t pico_tree_delete_node(struct pico_tree *tree, struct pico_tree_node *d, struct pico_tree_node **temp) +{ + struct pico_tree_node *min; + struct pico_tree_node *ltemp = d; + uint8_t nodeColor; + min = pico_tree_firstNode(d->rightChild); + nodeColor = min->color; + + *temp = min->rightChild; + if(min->parent == ltemp && IS_NOT_LEAF(*temp)) + (*temp)->parent = min; + else{ + switchNodes(tree, min, min->rightChild); + min->rightChild = ltemp->rightChild; + if(IS_NOT_LEAF(min->rightChild)) min->rightChild->parent = min; + } + + switchNodes(tree, ltemp, min); + min->leftChild = ltemp->leftChild; + + if(IS_NOT_LEAF(min->leftChild)) + min->leftChild->parent = min; + + min->color = ltemp->color; + return nodeColor; +} + +static uint8_t pico_tree_delete_check_switch(struct pico_tree *tree, struct pico_tree_node *delete, struct pico_tree_node **temp) +{ + struct pico_tree_node *ltemp = delete; + uint8_t nodeColor = delete->color; + if(IS_LEAF(delete->leftChild)) + { + *temp = ltemp->rightChild; + switchNodes(tree, ltemp, ltemp->rightChild); + } + else + if(IS_LEAF(delete->rightChild)) + { + struct pico_tree_node *_ltemp = delete; + *temp = _ltemp->leftChild; + switchNodes(tree, _ltemp, _ltemp->leftChild); + } + else{ + nodeColor = pico_tree_delete_node(tree, delete, temp); + } + + return nodeColor; + +} + +/* The memory manager also uses the pico_tree to keep track of all the different slab sizes it has. + * These nodes should be placed in the manager page which is in a different memory region then the nodes + * which are used for the pico stack in general. + * Therefore the following wrapper for pico_tree_delete is created. + * The actual implementation can be found in pico_tree_delete_implementation. + */ +void *pico_tree_delete(struct pico_tree *tree, void *key) +{ + return pico_tree_delete_implementation(tree, key, USE_PICO_ZALLOC); +} + +static inline void if_nodecolor_black_fix_collisions(struct pico_tree *tree, struct pico_tree_node *temp, uint8_t nodeColor) +{ + /* deleted node is black, this will mess up the black path property */ + if(nodeColor == BLACK) + fix_delete_collisions(tree, temp); +} + +void *pico_tree_delete_implementation(struct pico_tree *tree, void *key, uint8_t allocator) +{ + struct pico_tree_node *temp; + uint8_t nodeColor; /* keeps the color of the node to be deleted */ + void *lkey; /* keeps a copy of the key which will be removed */ + struct pico_tree_node *delete; /* keeps a copy of the node to be extracted */ + if (!key) + return NULL; + delete = pico_tree_findNode(tree, key); + + /* this key isn't in the tree, bail out */ + if(!delete) + return NULL; + + lkey = delete->keyValue; + nodeColor = pico_tree_delete_check_switch(tree, delete, &temp); + + if_nodecolor_black_fix_collisions(tree, temp, nodeColor); + + if(allocator == USE_PICO_ZALLOC) + PICO_FREE(delete); + +#ifdef PICO_SUPPORT_MM + else + pico_mem_page0_free(delete); +#endif + return lkey; +} + +int pico_tree_empty(struct pico_tree *tree) +{ + return (!tree->root || IS_LEAF(tree->root)); +} + +/* + * Private functions + */ +static void rotateToLeft(struct pico_tree*tree, struct pico_tree_node*node) +{ + struct pico_tree_node*temp; + + temp = node->rightChild; + + if(temp == &LEAF) return; + + node->rightChild = temp->leftChild; + + if(IS_NOT_LEAF(temp->leftChild)) + temp->leftChild->parent = node; + + temp->parent = node->parent; + + if(IS_LEAF(node->parent)) + tree->root = temp; + else + if(node == node->parent->leftChild) + node->parent->leftChild = temp; + else + node->parent->rightChild = temp; + + temp->leftChild = node; + node->parent = temp; +} + + +static void rotateToRight(struct pico_tree *tree, struct pico_tree_node *node) +{ + struct pico_tree_node*temp; + + temp = node->leftChild; + node->leftChild = temp->rightChild; + + if(temp == &LEAF) return; + + if(IS_NOT_LEAF(temp->rightChild)) + temp->rightChild->parent = node; + + temp->parent = node->parent; + + if(IS_LEAF(node->parent)) + tree->root = temp; + else + if(node == node->parent->rightChild) + node->parent->rightChild = temp; + else + node->parent->leftChild = temp; + + temp->rightChild = node; + node->parent = temp; + return; +} + +static struct pico_tree_node *create_node(struct pico_tree *tree, void*key, uint8_t allocator) +{ + struct pico_tree_node *temp = NULL; + IGNORE_PARAMETER(tree); + if(allocator == USE_PICO_ZALLOC) + temp = (struct pico_tree_node *)PICO_ZALLOC(sizeof(struct pico_tree_node)); + +#ifdef PICO_SUPPORT_MM + else + temp = (struct pico_tree_node *)pico_mem_page0_zalloc(sizeof(struct pico_tree_node)); +#endif + + if(!temp) + return NULL; + + temp->keyValue = key; + temp->parent = &LEAF; + temp->leftChild = &LEAF; + temp->rightChild = &LEAF; + /* by default every new node is red */ + temp->color = RED; + return temp; +} + +/* + * This function fixes the possible collisions in the tree. + * Eg. if a node is red his children must be black ! + */ +static void fix_insert_collisions(struct pico_tree*tree, struct pico_tree_node*node) +{ + struct pico_tree_node*temp; + + while(node->parent->color == RED && IS_NOT_LEAF(GRANPA(node))) + { + if(AM_I_RIGHT_CHILD(node->parent)) + { + temp = GRANPA(node)->leftChild; + if(temp->color == RED) { + node->parent->color = BLACK; + temp->color = BLACK; + GRANPA(node)->color = RED; + node = GRANPA(node); + } + else if(temp->color == BLACK) { + if(node == node->parent->leftChild) { + node = node->parent; + rotateToRight(tree, node); + } + + node->parent->color = BLACK; + GRANPA(node)->color = RED; + rotateToLeft(tree, GRANPA(node)); + } + } + else if(AM_I_LEFT_CHILD(node->parent)) + { + temp = GRANPA(node)->rightChild; + if(temp->color == RED) { + node->parent->color = BLACK; + temp->color = BLACK; + GRANPA(node)->color = RED; + node = GRANPA(node); + } + else if(temp->color == BLACK) { + if(AM_I_RIGHT_CHILD(node)) { + node = node->parent; + rotateToLeft(tree, node); + } + + node->parent->color = BLACK; + GRANPA(node)->color = RED; + rotateToRight(tree, GRANPA(node)); + } + } + } + /* make sure that the root of the tree stays black */ + tree->root->color = BLACK; +} + +static void switchNodes(struct pico_tree*tree, struct pico_tree_node*nodeA, struct pico_tree_node*nodeB) +{ + + if(IS_LEAF(nodeA->parent)) + tree->root = nodeB; + else + if(IS_NOT_LEAF(nodeA)) + { + if(AM_I_LEFT_CHILD(nodeA)) + nodeA->parent->leftChild = nodeB; + else + nodeA->parent->rightChild = nodeB; + } + + if(IS_NOT_LEAF(nodeB)) nodeB->parent = nodeA->parent; + +} + +/* + * This function fixes the possible collisions in the tree. + * Eg. if a node is red his children must be black ! + * In this case the function fixes the constant black path property. + */ +static void fix_delete_collisions(struct pico_tree*tree, struct pico_tree_node *node) +{ + struct pico_tree_node*temp; + + while( node != tree->root && node->color == BLACK && IS_NOT_LEAF(node)) + { + if(AM_I_LEFT_CHILD(node)) { + + temp = node->parent->rightChild; + if(temp->color == RED) + { + temp->color = BLACK; + node->parent->color = RED; + rotateToLeft(tree, node->parent); + temp = node->parent->rightChild; + } + + if(temp->leftChild->color == BLACK && temp->rightChild->color == BLACK) + { + temp->color = RED; + node = node->parent; + } + else + { + if(temp->rightChild->color == BLACK) + { + temp->leftChild->color = BLACK; + temp->color = RED; + rotateToRight(tree, temp); + temp = temp->parent->rightChild; + } + + temp->color = node->parent->color; + node->parent->color = BLACK; + temp->rightChild->color = BLACK; + rotateToLeft(tree, node->parent); + node = tree->root; + } + } + else{ + temp = node->parent->leftChild; + if(temp->color == RED) + { + temp->color = BLACK; + node->parent->color = RED; + rotateToRight(tree, node->parent); + temp = node->parent->leftChild; + } + + if(temp->rightChild->color == BLACK && temp->leftChild->color == BLACK) + { + temp->color = RED; + node = node->parent; + } + else{ + if(temp->leftChild->color == BLACK) + { + temp->rightChild->color = BLACK; + temp->color = RED; + rotateToLeft(tree, temp); + temp = temp->parent->leftChild; + } + + temp->color = node->parent->color; + node->parent->color = BLACK; + temp->leftChild->color = BLACK; + rotateToRight(tree, node->parent); + node = tree->root; + } + } + } + node->color = BLACK; +} diff --git a/ext/picotcp/uncrustify.cfg b/ext/picotcp/uncrustify.cfg new file mode 100644 index 0000000..ba664df --- /dev/null +++ b/ext/picotcp/uncrustify.cfg @@ -0,0 +1,1579 @@ +# Uncrustify 0.60 + +# +# General options +# + +# The type of line endings +newlines = lf # auto/lf/crlf/cr + +# The original size of tabs in the input +input_tab_size = 4 # number + +# The size of tabs in the output (only used if align_with_tabs=true) +output_tab_size = 4 # number + +# The ASCII value of the string escape char, usually 92 (\) or 94 (^). (Pawn) +string_escape_char = 92 # number + +# Alternate string escape char for Pawn. Only works right before the quote char. +string_escape_char2 = 0 # number + +# Allow interpreting '>=' and '>>=' as part of a template in 'void f(list>=val);'. +# If true (default), 'assert(x<0 && y>=3)' will be broken. +# Improvements to template detection may make this option obsolete. +tok_split_gte = false # false/true + +# Control what to do with the UTF-8 BOM (recommend 'remove') +utf8_bom = ignore # ignore/add/remove/force + +# If the file contains bytes with values between 128 and 255, but is not UTF-8, then output as UTF-8 +utf8_byte = false # false/true + +# Force the output encoding to UTF-8 +utf8_force = false # false/true + +# +# Indenting +# + +# The number of columns to indent per level. +# Usually 2, 3, 4, or 8. +indent_columns = 4 # number + +# The continuation indent. If non-zero, this overrides the indent of '(' and '=' continuation indents. +# For FreeBSD, this is set to 4. Negative value is absolute and not increased for each ( level +indent_continue = 0 # number + +# How to use tabs when indenting code +# 0=spaces only +# 1=indent with tabs to brace level, align with spaces +# 2=indent and align with tabs, using spaces when not on a tabstop +indent_with_tabs = 0 # number + +# Comments that are not a brace level are indented with tabs on a tabstop. +# Requires indent_with_tabs=2. If false, will use spaces. +indent_cmt_with_tabs = false # false/true + +# Whether to indent strings broken by '\' so that they line up +indent_align_string = false # false/true + +# The number of spaces to indent multi-line XML strings. +# Requires indent_align_string=True +indent_xml_string = 0 # number + +# Spaces to indent '{' from level +indent_brace = 0 # number + +# Whether braces are indented to the body level +indent_braces = false # false/true + +# Disabled indenting function braces if indent_braces is true +indent_braces_no_func = false # false/true + +# Disabled indenting class braces if indent_braces is true +indent_braces_no_class = false # false/true + +# Disabled indenting struct braces if indent_braces is true +indent_braces_no_struct = false # false/true + +# Indent based on the size of the brace parent, i.e. 'if' => 3 spaces, 'for' => 4 spaces, etc. +indent_brace_parent = false # false/true + +# Whether the 'namespace' body is indented +indent_namespace = false # false/true + +# The number of spaces to indent a namespace block +indent_namespace_level = 0 # number + +# If the body of the namespace is longer than this number, it won't be indented. +# Requires indent_namespace=true. Default=0 (no limit) +indent_namespace_limit = 0 # number + +# Whether the 'extern "C"' body is indented +indent_extern = false # false/true + +# Whether the 'class' body is indented +indent_class = false # false/true + +# Whether to indent the stuff after a leading class colon +indent_class_colon = false # false/true + +# Virtual indent from the ':' for member initializers. Default is 2 +indent_ctor_init_leading = 2 # number + +# Additional indenting for constructor initializer list +indent_ctor_init = 0 # number + +# False=treat 'else\nif' as 'else if' for indenting purposes +# True=indent the 'if' one level +indent_else_if = false # false/true + +# Amount to indent variable declarations after a open brace. neg=relative, pos=absolute +indent_var_def_blk = 0 # number + +# Indent continued variable declarations instead of aligning. +indent_var_def_cont = false # false/true + +# True: force indentation of function definition to start in column 1 +# False: use the default behavior +indent_func_def_force_col1 = false # false/true + +# True: indent continued function call parameters one indent level +# False: align parameters under the open paren +indent_func_call_param = false # false/true + +# Same as indent_func_call_param, but for function defs +indent_func_def_param = false # false/true + +# Same as indent_func_call_param, but for function protos +indent_func_proto_param = false # false/true + +# Same as indent_func_call_param, but for class declarations +indent_func_class_param = false # false/true + +# Same as indent_func_call_param, but for class variable constructors +indent_func_ctor_var_param = false # false/true + +# Same as indent_func_call_param, but for templates +indent_template_param = false # false/true + +# Double the indent for indent_func_xxx_param options +indent_func_param_double = false # false/true + +# Indentation column for standalone 'const' function decl/proto qualifier +indent_func_const = 0 # number + +# Indentation column for standalone 'throw' function decl/proto qualifier +indent_func_throw = 0 # number + +# The number of spaces to indent a continued '->' or '.' +# Usually set to 0, 1, or indent_columns. +indent_member = 0 # number + +# Spaces to indent single line ('//') comments on lines before code +indent_sing_line_comments = 0 # number + +# If set, will indent trailing single line ('//') comments relative +# to the code instead of trying to keep the same absolute column +indent_relative_single_line_comments = false # false/true + +# Spaces to indent 'case' from 'switch' +# Usually 0 or indent_columns. +indent_switch_case = 0 # number + +# Spaces to shift the 'case' line, without affecting any other lines +# Usually 0. +indent_case_shift = 0 # number + +# Spaces to indent '{' from 'case'. +# By default, the brace will appear under the 'c' in case. +# Usually set to 0 or indent_columns. +indent_case_brace = 0 # number + +# Whether to indent comments found in first column +indent_col1_comment = false # false/true + +# How to indent goto labels +# >0 : absolute column where 1 is the leftmost column +# <=0 : subtract from brace indent +indent_label = 1 # number + +# Same as indent_label, but for access specifiers that are followed by a colon +indent_access_spec = 1 # number + +# Indent the code after an access specifier by one level. +# If set, this option forces 'indent_access_spec=0' +indent_access_spec_body = false # false/true + +# If an open paren is followed by a newline, indent the next line so that it lines up after the open paren (not recommended) +indent_paren_nl = false # false/true + +# Controls the indent of a close paren after a newline. +# 0: Indent to body level +# 1: Align under the open paren +# 2: Indent to the brace level +indent_paren_close = 0 # number + +# Controls the indent of a comma when inside a paren.If TRUE, aligns under the open paren +indent_comma_paren = false # false/true + +# Controls the indent of a BOOL operator when inside a paren.If TRUE, aligns under the open paren +indent_bool_paren = false # false/true + +# If 'indent_bool_paren' is true, controls the indent of the first expression. If TRUE, aligns the first expression to the following ones +indent_first_bool_expr = false # false/true + +# If an open square is followed by a newline, indent the next line so that it lines up after the open square (not recommended) +indent_square_nl = false # false/true + +# Don't change the relative indent of ESQL/C 'EXEC SQL' bodies +indent_preserve_sql = false # false/true + +# Align continued statements at the '='. Default=True +# If FALSE or the '=' is followed by a newline, the next line is indent one tab. +indent_align_assign = true # false/true + +# Indent OC blocks at brace level instead of usual rules. +indent_oc_block = false # false/true + +# Indent OC blocks in a message relative to the parameter name. +# 0=use indent_oc_block rules, 1+=spaces to indent +indent_oc_block_msg = 0 # number + +# Minimum indent for subsequent parameters +indent_oc_msg_colon = 0 # number + +# +# Spacing options +# + +# Add or remove space around arithmetic operator '+', '-', '/', '*', etc +sp_arith = add # ignore/add/remove/force + +# Add or remove space around assignment operator '=', '+=', etc +sp_assign = add # ignore/add/remove/force + +# Add or remove space around '=' in C++11 lambda capture specifications. Overrides sp_assign +sp_cpp_lambda_assign = ignore # ignore/add/remove/force + +# Add or remove space after the capture specification in C++11 lambda. +sp_cpp_lambda_paren = ignore # ignore/add/remove/force + +# Add or remove space around assignment operator '=' in a prototype +sp_assign_default = add # ignore/add/remove/force + +# Add or remove space before assignment operator '=', '+=', etc. Overrides sp_assign. +sp_before_assign = add # ignore/add/remove/force + +# Add or remove space after assignment operator '=', '+=', etc. Overrides sp_assign. +sp_after_assign = add # ignore/add/remove/force + +# Add or remove space around assignment '=' in enum +sp_enum_assign = add # ignore/add/remove/force + +# Add or remove space before assignment '=' in enum. Overrides sp_enum_assign. +sp_enum_before_assign = add # ignore/add/remove/force + +# Add or remove space after assignment '=' in enum. Overrides sp_enum_assign. +sp_enum_after_assign = add # ignore/add/remove/force + +# Add or remove space around preprocessor '##' concatenation operator. Default=Add +sp_pp_concat = add # ignore/add/remove/force + +# Add or remove space after preprocessor '#' stringify operator. Also affects the '#@' charizing operator. +sp_pp_stringify = ignore # ignore/add/remove/force + +# Add or remove space before preprocessor '#' stringify operator as in '#define x(y) L#y'. +sp_before_pp_stringify = ignore # ignore/add/remove/force + +# Add or remove space around boolean operators '&&' and '||' +sp_bool = add # ignore/add/remove/force + +# Add or remove space around compare operator '<', '>', '==', etc +sp_compare = add # ignore/add/remove/force + +# Add or remove space inside '(' and ')' +sp_inside_paren = remove # ignore/add/remove/force + +# Add or remove space between nested parens +sp_paren_paren = remove # ignore/add/remove/force + +# Whether to balance spaces inside nested parens +sp_balance_nested_parens = false # false/true + +# Add or remove space between ')' and '{' +sp_paren_brace = ignore # ignore/add/remove/force + +# Add or remove space before pointer star '*' +sp_before_ptr_star = ignore # ignore/add/remove/force + +# Add or remove space before pointer star '*' that isn't followed by a variable name +# If set to 'ignore', sp_before_ptr_star is used instead. +sp_before_unnamed_ptr_star = ignore # ignore/add/remove/force + +# Add or remove space between pointer stars '*' +sp_between_ptr_star = ignore # ignore/add/remove/force + +# Add or remove space after pointer star '*', if followed by a word. +sp_after_ptr_star = remove # ignore/add/remove/force + +# Add or remove space after a pointer star '*', if followed by a func proto/def. +sp_after_ptr_star_func = ignore # ignore/add/remove/force + +# Add or remove space after a pointer star '*', if followed by an open paren (function types). +sp_ptr_star_paren = ignore # ignore/add/remove/force + +# Add or remove space before a pointer star '*', if followed by a func proto/def. +sp_before_ptr_star_func = ignore # ignore/add/remove/force + +# Add or remove space before a reference sign '&' +sp_before_byref = ignore # ignore/add/remove/force + +# Add or remove space before a reference sign '&' that isn't followed by a variable name +# If set to 'ignore', sp_before_byref is used instead. +sp_before_unnamed_byref = ignore # ignore/add/remove/force + +# Add or remove space after reference sign '&', if followed by a word. +sp_after_byref = remove # ignore/add/remove/force + +# Add or remove space after a reference sign '&', if followed by a func proto/def. +sp_after_byref_func = remove # ignore/add/remove/force + +# Add or remove space before a reference sign '&', if followed by a func proto/def. +sp_before_byref_func = ignore # ignore/add/remove/force + +# Add or remove space between type and word. Default=Force +sp_after_type = force # ignore/add/remove/force + +# Add or remove space before the paren in the D constructs 'template Foo(' and 'class Foo('. +sp_before_template_paren = ignore # ignore/add/remove/force + +# Add or remove space in 'template <' vs 'template<'. +# If set to ignore, sp_before_angle is used. +sp_template_angle = ignore # ignore/add/remove/force + +# Add or remove space before '<>' +sp_before_angle = ignore # ignore/add/remove/force + +# Add or remove space inside '<' and '>' +sp_inside_angle = ignore # ignore/add/remove/force + +# Add or remove space after '<>' +sp_after_angle = ignore # ignore/add/remove/force + +# Add or remove space between '<>' and '(' as found in 'new List();' +sp_angle_paren = ignore # ignore/add/remove/force + +# Add or remove space between '<>' and a word as in 'List m;' +sp_angle_word = ignore # ignore/add/remove/force + +# Add or remove space between '>' and '>' in '>>' (template stuff C++/C# only). Default=Add +sp_angle_shift = add # ignore/add/remove/force + +# Permit removal of the space between '>>' in 'foo >' (C++11 only). Default=False +# sp_angle_shift cannot remove the space without this option. +sp_permit_cpp11_shift = false # false/true + +# Add or remove space before '(' of 'if', 'for', 'switch', and 'while' +sp_before_sparen = ignore # ignore/add/remove/force + +# Add or remove space inside if-condition '(' and ')' +sp_inside_sparen = ignore # ignore/add/remove/force + +# Add or remove space before if-condition ')'. Overrides sp_inside_sparen. +sp_inside_sparen_close = ignore # ignore/add/remove/force + +# Add or remove space before if-condition '('. Overrides sp_inside_sparen. +sp_inside_sparen_open = ignore # ignore/add/remove/force + +# Add or remove space after ')' of 'if', 'for', 'switch', and 'while' +sp_after_sparen = ignore # ignore/add/remove/force + +# Add or remove space between ')' and '{' of 'if', 'for', 'switch', and 'while' +sp_sparen_brace = ignore # ignore/add/remove/force + +# Add or remove space between 'invariant' and '(' in the D language. +sp_invariant_paren = ignore # ignore/add/remove/force + +# Add or remove space after the ')' in 'invariant (C) c' in the D language. +sp_after_invariant_paren = ignore # ignore/add/remove/force + +# Add or remove space before empty statement ';' on 'if', 'for' and 'while' +sp_special_semi = ignore # ignore/add/remove/force + +# Add or remove space before ';'. Default=Remove +sp_before_semi = remove # ignore/add/remove/force + +# Add or remove space before ';' in non-empty 'for' statements +sp_before_semi_for = ignore # ignore/add/remove/force + +# Add or remove space before a semicolon of an empty part of a for statement. +sp_before_semi_for_empty = ignore # ignore/add/remove/force + +# Add or remove space after ';', except when followed by a comment. Default=Add +sp_after_semi = remove # ignore/add/remove/force + +# Add or remove space after ';' in non-empty 'for' statements. Default=Force +sp_after_semi_for = force # ignore/add/remove/force + +# Add or remove space after the final semicolon of an empty part of a for statement: for ( ; ; ). +sp_after_semi_for_empty = ignore # ignore/add/remove/force + +# Add or remove space before '[' (except '[]') +sp_before_square = ignore # ignore/add/remove/force + +# Add or remove space before '[]' +sp_before_squares = ignore # ignore/add/remove/force + +# Add or remove space inside a non-empty '[' and ']' +sp_inside_square = ignore # ignore/add/remove/force + +# Add or remove space after ',' +sp_after_comma = add # ignore/add/remove/force + +# Add or remove space before ',' +sp_before_comma = remove # ignore/add/remove/force + +# Add or remove space between an open paren and comma: '(,' vs '( ,' +sp_paren_comma = force # ignore/add/remove/force + +# Add or remove space before the variadic '...' when preceded by a non-punctuator +sp_before_ellipsis = ignore # ignore/add/remove/force + +# Add or remove space after class ':' +sp_after_class_colon = ignore # ignore/add/remove/force + +# Add or remove space before class ':' +sp_before_class_colon = ignore # ignore/add/remove/force + +# Add or remove space before case ':'. Default=Remove +sp_before_case_colon = remove # ignore/add/remove/force + +# Add or remove space between 'operator' and operator sign +sp_after_operator = ignore # ignore/add/remove/force + +# Add or remove space between the operator symbol and the open paren, as in 'operator ++(' +sp_after_operator_sym = ignore # ignore/add/remove/force + +# Add or remove space after C/D cast, i.e. 'cast(int)a' vs 'cast(int) a' or '(int)a' vs '(int) a' +sp_after_cast = ignore # ignore/add/remove/force + +# Add or remove spaces inside cast parens +sp_inside_paren_cast = ignore # ignore/add/remove/force + +# Add or remove space between the type and open paren in a C++ cast, i.e. 'int(exp)' vs 'int (exp)' +sp_cpp_cast_paren = ignore # ignore/add/remove/force + +# Add or remove space between 'sizeof' and '(' +sp_sizeof_paren = ignore # ignore/add/remove/force + +# Add or remove space after the tag keyword (Pawn) +sp_after_tag = ignore # ignore/add/remove/force + +# Add or remove space inside enum '{' and '}' +sp_inside_braces_enum = ignore # ignore/add/remove/force + +# Add or remove space inside struct/union '{' and '}' +sp_inside_braces_struct = ignore # ignore/add/remove/force + +# Add or remove space inside '{' and '}' +sp_inside_braces = ignore # ignore/add/remove/force + +# Add or remove space inside '{}' +sp_inside_braces_empty = ignore # ignore/add/remove/force + +# Add or remove space between return type and function name +# A minimum of 1 is forced except for pointer return types. +sp_type_func = ignore # ignore/add/remove/force + +# Add or remove space between function name and '(' on function declaration +sp_func_proto_paren = ignore # ignore/add/remove/force + +# Add or remove space between function name and '(' on function definition +sp_func_def_paren = ignore # ignore/add/remove/force + +# Add or remove space inside empty function '()' +sp_inside_fparens = ignore # ignore/add/remove/force + +# Add or remove space inside function '(' and ')' +sp_inside_fparen = ignore # ignore/add/remove/force + +# Add or remove space inside the first parens in the function type: 'void (*x)(...)' +sp_inside_tparen = ignore # ignore/add/remove/force + +# Add or remove between the parens in the function type: 'void (*x)(...)' +sp_after_tparen_close = ignore # ignore/add/remove/force + +# Add or remove space between ']' and '(' when part of a function call. +sp_square_fparen = ignore # ignore/add/remove/force + +# Add or remove space between ')' and '{' of function +sp_fparen_brace = ignore # ignore/add/remove/force + +# Add or remove space between function name and '(' on function calls +sp_func_call_paren = ignore # ignore/add/remove/force + +# Add or remove space between function name and '()' on function calls without parameters. +# If set to 'ignore' (the default), sp_func_call_paren is used. +sp_func_call_paren_empty = ignore # ignore/add/remove/force + +# Add or remove space between the user function name and '(' on function calls +# You need to set a keyword to be a user function, like this: 'set func_call_user _' in the config file. +sp_func_call_user_paren = ignore # ignore/add/remove/force + +# Add or remove space between a constructor/destructor and the open paren +sp_func_class_paren = ignore # ignore/add/remove/force + +# Add or remove space between 'return' and '(' +sp_return_paren = ignore # ignore/add/remove/force + +# Add or remove space between '__attribute__' and '(' +sp_attribute_paren = ignore # ignore/add/remove/force + +# Add or remove space between 'defined' and '(' in '#if defined (FOO)' +sp_defined_paren = ignore # ignore/add/remove/force + +# Add or remove space between 'throw' and '(' in 'throw (something)' +sp_throw_paren = ignore # ignore/add/remove/force + +# Add or remove space between 'throw' and anything other than '(' as in '@throw [...];' +sp_after_throw = ignore # ignore/add/remove/force + +# Add or remove space between 'catch' and '(' in 'catch (something) { }' +# If set to ignore, sp_before_sparen is used. +sp_catch_paren = ignore # ignore/add/remove/force + +# Add or remove space between 'version' and '(' in 'version (something) { }' (D language) +# If set to ignore, sp_before_sparen is used. +sp_version_paren = ignore # ignore/add/remove/force + +# Add or remove space between 'scope' and '(' in 'scope (something) { }' (D language) +# If set to ignore, sp_before_sparen is used. +sp_scope_paren = ignore # ignore/add/remove/force + +# Add or remove space between macro and value +sp_macro = ignore # ignore/add/remove/force + +# Add or remove space between macro function ')' and value +sp_macro_func = ignore # ignore/add/remove/force + +# Add or remove space between 'else' and '{' if on the same line +sp_else_brace = ignore # ignore/add/remove/force + +# Add or remove space between '}' and 'else' if on the same line +sp_brace_else = ignore # ignore/add/remove/force + +# Add or remove space between '}' and the name of a typedef on the same line +sp_brace_typedef = ignore # ignore/add/remove/force + +# Add or remove space between 'catch' and '{' if on the same line +sp_catch_brace = ignore # ignore/add/remove/force + +# Add or remove space between '}' and 'catch' if on the same line +sp_brace_catch = ignore # ignore/add/remove/force + +# Add or remove space between 'finally' and '{' if on the same line +sp_finally_brace = ignore # ignore/add/remove/force + +# Add or remove space between '}' and 'finally' if on the same line +sp_brace_finally = ignore # ignore/add/remove/force + +# Add or remove space between 'try' and '{' if on the same line +sp_try_brace = ignore # ignore/add/remove/force + +# Add or remove space between get/set and '{' if on the same line +sp_getset_brace = ignore # ignore/add/remove/force + +# Add or remove space before the '::' operator +sp_before_dc = ignore # ignore/add/remove/force + +# Add or remove space after the '::' operator +sp_after_dc = ignore # ignore/add/remove/force + +# Add or remove around the D named array initializer ':' operator +sp_d_array_colon = ignore # ignore/add/remove/force + +# Add or remove space after the '!' (not) operator. Default=Remove +sp_not = remove # ignore/add/remove/force + +# Add or remove space after the '~' (invert) operator. Default=Remove +sp_inv = remove # ignore/add/remove/force + +# Add or remove space after the '&' (address-of) operator. Default=Remove +# This does not affect the spacing after a '&' that is part of a type. +sp_addr = remove # ignore/add/remove/force + +# Add or remove space around the '.' or '->' operators. Default=Remove +sp_member = remove # ignore/add/remove/force + +# Add or remove space after the '*' (dereference) operator. Default=Remove +# This does not affect the spacing after a '*' that is part of a type. +sp_deref = remove # ignore/add/remove/force + +# Add or remove space after '+' or '-', as in 'x = -5' or 'y = +7'. Default=Remove +sp_sign = remove # ignore/add/remove/force + +# Add or remove space before or after '++' and '--', as in '(--x)' or 'y++;'. Default=Remove +sp_incdec = remove # ignore/add/remove/force + +# Add or remove space before a backslash-newline at the end of a line. Default=Add +sp_before_nl_cont = add # ignore/add/remove/force + +# Add or remove space after the scope '+' or '-', as in '-(void) foo;' or '+(int) bar;' +sp_after_oc_scope = ignore # ignore/add/remove/force + +# Add or remove space after the colon in message specs +# '-(int) f:(int) x;' vs '-(int) f: (int) x;' +sp_after_oc_colon = ignore # ignore/add/remove/force + +# Add or remove space before the colon in message specs +# '-(int) f: (int) x;' vs '-(int) f : (int) x;' +sp_before_oc_colon = ignore # ignore/add/remove/force + +# Add or remove space after the colon in immutable dictionary expression +# 'NSDictionary *test = @{@"foo" :@"bar"};' +sp_after_oc_dict_colon = ignore # ignore/add/remove/force + +# Add or remove space before the colon in immutable dictionary expression +# 'NSDictionary *test = @{@"foo" :@"bar"};' +sp_before_oc_dict_colon = ignore # ignore/add/remove/force + +# Add or remove space after the colon in message specs +# '[object setValue:1];' vs '[object setValue: 1];' +sp_after_send_oc_colon = ignore # ignore/add/remove/force + +# Add or remove space before the colon in message specs +# '[object setValue:1];' vs '[object setValue :1];' +sp_before_send_oc_colon = ignore # ignore/add/remove/force + +# Add or remove space after the (type) in message specs +# '-(int)f: (int) x;' vs '-(int)f: (int)x;' +sp_after_oc_type = ignore # ignore/add/remove/force + +# Add or remove space after the first (type) in message specs +# '-(int) f:(int)x;' vs '-(int)f:(int)x;' +sp_after_oc_return_type = ignore # ignore/add/remove/force + +# Add or remove space between '@selector' and '(' +# '@selector(msgName)' vs '@selector (msgName)' +# Also applies to @protocol() constructs +sp_after_oc_at_sel = ignore # ignore/add/remove/force + +# Add or remove space between '@selector(x)' and the following word +# '@selector(foo) a:' vs '@selector(foo)a:' +sp_after_oc_at_sel_parens = ignore # ignore/add/remove/force + +# Add or remove space inside '@selector' parens +# '@selector(foo)' vs '@selector( foo )' +# Also applies to @protocol() constructs +sp_inside_oc_at_sel_parens = ignore # ignore/add/remove/force + +# Add or remove space before a block pointer caret +# '^int (int arg){...}' vs. ' ^int (int arg){...}' +sp_before_oc_block_caret = ignore # ignore/add/remove/force + +# Add or remove space after a block pointer caret +# '^int (int arg){...}' vs. '^ int (int arg){...}' +sp_after_oc_block_caret = ignore # ignore/add/remove/force + +# Add or remove space between the receiver and selector in a message. +# '[receiver selector ...]' +sp_after_oc_msg_receiver = ignore # ignore/add/remove/force + +# Add or remove space after @property. +sp_after_oc_property = ignore # ignore/add/remove/force + +# Add or remove space around the ':' in 'b ? t : f' +sp_cond_colon = ignore # ignore/add/remove/force + +# Add or remove space around the '?' in 'b ? t : f' +sp_cond_question = ignore # ignore/add/remove/force + +# Fix the spacing between 'case' and the label. Only 'ignore' and 'force' make sense here. +sp_case_label = ignore # ignore/add/remove/force + +# Control the space around the D '..' operator. +sp_range = ignore # ignore/add/remove/force + +# Control the spacing after ':' in 'for (TYPE VAR : EXPR)' (Java) +sp_after_for_colon = ignore # ignore/add/remove/force + +# Control the spacing before ':' in 'for (TYPE VAR : EXPR)' (Java) +sp_before_for_colon = ignore # ignore/add/remove/force + +# Control the spacing in 'extern (C)' (D) +sp_extern_paren = ignore # ignore/add/remove/force + +# Control the space after the opening of a C++ comment '// A' vs '//A' +sp_cmt_cpp_start = ignore # ignore/add/remove/force + +# Controls the spaces between #else or #endif and a trailing comment +sp_endif_cmt = ignore # ignore/add/remove/force + +# Controls the spaces after 'new', 'delete', and 'delete[]' +sp_after_new = ignore # ignore/add/remove/force + +# Controls the spaces before a trailing or embedded comment +sp_before_tr_emb_cmt = ignore # ignore/add/remove/force + +# Number of spaces before a trailing or embedded comment +sp_num_before_tr_emb_cmt = 0 # number + +# Control space between a Java annotation and the open paren. +sp_annotation_paren = ignore # ignore/add/remove/force + +# +# Code alignment (not left column spaces/tabs) +# + +# Whether to keep non-indenting tabs +align_keep_tabs = false # false/true + +# Whether to use tabs for aligning +align_with_tabs = false # false/true + +# Whether to bump out to the next tab when aligning +align_on_tabstop = false # false/true + +# Whether to left-align numbers +align_number_left = false # false/true + +# Align variable definitions in prototypes and functions +align_func_params = false # false/true + +# Align parameters in single-line functions that have the same name. +# The function names must already be aligned with each other. +align_same_func_call_params = false # false/true + +# The span for aligning variable definitions (0=don't align) +align_var_def_span = 0 # number + +# How to align the star in variable definitions. +# 0=Part of the type 'void * foo;' +# 1=Part of the variable 'void *foo;' +# 2=Dangling 'void *foo;' +align_var_def_star_style = 1 # number + +# How to align the '&' in variable definitions. +# 0=Part of the type +# 1=Part of the variable +# 2=Dangling +align_var_def_amp_style = 1 # number + +# The threshold for aligning variable definitions (0=no limit) +align_var_def_thresh = 0 # number + +# The gap for aligning variable definitions +align_var_def_gap = 0 # number + +# Whether to align the colon in struct bit fields +align_var_def_colon = false # false/true + +# Whether to align any attribute after the variable name +align_var_def_attribute = false # false/true + +# Whether to align inline struct/enum/union variable definitions +align_var_def_inline = false # false/true + +# The span for aligning on '=' in assignments (0=don't align) +align_assign_span = 0 # number + +# The threshold for aligning on '=' in assignments (0=no limit) +align_assign_thresh = 0 # number + +# The span for aligning on '=' in enums (0=don't align) +align_enum_equ_span = 0 # number + +# The threshold for aligning on '=' in enums (0=no limit) +align_enum_equ_thresh = 0 # number + +# The span for aligning struct/union (0=don't align) +align_var_struct_span = 0 # number + +# The threshold for aligning struct/union member definitions (0=no limit) +align_var_struct_thresh = 0 # number + +# The gap for aligning struct/union member definitions +align_var_struct_gap = 0 # number + +# The span for aligning struct initializer values (0=don't align) +align_struct_init_span = 0 # number + +# The minimum space between the type and the synonym of a typedef +align_typedef_gap = 0 # number + +# The span for aligning single-line typedefs (0=don't align) +align_typedef_span = 0 # number + +# How to align typedef'd functions with other typedefs +# 0: Don't mix them at all +# 1: align the open paren with the types +# 2: align the function type name with the other type names +align_typedef_func = 0 # number + +# Controls the positioning of the '*' in typedefs. Just try it. +# 0: Align on typedef type, ignore '*' +# 1: The '*' is part of type name: typedef int *pint; +# 2: The '*' is part of the type, but dangling: typedef int *pint; +align_typedef_star_style = 0 # number + +# Controls the positioning of the '&' in typedefs. Just try it. +# 0: Align on typedef type, ignore '&' +# 1: The '&' is part of type name: typedef int &pint; +# 2: The '&' is part of the type, but dangling: typedef int &pint; +align_typedef_amp_style = 0 # number + +# The span for aligning comments that end lines (0=don't align) +align_right_cmt_span = 0 # number + +# If aligning comments, mix with comments after '}' and #endif with less than 3 spaces before the comment +align_right_cmt_mix = false # false/true + +# If a trailing comment is more than this number of columns away from the text it follows, +# it will qualify for being aligned. This has to be > 0 to do anything. +align_right_cmt_gap = 0 # number + +# Align trailing comment at or beyond column N; 'pulls in' comments as a bonus side effect (0=ignore) +align_right_cmt_at_col = 0 # number + +# The span for aligning function prototypes (0=don't align) +align_func_proto_span = 0 # number + +# Minimum gap between the return type and the function name. +align_func_proto_gap = 0 # number + +# Align function protos on the 'operator' keyword instead of what follows +align_on_operator = false # false/true + +# Whether to mix aligning prototype and variable declarations. +# If true, align_var_def_XXX options are used instead of align_func_proto_XXX options. +align_mix_var_proto = false # false/true + +# Align single-line functions with function prototypes, uses align_func_proto_span +align_single_line_func = false # false/true + +# Aligning the open brace of single-line functions. +# Requires align_single_line_func=true, uses align_func_proto_span +align_single_line_brace = false # false/true + +# Gap for align_single_line_brace. +align_single_line_brace_gap = 0 # number + +# The span for aligning ObjC msg spec (0=don't align) +align_oc_msg_spec_span = 0 # number + +# Whether to align macros wrapped with a backslash and a newline. +# This will not work right if the macro contains a multi-line comment. +align_nl_cont = false # false/true + +# # Align macro functions and variables together +align_pp_define_together = false # false/true + +# The minimum space between label and value of a preprocessor define +align_pp_define_gap = 0 # number + +# The span for aligning on '#define' bodies (0=don't align) +align_pp_define_span = 0 # number + +# Align lines that start with '<<' with previous '<<'. Default=true +align_left_shift = true # false/true + +# Span for aligning parameters in an Obj-C message call on the ':' (0=don't align) +align_oc_msg_colon_span = 0 # number + +# If true, always align with the first parameter, even if it is too short. +align_oc_msg_colon_first = false # false/true + +# Aligning parameters in an Obj-C '+' or '-' declaration on the ':' +align_oc_decl_colon = false # false/true + +# +# Newline adding and removing options +# + +# Whether to collapse empty blocks between '{' and '}' +nl_collapse_empty_body = false # false/true + +# Don't split one-line braced assignments - 'foo_t f = { 1, 2 };' +nl_assign_leave_one_liners = false # false/true + +# Don't split one-line braced statements inside a class xx { } body +nl_class_leave_one_liners = false # false/true + +# Don't split one-line enums: 'enum foo { BAR = 15 };' +nl_enum_leave_one_liners = false # false/true + +# Don't split one-line get or set functions +nl_getset_leave_one_liners = false # false/true + +# Don't split one-line function definitions - 'int foo() { return 0; }' +nl_func_leave_one_liners = false # false/true + +# Don't split one-line if/else statements - 'if(a) b++;' +nl_if_leave_one_liners = false # false/true + +# Don't split one-line OC messages +nl_oc_msg_leave_one_liner = false # false/true + +# Add or remove newlines at the start of the file +nl_start_of_file = ignore # ignore/add/remove/force + +# The number of newlines at the start of the file (only used if nl_start_of_file is 'add' or 'force' +nl_start_of_file_min = 0 # number + +# Add or remove newline at the end of the file +nl_end_of_file = ignore # ignore/add/remove/force + +# The number of newlines at the end of the file (only used if nl_end_of_file is 'add' or 'force') +nl_end_of_file_min = 0 # number + +# Add or remove newline between '=' and '{' +nl_assign_brace = ignore # ignore/add/remove/force + +# Add or remove newline between '=' and '[' (D only) +nl_assign_square = ignore # ignore/add/remove/force + +# Add or remove newline after '= [' (D only). Will also affect the newline before the ']' +nl_after_square_assign = ignore # ignore/add/remove/force + +# The number of blank lines after a block of variable definitions at the top of a function body +# 0 = No change (default) +nl_func_var_def_blk = 0 # number + +# The number of newlines before a block of typedefs +# 0 = No change (default) +nl_typedef_blk_start = 0 # number + +# The number of newlines after a block of typedefs +# 0 = No change (default) +nl_typedef_blk_end = 0 # number + +# The maximum consecutive newlines within a block of typedefs +# 0 = No change (default) +nl_typedef_blk_in = 0 # number + +# The number of newlines before a block of variable definitions not at the top of a function body +# 0 = No change (default) +nl_var_def_blk_start = 0 # number + +# The number of newlines after a block of variable definitions not at the top of a function body +# 0 = No change (default) +nl_var_def_blk_end = 0 # number + +# The maximum consecutive newlines within a block of variable definitions +# 0 = No change (default) +nl_var_def_blk_in = 0 # number + +# Add or remove newline between a function call's ')' and '{', as in: +# list_for_each(item, &list) { } +nl_fcall_brace = ignore # ignore/add/remove/force + +# Add or remove newline between 'enum' and '{' +nl_enum_brace = ignore # ignore/add/remove/force + +# Add or remove newline between 'struct and '{' +nl_struct_brace = ignore # ignore/add/remove/force + +# Add or remove newline between 'union' and '{' +nl_union_brace = ignore # ignore/add/remove/force + +# Add or remove newline between 'if' and '{' +nl_if_brace = ignore # ignore/add/remove/force + +# Add or remove newline between '}' and 'else' +nl_brace_else = ignore # ignore/add/remove/force + +# Add or remove newline between 'else if' and '{' +# If set to ignore, nl_if_brace is used instead +nl_elseif_brace = ignore # ignore/add/remove/force + +# Add or remove newline between 'else' and '{' +nl_else_brace = ignore # ignore/add/remove/force + +# Add or remove newline between 'else' and 'if' +nl_else_if = ignore # ignore/add/remove/force + +# Add or remove newline between '}' and 'finally' +nl_brace_finally = ignore # ignore/add/remove/force + +# Add or remove newline between 'finally' and '{' +nl_finally_brace = ignore # ignore/add/remove/force + +# Add or remove newline between 'try' and '{' +nl_try_brace = ignore # ignore/add/remove/force + +# Add or remove newline between get/set and '{' +nl_getset_brace = ignore # ignore/add/remove/force + +# Add or remove newline between 'for' and '{' +nl_for_brace = ignore # ignore/add/remove/force + +# Add or remove newline between 'catch' and '{' +nl_catch_brace = ignore # ignore/add/remove/force + +# Add or remove newline between '}' and 'catch' +nl_brace_catch = ignore # ignore/add/remove/force + +# Add or remove newline between 'while' and '{' +nl_while_brace = ignore # ignore/add/remove/force + +# Add or remove newline between 'scope (x)' and '{' (D) +nl_scope_brace = ignore # ignore/add/remove/force + +# Add or remove newline between 'unittest' and '{' (D) +nl_unittest_brace = ignore # ignore/add/remove/force + +# Add or remove newline between 'version (x)' and '{' (D) +nl_version_brace = ignore # ignore/add/remove/force + +# Add or remove newline between 'using' and '{' +nl_using_brace = ignore # ignore/add/remove/force + +# Add or remove newline between two open or close braces. +# Due to general newline/brace handling, REMOVE may not work. +nl_brace_brace = ignore # ignore/add/remove/force + +# Add or remove newline between 'do' and '{' +nl_do_brace = ignore # ignore/add/remove/force + +# Add or remove newline between '}' and 'while' of 'do' statement +nl_brace_while = ignore # ignore/add/remove/force + +# Add or remove newline between 'switch' and '{' +nl_switch_brace = ignore # ignore/add/remove/force + +# Add a newline between ')' and '{' if the ')' is on a different line than the if/for/etc. +# Overrides nl_for_brace, nl_if_brace, nl_switch_brace, nl_while_switch, and nl_catch_brace. +nl_multi_line_cond = false # false/true + +# Force a newline in a define after the macro name for multi-line defines. +nl_multi_line_define = false # false/true + +# Whether to put a newline before 'case' statement +nl_before_case = false # false/true + +# Add or remove newline between ')' and 'throw' +nl_before_throw = ignore # ignore/add/remove/force + +# Whether to put a newline after 'case' statement +nl_after_case = false # false/true + +# Add or remove a newline between a case ':' and '{'. Overrides nl_after_case. +nl_case_colon_brace = ignore # ignore/add/remove/force + +# Newline between namespace and { +nl_namespace_brace = ignore # ignore/add/remove/force + +# Add or remove newline between 'template<>' and whatever follows. +nl_template_class = ignore # ignore/add/remove/force + +# Add or remove newline between 'class' and '{' +nl_class_brace = ignore # ignore/add/remove/force + +# Add or remove newline after each ',' in the constructor member initialization +nl_class_init_args = ignore # ignore/add/remove/force + +# Add or remove newline between return type and function name in a function definition +nl_func_type_name = ignore # ignore/add/remove/force + +# Add or remove newline between return type and function name inside a class {} +# Uses nl_func_type_name or nl_func_proto_type_name if set to ignore. +nl_func_type_name_class = ignore # ignore/add/remove/force + +# Add or remove newline between function scope and name in a definition +# Controls the newline after '::' in 'void A::f() { }' +nl_func_scope_name = ignore # ignore/add/remove/force + +# Add or remove newline between return type and function name in a prototype +nl_func_proto_type_name = ignore # ignore/add/remove/force + +# Add or remove newline between a function name and the opening '(' +nl_func_paren = remove # ignore/add/remove/force + +# Add or remove newline between a function name and the opening '(' in the definition +nl_func_def_paren = remove # ignore/add/remove/force + +# Add or remove newline after '(' in a function declaration +nl_func_decl_start = remove # ignore/add/remove/force + +# Add or remove newline after '(' in a function definition +nl_func_def_start = remove # ignore/add/remove/force + +# Overrides nl_func_decl_start when there is only one parameter. +nl_func_decl_start_single = ignore # ignore/add/remove/force + +# Overrides nl_func_def_start when there is only one parameter. +nl_func_def_start_single = ignore # ignore/add/remove/force + +# Add or remove newline after each ',' in a function declaration +nl_func_decl_args = ignore # ignore/add/remove/force + +# Add or remove newline after each ',' in a function definition +nl_func_def_args = ignore # ignore/add/remove/force + +# Add or remove newline before the ')' in a function declaration +nl_func_decl_end = ignore # ignore/add/remove/force + +# Add or remove newline before the ')' in a function definition +nl_func_def_end = remove # ignore/add/remove/force + +# Overrides nl_func_decl_end when there is only one parameter. +nl_func_decl_end_single = ignore # ignore/add/remove/force + +# Overrides nl_func_def_end when there is only one parameter. +nl_func_def_end_single = ignore # ignore/add/remove/force + +# Add or remove newline between '()' in a function declaration. +nl_func_decl_empty = ignore # ignore/add/remove/force + +# Add or remove newline between '()' in a function definition. +nl_func_def_empty = ignore # ignore/add/remove/force + +# Whether to put each OC message parameter on a separate line +# See nl_oc_msg_leave_one_liner +nl_oc_msg_args = false # false/true + +# Add or remove newline between function signature and '{' +nl_fdef_brace = add # ignore/add/remove/force + +# Add or remove a newline between the return keyword and return expression. +nl_return_expr = ignore # ignore/add/remove/force + +# Whether to put a newline after semicolons, except in 'for' statements +nl_after_semicolon = true # false/true + +# Whether to put a newline after brace open. +# This also adds a newline before the matching brace close. +nl_after_brace_open = true # false/true + +# If nl_after_brace_open and nl_after_brace_open_cmt are true, a newline is +# placed between the open brace and a trailing single-line comment. +nl_after_brace_open_cmt = false # false/true + +# Whether to put a newline after a virtual brace open with a non-empty body. +# These occur in un-braced if/while/do/for statement bodies. +nl_after_vbrace_open = false # false/true + +# Whether to put a newline after a virtual brace open with an empty body. +# These occur in un-braced if/while/do/for statement bodies. +nl_after_vbrace_open_empty = false # false/true + +# Whether to put a newline after a brace close. +# Does not apply if followed by a necessary ';'. +nl_after_brace_close = false # false/true + +# Whether to put a newline after a virtual brace close. +# Would add a newline before return in: 'if (foo) a++; return;' +nl_after_vbrace_close = false # false/true + +# Control the newline between the close brace and 'b' in: 'struct { int a; } b;' +# Affects enums, unions, and structures. If set to ignore, uses nl_after_brace_close +nl_brace_struct_var = ignore # ignore/add/remove/force + +# Whether to alter newlines in '#define' macros +nl_define_macro = false # false/true + +# Whether to not put blanks after '#ifxx', '#elxx', or before '#endif' +nl_squeeze_ifdef = false # false/true + +# Add or remove blank line before 'if' +nl_before_if = ignore # ignore/add/remove/force + +# Add or remove blank line after 'if' statement +nl_after_if = add # ignore/add/remove/force + +# Add or remove blank line before 'for' +nl_before_for = ignore # ignore/add/remove/force + +# Add or remove blank line after 'for' statement +nl_after_for = remove # ignore/add/remove/force + +# Add or remove blank line before 'while' +nl_before_while = ignore # ignore/add/remove/force + +# Add or remove blank line after 'while' statement +nl_after_while = remove # ignore/add/remove/force + +# Add or remove blank line before 'switch' +nl_before_switch = ignore # ignore/add/remove/force + +# Add or remove blank line after 'switch' statement +nl_after_switch = ignore # ignore/add/remove/force + +# Add or remove blank line before 'do' +nl_before_do = ignore # ignore/add/remove/force + +# Add or remove blank line after 'do/while' statement +nl_after_do = ignore # ignore/add/remove/force + +# Whether to double-space commented-entries in struct/enum +nl_ds_struct_enum_cmt = false # false/true + +# Whether to double-space before the close brace of a struct/union/enum +# (lower priority than 'eat_blanks_before_close_brace') +nl_ds_struct_enum_close_brace = false # false/true + +# Add or remove a newline around a class colon. +# Related to pos_class_colon, nl_class_init_args, and pos_comma. +nl_class_colon = ignore # ignore/add/remove/force + +# Change simple unbraced if statements into a one-liner +# 'if(b)\n i++;' => 'if(b) i++;' +nl_create_if_one_liner = false # false/true + +# Change simple unbraced for statements into a one-liner +# 'for (i=0;i<5;i++)\n foo(i);' => 'for (i=0;i<5;i++) foo(i);' +nl_create_for_one_liner = false # false/true + +# Change simple unbraced while statements into a one-liner +# 'while (i<5)\n foo(i++);' => 'while (i<5) foo(i++);' +nl_create_while_one_liner = false # false/true + +# +# Positioning options +# + +# The position of arithmetic operators in wrapped expressions +pos_arith = ignore # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force + +# The position of assignment in wrapped expressions. +# Do not affect '=' followed by '{' +pos_assign = ignore # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force + +# The position of boolean operators in wrapped expressions +pos_bool = ignore # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force + +# The position of comparison operators in wrapped expressions +pos_compare = ignore # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force + +# The position of conditional (b ? t : f) operators in wrapped expressions +pos_conditional = ignore # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force + +# The position of the comma in wrapped expressions +pos_comma = ignore # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force + +# The position of the comma in the constructor initialization list +pos_class_comma = ignore # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force + +# The position of colons between constructor and member initialization +pos_class_colon = ignore # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force + +# +# Line Splitting options +# + +# Try to limit code width to N number of columns +code_width = 0 # number + +# Whether to fully split long 'for' statements at semi-colons +ls_for_split_full = false # false/true + +# Whether to fully split long function protos/calls at commas +ls_func_split_full = false # false/true + +# Whether to split lines as close to code_width as possible and ignore some groupings +ls_code_width = false # false/true + +# +# Blank line options +# + +# The maximum consecutive newlines +nl_max = 0 # number + +# The number of newlines after a function prototype, if followed by another function prototype +nl_after_func_proto = 0 # number + +# The number of newlines after a function prototype, if not followed by another function prototype +nl_after_func_proto_group = 0 # number + +# The number of newlines after '}' of a multi-line function body +nl_after_func_body = 0 # number + +# The number of newlines after '}' of a multi-line function body in a class declaration +nl_after_func_body_class = 0 # number + +# The number of newlines after '}' of a single line function body +nl_after_func_body_one_liner = 0 # number + +# The minimum number of newlines before a multi-line comment. +# Doesn't apply if after a brace open or another multi-line comment. +nl_before_block_comment = 0 # number + +# The minimum number of newlines before a single-line C comment. +# Doesn't apply if after a brace open or other single-line C comments. +nl_before_c_comment = 0 # number + +# The minimum number of newlines before a CPP comment. +# Doesn't apply if after a brace open or other CPP comments. +nl_before_cpp_comment = 0 # number + +# Whether to force a newline after a multi-line comment. +nl_after_multiline_comment = false # false/true + +# The number of newlines after '}' or ';' of a struct/enum/union definition +nl_after_struct = 0 # number + +# The number of newlines after '}' or ';' of a class definition +nl_after_class = 0 # number + +# The number of newlines before a 'private:', 'public:', 'protected:', 'signals:', or 'slots:' label. +# Will not change the newline count if after a brace open. +# 0 = No change. +nl_before_access_spec = 0 # number + +# The number of newlines after a 'private:', 'public:', 'protected:', 'signals:', or 'slots:' label. +# 0 = No change. +nl_after_access_spec = 0 # number + +# The number of newlines between a function def and the function comment. +# 0 = No change. +nl_comment_func_def = 0 # number + +# The number of newlines after a try-catch-finally block that isn't followed by a brace close. +# 0 = No change. +nl_after_try_catch_finally = 0 # number + +# The number of newlines before and after a property, indexer or event decl. +# 0 = No change. +nl_around_cs_property = 0 # number + +# The number of newlines between the get/set/add/remove handlers in C#. +# 0 = No change. +nl_between_get_set = 0 # number + +# Add or remove newline between C# property and the '{' +nl_property_brace = ignore # ignore/add/remove/force + +# Whether to remove blank lines after '{' +eat_blanks_after_open_brace = false # false/true + +# Whether to remove blank lines before '}' +eat_blanks_before_close_brace = false # false/true + +# How aggressively to remove extra newlines not in preproc. +# 0: No change +# 1: Remove most newlines not handled by other config +# 2: Remove all newlines and reformat completely by config +nl_remove_extra_newlines = 0 # number + +# Whether to put a blank line before 'return' statements, unless after an open brace. +nl_before_return = false # false/true + +# Whether to put a blank line after 'return' statements, unless followed by a close brace. +nl_after_return = false # false/true + +# Whether to put a newline after a Java annotation statement. +# Only affects annotations that are after a newline. +nl_after_annotation = ignore # ignore/add/remove/force + +# Controls the newline between two annotations. +nl_between_annotation = ignore # ignore/add/remove/force + +# +# Code modifying options (non-whitespace) +# + +# Add or remove braces on single-line 'do' statement +mod_full_brace_do = ignore # ignore/add/remove/force + +# Add or remove braces on single-line 'for' statement +mod_full_brace_for = ignore # ignore/add/remove/force + +# Add or remove braces on single-line function definitions. (Pawn) +mod_full_brace_function = ignore # ignore/add/remove/force + +# Add or remove braces on single-line 'if' statement. Will not remove the braces if they contain an 'else'. +mod_full_brace_if = ignore # ignore/add/remove/force + +# Make all if/elseif/else statements in a chain be braced or not. Overrides mod_full_brace_if. +# If any must be braced, they are all braced. If all can be unbraced, then the braces are removed. +mod_full_brace_if_chain = false # false/true + +# Don't remove braces around statements that span N newlines +mod_full_brace_nl = 0 # number + +# Add or remove braces on single-line 'while' statement +mod_full_brace_while = ignore # ignore/add/remove/force + +# Add or remove braces on single-line 'using ()' statement +mod_full_brace_using = ignore # ignore/add/remove/force + +# Add or remove unnecessary paren on 'return' statement +mod_paren_on_return = ignore # ignore/add/remove/force + +# Whether to change optional semicolons to real semicolons +mod_pawn_semicolon = false # false/true + +# Add parens on 'while' and 'if' statement around bools +mod_full_paren_if_bool = false # false/true + +# Whether to remove superfluous semicolons +mod_remove_extra_semicolon = false # false/true + +# If a function body exceeds the specified number of newlines and doesn't have a comment after +# the close brace, a comment will be added. +mod_add_long_function_closebrace_comment = 0 # number + +# If a switch body exceeds the specified number of newlines and doesn't have a comment after +# the close brace, a comment will be added. +mod_add_long_switch_closebrace_comment = 0 # number + +# If an #ifdef body exceeds the specified number of newlines and doesn't have a comment after +# the #endif, a comment will be added. +mod_add_long_ifdef_endif_comment = 0 # number + +# If an #ifdef or #else body exceeds the specified number of newlines and doesn't have a comment after +# the #else, a comment will be added. +mod_add_long_ifdef_else_comment = 0 # number + +# If TRUE, will sort consecutive single-line 'import' statements [Java, D] +mod_sort_import = false # false/true + +# If TRUE, will sort consecutive single-line 'using' statements [C#] +mod_sort_using = false # false/true + +# If TRUE, will sort consecutive single-line '#include' statements [C/C++] and '#import' statements [Obj-C] +# This is generally a bad idea, as it may break your code. +mod_sort_include = false # false/true + +# If TRUE, it will move a 'break' that appears after a fully braced 'case' before the close brace. +mod_move_case_break = false # false/true + +# Will add or remove the braces around a fully braced case statement. +# Will only remove the braces if there are no variable declarations in the block. +mod_case_brace = ignore # ignore/add/remove/force + +# If TRUE, it will remove a void 'return;' that appears as the last statement in a function. +mod_remove_empty_return = false # false/true + +# +# Comment modifications +# + +# Try to wrap comments at cmt_width columns +cmt_width = 0 # number + +# Set the comment reflow mode (default: 0) +# 0: no reflowing (apart from the line wrapping due to cmt_width) +# 1: no touching at all +# 2: full reflow +cmt_reflow_mode = 0 # number + +# If false, disable all multi-line comment changes, including cmt_width. keyword substitution, and leading chars. +# Default is true. +cmt_indent_multi = true # false/true + +# Whether to group c-comments that look like they are in a block +cmt_c_group = false # false/true + +# Whether to put an empty '/*' on the first line of the combined c-comment +cmt_c_nl_start = false # false/true + +# Whether to put a newline before the closing '*/' of the combined c-comment +cmt_c_nl_end = false # false/true + +# Whether to group cpp-comments that look like they are in a block +cmt_cpp_group = false # false/true + +# Whether to put an empty '/*' on the first line of the combined cpp-comment +cmt_cpp_nl_start = false # false/true + +# Whether to put a newline before the closing '*/' of the combined cpp-comment +cmt_cpp_nl_end = false # false/true + +# Whether to change cpp-comments into c-comments +cmt_cpp_to_c = true # false/true + +# Whether to put a star on subsequent comment lines +cmt_star_cont = false # false/true + +# The number of spaces to insert at the start of subsequent comment lines +cmt_sp_before_star_cont = 0 # number + +# The number of spaces to insert after the star on subsequent comment lines +cmt_sp_after_star_cont = 0 # number + +# For multi-line comments with a '*' lead, remove leading spaces if the first and last lines of +# the comment are the same length. Default=True +cmt_multi_check_last = false # false/true + +# The filename that contains text to insert at the head of a file if the file doesn't start with a C/C++ comment. +# Will substitute $(filename) with the current file's name. +cmt_insert_file_header = "" # string + +# The filename that contains text to insert at the end of a file if the file doesn't end with a C/C++ comment. +# Will substitute $(filename) with the current file's name. +cmt_insert_file_footer = "" # string + +# The filename that contains text to insert before a function implementation if the function isn't preceded with a C/C++ comment. +# Will substitute $(function) with the function name and $(javaparam) with the javadoc @param and @return stuff. +# Will also substitute $(fclass) with the class name: void CFoo::Bar() { ... } +cmt_insert_func_header = "" # string + +# The filename that contains text to insert before a class if the class isn't preceded with a C/C++ comment. +# Will substitute $(class) with the class name. +cmt_insert_class_header = "" # string + +# The filename that contains text to insert before a Obj-C message specification if the method isn't preceeded with a C/C++ comment. +# Will substitute $(message) with the function name and $(javaparam) with the javadoc @param and @return stuff. +cmt_insert_oc_msg_header = "" # string + +# If a preprocessor is encountered when stepping backwards from a function name, then +# this option decides whether the comment should be inserted. +# Affects cmt_insert_oc_msg_header, cmt_insert_func_header and cmt_insert_class_header. +cmt_insert_before_preproc = false # false/true + +# +# Preprocessor options +# + +# Control indent of preprocessors inside #if blocks at brace level 0 +pp_indent = ignore # ignore/add/remove/force + +# Whether to indent #if/#else/#endif at the brace level (true) or from column 1 (false) +pp_indent_at_level = false # false/true + +# If pp_indent_at_level=false, specifies the number of columns to indent per level. Default=1. +pp_indent_count = 1 # number + +# Add or remove space after # based on pp_level of #if blocks +pp_space = ignore # ignore/add/remove/force + +# Sets the number of spaces added with pp_space +pp_space_count = 0 # number + +# The indent for #region and #endregion in C# and '#pragma region' in C/C++ +pp_indent_region = 0 # number + +# Whether to indent the code between #region and #endregion +pp_region_indent_code = false # false/true + +# If pp_indent_at_level=true, sets the indent for #if, #else, and #endif when not at file-level +pp_indent_if = 0 # number + +# Control whether to indent the code between #if, #else and #endif when not at file-level +pp_if_indent_code = false # false/true + +# Whether to indent '#define' at the brace level (true) or from column 1 (false) +pp_define_at_level = false # false/true + +# You can force a token to be a type with the 'type' option. +# Example: +# type myfoo1 myfoo2 +# +# You can create custom macro-based indentation using macro-open, +# macro-else and macro-close. +# Example: +# macro-open BEGIN_TEMPLATE_MESSAGE_MAP +# macro-open BEGIN_MESSAGE_MAP +# macro-close END_MESSAGE_MAP +# +# You can assign any keyword to any type with the set option. +# set func_call_user _ N_ +# +# The full syntax description of all custom definition config entries +# is shown below: +# +# define custom tokens as: +# - embed whitespace in token using '' escape character, or +# put token in quotes +# - these: ' " and ` are recognized as quote delimiters +# +# type token1 token2 token3 ... +# ^ optionally specify multiple tokens on a single line +# define def_token output_token +# ^ output_token is optional, then NULL is assumed +# macro-open token +# macro-close token +# macro-else token +# set id token1 token2 ... +# ^ optionally specify multiple tokens on a single line +# ^ id is one of the names in token_enum.h sans the CT_ prefix, +# e.g. PP_PRAGMA +# +# all tokens are separated by any mix of ',' commas, '=' equal signs +# and whitespace (space, tab) +# +set IF IF_APPNAME diff --git a/include/README.md b/include/README.md new file mode 100644 index 0000000..5c3216e --- /dev/null +++ b/include/README.md @@ -0,0 +1,4 @@ +ZeroTier SDK API +====== + +This is the externally facing C++ API for the SDK. It provides a platform-agnostic interface to the ZeroTier network virtualization service. diff --git a/include/ZeroTierSDK.h b/include/ZeroTierSDK.h new file mode 100644 index 0000000..a8deaaa --- /dev/null +++ b/include/ZeroTierSDK.h @@ -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 . + */ + +/* + * This defines the external C++ API for the ZeroTier SDK's service + */ + +#ifndef ZT_ZEROTIERSDK_H +#define ZT_ZEROTIERSDK_H + +#include + +/****************************************************************************/ +/* Defines */ +/****************************************************************************/ + +#define SDK_MTU 1200 //ZT_MAX_MTU // 2800, usually +#define UNIX_SOCK_BUF_SIZE 1024*1024 +#define ZT_PHY_POLL_INTERVAL 50 // in ms +// picoTCP +#define MAX_PICO_FRAME_RX_BUF_SZ ZT_MAX_MTU * 128 +// TCP +#define DEFAULT_TCP_TX_BUF_SZ 1024 * 1024 +#define DEFAULT_TCP_RX_BUF_SZ 1024 * 1024 +#define DEFAULT_TCP_TX_BUF_SOFTMAX DEFAULT_TCP_TX_BUF_SZ * 0.80 +#define DEFAULT_TCP_TX_BUF_SOFTMIN DEFAULT_TCP_TX_BUF_SZ * 0.20 +#define DEFAULT_TCP_RX_BUF_SOFTMAX DEFAULT_TCP_RX_BUF_SZ * 0.80 +#define DEFAULT_TCP_RX_BUF_SOFTMIN DEFAULT_TCP_RX_BUF_SZ * 0.20 +// UDP +#define DEFAULT_UDP_TX_BUF_SZ ZT_MAX_MTU +#define DEFAULT_UDP_RX_BUF_SZ ZT_MAX_MTU * 10 + + +/****************************************************************************/ +/* Socket API Signatures */ +/****************************************************************************/ + +#define SETSOCKOPT_SIG int fd, int level, int optname, const void *optval, socklen_t optlen +#define GETSOCKOPT_SIG int fd, int level, int optname, void *optval, socklen_t *optlen +#define SENDMSG_SIG int fd, const struct msghdr *msg, int flags +#define SENDTO_SIG int fd, const void *buf, size_t len, int flags, const struct sockaddr *addr, socklen_t addrlen +#define RECV_SIG int fd, void *buf, size_t len, int flags +#define RECVFROM_SIG int fd, void *buf, size_t len, int flags, struct sockaddr *addr, socklen_t *addrlen +#define RECVMSG_SIG int fd, struct msghdr *msg,int flags +#define SEND_SIG int fd, const void *buf, size_t len, int flags +#define WRITE_SIG int fd, const void *buf, size_t len +#define READ_SIG int fd, void *buf, size_t len +#define SOCKET_SIG int socket_family, int socket_type, int protocol +#define CONNECT_SIG int fd, const struct sockaddr *addr, socklen_t addrlen +#define BIND_SIG int fd, const struct sockaddr *addr, socklen_t addrlen +#define LISTEN_SIG int fd, int backlog +#define ACCEPT4_SIG int fd, struct sockaddr *addr, socklen_t *addrlen, int flags +#define ACCEPT_SIG int fd, struct sockaddr *addr, socklen_t *addrlen +#define CLOSE_SIG int fd +#define GETSOCKNAME_SIG int fd, struct sockaddr *addr, socklen_t *addrlen +#define GETPEERNAME_SIG int fd, struct sockaddr *addr, socklen_t *addrlen +#define FCNTL_SIG int fd, int cmd, int flags +#define SYSCALL_SIG long number, ... + +/****************************************************************************/ +/* SDK Socket API */ +/****************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Start core ZeroTier service (generates identity) + */ +void zts_start(const char *path); + + +void *zts_start_core_service(void *thread_id); + +char *zts_core_version(); +void zts_stop_service(); +void zts_stop(); +int zts_service_is_running(); +void zts_join_network(const char * nwid); +void zts_join_network_soft(const char * filepath, const char * nwid); +void zts_leave_network_soft(const char * filepath, const char * nwid); +void zts_leave_network(const char * nwid); +void zts_get_ipv4_address(const char *nwid, char *addrstr); +void zts_get_ipv6_address(const char *nwid, char *addrstr); +int zts_has_address(const char *nwid); +int zts_get_device_id(char *devID); +int zts_get_device_id_from_file(const char *filepath, char *devID); +int zts_get_peer_address(char *peer, const char *devID); +unsigned long zts_get_peer_count(); +//int zts_get_peer_list(); +char *zts_get_homepath(); +void zts_get_6plane_addr(char *addr, const char *nwid, const char *devID); +void zts_get_rfc4193_addr(char *addr, const char *nwid, const char *devID); +// BSD-like socket API +int zts_socket(SOCKET_SIG); +int zts_connect(CONNECT_SIG); +int zts_bind(BIND_SIG); +#if defined(__linux__) + int zts_accept4(ACCEPT4_SIG); +#endif +int zts_accept(ACCEPT_SIG); +int zts_listen(LISTEN_SIG); +int zts_setsockopt(SETSOCKOPT_SIG); +int zts_getsockopt(GETSOCKOPT_SIG); +int zts_getsockname(GETSOCKNAME_SIG); +int zts_getpeername(GETPEERNAME_SIG); +int zts_close(CLOSE_SIG); +int zts_fcntl(FCNTL_SIG); +ssize_t zts_sendto(SENDTO_SIG); +ssize_t zts_sendmsg(SENDMSG_SIG); +ssize_t zts_recvfrom(RECVFROM_SIG); +ssize_t zts_recvmsg(RECVMSG_SIG); + +/****************************************************************************/ +/* Debug */ +/****************************************************************************/ + +#include +#include +#include + +#define DEBUG_LEVEL 5 // Set this to adjust what you'd like to see in the debug traces + +#define MSG_ERROR 1 // Errors +#define MSG_TRANSFER 2 // RX/TX specific statements +#define MSG_INFO 3 // Information which is generally useful to any developer +#define MSG_EXTRA 4 // If nothing in your world makes sense +#define MSG_FLOW 5 // High-level flow messages + +#define __SHOW_FILENAMES__ true +#define __SHOW_COLOR__ true + +// Colors +#if defined(__APPLE__) + #include "TargetConditionals.h" +#endif +#if defined(__SHOW_COLOR__) && !defined(__ANDROID__) && !defined(TARGET_OS_IPHONE) && !defined(TARGET_IPHONE_SIMULATOR) && !defined(__APP_FRAMEWORK__) + #define RED "\x1B[31m" + #define GRN "\x1B[32m" + #define YEL "\x1B[33m" + #define BLU "\x1B[34m" + #define MAG "\x1B[35m" + #define CYN "\x1B[36m" + #define WHT "\x1B[37m" + #define RESET "\x1B[0m" +#else + #define RED + #define GRN + #define YEL + #define BLU + #define MAG + #define CYN + #define WHT + #define RESET +#endif + +// filenames +#if __SHOW_FILENAMES__ + #if __SHOW_FULL_FILENAME_PATH__ + #define __FILENAME__ __FILE__ // show the entire mess + #else + #define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) // shorten + #endif +#else + #define __FILENAME__ // omit filename +#endif + +#ifdef __linux__ + #define THREAD_ID 0 /*(long)getpid()*/ +#elif __APPLE__ + #define THREAD_ID 0 /*(long)syscall(SYS_thread_selfid)*/ +#endif + +#if defined(__JNI_LIB__) + #include +#endif +#if defined(__ANDROID__) + #include + #define LOG_TAG "ZTSDK" +#endif + + #if DEBUG_LEVEL >= MSG_ERROR + #define DEBUG_ERROR(fmt, args...) fprintf(stderr, RED "ZT_ERROR[%ld] : %14s:%4d:%25s: " fmt "\n" RESET, THREAD_ID, __FILENAME__, __LINE__, __FUNCTION__, ##args) + #else + #define DEBUG_ERROR(fmt, args...) + #endif + + #if DEBUG_LEVEL >= MSG_INFO + #if defined(__ANDROID__) + #define DEBUG_INFO(fmt, args...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, "ZT_INFO : %14s:%4d:%20s: " fmt "\n", __FILENAME__, __LINE__, __FUNCTION__, ##args)) + #define DEBUG_BLANK(fmt, args...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, "ZT_INFO : %14s:%4d:" fmt "\n", __FILENAME__, __LINE__, __FUNCTION__, ##args)) + #define DEBUG_ATTN(fmt, args...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, "ZT_INFO : %14s:%4d:%25s: " fmt "\n", __FILENAME__, __LINE__, __FUNCTION__, ##args)) + #define DEBUG_STACK(fmt, args...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, "ZT_STACK: %14s:%4d:%25s: " fmt "\n", __FILENAME__, __LINE__, __FUNCTION__, ##args)) + #else + #define DEBUG_INFO(fmt, args...) fprintf(stderr, "ZT_INFO [%ld] : %14s:%4d:%25s: " fmt "\n", THREAD_ID, __FILENAME__, __LINE__, __FUNCTION__, ##args) + #define DEBUG_ATTN(fmt, args...) fprintf(stderr, CYN "ZT_ATTN [%ld] : %14s:%4d:%25s: " fmt "\n" RESET, THREAD_ID, __FILENAME__, __LINE__, __FUNCTION__, ##args) + #define DEBUG_STACK(fmt, args...) fprintf(stderr, YEL "ZT_STACK[%ld] : %14s:%4d:%25s: " fmt "\n" RESET, THREAD_ID, __FILENAME__, __LINE__, __FUNCTION__, ##args) + #define DEBUG_BLANK(fmt, args...) fprintf(stderr, "ZT_INFO [%ld] : %14s:%4d:" fmt "\n", THREAD_ID, __FILENAME__, __LINE__, ##args) + #endif + #else + #define DEBUG_INFO(fmt, args...) + #define DEBUG_BLANK(fmt, args...) + #define DEBUG_ATTN(fmt, args...) + #define DEBUG_STACK(fmt, args...) + #endif + + #if DEBUG_LEVEL >= MSG_TRANSFER + #if defined(__ANDROID__) + #define DEBUG_TRANS(fmt, args...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, "ZT_TRANS : %14s:%4d:%25s: " fmt "\n", __FILENAME__, __LINE__, __FUNCTION__, ##args)) + #else + #define DEBUG_TRANS(fmt, args...) fprintf(stderr, GRN "ZT_TRANS[%ld] : %14s:%4d:%25s: " fmt "\n" RESET, THREAD_ID, __FILENAME__, __LINE__, __FUNCTION__, ##args) + #endif + #else + #define DEBUG_TRANS(fmt, args...) + #endif + + #if DEBUG_LEVEL >= MSG_EXTRA + #if defined(__ANDROID__) + #define DEBUG_EXTRA(fmt, args...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, "ZT_EXTRA : %14s:%4d:%25s: " fmt "\n", __FILENAME__, __LINE__, __FUNCTION__, ##args)) + #else + #define DEBUG_EXTRA(fmt, args...) fprintf(stderr, "ZT_EXTRA[%ld] : %14s:%4d:%25s: " fmt "\n", THREAD_ID, __FILENAME__, __LINE__, __FUNCTION__, ##args) + #endif + #else + #define DEBUG_EXTRA(fmt, args...) + #endif + +#if DEBUG_LEVEL >= MSG_FLOW + #if defined(__ANDROID__) + #define DEBUG_FLOW(fmt, args...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, "ZT_FLOW : %14s:%4d:%25s: " fmt "\n", __FILENAME__, __LINE__, __FUNCTION__, ##args)) + #else + #define DEBUG_FLOW(fmt, args...) fprintf(stderr, "ZT_FLOW [%ld] : %14s:%4d:%25s: " fmt "\n", THREAD_ID, __FILENAME__, __LINE__, __FUNCTION__, ##args) + #endif + #else + #define DEBUG_FLOW(fmt, args...) + #endif + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // ZT_ZEROTIERSDK_H diff --git a/make-linux.mk b/make-linux.mk new file mode 100644 index 0000000..0b57e52 --- /dev/null +++ b/make-linux.mk @@ -0,0 +1,15 @@ + +# 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 + + + +all: + +static_lib: $(OBJS) diff --git a/make-mac.mk b/make-mac.mk new file mode 100644 index 0000000..915d54d --- /dev/null +++ b/make-mac.mk @@ -0,0 +1,168 @@ + +# 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 + +############################################################################## +## General Configuration ## +############################################################################## + +include objects.mk + +# Target output filenames +STATIC_LIB_NAME = libzt.a +INTERCEPT_NAME = libztintercept.so +SDK_SERVICE_NAME = zerotier-sdk-service +ONE_SERVICE_NAME = zerotier-one +PICO_LIB_NAME = libpicotcp.a +# +STATIC_LIB = $(BUILD)/$(STATIC_LIB_NAME) +SDK_INTERCEPT = $(BUILD)/$(INTERCEPT_NAME) +SDK_SERVICE = $(BUILD)/$(SDK_SERVICE_NAME) +ONE_SERVICE = $(BUILD)/$(ONE_SERVICE_NAME) +PICO_LIB = ext/picotcp/build/lib/$(PICO_LIB_NAME) + +# Debug output for ZeroTier service +ifeq ($(ZT_DEBUG),1) + DEFS+=-DZT_TRACE + #CFLAGS+=-Wall -fPIE -fvisibility=hidden -pthread $(INCLUDES) $(DEFS) + 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. +#ext/lz4/lz4.o node/Salsa20.o node/SHA512.o node/C25519.o node/Poly1305.o: CFLAGS = -Wall -O2 -g -pthread $(INCLUDES) $(DEFS) +else + CFLAGS?=-Ofast -fstack-protector + CFLAGS+=-Wall -fPIE -fvisibility=hidden -pthread $(INCLUDES) $(DEFS) + #CFLAGS+=$(ARCH_FLAGS) -Wall -flto -fPIC -pthread -mmacosx-version-min=10.7 -DNDEBUG -Wno-unused-private-field $(INCLUDES) $(DEFS) + STRIP=strip +endif + +CXXFLAGS=$(CFLAGS) -Wno-format -fno-rtti -std=c++11 -DZT_SDK + +INCLUDES+= -Iext \ + -I$(ZTO)/osdep \ + -I$(ZTO)/node \ + -I$(ZTO)/service \ + -I$(ZTO)/include \ + -I../$(ZTO)/osdep \ + -I../$(ZTO)/node \ + -I../$(ZTO)/service \ + -I. \ + -Isrc \ + -Iinclude \ + -Iext/picotcp/include \ + -Iext/picotcp/build/include + +############################################################################## +## User Build Flags ## +############################################################################## + +# Debug option, prints filenames, lines, functions, arguments, etc +# Also enables debug symbols for debugging with tools like gdb, etc +ifeq ($(SDK_DEBUG),1) + SDK_FLAGS+=-DSDK_PICOTCP + CXXFLAGS+=-g + INCLUDES+= -I$(PICOTCP_DIR)/include \ + -I$(PICOTCP_DIR)/build/include \ + -Isrc/stack_drivers/picotcp +endif + +############################################################################## +## Stack Configuration ## +############################################################################## + +# Stack config flags +ifeq ($(SDK_PICOTCP),1) + SDK_FLAGS+=-DSDK_PICOTCP + INCLUDES+= -I$(PICOTCP_DIR)/include \ + -I$(PICOTCP_DIR)/build/include \ + -Isrc/stack_drivers/picotcp +endif +ifeq ($(SDK_IPV4),1) + SDK_FLAGS+=-DSDK_IPV4 +endif +ifeq ($(SDK_IPV6),1) + SDK_FLAGS+=-DSDK_IPV6 +endif + + +############################################################################## +## Files ## +############################################################################## + +STACK_DRIVER_FILES:=src/picoTCP.cpp +TAP_FILES:=src/SocketTap.cpp \ + src/SDKService.cpp + +RPC_FILES:=src/RPC.c +SOCKET_API_FILES:=src/Socket.c + +SDK_OBJS+= RPC.o \ + SocketTap.o \ + Socket.o \ + picoTCP.o \ + SDKService.o + +PICO_OBJS+= ext/picotcp/build/lib/pico_device.o \ +ext/picotcp/build/lib/pico_frame.o \ +ext/picotcp/build/lib/pico_md5.o \ +ext/picotcp/build/lib/pico_protocol.o \ +ext/picotcp/build/lib/pico_socket_multicast.o \ +ext/picotcp/build/lib/pico_socket.o \ +ext/picotcp/build/lib/pico_stack.o \ +ext/picotcp/build/lib/pico_tree.o + +all: + +############################################################################## +## User-Space Stack ## +############################################################################## + +picotcp: + cd ext/picotcp; make lib ARCH=shared IPV4=1 IPV6=1 + +############################################################################## +## Static Libraries ## +############################################################################## + +static_lib: picotcp $(ZTO_OBJS) + $(CXX) $(CXXFLAGS) $(SDK_FLAGS) $(RPC_FILES) $(TAP_FILES) $(STACK_DRIVER_FILES) $(SOCKET_API_FILES) -c + libtool -static -o $(STATIC_LIB) $(ZTO_OBJS) $(SDK_OBJS) $(PICO_LIB) + +jni_static_lib: picotcp $(ZTO_OBJS) + +############################################################################## +## Unit Tests ## +############################################################################## + +static_lib_test: + $(CXX) -Iinclude tests/socket/simple.cpp -o build/simple.out -Lbuild -lzt + +############################################################################## +## Misc ## +############################################################################## + +clean: + -rm -rf $(BUILD)/* + -rm -rf zerotier-cli zerotier-idtool + -find . -type f \( -name $(ONE_SERVICE_NAME) -o -name $(SDK_SERVICE_NAME) \) -delete + -find . -type f \( -name '*.o' -o -name '*.so' -o -name '*.o.d' -o -name '*.out' -o -name '*.log' -o -name '*.dSYM' \) -delete + +# Check for the presence of built frameworks/bundles/libaries +check: + -./check.sh $(PICO_LIB) + -./check.sh $(SDK_INTERCEPT) + -./check.sh $(ONE_SERVICE) + -./check.sh $(SDK_SERVICE) + -./check.sh $(STATIC_LIB) + + +#osx_static_lib: pico $(ZTO_OBJS) +# $(CXX) $(CXXFLAGS) $(STACK_FLAGS) $(DEFS) $(INCLUDES) $(ZTFLAGS) -DSDK_SERVICE -DSDK -DSDK_BUNDLED $(PICO_DRIVER_FILES) $(SDK_INTERCEPT_C_FILES) $(SDK_SERVICE_CPP_FILES) src/service.cpp -c +# libtool -static -o build/libzt.a picotcp.o proxy.o tap.o one.o OneService.o service.o sockets.o rpc.o intercept.o $(ZTO_OBJS) \ No newline at end of file diff --git a/objects.mk b/objects.mk new file mode 100644 index 0000000..88b7e38 --- /dev/null +++ b/objects.mk @@ -0,0 +1,37 @@ +ZTO_OBJS=\ + zto/controller/EmbeddedNetworkController.o \ + zto/controller/JSONDB.o \ + zto/node/C25519.o \ + zto/node/Capability.o \ + zto/node/CertificateOfMembership.o \ + zto/node/CertificateOfOwnership.o \ + zto/node/Cluster.o \ + zto/node/Identity.o \ + zto/node/IncomingPacket.o \ + zto/node/InetAddress.o \ + zto/node/Membership.o \ + zto/node/Multicaster.o \ + zto/node/Network.o \ + zto/node/NetworkConfig.o \ + zto/node/Node.o \ + zto/node/OutboundMulticast.o \ + zto/node/Packet.o \ + zto/node/Path.o \ + zto/node/Peer.o \ + zto/node/Poly1305.o \ + zto/node/Revocation.o \ + zto/node/Salsa20.o \ + zto/node/SelfAwareness.o \ + zto/node/SHA512.o \ + zto/node/Switch.o \ + zto/node/Tag.o \ + zto/node/Topology.o \ + zto/node/Utils.o \ + zto/osdep/ManagedRoute.o \ + zto/osdep/Http.o \ + zto/osdep/OSUtils.o \ + zto/service/ClusterGeoIpService.o \ + zto/service/SoftwareUpdater.o \ + zto/service/OneService.o \ + zto/ext/http-parser/http_parser.o + diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..67d098f --- /dev/null +++ b/src/README.md @@ -0,0 +1,2 @@ +src +====== \ No newline at end of file diff --git a/src/RPC.c b/src/RPC.c new file mode 100644 index 0000000..e1064f3 --- /dev/null +++ b/src/RPC.c @@ -0,0 +1,343 @@ +/* + * 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/ + */ + +#ifdef USE_GNU_SOURCE +#define _GNU_SOURCE +#endif + +#if defined(__linux__) + #include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ZeroTierSDK.h" +#include "RPC.h" + +// externs common between SDK_Intercept and SDK_Socket from SDK.h +int (*realsocket)(SOCKET_SIG); +int (*realconnect)(CONNECT_SIG); + +#ifdef __cplusplus +extern "C" { +#endif + +#define SERVICE_CONNECT_ATTEMPTS 30 + +ssize_t sock_fd_write(int sock, int fd); +ssize_t sock_fd_read(int sock, void *buf, ssize_t bufsize, int *fd); + +static int rpc_count; + +static pthread_mutex_t lock; +void rpc_mutex_init() { + if(pthread_mutex_init(&lock, NULL) != 0) { + } +} +void rpc_mutex_destroy() { + pthread_mutex_destroy(&lock); +} + +/* + * Reads a new file descriptor from the service + */ +int get_new_fd(int sock) +{ + char buf[BUF_SZ]; + int newfd; + ssize_t size = sock_fd_read(sock, buf, sizeof(buf), &newfd); + if(size > 0) + return newfd; + return -1; +} + +/* + * Reads a return value from the service and sets errno (if applicable) + */ +int get_retval(int rpc_sock) +{ + if(rpc_sock >= 0) { + int retval; + int sz = sizeof(char) + sizeof(retval) + sizeof(errno); + char retbuf[BUF_SZ]; + memset(&retbuf, 0, sz); + long n_read = read(rpc_sock, &retbuf, sz); + if(n_read > 0) { + memcpy(&retval, &retbuf[1], sizeof(retval)); + memcpy(&errno, &retbuf[1+sizeof(retval)], sizeof(errno)); + return retval; + } + } + return -1; +} + +int load_symbols_rpc() +{ +#if defined(__IOS__) || defined(__UNITY_3D__) + realsocket = dlsym(RTLD_NEXT, "socket"); + realconnect = dlsym(RTLD_NOW, "connect"); + if(!realconnect || !realsocket) + return -1; +#endif + return 1; +} + +int rpc_join(char * sockname) +{ + if(sockname == NULL) { + DEBUG_ERROR("warning, rpc netpath is NULL"); + } + if(!load_symbols_rpc()) + return -1; + struct sockaddr_un addr; + int conn_err = -1, attempts = 0; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, sockname, sizeof(addr.sun_path)-1); + int sock; + +#if defined(SDK_INTERCEPT) + if((sock = realsocket(AF_UNIX, SOCK_STREAM, 0)) < 0){ +#else + if((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0){ +#endif + DEBUG_ERROR("error creating RPC socket"); + return -1; + } + while((conn_err != 0) /* && (attempts < SERVICE_CONNECT_ATTEMPTS) */){ + #if defined(SDK_INTERCEPT) + if((conn_err = realconnect(sock, (struct sockaddr*)&addr, sizeof(addr))) != 0) { + #else + if((conn_err = connect(sock, (struct sockaddr*)&addr, sizeof(addr))) != 0) { + #endif + DEBUG_ERROR("error connecting to RPC socket (%s). Re-attempting...", sockname); + usleep(100000); + } + else + return sock; + attempts++; + } + return -1; +} + +/* + * Send a command to the service + */ +int rpc_send_command(char *path, int cmd, int forfd, void *data, int len) +{ + pthread_mutex_lock(&lock); + char c, padding[] = {PADDING}; + char cmdbuf[BUF_SZ], CANARY[CANARY_SZ+PADDING_SZ], metabuf[BUF_SZ]; + + memcpy(CANARY+CANARY_SZ, padding, sizeof(padding)); + uint64_t canary_num; + // ephemeral RPC socket used only for this command + int rpc_sock = rpc_join(path); + + // Generate token + int fdrand = open("/dev/urandom", O_RDONLY); + if(read(fdrand, &CANARY, CANARY_SZ) < 0) { + DEBUG_ERROR("unable to read from /dev/urandom for RPC canary data"); + return -1; + } + + close(fdrand); + memcpy(&canary_num, CANARY, CANARY_SZ); + cmdbuf[CMD_ID_IDX] = cmd; + memcpy(&cmdbuf[CANARY_IDX], &canary_num, CANARY_SZ); + memcpy(&cmdbuf[STRUCT_IDX], data, len); + + rpc_count++; + memset(metabuf, 0, BUF_SZ); +#if defined(__linux__) + #if !defined(__ANDROID__) + pid_t pid = 5; //syscall(SYS_getpid); + pid_t tid = 4;//syscall(SYS_gettid); + #else + // Dummy values + pid_t pid = 5; + pid_t tid = gettid(); + #endif +#endif + char timestring[20]; + time_t timestamp; + timestamp = time(NULL); + strftime(timestring, sizeof(timestring), "%H:%M:%S", localtime(×tamp)); +#if defined(__linux__) + memcpy(&metabuf[IDX_PID], &pid, sizeof(pid_t) ); /* pid */ + memcpy(&metabuf[IDX_TID], &tid, sizeof(pid_t) ); /* tid */ +#endif + memcpy(&metabuf[IDX_TIME], ×tring, 20 ); /* timestamp */ + + /* Combine command flag+payload with RPC metadata */ + memcpy(metabuf, RPC_PHRASE, RPC_PHRASE_SZ); // Write signal phrase + memcpy(&metabuf[IDX_PAYLOAD], cmdbuf, len + 1 + CANARY_SZ); + + // Write RPC + long n_write = write(rpc_sock, &metabuf, BUF_SZ); + if(n_write < 0) { + DEBUG_ERROR("error writing command to service (CMD = %d)", cmdbuf[CMD_ID_IDX]); + errno = 0; + } + // Write token to corresponding data stream + if(read(rpc_sock, &c, 1) < 0) { + DEBUG_ERROR("unable to read RPC ACK byte from service."); + close(rpc_sock); + return -1; + } + if(c == 'z' && n_write > 0 && forfd > -1){ + if(send(forfd, &CANARY, CANARY_SZ+PADDING_SZ, 0) < 0) { + perror("send: \n"); + DEBUG_ERROR("unable to write canary to stream (fd=%d)", forfd); + close(rpc_sock); + return -1; + } + } + // Process response from service + int ret = ERR_OK; + if(n_write > 0) { + if(cmdbuf[CMD_ID_IDX]==RPC_SOCKET) { + pthread_mutex_unlock(&lock); + return rpc_sock; // Used as new socket + } + if(cmdbuf[CMD_ID_IDX]==RPC_CONNECT + || cmdbuf[CMD_ID_IDX]==RPC_BIND + || cmdbuf[CMD_ID_IDX]==RPC_LISTEN) { + ret = get_retval(rpc_sock); + } + if(cmdbuf[CMD_ID_IDX]==RPC_GETSOCKNAME || cmdbuf[CMD_ID_IDX]==RPC_GETPEERNAME) { + pthread_mutex_unlock(&lock); + return rpc_sock; // Don't close rpc here, we'll use it to read getsockopt_st + } + } + else + ret = -1; + close(rpc_sock); // We're done with this RPC socket, close it (if type-R) + pthread_mutex_unlock(&lock); + return ret; +} + +/* + * Send file descriptor + */ +ssize_t sock_fd_write(int sock, int fd) +{ + ssize_t size; + struct msghdr msg; + struct iovec iov; + char buf = '\0'; + int buflen = 1; + union { + struct cmsghdr cmsghdr; + char control[CMSG_SPACE(sizeof (int))]; + } cmsgu; + struct cmsghdr *cmsg; + iov.iov_base = &buf; + iov.iov_len = buflen; + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + if (fd != -1) { + msg.msg_control = cmsgu.control; + msg.msg_controllen = sizeof(cmsgu.control); + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = CMSG_LEN(sizeof (int)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + *((int *) CMSG_DATA(cmsg)) = fd; + } else { + msg.msg_control = NULL; + msg.msg_controllen = 0; + } + size = sendmsg(sock, &msg, 0); + if (size < 0) + perror ("sendmsg"); + return size; +} +/* + * Read a file descriptor + */ +ssize_t sock_fd_read(int sock, void *buf, ssize_t bufsize, int *fd) +{ + ssize_t size; + if (fd) { + struct msghdr msg; + struct iovec iov; + union { + struct cmsghdr cmsghdr; + char control[CMSG_SPACE(sizeof (int))]; + } cmsgu; + + struct cmsghdr *cmsg; + iov.iov_base = buf; + iov.iov_len = bufsize; + msg.msg_name = NULL; + msg.msg_namelen = 0; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = cmsgu.control; + msg.msg_controllen = sizeof(cmsgu.control); + size = recvmsg (sock, &msg, 0); + + if (size < 0) + return -1; + cmsg = CMSG_FIRSTHDR(&msg); + if (cmsg && cmsg->cmsg_len == CMSG_LEN(sizeof(int))) { + if (cmsg->cmsg_level != SOL_SOCKET) { + DEBUG_ERROR("invalid cmsg_level %d",cmsg->cmsg_level); + return -1; + } + if (cmsg->cmsg_type != SCM_RIGHTS) { + DEBUG_ERROR("invalid cmsg_type %d",cmsg->cmsg_type); + return -1; + } + *fd = *((int *) CMSG_DATA(cmsg)); + } else { +*fd = -1;} + } else { + size = read (sock, buf, bufsize); + if (size < 0) { + DEBUG_ERROR("sock_fd_read(): read: Error"); + return -1; + } + } + return size; +} + +#ifdef __cplusplus +} +#endif diff --git a/src/RPC.h b/src/RPC.h new file mode 100644 index 0000000..1385577 --- /dev/null +++ b/src/RPC.h @@ -0,0 +1,144 @@ +/* + * 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 __RPCLIB_H_ +#define __RPCLIB_H_ + +#include + +#define CANARY_SZ sizeof(uint64_t) +#define PADDING_SZ 12 +#define PADDING 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 + +#define RPC_PHRASE "zerotier\0" +#define RPC_PHRASE_SZ 9 +#define RPC_TIMESTAMP_SZ 20 +// 1st RPC section (metdata) +#define IDX_SIGNAL_PHRASE 0 +#define IDX_PID IDX_SIGNAL_PHRASE + RPC_PHRASE_SZ +#define IDX_TID sizeof(pid_t) + IDX_PID +#define IDX_TIME IDX_TID + sizeof(int) +#define IDX_PAYLOAD IDX_TIME + RPC_TIMESTAMP_SZ +// 2nd RPC section (payload and canary) +#define CMD_ID_IDX 0 +#define CANARY_IDX 1 +#define STRUCT_IDX CANARY_IDX+CANARY_SZ + +#define BUF_SZ 512 + +#define ERR_OK 0 + +/* RPC codes */ +#define RPC_UNDEFINED 0 +#define RPC_CONNECT 1 +#define RPC_CONNECT_SOCKARG 2 +#define RPC_CLOSE 3 +#define RPC_READ 4 +#define RPC_WRITE 5 +#define RPC_BIND 6 +#define RPC_ACCEPT 7 +#define RPC_LISTEN 8 +#define RPC_SOCKET 9 +#define RPC_SHUTDOWN 10 +#define RPC_GETSOCKNAME 11 +#define RPC_GETPEERNAME 12 +#define RPC_RETVAL 13 +#define RPC_IS_CONNECTED 14 + + +#ifdef __cplusplus +extern "C" { +#endif + +int get_retval(int); +int rpc_join( char * sockname); +int rpc_send_command(char *path, int cmd, int forfd, void *data, int len); + +int get_new_fd(int sock); +ssize_t sock_fd_write(int sock, int fd); +ssize_t sock_fd_read(int sock, void *buf, ssize_t bufsize, int *fd); + +void rpc_mutex_destroy(); +void rpc_mutex_init(); + + +/* Structures used for sending commands via RPC mechanism */ + +struct bind_st { + int fd; + struct sockaddr_storage addr; + socklen_t addrlen; + int tid; +}; + +struct connect_st { + int fd; + struct sockaddr_storage addr; + socklen_t addrlen; + int tid; +}; + +struct close_st { + int fd; +}; + +struct listen_st { + int fd; + int backlog; + int tid; +}; + +struct socket_st { + int socket_family; + int socket_type; + int protocol; + int tid; +}; + +struct accept_st { + int fd; + struct sockaddr_storage addr; + socklen_t addrlen; + int tid; +}; + +struct shutdown_st { + int socket; + int how; +}; + +struct getsockname_st { + int fd; + struct sockaddr_storage addr; + socklen_t addrlen; +}; + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/src/SDKService.cpp b/src/SDKService.cpp new file mode 100644 index 0000000..2ac4572 --- /dev/null +++ b/src/SDKService.cpp @@ -0,0 +1,435 @@ +/* + * 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/ + */ + +#if defined(__ANDROID__) || defined(__JNI_LIB__) + #include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "OneService.hpp" +#include "Utils.hpp" +#include "OSUtils.hpp" +#include "InetAddress.hpp" +#include "ZeroTierOne.h" + +#include "SocketTap.hpp" +#include "ZeroTierSDK.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static ZeroTier::OneService *zt1Service; + +std::string service_path; +std::string localHomeDir; // Local shortened path +std::string givenHomeDir; // What the user/application provides as a suggestion +std::string homeDir; // The resultant platform-specific dir we *must* use internally +std::string netDir; // Where network .conf files are to be written + + +/****************************************************************************/ +/* SDK Socket API */ +/****************************************************************************/ + +void zts_start(const char *path) +{ + DEBUG_INFO("path=%s", path); + if(path) + homeDir = path; + zts_start_core_service(NULL); +} + +// Stop the service, proxy server, stack, etc +void zts_stop() { + DEBUG_INFO(); + zts_stop_service(); +} + +char *zts_core_version() { + return (char*)"1.2.2"; +} + + // ------------------------------------------------------------------------------ + // --------------------------------- Base zts_* API ----------------------------- + // ------------------------------------------------------------------------------ + +// Prototypes +void *zts_start_core_service(void *thread_id); +void zts_init_rpc(const char * path, const char * nwid); + +// Basic ZT service controls +void zts_join_network(const char * nwid) { + DEBUG_ERROR(); + std::string confFile = zt1Service->givenHomePath() + "/networks.d/" + nwid + ".conf"; + if(!ZeroTier::OSUtils::mkdir(netDir)) { + DEBUG_ERROR("unable to create: %s", netDir.c_str()); + } + if(!ZeroTier::OSUtils::writeFile(confFile.c_str(), "")) { + DEBUG_ERROR("unable to write network conf file: %s", confFile.c_str()); + } + zt1Service->join(nwid); + // Provide the API with the RPC information + zts_init_rpc(homeDir.c_str(), nwid); +} +// Just create the dir and conf file required, don't instruct the core to do anything +void zts_join_network_soft(const char * filepath, const char * nwid) { + std::string net_dir = std::string(filepath) + "/networks.d/"; + std::string confFile = net_dir + std::string(nwid) + ".conf"; + if(!ZeroTier::OSUtils::mkdir(net_dir)) { + DEBUG_ERROR("unable to create: %s", net_dir.c_str()); + } + if(!ZeroTier::OSUtils::fileExists(confFile.c_str(),false)) { + if(!ZeroTier::OSUtils::writeFile(confFile.c_str(), "")) { + DEBUG_ERROR("unable to write network conf file: %s", confFile.c_str()); + } + } +} +// Prevent service from joining network upon startup +void zts_leave_network_soft(const char * filepath, const char * nwid) { + std::string net_dir = std::string(filepath) + "/networks.d/"; + ZeroTier::OSUtils::rm((net_dir + nwid + ".conf").c_str()); +} +// Instruct the service to leave the network +void zts_leave_network(const char * nwid) { + if(zt1Service) + zt1Service->leave(nwid); +} +// Check whether the service is running +int zts_service_is_running() { + return !zt1Service ? false : zt1Service->isRunning(); +} +// Stop the service +void zts_stop_service() { + if(zt1Service) + zt1Service->terminate(); +} + + +// FIXME: Re-implemented to make it play nicer with the C-linkage required for Xcode integrations +// Now only returns first assigned address per network. Shouldn't normally be a problem. + +// Get IPV4 Address for this device on given network +int zts_has_address(const char *nwid) +{ + char ipv4_addr[64], ipv6_addr[64]; + memset(ipv4_addr, 0, 64); + memset(ipv6_addr, 0, 64); + zts_get_ipv4_address(nwid, ipv4_addr); + zts_get_ipv6_address(nwid, ipv6_addr); + if(!strcmp(ipv4_addr, "-1.-1.-1.-1/-1") && !strcmp(ipv4_addr, "-1.-1.-1.-1/-1")) { + return false; + } + return true; +} +void zts_get_ipv4_address(const char *nwid, char *addrstr) +{ + uint64_t nwid_int = strtoull(nwid, NULL, 16); + ZeroTier::SocketTap *tap = zt1Service->getTap(nwid_int); + if(tap && tap->_ips.size()){ + for(int i=0; i_ips.size(); i++) { + if(tap->_ips[i].isV4()) { + std::string addr = tap->_ips[i].toString(); + // DEBUG_EXTRA("addr=%s, addrlen=%d", addr.c_str(), addr.length()); + memcpy(addrstr, addr.c_str(), addr.length()); // first address found that matches protocol version 4 + return; + } + } + } + else { + memcpy(addrstr, "-1.-1.-1.-1/-1", 14); + } +} +// Get IPV6 Address for this device on given network +void zts_get_ipv6_address(const char *nwid, char *addrstr) +{ + uint64_t nwid_int = strtoull(nwid, NULL, 16); + ZeroTier::SocketTap *tap = zt1Service->getTap(nwid_int); + if(tap && tap->_ips.size()){ + for(int i=0; i_ips.size(); i++) { + if(tap->_ips[i].isV6()) { + std::string addr = tap->_ips[i].toString(); + // DEBUG_EXTRA("addr=%s, addrlen=%d", addr.c_str(), addr.length()); + memcpy(addrstr, addr.c_str(), addr.length()); // first address found that matches protocol version 4 + return; + } + } + } + else { + memcpy(addrstr, "-1.-1.-1.-1/-1", 14); + } +} +// Get device ID (from running service) +int zts_get_device_id(char *devID) { + if(zt1Service) { + char id[10]; + sprintf(id, "%lx",zt1Service->getNode()->address()); + memcpy(devID, id, 10); + return 0; + } + else + return -1; +} +// Get device ID (from file) +int zts_get_device_id_from_file(const char *filepath, char *devID) { + std::string fname("identity.public"); + std::string fpath(filepath); + + if(ZeroTier::OSUtils::fileExists((fpath + ZT_PATH_SEPARATOR_S + fname).c_str(),false)) { + std::string oldid; + ZeroTier::OSUtils::readFile((fpath + ZT_PATH_SEPARATOR_S + fname).c_str(),oldid); + memcpy(devID, oldid.c_str(), 10); // first 10 bytes of file + return 0; + } + return -1; +} +// Get the IP address of a peer if a direct path is available +int zts_get_peer_address(char *peer, const char *devID) { + if(zt1Service) { + ZT_PeerList *pl = zt1Service->getNode()->peers(); + // uint64_t addr; + for(int i=0; ipeerCount; i++) { + // ZT_Peer *p = &(pl->peers[i]); + // DEBUG_INFO("peer[%d] = %lx", i, p->address); + } + return pl->peerCount; + } + else + return -1; +} +// Return the number of peers on this network +unsigned long zts_get_peer_count() { + if(zt1Service) + return zt1Service->getNode()->peers()->peerCount; + else + return 0; +} +// Return the home path for this instance of ZeroTier +char *zts_get_homepath() { + return (char*)givenHomeDir.c_str(); +} +// Returns a 6PLANE IPv6 address given a network ID and zerotier ID +void zts_get_6plane_addr(char *addr, const char *nwid, const char *devID) +{ + ZeroTier::InetAddress _6planeAddr = ZeroTier::InetAddress::makeIpv66plane(ZeroTier::Utils::hexStrToU64(nwid),ZeroTier::Utils::hexStrToU64(devID)); + memcpy(addr, _6planeAddr.toIpString().c_str(), 40); +} +// Returns a RFC 4193 IPv6 address given a network ID and zerotier ID +void zts_get_rfc4193_addr(char *addr, const char *nwid, const char *devID) +{ + ZeroTier::InetAddress _6planeAddr = ZeroTier::InetAddress::makeIpv6rfc4193(ZeroTier::Utils::hexStrToU64(nwid),ZeroTier::Utils::hexStrToU64(devID)); + memcpy(addr, _6planeAddr.toIpString().c_str(), 40); +} + + // ------------------------------------------------------------------------------ + // ------------------------------ EXPORTED JNI METHODS -------------------------- + // ------------------------------------------------------------------------------ + // JNI naming convention: Java_PACKAGENAME_CLASSNAME_METHODNAME + + +#if defined(__ANDROID__) || defined(__JNI_LIB__) + // Returns whether the ZeroTier service is running + JNIEXPORT jboolean JNICALL Java_zerotier_ZeroTier_zt_1service_1is_1running(JNIEnv *env, jobject thisObj) { + if(zt1Service) + return zts_service_is_running(); + return false; + } + // Returns path for ZT config/data files + JNIEXPORT jstring JNICALL Java_zerotier_ZeroTier_zt_1get_1homepath(JNIEnv *env, jobject thisObj) { + return (*env).NewStringUTF(zts_get_homepath()); + } + // Join a network + JNIEXPORT void JNICALL Java_zerotier_ZeroTier_zt_1join_1network(JNIEnv *env, jobject thisObj, jstring nwid) { + const char *nwidstr; + if(nwid) { + nwidstr = env->GetStringUTFChars(nwid, NULL); + zts_join_network(nwidstr); + } + } + // Leave a network + JNIEXPORT void JNICALL Java_zerotier_ZeroTier_zt_1leave_1network(JNIEnv *env, jobject thisObj, jstring nwid) { + const char *nwidstr; + if(nwid) { + nwidstr = env->GetStringUTFChars(nwid, NULL); + zts_leave_network(nwidstr); + } + } + // FIXME: Re-implemented to make it play nicer with the C-linkage required for Xcode integrations + // Now only returns first assigned address per network. Shouldn't normally be a problem + JNIEXPORT jobject JNICALL Java_zerotier_ZeroTier_zt_1get_1ipv4_1address(JNIEnv *env, jobject thisObj, jstring nwid) { + const char *nwid_str = env->GetStringUTFChars(nwid, NULL); + char address_string[32]; + memset(address_string, 0, 32); + zts_get_ipv4_address(nwid_str, address_string); + jclass clazz = (*env).FindClass("java/util/ArrayList"); + jobject addresses = (*env).NewObject(clazz, (*env).GetMethodID(clazz, "", "()V")); + jstring _str = (*env).NewStringUTF(address_string); + env->CallBooleanMethod(addresses, env->GetMethodID(clazz, "add", "(Ljava/lang/Object;)Z"), _str); + return addresses; + } + + JNIEXPORT jobject JNICALL Java_zerotier_ZeroTier_zt_1get_1ipv6_1address(JNIEnv *env, jobject thisObj, jstring nwid) { + const char *nwid_str = env->GetStringUTFChars(nwid, NULL); + char address_string[32]; + memset(address_string, 0, 32); + zts_get_ipv6_address(nwid_str, address_string); + jclass clazz = (*env).FindClass("java/util/ArrayList"); + jobject addresses = (*env).NewObject(clazz, (*env).GetMethodID(clazz, "", "()V")); + jstring _str = (*env).NewStringUTF(address_string); + env->CallBooleanMethod(addresses, env->GetMethodID(clazz, "add", "(Ljava/lang/Object;)Z"), _str); + return addresses; + } + + // Returns the device is in integer form + JNIEXPORT jint Java_zerotier_ZeroTier_zt_1get_1device_1id() { + return zts_get_device_id(NULL); // TODO + } + // Returns whether the path to an endpoint is currently relayed by a root server + JNIEXPORT jboolean JNICALL Java_zerotier_ZeroTier_zt_1is_1relayed() { + return 0; + // TODO + // zts_is_relayed(); + } +#endif + + + // ------------------------------------------------------------------------------ + // --------------------------- zts_start_core_service --------------------------- + // ------------------------------------------------------------------------------ + + +// Starts a ZeroTier service in the background +void *zts_start_core_service(void *thread_id) { + + #if defined(SDK_BUNDLED) + if(thread_id) + homeDir = std::string((char*)thread_id); + #endif + + #if defined(__IOS__) + char current_dir[MAX_DIR_SZ]; + // Go to the app's data directory so we can shorten the sun_path we bind to + getcwd(current_dir, MAX_DIR_SZ); + std::string targetDir = homeDir; // + "/../../"; + chdir(targetDir.c_str()); + homeDir = localHomeDir; + #endif + + #if defined(__APPLE__) + #include "TargetConditionals.h" + #if TARGET_IPHONE_SIMULATOR + // homeDir = "dont/run/this/in/the/simulator/it/wont/work"; + #elif TARGET_OS_IPHONE + localHomeDir = "ZeroTier/One"; + std::string del = givenHomeDir.length() && givenHomeDir[givenHomeDir.length()-1]!='/' ? "/" : ""; + homeDir = givenHomeDir + del + localHomeDir; + #endif + #endif + + #if defined(__APPLE__) && !defined(__IOS__) + localHomeDir = homeDir; // Used for RPC and *can* differ from homeDir on some platforms + #endif + + DEBUG_INFO("homeDir=%s", homeDir.c_str()); + // Where network .conf files will be stored + netDir = homeDir + "/networks.d"; + zt1Service = (ZeroTier::OneService *)0; + + // Construct path for network config and supporting service files + if (homeDir.length()) { + std::vector hpsp(ZeroTier::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 (!ZeroTier::OSUtils::mkdir(ptmp)) { + DEBUG_ERROR("home path does not exist, and could not create"); + perror("error\n"); + } + } + } + } + else { + DEBUG_ERROR("homeDir is empty, could not construct path"); + return NULL; + } + + DEBUG_INFO("starting service..."); + + // Generate random port for new service instance + unsigned int randp = 0; + ZeroTier::Utils::getSecureRandom(&randp,sizeof(randp)); + int servicePort = 9000 + (randp % 1000); + + for(;;) { + zt1Service = ZeroTier::OneService::newInstance(homeDir.c_str(),servicePort); + switch(zt1Service->run()) { + case ZeroTier::OneService::ONE_STILL_RUNNING: // shouldn't happen, run() won't return until done + case ZeroTier::OneService::ONE_NORMAL_TERMINATION: + break; + case ZeroTier::OneService::ONE_UNRECOVERABLE_ERROR: + DEBUG_ERROR("fatal error: %s",zt1Service->fatalErrorMessage().c_str()); + break; + case ZeroTier::OneService::ONE_IDENTITY_COLLISION: { + delete zt1Service; + zt1Service = (ZeroTier::OneService *)0; + std::string oldid; + ZeroTier::OSUtils::readFile((homeDir + ZT_PATH_SEPARATOR_S + "identity.secret").c_str(),oldid); + if (oldid.length()) { + ZeroTier::OSUtils::writeFile((homeDir + ZT_PATH_SEPARATOR_S + "identity.secret.saved_after_collision").c_str(),oldid); + ZeroTier::OSUtils::rm((homeDir + ZT_PATH_SEPARATOR_S + "identity.secret").c_str()); + ZeroTier::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 = (ZeroTier::OneService *)0; + return NULL; +} + +#ifdef __cplusplus +} +#endif diff --git a/src/Socket.c b/src/Socket.c new file mode 100644 index 0000000..3dec023 --- /dev/null +++ b/src/Socket.c @@ -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 . + */ + +#include +#include +#include +#include +#include + +// For defining the Android direct-call API +#if defined(__ANDROID__) || defined(__JNI_LIB__) + #include +#endif + +#ifdef __cplusplus + extern "C" { +#endif + + +#if defined(__linux__) + #define SOCK_MAX (SOCK_PACKET + 1) +#endif +#define SOCK_TYPE_MASK 0xf + +#include "ZeroTierSDK.h" +#include "RPC.h" + +char *api_netpath; + +/****************************************************************************/ +/* zts_init_rpc() */ +/****************************************************************************/ + + int service_initialized = 0; + + // Assembles (and/or) sets the RPC path for communication with the ZeroTier service + void zts_init_rpc(const char *path, const char *nwid) + { + // If no path, construct one or get it fron system env vars + if(!api_netpath) { + rpc_mutex_init(); + // Provided by user + #if defined(SDK_BUNDLED) + // Get the path/nwid from the user application + // netpath = [path + "/nc_" + nwid] + char *fullpath = (char *)malloc(strlen(path)+strlen(nwid)+1+4); + if(fullpath) { + zts_join_network_soft(path, nwid); + strcpy(fullpath, path); + strcat(fullpath, "/nc_"); + strcat(fullpath, nwid); + api_netpath = fullpath; + } + // Provided by Env + #else + // Get path/nwid from environment variables + if (!api_netpath) { + api_netpath = getenv("ZT_NC_NETWORK"); + DEBUG_INFO("$ZT_NC_NETWORK=%s", api_netpath); + } + #endif + } + + // start the SDK service if this is bundled + #if defined(SDK_BUNDLED) + if(!service_initialized) { + DEBUG_ATTN("api_netpath = %s", api_netpath); + pthread_t service_thread; + pthread_create(&service_thread, NULL, zts_start_core_service, (void *)(path)); + service_initialized = 1; + DEBUG_ATTN("waiting for service to assign address to network stack"); + // wait for zt service to assign the network stack an address + sleep(1); + while(!zts_has_address(nwid)) { usleep(1000); } + } + #endif + } + + void get_api_netpath() { zts_init_rpc("",""); } + +/****************************************************************************/ +/* socket() */ +/****************************************************************************/ + + // int socket_family, int socket_type, int protocol + +#if defined(SDK_LANG_JAVA) + JNIEXPORT jint JNICALL Java_zerotier_ZeroTier_zt_1socket(JNIEnv *env, jobject thisObj, jint family, jint type, jint protocol) { + return zts_socket(family, type, protocol); + } +#endif + +#ifdef DYNAMIC_LIB + int zt_socket(SOCKET_SIG) +#else + int zts_socket(SOCKET_SIG) +#endif + { + get_api_netpath(); + DEBUG_INFO(""); + // Check that type makes sense +#if defined(__linux__) && !defined(__ANDROID__) + int flags = socket_type & ~SOCK_TYPE_MASK; + if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK)) { + errno = EINVAL; + return -1; + } +#endif + socket_type &= SOCK_TYPE_MASK; + // Check protocol is in range +#if defined(__linux__) + if (socket_family < 0 || socket_family >= NPROTO){ + errno = EAFNOSUPPORT; + return -1; + } + if (socket_type < 0 || socket_type >= SOCK_MAX) { + errno = EINVAL; + return -1; + } +#endif + // Assemble and send RPC + struct socket_st rpc_st; + rpc_st.socket_family = socket_family; + rpc_st.socket_type = socket_type; + rpc_st.protocol = protocol; + // -1 is passed since we we're generating the new socket in this call + return rpc_send_command(api_netpath, RPC_SOCKET, -1, &rpc_st, sizeof(struct socket_st)); + } + + + + + + +#ifdef __cplusplus + } +#endif \ No newline at end of file diff --git a/src/SocketTap.cpp b/src/SocketTap.cpp new file mode 100644 index 0000000..e2bb69b --- /dev/null +++ b/src/SocketTap.cpp @@ -0,0 +1,510 @@ +/* + * 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 "SocketTap.hpp" +#include "ZeroTierSDK.h" +#include "RPC.h" +#include "picoTCP.hpp" + +#include "Utils.hpp" +#include "OSUtils.hpp" +#include "Constants.hpp" +#include "Phy.hpp" + + +namespace ZeroTier { + +// Ignore these +void SocketTap::phyOnDatagram(PhySocket *sock,void **uptr,const struct sockaddr *local_address, const struct sockaddr *from,void *data,unsigned long len) {} +void SocketTap::phyOnTcpConnect(PhySocket *sock,void **uptr,bool success) {} +void SocketTap::phyOnTcpAccept(PhySocket *sockL,PhySocket *sockN,void **uptrL,void **uptrN,const struct sockaddr *from) {} +void SocketTap::phyOnTcpClose(PhySocket *sock,void **uptr) {} +void SocketTap::phyOnTcpData(PhySocket *sock,void **uptr,void *data,unsigned long len) {} +void SocketTap::phyOnTcpWritable(PhySocket *sock,void **uptr, bool stack_invoked) {} + +int SocketTap::sendReturnValue(int fd, int retval, int _errno) +{ + //DEBUG_INFO("fd=%d, retval=%d, errno=%d", fd, retval, _errno); + int sz = sizeof(char) + sizeof(retval) + sizeof(errno); + char retmsg[sz]; + memset(&retmsg, 0, sizeof(retmsg)); + retmsg[0]=RPC_RETVAL; + memcpy(&retmsg[1], &retval, sizeof(retval)); + memcpy(&retmsg[1]+sizeof(retval), &_errno, sizeof(_errno)); + return write(fd, &retmsg, sz); +} +// Unpacks the buffer from an RPC command +void SocketTap::unloadRPC(void *data, pid_t &pid, pid_t &tid, + char (timestamp[RPC_TIMESTAMP_SZ]), char (CANARY[sizeof(uint64_t)]), char &cmd, void* &payload) + { + unsigned char *buf = (unsigned char*)data; + memcpy(&pid, &buf[IDX_PID], sizeof(pid_t)); + memcpy(&tid, &buf[IDX_TID], sizeof(pid_t)); + memcpy(timestamp, &buf[IDX_TIME], RPC_TIMESTAMP_SZ); + memcpy(&cmd, &buf[IDX_PAYLOAD], sizeof(char)); + memcpy(CANARY, &buf[IDX_PAYLOAD+1], CANARY_SZ); +} + +/*------------------------------------------------------------------------------ +-------------------------------- Tap Service ---------------------------------- +------------------------------------------------------------------------------*/ + +SocketTap::SocketTap( + 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) : + _homePath(homePath), + _mac(mac), + _mtu(mtu), + _nwid(nwid), + _handler(handler), + _arg(arg), + _phy(this,false,true), + _unixListenSocket((PhySocket *)0), + _enabled(true), + _run(true) +{ + char sockPath[4096]; + Utils::snprintf(sockPath,sizeof(sockPath),"%s%snc_%.16llx",homePath,ZT_PATH_SEPARATOR_S,_nwid,ZT_PATH_SEPARATOR_S,(unsigned long long)nwid); + _dev = sockPath; // in SDK mode, set device to be just the network ID + + _unixListenSocket = _phy.unixListen(sockPath,(void *)this); + chmod(sockPath, 0777); // To make the RPC socket available to all users + if (!_unixListenSocket) + DEBUG_ERROR("unable to bind to: path=%s", sockPath); + else + DEBUG_INFO("tap initialized on: path=%s", sockPath); + + picostack = new picoTCP(); + pico_stack_init(); + + _thread = Thread::start(this); +} + +SocketTap::~SocketTap() +{ + _run = false; + _phy.whack(); + _phy.whack(); // FIXME: Remove? + Thread::join(_thread); + _phy.close(_unixListenSocket,false); +} + +void SocketTap::setEnabled(bool en) +{ + _enabled = en; +} + +bool SocketTap::enabled() const +{ + return _enabled; +} + +bool SocketTap::addIp(const InetAddress &ip) +{ + // Initialize network stack's interface, assign addresses + picotap = this; + picostack->pico_init_interface(this, ip); + return true; +} + +bool SocketTap::removeIp(const InetAddress &ip) +{ + Mutex::Lock _l(_ips_m); + std::vector::iterator i(std::find(_ips.begin(),_ips.end(),ip)); + if (i == _ips.end()) + return false; + _ips.erase(i); + if (ip.isV4()) { + // TODO: De-register from network stacks + } + return true; +} + +std::vector SocketTap::ips() const +{ + Mutex::Lock _l(_ips_m); + return _ips; +} + +// Receive data from ZT tap service (virtual wire) and present it to network stack +// ----------------------------------------- +// | TAP <-> MEM BUFFER <-> STACK <-> APP | +// | |--------------->| | RX +// | APP <-> I/O BUFFER <-> STACK <-> TAP | +// | | +// ----------------------------------------- +void SocketTap::put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len) +{ + // DEBUG_EXTRA("RX packet: len=%d, etherType=%d", len, etherType); + // RX packet + picostack->pico_rx(this, from,to,etherType,data,len); + +} + +std::string SocketTap::deviceName() const +{ + return _dev; +} + +void SocketTap::setFriendlyName(const char *friendlyName) { +} + +void SocketTap::scanMulticastGroups(std::vector &added,std::vector &removed) +{ + std::vector newGroups; + Mutex::Lock _l(_multicastGroups_m); + // TODO: get multicast subscriptions from network stack + 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 SocketTap::threadMain() + throw() +{ + // Enter main thread loop for network stack + picostack->pico_loop(this); + +} + +Connection *SocketTap::getConnection(PhySocket *sock) +{ + for(size_t i=0;i<_Connections.size();++i) { + if(_Connections[i]->sock == sock) + return _Connections[i]; + } + return NULL; +} + +Connection *SocketTap::getConnection(struct pico_socket *sock) +{ + for(size_t i=0;i<_Connections.size();++i) { + if(_Connections[i]->picosock == sock) + return _Connections[i]; + } + return NULL; +} + +void SocketTap::closeConnection(PhySocket *sock) +{ + Mutex::Lock _l(_close_m); + // Here we assume _tcpconns_m is already locked by caller + if(!sock) { + DEBUG_EXTRA("invalid PhySocket"); + return; + } + picostack->pico_handleClose(sock); + Connection *conn = getConnection(sock); + if(!conn) + return; + for(size_t i=0;i<_Connections.size();++i) { + if(_Connections[i] == conn){ + _Connections.erase(_Connections.begin() + i); + delete conn; + break; + } + } + if(!sock) + return; + close(_phy.getDescriptor(sock)); + _phy.close(sock, false); +} + +void SocketTap::phyOnUnixClose(PhySocket *sock,void **uptr) { + //Mutex::Lock _l(_tcpconns_m); + //closeConnection(sock); +} + + +// Receive data from ZT tap service and present it to network stack +// ----------------------------------------- +// | TAP <-> MEM BUFFER <-> STACK <-> APP | +// | |--------------->| | RX +// | APP <-> I/O BUFFER <-> STACK <-> TAP | +// | | +// ----------------------------------------- +void SocketTap::handleRead(PhySocket *sock,void **uptr,bool stack_invoked) +{ + picostack->pico_handleRead(sock, uptr, stack_invoked); +} + +void SocketTap::phyOnUnixWritable(PhySocket *sock,void **uptr,bool stack_invoked) +{ + handleRead(sock,uptr,stack_invoked); +} + +void SocketTap::phyOnUnixData(PhySocket *sock, void **uptr, void *data, ssize_t len) +{ + //DEBUG_INFO("physock=%p, len=%d", sock, (int)len); + uint64_t CANARY_num; + pid_t pid, tid; + ssize_t wlen = len; + char tmpbuf[SDK_MTU]; + char cmd, timestamp[20], CANARY[CANARY_SZ], padding[] = {PADDING}; + void *payload; + unsigned char *buf = (unsigned char*)data; + std::pair sockdata; + PhySocket *rpcSock; + bool foundJob = false, detected_rpc = false; + Connection *conn; + // RPC + char phrase[RPC_PHRASE_SZ]; + memset(phrase, 0, RPC_PHRASE_SZ); + if(len == BUF_SZ) { + memcpy(phrase, buf, RPC_PHRASE_SZ); + if(strcmp(phrase, RPC_PHRASE) == 0) + detected_rpc = true; + } + if(detected_rpc) { + unloadRPC(data, pid, tid, timestamp, CANARY, cmd, payload); + memcpy(&CANARY_num, CANARY, CANARY_SZ); + // DEBUG_EXTRA(" RPC: physock=%p, (pid=%d, tid=%d, timestamp=%s, cmd=%d)", sock, pid, tid, timestamp, cmd); + + if(cmd == RPC_SOCKET) { + // DEBUG_INFO("RPC_SOCKET, physock=%p", sock); + // Create new stack socket and associate it with this sock + struct socket_st socket_rpc; + memcpy(&socket_rpc, &buf[IDX_PAYLOAD+STRUCT_IDX], sizeof(struct socket_st)); + Connection * new_conn; + if((new_conn = handleSocket(sock, uptr, &socket_rpc))) { + new_conn->pid = pid; // Merely kept to look up application path/names later, not strictly necessary + } + } else { + memcpy(&tmpbuf,data,len); + jobmap[CANARY_num] = std::pair(sock, tmpbuf); + + } + write(_phy.getDescriptor(sock), "z", 1); // RPC ACK byte to maintain order + } + // STREAM + else { + int data_start = -1, data_end = -1, canary_pos = -1, padding_pos = -1; + // Look for padding + std::string padding_pattern(padding, padding+PADDING_SZ); + std::string buffer(buf, buf + len); + padding_pos = buffer.find(padding_pattern); + canary_pos = padding_pos-CANARY_SZ; + // Grab token, next we'll use it to look up an RPC job + if(canary_pos > -1) { + memcpy(&CANARY_num, buf+canary_pos, CANARY_SZ); + if(CANARY_num != 0) { + // Find job + sockdata = jobmap[CANARY_num]; + if(!sockdata.first) { + return; + } else + foundJob = true; + } + } + conn = getConnection(sock); + if(!conn) + return; + + if(padding_pos == -1) { // [DATA] + memcpy(&conn->txbuf[conn->txsz], buf, wlen); + } else { // Padding found, implies a canary is present + // [CANARY] + if(len == CANARY_SZ+PADDING_SZ && canary_pos == 0) { + wlen = 0; // Nothing to write + } else { + // [CANARY] + [DATA] + if(len > CANARY_SZ+PADDING_SZ && canary_pos == 0) { + wlen = len - CANARY_SZ+PADDING_SZ; + data_start = padding_pos+PADDING_SZ; + memcpy((&conn->txbuf)+conn->txsz, buf+data_start, wlen); + } + // [DATA] + [CANARY] + if(len > CANARY_SZ+PADDING_SZ && canary_pos > 0 && canary_pos == len - CANARY_SZ+PADDING_SZ) { + wlen = len - CANARY_SZ+PADDING_SZ; + data_start = 0; + memcpy((&conn->txbuf)+conn->txsz, buf+data_start, wlen); + } + // [DATA] + [CANARY] + [DATA] + if(len > CANARY_SZ+PADDING_SZ && canary_pos > 0 && len > (canary_pos + CANARY_SZ+PADDING_SZ)) { + wlen = len - CANARY_SZ+PADDING_SZ; + data_start = 0; + data_end = padding_pos-CANARY_SZ; + memcpy((&conn->txbuf)+conn->txsz, buf+data_start, (data_end-data_start)+1); + memcpy((&conn->txbuf)+conn->txsz, buf+(padding_pos+PADDING_SZ), len-(canary_pos+CANARY_SZ+PADDING_SZ)); + } + } + } + + // Write data from stream + if(wlen) { + conn->txsz += wlen; + handleWrite(conn); + } + } + // Process RPC if we have a corresponding jobmap entry + if(foundJob) { + rpcSock = sockdata.first; + buf = (unsigned char*)sockdata.second; + unloadRPC(buf, pid, tid, timestamp, CANARY, cmd, payload); + //DEBUG_ERROR(" RPC: physock=%p, (pid=%d, tid=%d, timestamp=%s, cmd=%d)", sock, pid, tid, timestamp, cmd); + switch(cmd) { + case RPC_BIND: + //DEBUG_INFO("RPC_BIND, physock=%p", sock); + struct bind_st bind_rpc; + memcpy(&bind_rpc, &buf[IDX_PAYLOAD+STRUCT_IDX], sizeof(struct bind_st)); + handleBind(sock, rpcSock, uptr, &bind_rpc); + break; + case RPC_LISTEN: + //DEBUG_INFO("RPC_LISTEN, physock=%p", sock); + struct listen_st listen_rpc; + memcpy(&listen_rpc, &buf[IDX_PAYLOAD+STRUCT_IDX], sizeof(struct listen_st)); + handleListen(sock, rpcSock, uptr, &listen_rpc); + break; + case RPC_GETSOCKNAME: + //DEBUG_INFO("RPC_GETSOCKNAME, physock=%p", sock); + struct getsockname_st getsockname_rpc; + memcpy(&getsockname_rpc, &buf[IDX_PAYLOAD+STRUCT_IDX], sizeof(struct getsockname_st)); + handleGetsockname(sock, rpcSock, uptr, &getsockname_rpc); + break; + case RPC_GETPEERNAME: + //DEBUG_INFO("RPC_GETPEERNAME, physock=%p", sock); + struct getsockname_st getpeername_rpc; + memcpy(&getpeername_rpc, &buf[IDX_PAYLOAD+STRUCT_IDX], sizeof(struct getsockname_st)); + handleGetpeername(sock, rpcSock, uptr, &getpeername_rpc); + break; + case RPC_CONNECT: + //DEBUG_INFO("RPC_CONNECT, physock=%p", sock); + struct connect_st connect_rpc; + memcpy(&connect_rpc, &buf[IDX_PAYLOAD+STRUCT_IDX], sizeof(struct connect_st)); + handleConnect(sock, rpcSock, conn, &connect_rpc); + jobmap.erase(CANARY_num); + return; // Keep open RPC, we'll use it once in nc_connected to send retval + default: + return; + break; + } + Mutex::Lock _l(_tcpconns_m); + closeConnection(sockdata.first); // close RPC after sending retval, no longer needed + jobmap.erase(CANARY_num); + } +} + +/*------------------------------------------------------------------------------ +----------------------------- RPC Handler functions ---------------------------- +------------------------------------------------------------------------------*/ + +void SocketTap::handleGetsockname(PhySocket *sock, PhySocket *rpcSock, void **uptr, struct getsockname_st *getsockname_rpc) +{ + Mutex::Lock _l(_tcpconns_m); + Connection *conn = getConnection(sock); + if(conn->local_addr == NULL){ + DEBUG_EXTRA("no address info available. is it bound?"); + struct sockaddr_storage storage; + memset(&storage, 0, sizeof(struct sockaddr_storage)); + write(_phy.getDescriptor(rpcSock), NULL, sizeof(struct sockaddr_storage)); + return; + } + write(_phy.getDescriptor(rpcSock), conn->local_addr, sizeof(struct sockaddr_storage)); +} + +void SocketTap::handleGetpeername(PhySocket *sock, PhySocket *rpcSock, void **uptr, struct getsockname_st *getsockname_rpc) +{ + Mutex::Lock _l(_tcpconns_m); + Connection *conn = getConnection(sock); + if(conn->peer_addr == NULL){ + DEBUG_EXTRA("no peer address info available. is it connected?"); + struct sockaddr_storage storage; + memset(&storage, 0, sizeof(struct sockaddr_storage)); + write(_phy.getDescriptor(rpcSock), NULL, sizeof(struct sockaddr_storage)); + return; + } + write(_phy.getDescriptor(rpcSock), conn->peer_addr, sizeof(struct sockaddr_storage)); +} + +Connection * SocketTap::handleSocket(PhySocket *sock, void **uptr, struct socket_st* socket_rpc) +{ + return picostack->pico_handleSocket(sock, uptr, socket_rpc); +} + + +// Connect a stack's PCB/socket/Connection object to a remote host +void SocketTap::handleConnect(PhySocket *sock, PhySocket *rpcSock, Connection *conn, struct connect_st* connect_rpc) +{ + Mutex::Lock _l(_tcpconns_m); + picostack->pico_handleConnect(sock, rpcSock, conn, connect_rpc); +} + +void SocketTap::handleBind(PhySocket *sock, PhySocket *rpcSock, void **uptr, struct bind_st *bind_rpc) +{ + Mutex::Lock _l(_tcpconns_m); + if(!_ips.size()) { + // We haven't been given an address yet. Binding at this stage is premature + DEBUG_ERROR("cannot bind yet. ZT address hasn't been provided"); + sendReturnValue(_phy.getDescriptor(rpcSock), -1, ENOMEM); + return; + } + picostack->pico_handleBind(sock,rpcSock,uptr,bind_rpc); +} + +void SocketTap::handleListen(PhySocket *sock, PhySocket *rpcSock, void **uptr, struct listen_st *listen_rpc) +{ + Mutex::Lock _l(_tcpconns_m); + picostack->pico_handleListen(sock, rpcSock, uptr, listen_rpc); +} + +// Write to the network stack (and thus out onto the network) +void SocketTap::handleWrite(Connection *conn) +{ + picostack->pico_handleWrite(conn); +} + +} // namespace ZeroTier + diff --git a/src/SocketTap.hpp b/src/SocketTap.hpp new file mode 100644 index 0000000..5326a04 --- /dev/null +++ b/src/SocketTap.hpp @@ -0,0 +1,244 @@ +/* + * 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_SocketTap_HPP +#define ZT_SocketTap_HPP + +#include +#include +#include +#include +#include +#include +#include + +#include "Constants.hpp" +#include "MulticastGroup.hpp" +#include "Mutex.hpp" +#include "InetAddress.hpp" +#include "Thread.hpp" +#include "Phy.hpp" + +#include "ZeroTierSDK.h" +#include "RPC.h" +#include "picoTCP.hpp" + +#include "pico_protocol.h" +#include "pico_stack.h" +#include "pico_ipv4.h" +#include "pico_icmp4.h" +#include "pico_dev_tap.h" +#include "pico_protocol.h" +#include "pico_socket.h" +#include "pico_device.h" +#include "pico_ipv6.h" + +// ZT RPC structs +struct socket_st; +struct listen_st; +struct bind_st; +struct connect_st; +struct getsockname_st; +struct accept_st; + +struct pico_socket; + +namespace ZeroTier { + + class SocketTap; + + extern SocketTap *picotap; + + /* + * TCP connection + */ + struct Connection + { + bool listening, probation, disabled; + int pid, txsz, rxsz, type; + PhySocket *rpcSock, *sock; + struct tcp_pcb *TCP_pcb; + struct udp_pcb *UDP_pcb; + struct sockaddr_storage *local_addr; // Address we've bound to locally + struct sockaddr_storage *peer_addr; // Address of connection call to remote host + unsigned short port; + unsigned char txbuf[DEFAULT_TCP_TX_BUF_SZ]; + unsigned char rxbuf[DEFAULT_TCP_RX_BUF_SZ]; + // pico + struct pico_socket *picosock; + }; + + /* + * A helper for passing a reference to _phy to LWIP callbacks as a "state" + */ + struct Larg + { + SocketTap *tap; + Connection *conn; + Larg(SocketTap *_tap, Connection *conn) : tap(_tap), conn(conn) {} + }; + + /* + * Network Containers instance -- emulates an Ethernet tap device as far as OneService knows + */ + class SocketTap + { + friend class Phy; + + public: + SocketTap( + 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); + + ~SocketTap(); + + void setEnabled(bool en); + bool enabled() const; + bool addIp(const InetAddress &ip); + bool removeIp(const InetAddress &ip); + std::vector ips() const; + std::vector _ips; + + 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); + + int sendReturnValue(int fd, int retval, int _errno); + void unloadRPC(void *data, pid_t &pid, pid_t &tid, char (timestamp[RPC_TIMESTAMP_SZ]), char (CANARY[sizeof(uint64_t)]), char &cmd, void* &payload); + + void threadMain() + throw(); + + std::string _homePath; + MAC _mac; + unsigned int _mtu; + uint64_t _nwid; + void (*_handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int); + void *_arg; + Phy _phy; + PhySocket *_unixListenSocket; + volatile bool _enabled; + volatile bool _run; + + // picoTCP + unsigned char pico_frame_rxbuf[MAX_PICO_FRAME_RX_BUF_SZ]; + int pico_frame_rxbuf_tot; + Mutex _pico_frame_rxbuf_m; + + void handleBind(PhySocket *sock, PhySocket *rpcsock, void **uptr, struct bind_st *bind_rpc); + void handleListen(PhySocket *sock, PhySocket *rpcsock, void **uptr, struct listen_st *listen_rpc); + Connection * handleSocket(PhySocket *sock, void **uptr, struct socket_st* socket_rpc); + void handleConnect(PhySocket *sock, PhySocket *rpcsock, Connection *conn, struct connect_st* connect_rpc); + + // void handleIsConnected(); + + /* + * Return the address that the socket is bound to + */ + void handleGetsockname(PhySocket *sock, PhySocket *rpcsock, void **uptr, struct getsockname_st *getsockname_rpc); + + /* + * Return the address of the peer connected to this socket + */ + void handleGetpeername(PhySocket *sock, PhySocket *rpcsock, void **uptr, struct getsockname_st *getsockname_rpc); + + /* + * Writes data from the application's socket to the LWIP connection + */ + void handleWrite(Connection *conn); + + // Unused -- no UDP or TCP from this thread/Phy<> + void phyOnDatagram(PhySocket *sock,void **uptr,const struct sockaddr *local_address, const struct sockaddr *from,void *data,unsigned long len); + void phyOnTcpConnect(PhySocket *sock,void **uptr,bool success); + void phyOnTcpAccept(PhySocket *sockL,PhySocket *sockN,void **uptrL,void **uptrN,const struct sockaddr *from); + void phyOnTcpClose(PhySocket *sock,void **uptr); + void phyOnTcpData(PhySocket *sock,void **uptr,void *data,unsigned long len); + void phyOnTcpWritable(PhySocket *sock,void **uptr, bool stack_invoked); + + void handleRead(PhySocket *sock,void **uptr,bool stack_invoked); + + /* + * Signals us to close the TcpConnection associated with this PhySocket + */ + void phyOnUnixClose(PhySocket *sock,void **uptr); + + /* + * Notifies us that there is data to be read from an application's socket + */ + void phyOnUnixData(PhySocket *sock,void **uptr,void *data,ssize_t len); + + /* + * Notifies us that we can write to an application's socket + */ + void phyOnUnixWritable(PhySocket *sock,void **uptr,bool lwip_invoked); + + /* + * Returns a pointer to a TcpConnection associated with a given PhySocket + */ + Connection *getConnection(PhySocket *sock); + + /* + * Returns a pointer to a TcpConnection associated with a given pico_socket + */ + Connection *getConnection(struct pico_socket *socket); + + /* + * Closes a TcpConnection, associated LWIP PCB strcuture, + * PhySocket, and underlying file descriptor + */ + void closeConnection(PhySocket *sock); + + + + picoTCP *picostack; + + + std::vector _Connections; + + std::map > jobmap; + pid_t rpcCounter; + + Thread _thread; + std::string _dev; // path to Unix domain socket + + std::vector _multicastGroups; + Mutex _multicastGroups_m; + + Mutex _ips_m, _tcpconns_m, _rx_buf_m, _close_m; + + }; + +} // namespace ZeroTier + +#endif diff --git a/src/picoTCP.cpp b/src/picoTCP.cpp new file mode 100644 index 0000000..6687c3c --- /dev/null +++ b/src/picoTCP.cpp @@ -0,0 +1,809 @@ +/* + * 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 "pico_eth.h" +#include "pico_stack.h" +#include "pico_ipv4.h" +#include "pico_icmp4.h" +#include "pico_dev_tap.h" +#include "pico_protocol.h" +#include "pico_socket.h" +#include "pico_device.h" +#include "pico_ipv6.h" + +#include "ZeroTierSDK.h" +#include "SocketTap.hpp" +#include "picoTCP.hpp" + +#include "Utils.hpp" +#include "OSUtils.hpp" +#include "Mutex.hpp" +#include "Constants.hpp" +#include "Phy.hpp" + +// stack locks +ZeroTier::Mutex _lock; +ZeroTier::Mutex _lock_mem; + +struct pico_socket; +struct pico_device; + +extern "C" int pico_stack_init(void); +extern "C" void pico_stack_tick(void); + +int pico_ipv4_to_string(PICO_IPV4_TO_STRING_SIG); +extern "C" int pico_ipv4_link_add(PICO_IPV4_LINK_ADD_SIG); +extern "C" int pico_device_init(PICO_DEVICE_INIT_SIG); +int pico_stack_recv(PICO_STACK_RECV_SIG); +int pico_icmp4_ping(PICO_ICMP4_PING_SIG); +extern "C" int pico_string_to_ipv4(PICO_STRING_TO_IPV4_SIG); +extern "C" int pico_string_to_ipv6(PICO_STRING_TO_IPV6_SIG); +int pico_socket_setoption(PICO_SOCKET_SETOPTION_SIG); +uint32_t pico_timer_add(PICO_TIMER_ADD_SIG); +int pico_socket_send(PICO_SOCKET_SEND_SIG); +int pico_socket_sendto(PICO_SOCKET_SENDTO_SIG); +int pico_socket_recv(PICO_SOCKET_RECV_SIG); +extern "C" int pico_socket_recvfrom(PICO_SOCKET_RECVFROM_SIG); +extern "C" struct pico_socket * pico_socket_open(PICO_SOCKET_OPEN_SIG); +int pico_socket_bind(PICO_SOCKET_BIND_SIG); +int pico_socket_connect(PICO_SOCKET_CONNECT_SIG); +extern "C" int pico_socket_listen(PICO_SOCKET_LISTEN_SIG); +int pico_socket_read(PICO_SOCKET_READ_SIG); +extern "C" int pico_socket_write(PICO_SOCKET_WRITE_SIG); +extern "C" int pico_socket_close(PICO_SOCKET_CLOSE_SIG); +int pico_socket_shutdown(PICO_SOCKET_SHUTDOWN_SIG); +struct pico_socket * pico_socket_accept(PICO_SOCKET_ACCEPT_SIG); +extern "C" struct pico_ipv6_link * pico_ipv6_link_add(PICO_IPV6_LINK_ADD_SIG); + + +namespace ZeroTier { + + // Reference to the tap interface + // This is needed due to the fact that there's a lot going on in the tap interface + // that needs to be updated on each of the network stack's callbacks and not every + // network stack provides a mechanism for storing a reference to the tap. + // + // In future releases this will be replaced with a new structure of static pointers that + // will make it easier to maintain multiple active tap interfaces + + struct pico_device picodev; + SocketTap * picotap; + + int pico_eth_send(struct pico_device *dev, void *buf, int len); + int pico_eth_poll(struct pico_device *dev, int loop_score); + + // Initialize network stack's interfaces and assign addresses + void picoTCP::pico_init_interface(SocketTap *tap, const InetAddress &ip) + { + if (std::find(tap->_ips.begin(),tap->_ips.end(),ip) == tap->_ips.end()) { + tap->_ips.push_back(ip); + std::sort(tap->_ips.begin(),tap->_ips.end()); + #if defined(SDK_IPV4) + if(ip.isV4()) + { + struct pico_ip4 ipaddr, netmask; + ipaddr.addr = *((uint32_t *)ip.rawIpData()); + netmask.addr = *((uint32_t *)ip.netmask().rawIpData()); + uint8_t mac[PICO_SIZE_ETH]; + tap->_mac.copyTo(mac, PICO_SIZE_ETH); + DEBUG_ATTN("mac = %s", tap->_mac.toString().c_str()); + picodev.send = pico_eth_send; // tx + picodev.poll = pico_eth_poll; // rx + picodev.mtu = tap->_mtu; + if( 0 != pico_device_init(&(picodev), "p0", mac)) { + DEBUG_ERROR("device init failed"); + return; + } + pico_ipv4_link_add(&(picodev), ipaddr, netmask); + // DEBUG_INFO("device initialized as ipv4_addr = %s", ipv4_str); + // pico_icmp4_ping("10.8.8.1", 20, 1000, 10000, 64, cb_ping); + } + #elif defined(SDK_IPV6) + if(ip.isV6()) + { + struct pico_ip6 ipaddr, netmask; + char ipv6_str[INET6_ADDRSTRLEN], nm_str[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, ip.rawIpData(), ipv6_str, INET6_ADDRSTRLEN); + inet_ntop(AF_INET6, ip.netmask().rawIpData(), nm_str, INET6_ADDRSTRLEN); + pico_string_to_ipv6(ipv6_str, ipaddr.addr); + pico_string_to_ipv6(nm_str, netmask.addr); + pico_ipv6_link_add(&(picodev), ipaddr, netmask); + picodev.send = pico_eth_send; // tx + picodev.poll = pico_eth_poll; // rx + uint8_t mac[PICO_SIZE_ETH]; + tap->_mac.copyTo(mac, PICO_SIZE_ETH); + DEBUG_ATTN("mac = %s", tap->_mac.toString().c_str()); + if( 0 != pico_device_init(&(picodev), "p0", mac)) { + DEBUG_ERROR("device init failed"); + return; + } + DEBUG_ATTN("device initialized as ipv6_addr = %s", ipv6_str); + } + #endif + } + } + + // Main stack loop + void picoTCP::pico_loop(SocketTap *tap) + { + while(tap->_run) + { + tap->_phy.poll(ZT_PHY_POLL_INTERVAL); // in ms + pico_stack_tick(); + } + } + + // RX packets from [ZT->STACK] onto RXBUF + // Also notify the tap service that data can be read: + // [RXBUF -> (ZTSOCK->APP)] + // ----------------------------------------- + // | TAP <-> MEM BUFFER <-> STACK <-> APP | + // | | + // | APP <-> I/O BUFFER <-> STACK <-> TAP | + // | |<-----------------| | RX + // ----------------------------------------- + // After this step, buffer will be emptied periodically by pico_handleRead() + void picoTCP::pico_cb_tcp_read(ZeroTier::SocketTap *tap, struct pico_socket *s) + { + Connection *conn = tap->getConnection(s); + if(conn) { + int r; + uint16_t port = 0; + union { + struct pico_ip4 ip4; + struct pico_ip6 ip6; + } peer; + + do { + int avail = DEFAULT_TCP_RX_BUF_SZ - conn->rxsz; + if(avail) { + r = pico_socket_recvfrom(s, conn->rxbuf + (conn->rxsz), SDK_MTU, (void *)&peer.ip4.addr, &port); + // DEBUG_ATTN("received packet (%d byte) from %08X:%u", r, long_be2(peer.ip4.addr), short_be(port)); + tap->_phy.setNotifyWritable(conn->sock, true); + if (r > 0) + conn->rxsz += r; + } + else + DEBUG_ERROR("not enough space left on I/O RX buffer for pico_socket(%p)", s); + } + while(r > 0); + return; + } + DEBUG_ERROR("invalid connection"); + } + + // RX packets from the stack onto internal buffer + // Also notifies the tap service that data can be read + // ----------------------------------------- + // | TAP <-> MEM BUFFER <-> STACK <-> APP | + // | | + // | APP <-> I/O BUFFER <-> STACK <-> TAP | + // | |<-----------------| | RX + // ----------------------------------------- + // After this step, buffer will be emptied periodically by pico_handleRead() + // Read payload is encapsulated as such: + // + // [addr|payload_len|payload] + // + void picoTCP::pico_cb_udp_read(SocketTap *tap, struct pico_socket *s) + { + Connection *conn = tap->getConnection(s); + if(conn) { + + uint16_t port = 0; + union { + struct pico_ip4 ip4; + struct pico_ip6 ip6; + } peer; + + char tmpbuf[SDK_MTU]; + unsigned char *addr_pos, *sz_pos, *payload_pos; + struct sockaddr_in addr_in; + addr_in.sin_addr.s_addr = peer.ip4.addr; + addr_in.sin_port = port; + + // RX + int r = pico_socket_recvfrom(s, tmpbuf, SDK_MTU, (void *)&peer.ip4.addr, &port); + //DEBUG_FLOW(" [ RXBUF <- STACK] Receiving (%d) from stack, copying to receving buffer", r); + + // Mutex::Lock _l2(tap->_rx_buf_m); + // struct sockaddr_in6 addr_in6; + // addr_in6.sin6_addr.s6_addr; + // addr_in6.sin6_port = Utils::ntoh(s->remote_port); + // DEBUG_ATTN("remote_port=%d, local_port=%d", s->remote_port, Utils::ntoh(s->local_port)); + tap->_rx_buf_m.lock(); + if(conn->rxsz == DEFAULT_UDP_RX_BUF_SZ) { // if UDP buffer full + //DEBUG_FLOW(" [ RXBUF <- STACK] UDP RX buffer full. Discarding oldest payload segment"); + memmove(conn->rxbuf, conn->rxbuf + SDK_MTU, DEFAULT_UDP_RX_BUF_SZ - SDK_MTU); + addr_pos = conn->rxbuf + (DEFAULT_UDP_RX_BUF_SZ - SDK_MTU); // TODO: + sz_pos = addr_pos + sizeof(struct sockaddr_storage); + conn->rxsz -= SDK_MTU; + } + else { + addr_pos = conn->rxbuf + conn->rxsz; // where we'll prepend the size of the address + sz_pos = addr_pos + sizeof(struct sockaddr_storage); + } + payload_pos = addr_pos + sizeof(struct sockaddr_storage) + sizeof(r); + memcpy(addr_pos, &addr_in, sizeof(struct sockaddr_storage)); + + memcpy(payload_pos, tmpbuf, r); // write payload to app's socket + + // Adjust buffer size + if(r) { + conn->rxsz += SDK_MTU; + memcpy(sz_pos, &r, sizeof(r)); + } + if (r < 0) { + DEBUG_ERROR("unable to read from picosock=%p", s); + } + tap->_rx_buf_m.unlock(); + + // TODO: Revisit logic + if(r) + tap->phyOnUnixWritable(conn->sock, NULL, true); + //DEBUG_EXTRA(" Copied onto rxbuf (%d) from stack socket", r); + return; + } + } + + // TX packets from internal buffer to network + void picoTCP::pico_cb_tcp_write(SocketTap *tap, struct pico_socket *s) + { + Connection *conn = tap->getConnection(s); + if(!conn) + DEBUG_ERROR("invalid connection"); + if(!conn->txsz) + return; + // Only called from a locked context, no need to lock anything + if(conn->txsz > 0) { + int r, max_write_len = conn->txsz < SDK_MTU ? conn->txsz : SDK_MTU; + if((r = pico_socket_write(s, &conn->txbuf, max_write_len)) < 0) { + DEBUG_ERROR("unable to write to picosock=%p", s); + return; + } + int sz = (conn->txsz)-r; + if(sz) + memmove(&conn->txbuf, (conn->txbuf+r), sz); + conn->txsz -= r; + + #if DEBUG_LEVEL >= MSG_TRANSFER + int max = conn->type == SOCK_STREAM ? DEFAULT_TCP_TX_BUF_SZ : DEFAULT_UDP_TX_BUF_SZ; + DEBUG_TRANS("[TCP TX] ---> :: {TX: %.3f%%, RX: %.3f%%, physock=%p} :: %d bytes", + (float)conn->txsz / (float)max, (float)conn->rxsz / max, conn->sock, r); + #endif + + return; + } + } + + // Main callback for TCP connections + void picoTCP::pico_cb_socket_activity(uint16_t ev, struct pico_socket *s) + { + int err; + Mutex::Lock _l(picotap->_tcpconns_m); + Connection *conn = picotap->getConnection(s); + if(!conn) { + DEBUG_ERROR("invalid connection"); + } + // Accept connection (analogous to lwip_nc_accept) + if (ev & PICO_SOCK_EV_CONN) { + DEBUG_INFO("connection established with server, picosock=%p",(conn->picosock)); + uint32_t peer; + uint16_t port; + struct pico_socket *client = pico_socket_accept(s, &peer, &port); + if(!client) { + DEBUG_EXTRA("unable to accept conn. (event might not be incoming, not necessarily an error), picosock=%p", (conn->picosock)); + } + ZT_PHY_SOCKFD_TYPE fds[2]; + if(socketpair(PF_LOCAL, SOCK_STREAM, 0, fds) < 0) { + if(errno < 0) { + // FIXME: Return a value to the client + //tap->sendReturnValue(conn, -1, errno); + DEBUG_ERROR("unable to create socketpair"); + return; + } + } + Connection *newTcpConn = new Connection(); + picotap->_Connections.push_back(newTcpConn); + newTcpConn->type = SOCK_STREAM; + newTcpConn->sock = picotap->_phy.wrapSocket(fds[0], newTcpConn); + newTcpConn->picosock = client; + int fd = picotap->_phy.getDescriptor(conn->sock); + if(sock_fd_write(fd, fds[1]) < 0) { + DEBUG_ERROR("error sending new fd to client application"); + } + DEBUG_EXTRA("conn=%p, physock=%p, listen_picosock=%p, new_picosock=%p, fd=%d", newTcpConn, newTcpConn->sock, s, client, fds[1]); + } + if (ev & PICO_SOCK_EV_FIN) { + DEBUG_INFO("socket closed. exit normally. picosock=%p\n\n", s); + //pico_timer_add(2000, compare_results, NULL); + } + if (ev & PICO_SOCK_EV_ERR) { + DEBUG_INFO("socket error received" /*, strerror(pico_err)*/); + } + if (ev & PICO_SOCK_EV_CLOSE) { + err = pico_socket_close(s); + DEBUG_INFO("socket closure = %d, picosock=%p", err, s); + if(err==0) { + picotap->closeConnection(conn->sock); + } + return; + } + // Read from picoTCP socket + if (ev & PICO_SOCK_EV_RD) { + if(conn->type==SOCK_STREAM) + pico_cb_tcp_read(picotap, s); + if(conn->type==SOCK_DGRAM) + pico_cb_udp_read(picotap, s); + } + // Write to picoTCP socket + if (ev & PICO_SOCK_EV_WR) { + pico_cb_tcp_write(picotap, s); + } + } + + // Called when an incoming ping is received + /* + static void pico_cb_ping(struct pico_icmp4_stats *s) + { + DEBUG_INFO(); + char host[30]; + pico_ipv4_to_string(host, s->dst.addr); + if (s->err == 0) { + printf("%lu bytes from %s: icmp_req=%lu ttl=%lu time=%lu ms\n", s->size, + host, s->seq, s->ttl, (long unsigned int)s->time); + } else { + printf("PING %lu to %s: Error %d\n", s->seq, host, s->err); + } + } + */ + + // Called from the stack, sends data to the tap device (in our case, the ZeroTier service) + // ----------------------------------------- + // | TAP <-> MEM BUFFER <-> STACK <-> APP | + // | |<-------------------------| | TX + // | APP <-> I/O BUFFER <-> STACK <-> TAP | + // | | + // ----------------------------------------- + int pico_eth_send(struct pico_device *dev, void *buf, int len) + { + struct pico_eth_hdr *ethhdr; + ethhdr = (struct pico_eth_hdr *)buf; + + MAC src_mac; + MAC dest_mac; + src_mac.setTo(ethhdr->saddr, 6); + dest_mac.setTo(ethhdr->daddr, 6); + + picotap->_handler(picotap->_arg,NULL,picotap->_nwid,src_mac,dest_mac, + Utils::ntoh((uint16_t)ethhdr->proto),0, ((char*)buf) + sizeof(struct pico_eth_hdr),len - sizeof(struct pico_eth_hdr)); + return len; + } + + // Receives data from the tap device and encapsulates it into a ZeroTier ethernet frame and places it in a locked memory buffer + // ----------------------------------------- + // | TAP <-> MEM BUFFER <-> STACK <-> APP | + // | |--------------->| | RX + // | APP <-> I/O BUFFER <-> STACK <-> TAP | + // | | + // ----------------------------------------- + // It will then periodically be transfered into the network stack via pico_eth_poll() + void picoTCP::pico_rx(SocketTap *tap, const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len) + { + // Since picoTCP only allows the reception of frames from within the polling function, we + // must enqueue each frame into a memory structure shared by both threads. This structure will + Mutex::Lock _l(tap->_pico_frame_rxbuf_m); + + // assemble new eth header + struct pico_eth_hdr ethhdr; + from.copyTo(ethhdr.saddr, 6); + to.copyTo(ethhdr.daddr, 6); + ethhdr.proto = Utils::hton((uint16_t)etherType); + int newlen = len + sizeof(int) + sizeof(struct pico_eth_hdr); + + int mylen; + while(newlen > (MAX_PICO_FRAME_RX_BUF_SZ-tap->pico_frame_rxbuf_tot) && ethhdr.proto == 56710) + { + mylen = 0; + //DEBUG_FLOW(" [ ZTWIRE -> FBUF ] not enough space left on RX frame buffer, dropping oldest packet in buffer"); + /* + memcpy(&mylen, picotap->pico_frame_rxbuf, sizeof(len)); + memmove(tap->pico_frame_rxbuf, tap->pico_frame_rxbuf + mylen, MAX_PICO_FRAME_RX_BUF_SZ-mylen); // shift buffer + picotap->pico_frame_rxbuf_tot-=mylen; + */ + memset(tap->pico_frame_rxbuf,0,MAX_PICO_FRAME_RX_BUF_SZ); + picotap->pico_frame_rxbuf_tot=0; + } + memcpy(tap->pico_frame_rxbuf + tap->pico_frame_rxbuf_tot, &newlen, sizeof(newlen)); // size of frame + meta + memcpy(tap->pico_frame_rxbuf + tap->pico_frame_rxbuf_tot + sizeof(newlen), ðhdr, sizeof(ethhdr)); // new eth header + memcpy(tap->pico_frame_rxbuf + tap->pico_frame_rxbuf_tot + sizeof(newlen) + sizeof(ethhdr), data, len); // frame data + tap->pico_frame_rxbuf_tot += newlen; + DEBUG_FLOW(" [ ZTWIRE -> FBUF ] Move FRAME(sz=%d) into FBUF(sz=%d), data_len=%d", newlen, picotap->pico_frame_rxbuf_tot, len); + } + + // Called periodically by the stack, this removes data from the locked memory buffer (FBUF) and feeds it into the stack. + // A maximum of 'loop_score' frames can be processed in each call + // ----------------------------------------- + // | TAP <-> MEM BUFFER <-> STACK <-> APP | + // | |----------------->| | RX + // | APP <-> I/O BUFFER <-> STACK <-> TAP | + // | | + // ----------------------------------------- + int pico_eth_poll(struct pico_device *dev, int loop_score) + { + // OPTIMIZATION: The copy logic and/or buffer structure should be reworked for better performance after the BETA + // SocketTap *tap = (SocketTap*)netif->state; + Mutex::Lock _l(picotap->_pico_frame_rxbuf_m); + unsigned char frame[SDK_MTU]; + int len; + while (picotap->pico_frame_rxbuf_tot > 0 && loop_score > 0) { + //DEBUG_FLOW(" [ FBUF -> STACK] Frame buffer SZ=%d", picotap->pico_frame_rxbuf_tot); + memset(frame, 0, sizeof(frame)); + len = 0; + memcpy(&len, picotap->pico_frame_rxbuf, sizeof(len)); // get frame len + if(len >= 0) { + //DEBUG_FLOW(" [ FBUF -> STACK] Moving FRAME of size (%d) from FBUF(sz=%d) into stack",len, picotap->pico_frame_rxbuf_tot-len); + memcpy(frame, picotap->pico_frame_rxbuf + sizeof(len), len-(sizeof(len)) ); // get frame data + memmove(picotap->pico_frame_rxbuf, picotap->pico_frame_rxbuf + len, MAX_PICO_FRAME_RX_BUF_SZ-len); // shift buffer + pico_stack_recv(dev, (uint8_t*)frame, (len-sizeof(len))); + picotap->pico_frame_rxbuf_tot-=len; + } + else { + DEBUG_ERROR("Skipping frame of size (%d)",len); + exit(0); + } + loop_score--; + } + return loop_score; + } + + // Creates a new pico_socket and Connection object to represent a new connection to be. + Connection *picoTCP::pico_handleSocket(PhySocket *sock, void **uptr, struct socket_st* socket_rpc) + { + struct pico_socket * psock; + int protocol, protocol_version; + + #if defined(SDK_IPV4) + protocol_version = PICO_PROTO_IPV4; + #elif defined(SDK_IPV6) + protocol_version = PICO_PROTO_IPV6; + #endif + if(socket_rpc->socket_type == SOCK_DGRAM) { + protocol = PICO_PROTO_UDP; + psock = pico_socket_open(protocol_version, protocol, &pico_cb_socket_activity); + } + if(socket_rpc->socket_type == SOCK_STREAM) { + protocol = PICO_PROTO_TCP; + psock = pico_socket_open(protocol_version, protocol, &pico_cb_socket_activity); + } + + if(psock) { + DEBUG_ATTN("physock=%p, picosock=%p", sock, psock); + Connection * newConn = new Connection(); + *uptr = newConn; + newConn->type = socket_rpc->socket_type; + newConn->sock = sock; + + /* + int res = 0; + int sendbuff = UNIX_SOCK_BUF_SIZE; + socklen_t optlen = sizeof(sendbuff); + + res = setsockopt(picotap->_phy.getDescriptor(sock), SOL_SOCKET, SO_RCVBUF, &sendbuff, sizeof(sendbuff)); + if(res == -1) + //DEBUG_ERROR("Error while setting RX buffer limits"); + res = setsockopt(picotap->_phy.getDescriptor(sock), SOL_SOCKET, SO_SNDBUF, &sendbuff, sizeof(sendbuff)); + if(res == -1) + //DEBUG_ERROR("Error while setting TX buffer limits"); + + // Get buffer size + // optlen = sizeof(sendbuff); + // res = getsockopt(picotap->_phy.getDescriptor(sock), SOL_SOCKET, SO_SNDBUF, &sendbuff, &optlen); + // DEBUG_INFO("buflen=%d", sendbuff); + */ + + newConn->local_addr = NULL; + newConn->picosock = psock; + picotap->_Connections.push_back(newConn); + memset(newConn->rxbuf, 0, DEFAULT_UDP_RX_BUF_SZ); + return newConn; + } + else + DEBUG_ERROR("failed to create pico_socket"); + return NULL; + } + + // Writes data from the I/O buffer to the network stack + // ----------------------------------------- + // | TAP <-> MEM BUFFER <-> STACK <-> APP | + // | | + // | APP <-> I/O BUFFER <-> STACK <-> TAP | + // | |----------------->| | TX + // ----------------------------------------- + void picoTCP::pico_handleWrite(Connection *conn) + { + if(!conn || !conn->picosock) { + DEBUG_ERROR(" invalid connection"); + return; + } + + int max, r, max_write_len = conn->txsz < SDK_MTU ? conn->txsz : SDK_MTU; + if((r = pico_socket_write(conn->picosock, &conn->txbuf, max_write_len)) < 0) { + DEBUG_ERROR("unable to write to picosock=%p, r=%d", (conn->picosock), r); + return; + } + + // TODO: Errors + + /* + if(pico_err == PICO_ERR_EINVAL) + DEBUG_ERROR("PICO_ERR_EINVAL - invalid argument"); + if(pico_err == PICO_ERR_EIO) + DEBUG_ERROR("PICO_ERR_EIO - input/output error"); + if(pico_err == PICO_ERR_ENOTCONN) + DEBUG_ERROR("PICO_ERR_ENOTCONN - the socket is not connected"); + if(pico_err == PICO_ERR_ESHUTDOWN) + DEBUG_ERROR("PICO_ERR_ESHUTDOWN - cannot send after transport endpoint shutdown"); + if(pico_err == PICO_ERR_EADDRNOTAVAIL) + DEBUG_ERROR("PICO_ERR_EADDRNOTAVAIL - address not available"); + if(pico_err == PICO_ERR_EHOSTUNREACH) + DEBUG_ERROR("PICO_ERR_EHOSTUNREACH - host is unreachable"); + if(pico_err == PICO_ERR_ENOMEM) + DEBUG_ERROR("PICO_ERR_ENOMEM - not enough space"); + if(pico_err == PICO_ERR_EAGAIN) + DEBUG_ERROR("PICO_ERR_EAGAIN - resource temporarily unavailable"); + */ + + // adjust buffer + int sz = (conn->txsz)-r; + if(sz) + memmove(&conn->txbuf, (conn->txbuf+r), sz); + conn->txsz -= r; + + if(conn->type == SOCK_STREAM) { + max = DEFAULT_TCP_TX_BUF_SZ; + DEBUG_TRANS("[TCP TX] ---> :: {TX: %.3f%%, RX: %.3f%%, physock=%p} :: %d bytes", + (float)conn->txsz / (float)max, (float)conn->rxsz / max, conn->sock, r); + } + if(conn->type == SOCK_DGRAM) { + max = DEFAULT_UDP_TX_BUF_SZ; + DEBUG_TRANS("[UDP TX] ---> :: {TX: %.3f%%, RX: %.3f%%, physock=%p} :: %d bytes", + (float)conn->txsz / (float)max, (float)conn->rxsz / max, conn->sock, r); + } + } + + // Instructs the stack to connect to a remote host + void picoTCP::pico_handleConnect(PhySocket *sock, PhySocket *rpcSock, Connection *conn, struct connect_st* connect_rpc) + { + if(conn->picosock) { + struct sockaddr_in *addr = (struct sockaddr_in *) &connect_rpc->addr; + int ret; + // TODO: Rewrite this + #if defined(SDK_IPV4) + struct pico_ip4 zaddr; + struct sockaddr_in *in4 = (struct sockaddr_in*)&connect_rpc->addr; + char ipv4_str[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &(in4->sin_addr), ipv4_str, INET_ADDRSTRLEN); + pico_string_to_ipv4(ipv4_str, &(zaddr.addr)); + //DEBUG_ATTN("addr=%s:%d", ipv4_str, Utils::ntoh(addr->sin_port)); + ret = pico_socket_connect(conn->picosock, &zaddr, addr->sin_port); + #elif defined(SDK_IPV6) // "fd56:5799:d8f6:1238:8c99:9322:30ce:418a" + struct pico_ip6 zaddr; + struct sockaddr_in6 *in6 = (struct sockaddr_in6*)&connect_rpc->addr; + char ipv6_str[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, &(in6->sin6_addr), ipv6_str, INET6_ADDRSTRLEN); + pico_string_to_ipv6(ipv6_str, zaddr.addr); + //DEBUG_ATTN("addr=%s:%d", ipv6_str, Utils::ntoh(addr->sin_port)); + ret = pico_socket_connect(conn->picosock, &zaddr, addr->sin_port); + #endif + + memcpy(&(conn->peer_addr), &connect_rpc->addr, sizeof(struct sockaddr_storage)); + + if(ret == PICO_ERR_EPROTONOSUPPORT) + DEBUG_ERROR("PICO_ERR_EPROTONOSUPPORT"); + if(ret == PICO_ERR_EINVAL) + DEBUG_ERROR("PICO_ERR_EINVAL"); + if(ret == PICO_ERR_EHOSTUNREACH) + DEBUG_ERROR("PICO_ERR_EHOSTUNREACH"); + + picotap->sendReturnValue(picotap->_phy.getDescriptor(rpcSock), 0, ERR_OK); + } + } + + // Instructs the stack to bind to a given address + void picoTCP::pico_handleBind(PhySocket *sock, PhySocket *rpcSock, void **uptr, struct bind_st *bind_rpc) + { + Connection *conn = picotap->getConnection(sock); + if(!sock) { + DEBUG_ERROR("invalid connection"); + return; + } + struct sockaddr_in *addr = (struct sockaddr_in *) &bind_rpc->addr; + int ret; + // TODO: Rewrite this + #if defined(SDK_IPV4) + struct pico_ip4 zaddr; + struct sockaddr_in *in4 = (struct sockaddr_in*)&bind_rpc->addr; + char ipv4_str[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &(in4->sin_addr), ipv4_str, INET_ADDRSTRLEN); + pico_string_to_ipv4(ipv4_str, &(zaddr.addr)); + DEBUG_ATTN("addr=%s:%d, physock=%p, picosock=%p", ipv4_str, Utils::ntoh(addr->sin_port), sock, (conn->picosock)); + ret = pico_socket_bind(conn->picosock, &zaddr, (uint16_t*)&(addr->sin_port)); + #elif defined(SDK_IPV6) + struct pico_ip6 zaddr; + struct sockaddr_in6 *in6 = (struct sockaddr_in6*)&bind_rpc->addr; + char ipv6_str[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, &(in6->sin6_addr), ipv6_str, INET6_ADDRSTRLEN); + pico_string_to_ipv6(ipv6_str, zaddr.addr); + DEBUG_ATTN("addr=%s:%d, physock=%p, picosock=%p", ipv6_str, Utils::ntoh(addr->sin_port), sock, (conn->picosock)); + ret = pico_socket_bind(conn->picosock, &zaddr, (uint16_t*)&(addr->sin_port)); + #endif + if(ret < 0) { + DEBUG_ERROR("unable to bind pico_socket(%p), err=%d", (conn->picosock), ret); + if(ret == PICO_ERR_EINVAL) { + DEBUG_ERROR("PICO_ERR_EINVAL - invalid argument"); + picotap->sendReturnValue(picotap->_phy.getDescriptor(rpcSock), -1, EINVAL); + } + if(ret == PICO_ERR_ENOMEM) { + DEBUG_ERROR("PICO_ERR_ENOMEM - not enough space"); + picotap->sendReturnValue(picotap->_phy.getDescriptor(rpcSock), -1, ENOMEM); + } + if(ret == PICO_ERR_ENXIO) { + DEBUG_ERROR("PICO_ERR_ENXIO - no such device or address"); + picotap->sendReturnValue(picotap->_phy.getDescriptor(rpcSock), -1, ENXIO); + } + } + picotap->sendReturnValue(picotap->_phy.getDescriptor(rpcSock), ERR_OK, ERR_OK); // success + } + + // Puts a pico_socket into a listening state to receive incoming connection requests + void picoTCP::pico_handleListen(PhySocket *sock, PhySocket *rpcSock, void **uptr, struct listen_st *listen_rpc) + { + Connection *conn = picotap->getConnection(sock); + DEBUG_ATTN("physock=%p, conn=%p, picosock=%p", sock, conn, conn->picosock); + if(!sock || !conn) { + DEBUG_ERROR("invalid connection"); + return; + } + int ret, backlog = 100; + if((ret = pico_socket_listen(conn->picosock, backlog)) < 0) + { + if(ret == PICO_ERR_EINVAL) { + DEBUG_ERROR("PICO_ERR_EINVAL - invalid argument"); + picotap->sendReturnValue(picotap->_phy.getDescriptor(rpcSock), -1, EINVAL); + } + if(ret == PICO_ERR_EISCONN) { + DEBUG_ERROR("PICO_ERR_EISCONN - socket is connected"); + picotap->sendReturnValue(picotap->_phy.getDescriptor(rpcSock), -1, EISCONN); + } + } + picotap->sendReturnValue(picotap->_phy.getDescriptor(rpcSock), ERR_OK, ERR_OK); // success + } + + // Feeds data into the local app socket from the I/O buffer associated with the "connection" + // [ (APP<-ZTSOCK) <- RXBUF ] + // ----------------------------------------- + // | TAP <-> MEM BUFFER <-> STACK <-> APP | + // | | + // | APP <-> I/O BUFFER <-> STACK <-> TAP | + // | |<---------------| | RX + // ----------------------------------------- + void picoTCP::pico_handleRead(PhySocket *sock,void **uptr,bool lwip_invoked) + { + if(!lwip_invoked) { + // The stack thread writes to RXBUF as well + picotap->_tcpconns_m.lock(); + picotap->_rx_buf_m.lock(); + } + int tot = 0, n = -1, write_attempts = 0; + + Connection *conn = picotap->getConnection(sock); + if(conn && conn->rxsz) { + + // + if(conn->type==SOCK_DGRAM) { + // Try to write SDK_MTU-sized chunk to app socket + while(tot < SDK_MTU) { + write_attempts++; + n = picotap->_phy.streamSend(conn->sock, (conn->rxbuf)+tot, SDK_MTU); + tot += n; + DEBUG_FLOW(" [ ZTSOCK <- RXBUF] wrote = %d, errno=%d", n, errno); + // If socket is unavailable, attempt to write N times before giving up + if(errno==35) { + if(write_attempts == 1024) { + n = SDK_MTU; // say we wrote it, even though we didn't (drop packet) + tot = SDK_MTU; + } + } + } + int payload_sz, addr_sz_offset = sizeof(struct sockaddr_storage); + memcpy(&payload_sz, conn->rxbuf + addr_sz_offset, sizeof(int)); + struct sockaddr_storage addr; + memcpy(&addr, conn->rxbuf, addr_sz_offset); + // adjust buffer + //DEBUG_FLOW(" [ ZTSOCK <- RXBUF] Copying data from receiving buffer to ZT-controlled app socket (n=%d, payload_sz=%d)", n, payload_sz); + if(conn->rxsz-n > 0) { // If more remains on buffer + memcpy(conn->rxbuf, conn->rxbuf+SDK_MTU, conn->rxsz - SDK_MTU); + //DEBUG_FLOW(" [ ZTSOCK <- RXBUF] Data(%d) still on buffer, moving it up by one MTU", conn->rxsz-n); + ////memset(conn->rxbuf, 0, DEFAULT_UDP_RX_BUF_SZ); + ////conn->rxsz=SDK_MTU; + } + conn->rxsz -= SDK_MTU; + } + // + if(conn->type==SOCK_STREAM) { + n = picotap->_phy.streamSend(conn->sock, conn->rxbuf, conn->rxsz); + if(conn->rxsz-n > 0) // If more remains on buffer + memcpy(conn->rxbuf, conn->rxbuf+n, conn->rxsz - n); + conn->rxsz -= n; + } + // Notify ZT I/O loop that it has new buffer contents + if(n) { + if(conn->type==SOCK_STREAM) { + + #if DEBUG_LEVEL >= MSG_TRANSFER + float max = conn->type == SOCK_STREAM ? (float)DEFAULT_TCP_RX_BUF_SZ : (float)DEFAULT_UDP_RX_BUF_SZ; + DEBUG_TRANS("[TCP RX] <--- :: {TX: %.3f%%, RX: %.3f%%, physock=%p} :: %d bytes", + (float)conn->txsz / max, (float)conn->rxsz / max, conn->sock, n); + #endif + } + if(conn->rxsz == 0) { + picotap->_phy.setNotifyWritable(sock, false); + } + else { + picotap->_phy.setNotifyWritable(sock, true); + } + } + else { + picotap->_phy.setNotifyWritable(sock, false); + } + } + if(!lwip_invoked) { + picotap->_tcpconns_m.unlock(); + picotap->_rx_buf_m.unlock(); + } + DEBUG_FLOW(" [ ZTSOCK <- RXBUF] Emitted (%d) from RXBUF(%d) to socket", tot, conn->rxsz); + } + + // Closes a pico_socket + void picoTCP::pico_handleClose(PhySocket *sock) + { + /* + int ret; + if(conn && conn->picosock) { + if((ret = pico_socket_close(conn->picosock)) < 0) { + DEBUG_ERROR("error closing pico_socket(%p)", (void*)(conn->picosock)); + // sendReturnValue() + } + return; + } + DEBUG_ERROR("invalid connection or pico_socket"); + */ + } +} diff --git a/src/picoTCP.hpp b/src/picoTCP.hpp new file mode 100644 index 0000000..31631eb --- /dev/null +++ b/src/picoTCP.hpp @@ -0,0 +1,105 @@ +/* + * 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_PICOTCP_HPP +#define ZT_PICOTCP_HPP + + +#include "pico_eth.h" +#include "pico_stack.h" +#include "pico_ipv4.h" +#include "pico_icmp4.h" +#include "pico_dev_tap.h" +#include "pico_protocol.h" +#include "pico_socket.h" +#include "pico_device.h" +#include "pico_ipv6.h" + +#include "SocketTap.hpp" + +/****************************************************************************/ +/* PicoTCP API Signatures */ +/****************************************************************************/ + +#define PICO_IPV4_TO_STRING_SIG char *ipbuf, const uint32_t ip +#define PICO_TAP_CREATE_SIG char *name +#define PICO_IPV4_LINK_ADD_SIG struct pico_device *dev, struct pico_ip4 address, struct pico_ip4 netmask +#define PICO_DEVICE_INIT_SIG struct pico_device *dev, const char *name, uint8_t *mac +#define PICO_STACK_RECV_SIG struct pico_device *dev, uint8_t *buffer, uint32_t len +#define PICO_ICMP4_PING_SIG char *dst, int count, int interval, int timeout, int size, void (*cb)(struct pico_icmp4_stats *) +#define PICO_TIMER_ADD_SIG pico_time expire, void (*timer)(pico_time, void *), void *arg +#define PICO_STRING_TO_IPV4_SIG const char *ipstr, uint32_t *ip +#define PICO_STRING_TO_IPV6_SIG const char *ipstr, uint8_t *ip +#define PICO_SOCKET_SETOPTION_SIG struct pico_socket *s, int option, void *value +#define PICO_SOCKET_SEND_SIG struct pico_socket *s, const void *buf, int len +#define PICO_SOCKET_SENDTO_SIG struct pico_socket *s, const void *buf, int len, void *dst, uint16_t remote_port +#define PICO_SOCKET_RECV_SIG struct pico_socket *s, void *buf, int len +#define PICO_SOCKET_RECVFROM_SIG struct pico_socket *s, void *buf, int len, void *orig, uint16_t *remote_port +#define PICO_SOCKET_OPEN_SIG uint16_t net, uint16_t proto, void (*wakeup)(uint16_t ev, struct pico_socket *s) +#define PICO_SOCKET_BIND_SIG struct pico_socket *s, void *local_addr, uint16_t *port +#define PICO_SOCKET_CONNECT_SIG struct pico_socket *s, const void *srv_addr, uint16_t remote_port +#define PICO_SOCKET_LISTEN_SIG struct pico_socket *s, const int backlog +#define PICO_SOCKET_READ_SIG struct pico_socket *s, void *buf, int len +#define PICO_SOCKET_WRITE_SIG struct pico_socket *s, const void *buf, int len +#define PICO_SOCKET_CLOSE_SIG struct pico_socket *s +#define PICO_SOCKET_SHUTDOWN_SIG struct pico_socket *s, int mode +#define PICO_SOCKET_ACCEPT_SIG struct pico_socket *s, void *orig, uint16_t *port +#define PICO_IPV6_LINK_ADD_SIG struct pico_device *dev, struct pico_ip6 address, struct pico_ip6 netmask + +namespace ZeroTier +{ + class SocketTap; + struct Connection; + + class picoTCP + { + public: + + void pico_init_interface(ZeroTier::SocketTap *tap, const ZeroTier::InetAddress &ip); + void pico_loop(SocketTap *tap); + + //int pico_eth_send(struct pico_device *dev, void *buf, int len); + //int pico_eth_poll(struct pico_device *dev, int loop_score); + static void pico_cb_tcp_read(SocketTap *tap, struct pico_socket *s); + static void pico_cb_udp_read(SocketTap *tap, struct pico_socket *s); + static void pico_cb_tcp_write(SocketTap *tap, struct pico_socket *s); + static void pico_cb_socket_activity(uint16_t ev, struct pico_socket *s); + + void pico_rx(SocketTap *tap, const ZeroTier::MAC &from,const ZeroTier::MAC &to,unsigned int etherType,const void *data,unsigned int len); + Connection *pico_handleSocket(ZeroTier::PhySocket *sock, void **uptr, struct socket_st* socket_rpc); + void pico_handleWrite(Connection *conn); + void pico_handleConnect(ZeroTier::PhySocket *sock, ZeroTier::PhySocket *rpcSock, Connection *conn, struct connect_st* connect_rpc); + void pico_handleBind(ZeroTier::PhySocket *sock, ZeroTier::PhySocket *rpcSock, void **uptr, struct bind_st *bind_rpc); + void pico_handleListen(ZeroTier::PhySocket *sock, ZeroTier::PhySocket *rpcSock, void **uptr, struct listen_st *listen_rpc); + void pico_handleRead(ZeroTier::PhySocket *sock,void **uptr,bool lwip_invoked); + void pico_handleClose(ZeroTier::PhySocket *sock); + + + }; +} + +#endif \ No newline at end of file diff --git a/tests/socket/client.cpp b/tests/socket/client.cpp new file mode 100644 index 0000000..e69de29 diff --git a/tests/socket/comprehensive.cpp b/tests/socket/comprehensive.cpp new file mode 100644 index 0000000..f8fe832 --- /dev/null +++ b/tests/socket/comprehensive.cpp @@ -0,0 +1,64 @@ +// Comprehensive stress test for socket-like API + +#include + +/****************************************************************************/ +/* Test Functions */ +/****************************************************************************/ + +int test_for_correctness() +{ + return 0; +} + +int ipv4_udp_server() +{ + return 0; +} + +int ipv6_udp_server() +{ + return 0; +} + +int ipv4_tcp_server() +{ + return 0; +} + +int ipv6_tcp_server() +{ + return 0; +} + +/****************************************************************************/ +/* main */ +/****************************************************************************/ + +int main() +{ + int test_all = 1; + + if(test_all) + { + printf("Testing API calls for correctness\n"); + test_for_correctness(); + + printf("Testing as IPv4 UDP Server\n"); + ipv4_udp_server(); + + printf("Testing as IPv6 UDP Server\n"); + ipv6_udp_server(); + + printf("Testing as IPv4 TCP Server\n"); + ipv4_udp_server(); + + printf("Testing as IPv6 TCP Server\n"); + ipv6_udp_server(); + + printf("Testing \n"); + printf("\n"); + printf("\n"); + } + return 0; +} \ No newline at end of file diff --git a/tests/socket/server.cpp b/tests/socket/server.cpp new file mode 100644 index 0000000..f8fe832 --- /dev/null +++ b/tests/socket/server.cpp @@ -0,0 +1,64 @@ +// Comprehensive stress test for socket-like API + +#include + +/****************************************************************************/ +/* Test Functions */ +/****************************************************************************/ + +int test_for_correctness() +{ + return 0; +} + +int ipv4_udp_server() +{ + return 0; +} + +int ipv6_udp_server() +{ + return 0; +} + +int ipv4_tcp_server() +{ + return 0; +} + +int ipv6_tcp_server() +{ + return 0; +} + +/****************************************************************************/ +/* main */ +/****************************************************************************/ + +int main() +{ + int test_all = 1; + + if(test_all) + { + printf("Testing API calls for correctness\n"); + test_for_correctness(); + + printf("Testing as IPv4 UDP Server\n"); + ipv4_udp_server(); + + printf("Testing as IPv6 UDP Server\n"); + ipv6_udp_server(); + + printf("Testing as IPv4 TCP Server\n"); + ipv4_udp_server(); + + printf("Testing as IPv6 TCP Server\n"); + ipv6_udp_server(); + + printf("Testing \n"); + printf("\n"); + printf("\n"); + } + return 0; +} \ No newline at end of file diff --git a/tests/socket/simple.cpp b/tests/socket/simple.cpp new file mode 100644 index 0000000..6821d4a --- /dev/null +++ b/tests/socket/simple.cpp @@ -0,0 +1,16 @@ +// Comprehensive stress test for socket-like API + +#include + +#include "ZeroTierSDK.h" + +int main() +{ + printf("zts_core_version = %s\n", zts_core_version()); + + zts_start("./ztsdk"); // starts ZeroTier core, generates id in ./zt + + + zts_stop(); + return 0; +} \ No newline at end of file diff --git a/zto/AUTHORS.md b/zto/AUTHORS.md new file mode 100644 index 0000000..043ff00 --- /dev/null +++ b/zto/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/zto/COPYING b/zto/COPYING new file mode 100644 index 0000000..23d42df --- /dev/null +++ b/zto/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/zto/LICENSE.GPL-2 b/zto/LICENSE.GPL-2 new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/zto/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/zto/LICENSE.GPL-3 b/zto/LICENSE.GPL-3 new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/zto/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/zto/Makefile b/zto/Makefile new file mode 100644 index 0000000..9511862 --- /dev/null +++ b/zto/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/zto/README.md b/zto/README.md new file mode 100644 index 0000000..47bfc87 --- /dev/null +++ b/zto/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/zto/RELEASE-NOTES.md b/zto/RELEASE-NOTES.md new file mode 100644 index 0000000..b54b7ea --- /dev/null +++ b/zto/RELEASE-NOTES.md @@ -0,0 +1,132 @@ +ZeroTier Release Notes +====== + +# 2017-03-17 -- Version 1.2.2 + +Version 1.2.2 fixes a few bugs discovered after the 1.2.0 release. These are: + + * 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/zto/controller/EmbeddedNetworkController.cpp b/zto/controller/EmbeddedNetworkController.cpp new file mode 100644 index 0000000..ce56e90 --- /dev/null +++ b/zto/controller/EmbeddedNetworkController.cpp @@ -0,0 +1,1850 @@ +/* + * 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) +{ + OSUtils::mkdir(dbPath); + OSUtils::lockDownFile(dbPath,true); // networks might contain auth tokens, etc., so restrict directory permissions +} + +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(_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] == "dbtest") { + + json testRec; + const uint64_t now = OSUtils::now(); + testRec["clock"] = now; + testRec["uptime"] = (now - _startTime); + _db.put("dbtest",testRec); + + } + + 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/zto/controller/EmbeddedNetworkController.hpp b/zto/controller/EmbeddedNetworkController.hpp new file mode 100644 index 0000000..0ae2f3b --- /dev/null +++ b/zto/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/zto/controller/JSONDB.cpp b/zto/controller/JSONDB.cpp new file mode 100644 index 0000000..8b6de9b --- /dev/null +++ b/zto/controller/JSONDB.cpp @@ -0,0 +1,147 @@ +/* + * 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" + +namespace ZeroTier { + +static const nlohmann::json _EMPTY_JSON(nlohmann::json::object()); + +bool JSONDB::writeRaw(const std::string &n,const std::string &obj) +{ + if (!_isValidObjectName(n)) + return false; + + const std::string path(_genPath(n,true)); + if (!path.length()) + return false; + + const std::string buf(obj); + if (!OSUtils::writeFile(path.c_str(),buf)) + return false; + + return true; +} + +bool JSONDB::put(const std::string &n,const nlohmann::json &obj) +{ + if (!_isValidObjectName(n)) + return false; + + const std::string path(_genPath(n,true)); + if (!path.length()) + return false; + + const std::string buf(OSUtils::jsonDump(obj)); + if (!OSUtils::writeFile(path.c_str(),buf)) + return false; + + _E &e = _db[n]; + e.obj = obj; + + return true; +} + +const nlohmann::json &JSONDB::get(const std::string &n) +{ + if (!_isValidObjectName(n)) + return _EMPTY_JSON; + + std::map::iterator e(_db.find(n)); + if (e != _db.end()) + return e->second.obj; + + const std::string path(_genPath(n,false)); + if (!path.length()) + return _EMPTY_JSON; + std::string buf; + if (!OSUtils::readFile(path.c_str(),buf)) + return _EMPTY_JSON; + + _E &e2 = _db[n]; + try { + e2.obj = OSUtils::jsonParse(buf); + } catch ( ... ) { + e2.obj = _EMPTY_JSON; + buf = "{}"; + } + + return e2.obj; +} + +void JSONDB::erase(const std::string &n) +{ + if (!_isValidObjectName(n)) + return; + + std::string path(_genPath(n,true)); + if (!path.length()) + return; + + OSUtils::rm(path.c_str()); + _db.erase(n); +} + +void JSONDB::_reload(const std::string &p,const std::string &b) +{ + std::vector dl(OSUtils::listDirectory(p.c_str())); + 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)); + } + } +} + +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(); + + 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 "../ext/json/json.hpp" +#include "../osdep/OSUtils.hpp" + +namespace ZeroTier { + +/** + * Hierarchical JSON store that persists into the filesystem + */ +class JSONDB +{ +public: + JSONDB(const std::string &basePath) : + _basePath(basePath) + { + _reload(_basePath,std::string()); + } + + inline void reload() + { + _db.clear(); + _reload(_basePath,std::string()); + } + + 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) + { + 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: + void _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); } + }; + + std::string _basePath; + std::map _db; +}; + +} // namespace ZeroTier + +#endif diff --git a/zto/controller/README.md b/zto/controller/README.md new file mode 100644 index 0000000..db8d015 --- /dev/null +++ b/zto/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/zto/controller/migrate-sqlite/migrate.js b/zto/controller/migrate-sqlite/migrate.js new file mode 100644 index 0000000..ac9678a --- /dev/null +++ b/zto/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/zto/ext/README.md b/zto/ext/README.md new file mode 100644 index 0000000..be9484c --- /dev/null +++ b/zto/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/zto/ext/bin/tap-mac/tap.kext/Contents/Info.plist b/zto/ext/bin/tap-mac/tap.kext/Contents/Info.plist new file mode 100644 index 0000000..c20eefa --- /dev/null +++ b/zto/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/zto/ext/bin/tap-mac/tap.kext/Contents/MacOS/tap b/zto/ext/bin/tap-mac/tap.kext/Contents/MacOS/tap new file mode 100755 index 0000000..48bf962 Binary files /dev/null and b/zto/ext/bin/tap-mac/tap.kext/Contents/MacOS/tap differ diff --git a/zto/ext/bin/tap-mac/tap.kext/Contents/_CodeSignature/CodeResources b/zto/ext/bin/tap-mac/tap.kext/Contents/_CodeSignature/CodeResources new file mode 100644 index 0000000..0710b40 --- /dev/null +++ b/zto/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/zto/ext/bin/tap-windows-ndis6/x64/zttap300.cat b/zto/ext/bin/tap-windows-ndis6/x64/zttap300.cat new file mode 100644 index 0000000..8b9114c Binary files /dev/null and b/zto/ext/bin/tap-windows-ndis6/x64/zttap300.cat differ diff --git a/zto/ext/bin/tap-windows-ndis6/x64/zttap300.inf b/zto/ext/bin/tap-windows-ndis6/x64/zttap300.inf new file mode 100644 index 0000000..453797b --- /dev/null +++ b/zto/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/zto/ext/bin/tap-windows-ndis6/x64/zttap300.sys b/zto/ext/bin/tap-windows-ndis6/x64/zttap300.sys new file mode 100644 index 0000000..3d846a5 Binary files /dev/null and b/zto/ext/bin/tap-windows-ndis6/x64/zttap300.sys differ diff --git a/zto/ext/bin/tap-windows-ndis6/x86/zttap300.cat b/zto/ext/bin/tap-windows-ndis6/x86/zttap300.cat new file mode 100644 index 0000000..44347f5 Binary files /dev/null and b/zto/ext/bin/tap-windows-ndis6/x86/zttap300.cat differ diff --git a/zto/ext/bin/tap-windows-ndis6/x86/zttap300.inf b/zto/ext/bin/tap-windows-ndis6/x86/zttap300.inf new file mode 100644 index 0000000..453797b --- /dev/null +++ b/zto/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/zto/ext/bin/tap-windows-ndis6/x86/zttap300.sys b/zto/ext/bin/tap-windows-ndis6/x86/zttap300.sys new file mode 100644 index 0000000..664398e Binary files /dev/null and b/zto/ext/bin/tap-windows-ndis6/x86/zttap300.sys differ diff --git a/zto/ext/http-parser/AUTHORS b/zto/ext/http-parser/AUTHORS new file mode 100644 index 0000000..5323b68 --- /dev/null +++ b/zto/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/zto/ext/http-parser/LICENSE-MIT b/zto/ext/http-parser/LICENSE-MIT new file mode 100644 index 0000000..58010b3 --- /dev/null +++ b/zto/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/zto/ext/http-parser/README.md b/zto/ext/http-parser/README.md new file mode 100644 index 0000000..439b309 --- /dev/null +++ b/zto/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/zto/ext/http-parser/http_parser.c b/zto/ext/http-parser/http_parser.c new file mode 100644 index 0000000..895bf0c --- /dev/null +++ b/zto/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/zto/ext/http-parser/http_parser.h b/zto/ext/http-parser/http_parser.h new file mode 100644 index 0000000..45c72a0 --- /dev/null +++ b/zto/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/zto/ext/installfiles/linux/zerotier-one.init.rhel6 b/zto/ext/installfiles/linux/zerotier-one.init.rhel6 new file mode 100755 index 0000000..3ff2f18 --- /dev/null +++ b/zto/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/zto/ext/installfiles/mac-update/updater.tmpl.sh b/zto/ext/installfiles/mac-update/updater.tmpl.sh new file mode 100644 index 0000000..0b07f6d --- /dev/null +++ b/zto/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/zto/ext/installfiles/mac/ZeroTier One.pkgproj b/zto/ext/installfiles/mac/ZeroTier One.pkgproj new file mode 100755 index 0000000..c41b61c --- /dev/null +++ b/zto/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.2 + + 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/zto/ext/installfiles/mac/com.zerotier.one.plist b/zto/ext/installfiles/mac/com.zerotier.one.plist new file mode 100644 index 0000000..e3c227d --- /dev/null +++ b/zto/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/zto/ext/installfiles/mac/get-proxy-settings.sh b/zto/ext/installfiles/mac/get-proxy-settings.sh new file mode 100755 index 0000000..16ba0b4 --- /dev/null +++ b/zto/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/zto/ext/installfiles/mac/launch.sh b/zto/ext/installfiles/mac/launch.sh new file mode 100755 index 0000000..41c4b9c --- /dev/null +++ b/zto/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/zto/ext/installfiles/mac/postinst.sh b/zto/ext/installfiles/mac/postinst.sh new file mode 100755 index 0000000..2e4f591 --- /dev/null +++ b/zto/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/zto/ext/installfiles/mac/preinst.sh b/zto/ext/installfiles/mac/preinst.sh new file mode 100755 index 0000000..c2cb494 --- /dev/null +++ b/zto/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/zto/ext/installfiles/mac/uninstall.sh b/zto/ext/installfiles/mac/uninstall.sh new file mode 100755 index 0000000..d1effb9 --- /dev/null +++ b/zto/ext/installfiles/mac/uninstall.sh @@ -0,0 +1,48 @@ +#!/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' +mkdir -p /tmp/ZeroTierOne_uninstall_tmp +cp "/Library/Application Support/ZeroTier/One/*.secret" /tmp/ZeroTierOne_uninstall_tmp +rm -rf '/Library/Application Support/ZeroTier/One' +mkdir -p '/Library/Application Support/ZeroTier/One' +cp "/tmp/ZeroTierOne_uninstall_tmp/*.secret" '/Library/Application Support/ZeroTier/One' +chmod 0600 "/Library/Application Support/ZeroTier/One/*.secret" +rm -rf /tmp/ZeroTierOne_uninstall_tmp + +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/zto/ext/installfiles/windows/ZeroTier One Virtual Network Port (NDIS6_x64).aip b/zto/ext/installfiles/windows/ZeroTier One Virtual Network Port (NDIS6_x64).aip new file mode 100644 index 0000000..db8566c --- /dev/null +++ b/zto/ext/installfiles/windows/ZeroTier One Virtual Network Port (NDIS6_x64).aip @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/zto/ext/installfiles/windows/ZeroTier One Virtual Network Port (NDIS6_x86).aip b/zto/ext/installfiles/windows/ZeroTier One Virtual Network Port (NDIS6_x86).aip new file mode 100644 index 0000000..b83b382 --- /dev/null +++ b/zto/ext/installfiles/windows/ZeroTier One Virtual Network Port (NDIS6_x86).aip @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/zto/ext/installfiles/windows/ZeroTier One.aip b/zto/ext/installfiles/windows/ZeroTier One.aip new file mode 100644 index 0000000..29a02de --- /dev/null +++ b/zto/ext/installfiles/windows/ZeroTier One.aip @@ -0,0 +1,462 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/zto/ext/installfiles/windows/chocolatey/zerotier-one/tools/LICENSE.txt b/zto/ext/installfiles/windows/chocolatey/zerotier-one/tools/LICENSE.txt new file mode 100644 index 0000000..ce0564a --- /dev/null +++ b/zto/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/zto/ext/installfiles/windows/chocolatey/zerotier-one/tools/VERIFICATION.txt b/zto/ext/installfiles/windows/chocolatey/zerotier-one/tools/VERIFICATION.txt new file mode 100644 index 0000000..0a5bc76 --- /dev/null +++ b/zto/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/zto/ext/installfiles/windows/chocolatey/zerotier-one/tools/chocolateyinstall.ps1 b/zto/ext/installfiles/windows/chocolatey/zerotier-one/tools/chocolateyinstall.ps1 new file mode 100644 index 0000000..f8a7457 --- /dev/null +++ b/zto/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/zto/ext/installfiles/windows/chocolatey/zerotier-one/tools/chocolateyuninstall.ps1 b/zto/ext/installfiles/windows/chocolatey/zerotier-one/tools/chocolateyuninstall.ps1 new file mode 100644 index 0000000..81f7a5a --- /dev/null +++ b/zto/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/zto/ext/installfiles/windows/chocolatey/zerotier-one/zerotier-one.nuspec b/zto/ext/installfiles/windows/chocolatey/zerotier-one/zerotier-one.nuspec new file mode 100644 index 0000000..03987e3 --- /dev/null +++ b/zto/ext/installfiles/windows/chocolatey/zerotier-one/zerotier-one.nuspec @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + zerotier-one + + + + 1.2.2 + + + + + + + + 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/zto/ext/json/LICENSE.MIT b/zto/ext/json/LICENSE.MIT new file mode 100644 index 0000000..e2ac489 --- /dev/null +++ b/zto/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/zto/ext/json/README.md b/zto/ext/json/README.md new file mode 100644 index 0000000..4bcbe97 --- /dev/null +++ b/zto/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/zto/ext/json/json.hpp b/zto/ext/json/json.hpp new file mode 100644 index 0000000..9d48e7a --- /dev/null +++ b/zto/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/zto/ext/libnatpmp/Changelog.txt b/zto/ext/libnatpmp/Changelog.txt new file mode 100644 index 0000000..be75a0b --- /dev/null +++ b/zto/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/zto/ext/libnatpmp/JavaTest.java b/zto/ext/libnatpmp/JavaTest.java new file mode 100644 index 0000000..0379c18 --- /dev/null +++ b/zto/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/zto/ext/libnatpmp/LICENSE b/zto/ext/libnatpmp/LICENSE new file mode 100644 index 0000000..7fff2c2 --- /dev/null +++ b/zto/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/zto/ext/libnatpmp/Makefile b/zto/ext/libnatpmp/Makefile new file mode 100644 index 0000000..b67b3e8 --- /dev/null +++ b/zto/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/zto/ext/libnatpmp/README b/zto/ext/libnatpmp/README new file mode 100644 index 0000000..269392d --- /dev/null +++ b/zto/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/zto/ext/libnatpmp/build.bat b/zto/ext/libnatpmp/build.bat new file mode 100644 index 0000000..2d2f27c --- /dev/null +++ b/zto/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/zto/ext/libnatpmp/declspec.h b/zto/ext/libnatpmp/declspec.h new file mode 100644 index 0000000..a76be02 --- /dev/null +++ b/zto/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/zto/ext/libnatpmp/fr/free/miniupnp/libnatpmp/LibraryExtractor.java b/zto/ext/libnatpmp/fr/free/miniupnp/libnatpmp/LibraryExtractor.java new file mode 100644 index 0000000..5491d94 --- /dev/null +++ b/zto/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/zto/ext/libnatpmp/fr/free/miniupnp/libnatpmp/NatPmp.java b/zto/ext/libnatpmp/fr/free/miniupnp/libnatpmp/NatPmp.java new file mode 100644 index 0000000..2f1ddd3 --- /dev/null +++ b/zto/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/zto/ext/libnatpmp/fr/free/miniupnp/libnatpmp/NatPmpResponse.java b/zto/ext/libnatpmp/fr/free/miniupnp/libnatpmp/NatPmpResponse.java new file mode 100644 index 0000000..35c87ea --- /dev/null +++ b/zto/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/zto/ext/libnatpmp/fr/free/miniupnp/libnatpmp/URLUtils.java b/zto/ext/libnatpmp/fr/free/miniupnp/libnatpmp/URLUtils.java new file mode 100644 index 0000000..5b419ab --- /dev/null +++ b/zto/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/zto/ext/libnatpmp/getgateway.c b/zto/ext/libnatpmp/getgateway.c new file mode 100644 index 0000000..f743a08 --- /dev/null +++ b/zto/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/zto/ext/libnatpmp/getgateway.h b/zto/ext/libnatpmp/getgateway.h new file mode 100644 index 0000000..5d3df73 --- /dev/null +++ b/zto/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/zto/ext/libnatpmp/libnatpmpmodule.c b/zto/ext/libnatpmp/libnatpmpmodule.c new file mode 100644 index 0000000..0fd9914 --- /dev/null +++ b/zto/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/zto/ext/libnatpmp/msvc/libnatpmp.sln b/zto/ext/libnatpmp/msvc/libnatpmp.sln new file mode 100644 index 0000000..ac746d4 --- /dev/null +++ b/zto/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/zto/ext/libnatpmp/msvc/libnatpmp.vcproj b/zto/ext/libnatpmp/msvc/libnatpmp.vcproj new file mode 100644 index 0000000..9bae5c1 --- /dev/null +++ b/zto/ext/libnatpmp/msvc/libnatpmp.vcproj @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/zto/ext/libnatpmp/msvc/natpmpc-static.vcproj b/zto/ext/libnatpmp/msvc/natpmpc-static.vcproj new file mode 100644 index 0000000..c2052d9 --- /dev/null +++ b/zto/ext/libnatpmp/msvc/natpmpc-static.vcproj @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/zto/ext/libnatpmp/natpmp-jni.c b/zto/ext/libnatpmp/natpmp-jni.c new file mode 100644 index 0000000..feec1ce --- /dev/null +++ b/zto/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/zto/ext/libnatpmp/natpmp.c b/zto/ext/libnatpmp/natpmp.c new file mode 100644 index 0000000..9843c41 --- /dev/null +++ b/zto/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/zto/ext/libnatpmp/natpmp.def b/zto/ext/libnatpmp/natpmp.def new file mode 100644 index 0000000..cd11003 --- /dev/null +++ b/zto/ext/libnatpmp/natpmp.def @@ -0,0 +1,11 @@ +LIBRARY +; libnatpmp library + +EXPORTS + initnatpmp + closenatpmp + sendpublicaddressrequest + sendnewportmappingrequest + getnatpmprequesttimeout + readnatpmpresponseorretry + strnatpmperr diff --git a/zto/ext/libnatpmp/natpmp.h b/zto/ext/libnatpmp/natpmp.h new file mode 100644 index 0000000..7889d20 --- /dev/null +++ b/zto/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/zto/ext/libnatpmp/natpmpc.1 b/zto/ext/libnatpmp/natpmpc.1 new file mode 100644 index 0000000..5f0003d --- /dev/null +++ b/zto/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/zto/ext/libnatpmp/natpmpc.c b/zto/ext/libnatpmp/natpmpc.c new file mode 100644 index 0000000..611bd2d --- /dev/null +++ b/zto/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/zto/ext/libnatpmp/setup.py b/zto/ext/libnatpmp/setup.py new file mode 100644 index 0000000..aa774ee --- /dev/null +++ b/zto/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/zto/ext/libnatpmp/setupmingw32.py b/zto/ext/libnatpmp/setupmingw32.py new file mode 100644 index 0000000..d02fdfc --- /dev/null +++ b/zto/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/zto/ext/libnatpmp/testgetgateway.c b/zto/ext/libnatpmp/testgetgateway.c new file mode 100644 index 0000000..24cbe7d --- /dev/null +++ b/zto/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/zto/ext/libnatpmp/wingettimeofday.c b/zto/ext/libnatpmp/wingettimeofday.c new file mode 100644 index 0000000..cb730e1 --- /dev/null +++ b/zto/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/zto/ext/libnatpmp/wingettimeofday.h b/zto/ext/libnatpmp/wingettimeofday.h new file mode 100644 index 0000000..1d18d9f --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/Changelog.txt b/zto/ext/miniupnpc/Changelog.txt new file mode 100644 index 0000000..078bebc --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/LICENSE b/zto/ext/miniupnpc/LICENSE new file mode 100644 index 0000000..cb5a060 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/MANIFEST.in b/zto/ext/miniupnpc/MANIFEST.in new file mode 100644 index 0000000..54b86f9 --- /dev/null +++ b/zto/ext/miniupnpc/MANIFEST.in @@ -0,0 +1,5 @@ +include README +include miniupnpcmodule.c +include setup.py +include *.h +include libminiupnpc.a diff --git a/zto/ext/miniupnpc/README b/zto/ext/miniupnpc/README new file mode 100644 index 0000000..91535db --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/VERSION b/zto/ext/miniupnpc/VERSION new file mode 100644 index 0000000..cd5ac03 --- /dev/null +++ b/zto/ext/miniupnpc/VERSION @@ -0,0 +1 @@ +2.0 diff --git a/zto/ext/miniupnpc/apiversions.txt b/zto/ext/miniupnpc/apiversions.txt new file mode 100644 index 0000000..9464a86 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/codelength.h b/zto/ext/miniupnpc/codelength.h new file mode 100644 index 0000000..f5f8e30 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/connecthostport.c b/zto/ext/miniupnpc/connecthostport.c new file mode 100644 index 0000000..c12d7bd --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/connecthostport.h b/zto/ext/miniupnpc/connecthostport.h new file mode 100644 index 0000000..56941d6 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/external-ip.sh b/zto/ext/miniupnpc/external-ip.sh new file mode 100755 index 0000000..965d86b --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/igd_desc_parse.c b/zto/ext/miniupnpc/igd_desc_parse.c new file mode 100644 index 0000000..d2999ad --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/igd_desc_parse.h b/zto/ext/miniupnpc/igd_desc_parse.h new file mode 100644 index 0000000..0de546b --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/listdevices.c b/zto/ext/miniupnpc/listdevices.c new file mode 100644 index 0000000..a93c29f --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/mingw32make.bat b/zto/ext/miniupnpc/mingw32make.bat new file mode 100644 index 0000000..c5d3cc4 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/minihttptestserver.c b/zto/ext/miniupnpc/minihttptestserver.c new file mode 100644 index 0000000..d95dd7c --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/minisoap.c b/zto/ext/miniupnpc/minisoap.c new file mode 100644 index 0000000..7aa0213 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/minisoap.h b/zto/ext/miniupnpc/minisoap.h new file mode 100644 index 0000000..14c859d --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/minissdpc.c b/zto/ext/miniupnpc/minissdpc.c new file mode 100644 index 0000000..06b11e8 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/minissdpc.h b/zto/ext/miniupnpc/minissdpc.h new file mode 100644 index 0000000..a5c622b --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/miniupnpc.c b/zto/ext/miniupnpc/miniupnpc.c new file mode 100644 index 0000000..2dc5c95 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/miniupnpc.def b/zto/ext/miniupnpc/miniupnpc.def new file mode 100644 index 0000000..60e0bbe --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/miniupnpc.h b/zto/ext/miniupnpc/miniupnpc.h new file mode 100644 index 0000000..4cc45f7 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/miniupnpc_declspec.h b/zto/ext/miniupnpc/miniupnpc_declspec.h new file mode 100644 index 0000000..40adb92 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/miniupnpcmodule.c b/zto/ext/miniupnpc/miniupnpcmodule.c new file mode 100644 index 0000000..a5bdce4 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/miniupnpcstrings.h.in b/zto/ext/miniupnpc/miniupnpcstrings.h.in new file mode 100644 index 0000000..68bf429 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/miniupnpctypes.h b/zto/ext/miniupnpc/miniupnpctypes.h new file mode 100644 index 0000000..591c32f --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/miniwget.c b/zto/ext/miniupnpc/miniwget.c new file mode 100644 index 0000000..93c8aa6 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/miniwget.h b/zto/ext/miniupnpc/miniwget.h new file mode 100644 index 0000000..0701494 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/minixml.c b/zto/ext/miniupnpc/minixml.c new file mode 100644 index 0000000..3e201ec --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/minixml.h b/zto/ext/miniupnpc/minixml.h new file mode 100644 index 0000000..9f43aa4 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/minixmlvalid.c b/zto/ext/miniupnpc/minixmlvalid.c new file mode 100644 index 0000000..dad1488 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/portlistingparse.c b/zto/ext/miniupnpc/portlistingparse.c new file mode 100644 index 0000000..d1954f5 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/portlistingparse.h b/zto/ext/miniupnpc/portlistingparse.h new file mode 100644 index 0000000..661ad1f --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/pymoduletest.py b/zto/ext/miniupnpc/pymoduletest.py new file mode 100644 index 0000000..9fddd9c --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/receivedata.c b/zto/ext/miniupnpc/receivedata.c new file mode 100644 index 0000000..ef85a3d --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/receivedata.h b/zto/ext/miniupnpc/receivedata.h new file mode 100644 index 0000000..0520a11 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/setup.py b/zto/ext/miniupnpc/setup.py new file mode 100644 index 0000000..97e42bf --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/setupmingw32.py b/zto/ext/miniupnpc/setupmingw32.py new file mode 100644 index 0000000..43dfb46 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/testdesc/linksys_WAG200G_desc.values b/zto/ext/miniupnpc/testdesc/linksys_WAG200G_desc.values new file mode 100644 index 0000000..cf42221 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/testdesc/linksys_WAG200G_desc.xml b/zto/ext/miniupnpc/testdesc/linksys_WAG200G_desc.xml new file mode 100644 index 0000000..d428d73 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/testdesc/new_LiveBox_desc.values b/zto/ext/miniupnpc/testdesc/new_LiveBox_desc.values new file mode 100644 index 0000000..c55552e --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/testdesc/new_LiveBox_desc.xml b/zto/ext/miniupnpc/testdesc/new_LiveBox_desc.xml new file mode 100644 index 0000000..620eb55 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/testigddescparse.c b/zto/ext/miniupnpc/testigddescparse.c new file mode 100644 index 0000000..c1907fd --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/testminiwget.c b/zto/ext/miniupnpc/testminiwget.c new file mode 100644 index 0000000..5eb49ec --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/testminiwget.sh b/zto/ext/miniupnpc/testminiwget.sh new file mode 100755 index 0000000..690b405 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/testminixml.c b/zto/ext/miniupnpc/testminixml.c new file mode 100644 index 0000000..57c4a85 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/testreplyparse/DeletePortMapping.namevalue b/zto/ext/miniupnpc/testreplyparse/DeletePortMapping.namevalue new file mode 100644 index 0000000..48ca0cc --- /dev/null +++ b/zto/ext/miniupnpc/testreplyparse/DeletePortMapping.namevalue @@ -0,0 +1,3 @@ +NewRemoteHost= +NewExternalPort=123 +NewProtocol=TCP diff --git a/zto/ext/miniupnpc/testreplyparse/DeletePortMapping.xml b/zto/ext/miniupnpc/testreplyparse/DeletePortMapping.xml new file mode 100644 index 0000000..a955c53 --- /dev/null +++ b/zto/ext/miniupnpc/testreplyparse/DeletePortMapping.xml @@ -0,0 +1,6 @@ + +123 +TCP + + + diff --git a/zto/ext/miniupnpc/testreplyparse/GetExternalIPAddress.namevalue b/zto/ext/miniupnpc/testreplyparse/GetExternalIPAddress.namevalue new file mode 100644 index 0000000..5aa75f8 --- /dev/null +++ b/zto/ext/miniupnpc/testreplyparse/GetExternalIPAddress.namevalue @@ -0,0 +1,2 @@ +NewExternalIPAddress=1.2.3.4 + diff --git a/zto/ext/miniupnpc/testreplyparse/GetExternalIPAddress.xml b/zto/ext/miniupnpc/testreplyparse/GetExternalIPAddress.xml new file mode 100644 index 0000000..db7ec1f --- /dev/null +++ b/zto/ext/miniupnpc/testreplyparse/GetExternalIPAddress.xml @@ -0,0 +1,2 @@ +1.2.3.4 + diff --git a/zto/ext/miniupnpc/testreplyparse/GetSpecificPortMappingEntryReq.namevalue b/zto/ext/miniupnpc/testreplyparse/GetSpecificPortMappingEntryReq.namevalue new file mode 100644 index 0000000..26b169c --- /dev/null +++ b/zto/ext/miniupnpc/testreplyparse/GetSpecificPortMappingEntryReq.namevalue @@ -0,0 +1,3 @@ +NewProtocol=UDP +NewExternalPort=12345 +NewRemoteHost= diff --git a/zto/ext/miniupnpc/testreplyparse/GetSpecificPortMappingEntryReq.xml b/zto/ext/miniupnpc/testreplyparse/GetSpecificPortMappingEntryReq.xml new file mode 100644 index 0000000..bbb540e --- /dev/null +++ b/zto/ext/miniupnpc/testreplyparse/GetSpecificPortMappingEntryReq.xml @@ -0,0 +1,3 @@ + +12345UDP + diff --git a/zto/ext/miniupnpc/testreplyparse/GetSpecificPortMappingEntryResp.namevalue b/zto/ext/miniupnpc/testreplyparse/GetSpecificPortMappingEntryResp.namevalue new file mode 100644 index 0000000..2189789 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/testreplyparse/GetSpecificPortMappingEntryResp.xml b/zto/ext/miniupnpc/testreplyparse/GetSpecificPortMappingEntryResp.xml new file mode 100644 index 0000000..77e8d9c --- /dev/null +++ b/zto/ext/miniupnpc/testreplyparse/GetSpecificPortMappingEntryResp.xml @@ -0,0 +1,2 @@ +12345192.168.10.1101libminiupnpc0 + diff --git a/zto/ext/miniupnpc/testreplyparse/SetDefaultConnectionService.namevalue b/zto/ext/miniupnpc/testreplyparse/SetDefaultConnectionService.namevalue new file mode 100644 index 0000000..f78c7e2 --- /dev/null +++ b/zto/ext/miniupnpc/testreplyparse/SetDefaultConnectionService.namevalue @@ -0,0 +1 @@ +NewDefaultConnectionService=uuid:c6c05a33-f704-48df-9910-e099b3471d81:WANConnectionDevice:1,INVALID_SERVICE_ID diff --git a/zto/ext/miniupnpc/testreplyparse/SetDefaultConnectionService.xml b/zto/ext/miniupnpc/testreplyparse/SetDefaultConnectionService.xml new file mode 100644 index 0000000..ac04c07 --- /dev/null +++ b/zto/ext/miniupnpc/testreplyparse/SetDefaultConnectionService.xml @@ -0,0 +1 @@ +uuid:c6c05a33-f704-48df-9910-e099b3471d81:WANConnectionDevice:1,INVALID_SERVICE_ID diff --git a/zto/ext/miniupnpc/testreplyparse/readme.txt b/zto/ext/miniupnpc/testreplyparse/readme.txt new file mode 100644 index 0000000..3eb1f01 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/testupnpigd.py b/zto/ext/miniupnpc/testupnpigd.py new file mode 100755 index 0000000..6d167a4 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/testupnpreplyparse.c b/zto/ext/miniupnpc/testupnpreplyparse.c new file mode 100644 index 0000000..7ba7131 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/testupnpreplyparse.sh b/zto/ext/miniupnpc/testupnpreplyparse.sh new file mode 100755 index 0000000..992930b --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/updateminiupnpcstrings.sh b/zto/ext/miniupnpc/updateminiupnpcstrings.sh new file mode 100755 index 0000000..dde4354 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/upnpc.c b/zto/ext/miniupnpc/upnpc.c new file mode 100644 index 0000000..8e7edad --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/upnpcommands.h b/zto/ext/miniupnpc/upnpcommands.h new file mode 100644 index 0000000..22eda5e --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/upnpdev.c b/zto/ext/miniupnpc/upnpdev.c new file mode 100644 index 0000000..d89a993 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/upnpdev.h b/zto/ext/miniupnpc/upnpdev.h new file mode 100644 index 0000000..f49fbe1 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/upnperrors.c b/zto/ext/miniupnpc/upnperrors.c new file mode 100644 index 0000000..7ab8ee9 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/upnperrors.h b/zto/ext/miniupnpc/upnperrors.h new file mode 100644 index 0000000..3115aee --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/upnpreplyparse.c b/zto/ext/miniupnpc/upnpreplyparse.c new file mode 100644 index 0000000..5de5796 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/upnpreplyparse.h b/zto/ext/miniupnpc/upnpreplyparse.h new file mode 100644 index 0000000..6badd15 --- /dev/null +++ b/zto/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/zto/ext/miniupnpc/wingenminiupnpcstrings.c b/zto/ext/miniupnpc/wingenminiupnpcstrings.c new file mode 100644 index 0000000..50df06a --- /dev/null +++ b/zto/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/zto/ext/tap-mac/README.txt b/zto/ext/tap-mac/README.txt new file mode 100644 index 0000000..177b936 --- /dev/null +++ b/zto/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/zto/ext/tap-mac/tuntap/Makefile b/zto/ext/tap-mac/tuntap/Makefile new file mode 100644 index 0000000..53ab1a9 --- /dev/null +++ b/zto/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/zto/ext/tap-mac/tuntap/src/lock.cc b/zto/ext/tap-mac/tuntap/src/lock.cc new file mode 100644 index 0000000..9c78783 --- /dev/null +++ b/zto/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/zto/ext/tap-mac/tuntap/src/lock.h b/zto/ext/tap-mac/tuntap/src/lock.h new file mode 100644 index 0000000..51d3299 --- /dev/null +++ b/zto/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/zto/ext/tap-mac/tuntap/src/mem.cc b/zto/ext/tap-mac/tuntap/src/mem.cc new file mode 100644 index 0000000..cd3264f --- /dev/null +++ b/zto/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/zto/ext/tap-mac/tuntap/src/mem.h b/zto/ext/tap-mac/tuntap/src/mem.h new file mode 100644 index 0000000..4d06fd8 --- /dev/null +++ b/zto/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/zto/ext/tap-mac/tuntap/src/tap/Info.plist b/zto/ext/tap-mac/tuntap/src/tap/Info.plist new file mode 100644 index 0000000..bb9b03f --- /dev/null +++ b/zto/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/zto/ext/tap-mac/tuntap/src/tap/Makefile b/zto/ext/tap-mac/tuntap/src/tap/Makefile new file mode 100644 index 0000000..306a86d --- /dev/null +++ b/zto/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/zto/ext/tap-mac/tuntap/src/tap/kmod.cc b/zto/ext/tap-mac/tuntap/src/tap/kmod.cc new file mode 100644 index 0000000..f9c4a40 --- /dev/null +++ b/zto/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/zto/ext/tap-mac/tuntap/src/tap/tap.cc b/zto/ext/tap-mac/tuntap/src/tap/tap.cc new file mode 100644 index 0000000..b348a85 --- /dev/null +++ b/zto/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/zto/ext/tap-mac/tuntap/src/tap/tap.h b/zto/ext/tap-mac/tuntap/src/tap/tap.h new file mode 100644 index 0000000..a5164d4 --- /dev/null +++ b/zto/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/zto/ext/tap-mac/tuntap/src/tuntap.cc b/zto/ext/tap-mac/tuntap/src/tuntap.cc new file mode 100644 index 0000000..d0f8901 --- /dev/null +++ b/zto/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/zto/ext/tap-mac/tuntap/src/tuntap.h b/zto/ext/tap-mac/tuntap/src/tuntap.h new file mode 100644 index 0000000..d5f398d --- /dev/null +++ b/zto/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/zto/ext/tap-mac/tuntap/src/tuntap_mgr.cc b/zto/ext/tap-mac/tuntap/src/tuntap_mgr.cc new file mode 100644 index 0000000..f41394e --- /dev/null +++ b/zto/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/zto/ext/tap-mac/tuntap/src/util.h b/zto/ext/tap-mac/tuntap/src/util.h new file mode 100644 index 0000000..0f6955e --- /dev/null +++ b/zto/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/zto/include/README.md b/zto/include/README.md new file mode 100644 index 0000000..a3254ba --- /dev/null +++ b/zto/include/README.md @@ -0,0 +1,4 @@ +ZeroTier Node API +====== + +This is the externally facing plain C API, which wraps the Node class in the node/ folder. It provides a platform-agnostic interface to the core ZeroTier network virtualization engine. diff --git a/zto/include/ZeroTierOne.h b/zto/include/ZeroTierOne.h new file mode 100644 index 0000000..747e185 --- /dev/null +++ b/zto/include/ZeroTierOne.h @@ -0,0 +1,2147 @@ +/* + * 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 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/zto/make-bsd.mk b/zto/make-bsd.mk new file mode 100644 index 0000000..b038d13 --- /dev/null +++ b/zto/make-bsd.mk @@ -0,0 +1,84 @@ +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=0 +ifeq ($(CC_MACH),x86_64) + ZT_ARCHITECTURE=2 +endif +ifeq ($(CC_MACH),amd64) + ZT_ARCHITECTURE=2 +endif +ifeq ($(CC_MACH),i386) + ZT_ARCHITECTURE=1 +endif +ifeq ($(CC_MACH),i686) + ZT_ARCHITECTURE=1 +endif +ifeq ($(CC_MACH),arm) + ZT_ARCHITECTURE=3 +endif +ifeq ($(CC_MACH),arm64) + ZT_ARCHITECTURE=4 +endif +ifeq ($(CC_MACH),aarch64) + ZT_ARCHITECTURE=4 +endif +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-* + +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/zto/make-linux.mk b/zto/make-linux.mk new file mode 100644 index 0000000..a606d58 --- /dev/null +++ b/zto/make-linux.mk @@ -0,0 +1,213 @@ +# 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=0 +ifeq ($(CC_MACH),x86_64) + ZT_ARCHITECTURE=2 +endif +ifeq ($(CC_MACH),amd64) + ZT_ARCHITECTURE=2 +endif +ifeq ($(CC_MACH),i386) + ZT_ARCHITECTURE=1 +endif +ifeq ($(CC_MACH),i686) + ZT_ARCHITECTURE=1 +endif +ifeq ($(CC_MACH),arm) + ZT_ARCHITECTURE=3 +endif +ifeq ($(CC_MACH),armel) + ZT_ARCHITECTURE=3 +endif +ifeq ($(CC_MACH),armhf) + ZT_ARCHITECTURE=3 +endif +ifeq ($(CC_MACH),armv6) + ZT_ARCHITECTURE=3 +endif +ifeq ($(CC_MACH),armv7) + ZT_ARCHITECTURE=3 +endif +ifeq ($(CC_MACH),arm64) + ZT_ARCHITECTURE=4 +endif +ifeq ($(CC_MACH),aarch64) + ZT_ARCHITECTURE=4 +endif +DEFS+=-DZT_BUILD_PLATFORM=1 -DZT_BUILD_ARCHITECTURE=$(ZT_ARCHITECTURE) -DZT_SOFTWARE_UPDATE_DEFAULT="\"disable\"" + +# Define some conservative CPU instruction set flags for arm32 since there's a ton of variation out there +ifeq ($(ZT_ARCHITECTURE),3) + override CFLAGS+=-march=armv6zk -mcpu=arm1176jzf-s -mfloat-abi=hard -mfpu=vfp + override CXXFLAGS+=-march=armv6zk -mcpu=arm1176jzf-s -mfloat-abi=hard -mfpu=vfp + override DEFS+=-DZT_NO_TYPE_PUNNING +endif + +# Define this to build a static binary, which is needed to make this runnable on a few ancient Linux distros +ifeq ($(ZT_STATIC),1) + override LDFLAGS+=-static +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/zto/make-mac.mk b/zto/make-mac.mk new file mode 100644 index 0000000..8ff1b77 --- /dev/null +++ b/zto/make-mac.mk @@ -0,0 +1,111 @@ +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 + +# 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 + +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/zto/node/Address.hpp b/zto/node/Address.hpp new file mode 100644 index 0000000..4a5883b --- /dev/null +++ b/zto/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/zto/node/Array.hpp b/zto/node/Array.hpp new file mode 100644 index 0000000..19b29eb --- /dev/null +++ b/zto/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/zto/node/AtomicCounter.hpp b/zto/node/AtomicCounter.hpp new file mode 100644 index 0000000..a0f29ba --- /dev/null +++ b/zto/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/zto/node/Buffer.hpp b/zto/node/Buffer.hpp new file mode 100644 index 0000000..37f39e7 --- /dev/null +++ b/zto/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/zto/node/C25519.cpp b/zto/node/C25519.cpp new file mode 100644 index 0000000..e9ffecc --- /dev/null +++ b/zto/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/zto/node/Capability.cpp b/zto/node/Capability.cpp new file mode 100644 index 0000000..c178e56 --- /dev/null +++ b/zto/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/zto/node/Capability.hpp b/zto/node/Capability.hpp new file mode 100644 index 0000000..5ef6c99 --- /dev/null +++ b/zto/node/Capability.hpp @@ -0,0 +1,469 @@ +/* + * 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 "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: + 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/zto/node/CertificateOfMembership.cpp b/zto/node/CertificateOfMembership.cpp new file mode 100644 index 0000000..9bf7021 --- /dev/null +++ b/zto/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/zto/node/CertificateOfMembership.hpp b/zto/node/CertificateOfMembership.hpp new file mode 100644 index 0000000..ae976b5 --- /dev/null +++ b/zto/node/CertificateOfMembership.hpp @@ -0,0 +1,358 @@ +/* + * 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 "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: + /** + * 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 throw() { return (_qualifierCount != 0); } + + /** + * @return Timestamp for this cert and maximum delta for timestamp + */ + inline std::pair timestamp() const + { + for(unsigned int i=0;i<_qualifierCount;++i) { + if (_qualifiers[i].id == COM_RESERVED_ID_TIMESTAMP) + return std::pair(_qualifiers[i].value,_qualifiers[i].maxDelta); + } + return std::pair(0ULL,0ULL); + } + + /** + * @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 throw() { return (_signedBy); } + + /** + * @return Address that signed this certificate or null address if none + */ + inline const Address &signedBy() const throw() { 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 + throw() + { + 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 throw() { 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/zto/node/CertificateOfOwnership.cpp b/zto/node/CertificateOfOwnership.cpp new file mode 100644 index 0000000..2bd181e --- /dev/null +++ b/zto/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/zto/node/CertificateOfOwnership.hpp b/zto/node/CertificateOfOwnership.hpp new file mode 100644 index 0000000..8c47582 --- /dev/null +++ b/zto/node/CertificateOfOwnership.hpp @@ -0,0 +1,234 @@ +/* + * 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 "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: + 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/zto/node/CertificateOfRepresentation.hpp b/zto/node/CertificateOfRepresentation.hpp new file mode 100644 index 0000000..02e961c --- /dev/null +++ b/zto/node/CertificateOfRepresentation.hpp @@ -0,0 +1,176 @@ +/* + * 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 "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: + CertificateOfRepresentation() + { + memset(this,0,sizeof(CertificateOfRepresentation)); + } + + 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/zto/node/Cluster.cpp b/zto/node/Cluster.cpp new file mode 100644 index 0000000..54206f9 --- /dev/null +++ b/zto/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/zto/node/Cluster.hpp b/zto/node/Cluster.hpp new file mode 100644 index 0000000..08e32a9 --- /dev/null +++ b/zto/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/zto/node/Constants.hpp b/zto/node/Constants.hpp new file mode 100644 index 0000000..410a245 --- /dev/null +++ b/zto/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 produced 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/zto/node/Dictionary.hpp b/zto/node/Dictionary.hpp new file mode 100644 index 0000000..fa9e288 --- /dev/null +++ b/zto/node/Dictionary.hpp @@ -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 . + */ + +#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++] = '\n'; + 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 '\r': + case '\n': + case '\\': + case '=': + _d[j++] = '\\'; + if (j == C) { + _d[i] = (char)0; + return false; + } + switch(*p) { + case 0: _d[j++] = '0'; break; + case '\r': _d[j++] = 'r'; break; + case '\n': _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); + } + + /** + * Erase a key from this dictionary + * + * Use this before add() to ensure that a key is replaced if it might + * already be present. + * + * @param key Key to erase + * @return True if key was found and erased + */ + inline bool erase(const char *key) + { + char d2[C]; + char *saveptr = (char *)0; + unsigned int d2ptr = 0; + bool found = false; + for(char *f=Utils::stok(_d,"\r\n",&saveptr);(f);f=Utils::stok((char *)0,"\r\n",&saveptr)) { + if (*f) { + const char *p = f; + const char *k = key; + while ((*k)&&(*p)) { + if (*k != *p) + break; + ++k; + ++p; + } + if (*k) { + p = f; + while (*p) + d2[d2ptr++] = *(p++); + d2[d2ptr++] = '\n'; + } else { + found = true; + } + } + } + d2[d2ptr++] = (char)0; + memcpy(_d,d2,d2ptr); + return found; + } + + /** + * @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/zto/node/Hashtable.hpp b/zto/node/Hashtable.hpp new file mode 100644 index 0000000..66f2990 --- /dev/null +++ b/zto/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/zto/node/Identity.cpp b/zto/node/Identity.cpp new file mode 100644 index 0000000..89fdb83 --- /dev/null +++ b/zto/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,256,(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/zto/node/Identity.hpp b/zto/node/Identity.hpp new file mode 100644 index 0000000..e452273 --- /dev/null +++ b/zto/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/zto/node/IncomingPacket.cpp b/zto/node/IncomingPacket.cpp new file mode 100644 index 0000000..52794fd --- /dev/null +++ b/zto/node/IncomingPacket.cpp @@ -0,0 +1,1473 @@ +/* + * 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: { + InetAddress a(field(ptr,4),4,at(ptr + 4)); + + bool redundant = false; + if ((flags & ZT_PUSH_DIRECT_PATHS_FLAG_CLUSTER_REDIRECT) != 0) { + peer->setClusterOptimal(a); + } else { + redundant = peer->hasActivePathTo(now,a); + } + + if ( ((flags & ZT_PUSH_DIRECT_PATHS_FLAG_FORGET_PATH) == 0) && (!redundant) && (RR->node->shouldUsePathForZeroTierTraffic(tPtr,peer->address(),_path->localAddress(),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: { + InetAddress a(field(ptr,16),16,at(ptr + 16)); + + bool redundant = false; + if ((flags & ZT_PUSH_DIRECT_PATHS_FLAG_CLUSTER_REDIRECT) != 0) { + peer->setClusterOptimal(a); + } else { + redundant = peer->hasActivePathTo(now,a); + } + + if ( ((flags & ZT_PUSH_DIRECT_PATHS_FLAG_FORGET_PATH) == 0) && (!redundant) && (RR->node->shouldUsePathForZeroTierTraffic(tPtr,peer->address(),_path->localAddress(),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/zto/node/IncomingPacket.hpp b/zto/node/IncomingPacket.hpp new file mode 100644 index 0000000..3d4a2e0 --- /dev/null +++ b/zto/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/zto/node/InetAddress.cpp b/zto/node/InetAddress.cpp new file mode 100644 index 0000000..7d22eea --- /dev/null +++ b/zto/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/zto/node/InetAddress.hpp b/zto/node/InetAddress.hpp new file mode 100644 index 0000000..c37fa62 --- /dev/null +++ b/zto/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/zto/node/MAC.hpp b/zto/node/MAC.hpp new file mode 100644 index 0000000..95623f1 --- /dev/null +++ b/zto/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/zto/node/Membership.cpp b/zto/node/Membership.cpp new file mode 100644 index 0000000..22c13c8 --- /dev/null +++ b/zto/node/Membership.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 "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) +{ + for(unsigned int i=0;i= ZT_CREDENTIAL_PUSH_EVERY) || (force) ) ); + + const Capability *sendCap; + if (localCapabilityIndex >= 0) { + sendCap = &(nconf.capabilities[localCapabilityIndex]); + if ( (_localCaps[localCapabilityIndex].id != sendCap->id()) || ((now - _localCaps[localCapabilityIndex].lastPushed) >= ZT_CREDENTIAL_PUSH_EVERY) || (force) ) { + _localCaps[localCapabilityIndex].lastPushed = now; + _localCaps[localCapabilityIndex].id = sendCap->id(); + } 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) ) { + _localTags[t].lastPushed = now; + _localTags[t].id = nconf.tags[t].id(); + 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) ) { + _localCoos[c].lastPushed = now; + _localCoos[c].id = nconf.certificatesOfOwnership[c].id(); + 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); + } +} + +const Tag *Membership::getTag(const NetworkConfig &nconf,const uint32_t id) const +{ + const _RemoteCredential *const *t = std::lower_bound(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS]),(uint64_t)id,_RemoteCredentialComp()); + return ( ((t != &(_remoteTags[ZT_MAX_NETWORK_CAPABILITIES]))&&((*t)->id == (uint64_t)id)) ? ((((*t)->lastReceived)&&(_isCredentialTimestampValid(nconf,**t))) ? &((*t)->credential) : (const Tag *)0) : (const Tag *)0); +} + +Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment *RR,void *tPtr,const NetworkConfig &nconf,const CertificateOfMembership &com) +{ + const uint64_t newts = com.timestamp().first; + 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().first; + 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; + } +} + +Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment *RR,void *tPtr,const NetworkConfig &nconf,const Tag &tag) +{ + _RemoteCredential *const *htmp = std::lower_bound(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS]),(uint64_t)tag.id(),_RemoteCredentialComp()); + _RemoteCredential *have = ((htmp != &(_remoteTags[ZT_MAX_NETWORK_TAGS]))&&((*htmp)->id == (uint64_t)tag.id())) ? *htmp : (_RemoteCredential *)0; + if (have) { + if ( (!_isCredentialTimestampValid(nconf,*have)) || (have->credential.timestamp() > tag.timestamp()) ) { + TRACE("addCredential(Tag) for %s on %.16llx REJECTED (revoked or too old)",tag.issuedTo().toString().c_str(),tag.networkId()); + return ADD_REJECTED; + } + if (have->credential == tag) { + TRACE("addCredential(Tag) for %s on %.16llx ACCEPTED (redundant)",tag.issuedTo().toString().c_str(),tag.networkId()); + return ADD_ACCEPTED_REDUNDANT; + } + } + + switch(tag.verify(RR,tPtr)) { + default: + TRACE("addCredential(Tag) for %s on %.16llx REJECTED (invalid)",tag.issuedTo().toString().c_str(),tag.networkId()); + return ADD_REJECTED; + case 0: + TRACE("addCredential(Tag) for %s on %.16llx ACCEPTED (new)",tag.issuedTo().toString().c_str(),tag.networkId()); + if (!have) have = _newTag(tag.id()); + have->lastReceived = RR->node->now(); + have->credential = tag; + return ADD_ACCEPTED_NEW; + case 1: + return ADD_DEFERRED_FOR_WHOIS; + } +} + +Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment *RR,void *tPtr,const NetworkConfig &nconf,const Capability &cap) +{ + _RemoteCredential *const *htmp = std::lower_bound(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]),(uint64_t)cap.id(),_RemoteCredentialComp()); + _RemoteCredential *have = ((htmp != &(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]))&&((*htmp)->id == (uint64_t)cap.id())) ? *htmp : (_RemoteCredential *)0; + if (have) { + if ( (!_isCredentialTimestampValid(nconf,*have)) || (have->credential.timestamp() > cap.timestamp()) ) { + TRACE("addCredential(Capability) for %s on %.16llx REJECTED (revoked or too old)",cap.issuedTo().toString().c_str(),cap.networkId()); + return ADD_REJECTED; + } + if (have->credential == cap) { + TRACE("addCredential(Capability) for %s on %.16llx ACCEPTED (redundant)",cap.issuedTo().toString().c_str(),cap.networkId()); + return ADD_ACCEPTED_REDUNDANT; + } + } + + switch(cap.verify(RR,tPtr)) { + default: + TRACE("addCredential(Capability) for %s on %.16llx REJECTED (invalid)",cap.issuedTo().toString().c_str(),cap.networkId()); + return ADD_REJECTED; + case 0: + TRACE("addCredential(Capability) for %s on %.16llx ACCEPTED (new)",cap.issuedTo().toString().c_str(),cap.networkId()); + if (!have) have = _newCapability(cap.id()); + have->lastReceived = RR->node->now(); + have->credential = cap; + return ADD_ACCEPTED_NEW; + case 1: + return ADD_DEFERRED_FOR_WHOIS; + } +} + +Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment *RR,void *tPtr,const NetworkConfig &nconf,const Revocation &rev) +{ + switch(rev.verify(RR,tPtr)) { + default: + return ADD_REJECTED; + case 0: { + const uint64_t now = RR->node->now(); + switch(rev.type()) { + default: + return ADD_REJECTED; + case Revocation::CREDENTIAL_TYPE_COM: + return (_revokeCom(rev) ? ADD_ACCEPTED_NEW : ADD_ACCEPTED_REDUNDANT); + case Revocation::CREDENTIAL_TYPE_CAPABILITY: + return (_revokeCap(rev,now) ? ADD_ACCEPTED_NEW : ADD_ACCEPTED_REDUNDANT); + case Revocation::CREDENTIAL_TYPE_TAG: + return (_revokeTag(rev,now) ? ADD_ACCEPTED_NEW : ADD_ACCEPTED_REDUNDANT); + case Revocation::CREDENTIAL_TYPE_COO: + return (_revokeCoo(rev,now) ? ADD_ACCEPTED_NEW : ADD_ACCEPTED_REDUNDANT); + } + } + case 1: + return ADD_DEFERRED_FOR_WHOIS; + } +} + +Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment *RR,void *tPtr,const NetworkConfig &nconf,const CertificateOfOwnership &coo) +{ + _RemoteCredential *const *htmp = std::lower_bound(&(_remoteCoos[0]),&(_remoteCoos[ZT_MAX_CERTIFICATES_OF_OWNERSHIP]),(uint64_t)coo.id(),_RemoteCredentialComp()); + _RemoteCredential *have = ((htmp != &(_remoteCoos[ZT_MAX_CERTIFICATES_OF_OWNERSHIP]))&&((*htmp)->id == (uint64_t)coo.id())) ? *htmp : (_RemoteCredential *)0; + if (have) { + if ( (!_isCredentialTimestampValid(nconf,*have)) || (have->credential.timestamp() > coo.timestamp()) ) { + TRACE("addCredential(CertificateOfOwnership) for %s on %.16llx REJECTED (revoked or too old)",coo.issuedTo().toString().c_str(),coo.networkId()); + return ADD_REJECTED; + } + if (have->credential == coo) { + TRACE("addCredential(CertificateOfOwnership) for %s on %.16llx ACCEPTED (redundant)",coo.issuedTo().toString().c_str(),coo.networkId()); + return ADD_ACCEPTED_REDUNDANT; + } + } + + switch(coo.verify(RR,tPtr)) { + default: + TRACE("addCredential(CertificateOfOwnership) for %s on %.16llx REJECTED (invalid)",coo.issuedTo().toString().c_str(),coo.networkId()); + return ADD_REJECTED; + case 0: + TRACE("addCredential(CertificateOfOwnership) for %s on %.16llx ACCEPTED (new)",coo.issuedTo().toString().c_str(),coo.networkId()); + if (!have) have = _newCoo(coo.id()); + have->lastReceived = RR->node->now(); + have->credential = coo; + return ADD_ACCEPTED_NEW; + case 1: + return ADD_DEFERRED_FOR_WHOIS; + } +} + +Membership::_RemoteCredential *Membership::_newTag(const uint64_t id) +{ + _RemoteCredential *t = NULL; + uint64_t minlr = 0xffffffffffffffffULL; + for(unsigned int i=0;iid == ZT_MEMBERSHIP_CRED_ID_UNUSED) { + t = _remoteTags[i]; + break; + } else if (_remoteTags[i]->lastReceived <= minlr) { + t = _remoteTags[i]; + minlr = _remoteTags[i]->lastReceived; + } + } + + if (t) { + t->id = id; + t->lastReceived = 0; + t->revocationThreshold = 0; + t->credential = Tag(); + } + + std::sort(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS]),_RemoteCredentialComp()); + return t; +} + +Membership::_RemoteCredential *Membership::_newCapability(const uint64_t id) +{ + _RemoteCredential *c = NULL; + uint64_t minlr = 0xffffffffffffffffULL; + for(unsigned int i=0;iid == ZT_MEMBERSHIP_CRED_ID_UNUSED) { + c = _remoteCaps[i]; + break; + } else if (_remoteCaps[i]->lastReceived <= minlr) { + c = _remoteCaps[i]; + minlr = _remoteCaps[i]->lastReceived; + } + } + + if (c) { + c->id = id; + c->lastReceived = 0; + c->revocationThreshold = 0; + c->credential = Capability(); + } + + std::sort(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]),_RemoteCredentialComp()); + return c; +} + +Membership::_RemoteCredential *Membership::_newCoo(const uint64_t id) +{ + _RemoteCredential *c = NULL; + uint64_t minlr = 0xffffffffffffffffULL; + for(unsigned int i=0;iid == ZT_MEMBERSHIP_CRED_ID_UNUSED) { + c = _remoteCoos[i]; + break; + } else if (_remoteCoos[i]->lastReceived <= minlr) { + c = _remoteCoos[i]; + minlr = _remoteCoos[i]->lastReceived; + } + } + + if (c) { + c->id = id; + c->lastReceived = 0; + c->revocationThreshold = 0; + c->credential = CertificateOfOwnership(); + } + + std::sort(&(_remoteCoos[0]),&(_remoteCoos[ZT_MAX_CERTIFICATES_OF_OWNERSHIP]),_RemoteCredentialComp()); + return c; +} + +bool Membership::_revokeCom(const Revocation &rev) +{ + if (rev.threshold() > _comRevocationThreshold) { + _comRevocationThreshold = rev.threshold(); + return true; + } + return false; +} + +bool Membership::_revokeCap(const Revocation &rev,const uint64_t now) +{ + _RemoteCredential *const *htmp = std::lower_bound(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]),(uint64_t)rev.credentialId(),_RemoteCredentialComp()); + _RemoteCredential *have = ((htmp != &(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]))&&((*htmp)->id == (uint64_t)rev.credentialId())) ? *htmp : (_RemoteCredential *)0; + if (!have) have = _newCapability(rev.credentialId()); + if (rev.threshold() > have->revocationThreshold) { + have->lastReceived = now; + have->revocationThreshold = rev.threshold(); + return true; + } + return false; +} + +bool Membership::_revokeTag(const Revocation &rev,const uint64_t now) +{ + _RemoteCredential *const *htmp = std::lower_bound(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS]),(uint64_t)rev.credentialId(),_RemoteCredentialComp()); + _RemoteCredential *have = ((htmp != &(_remoteTags[ZT_MAX_NETWORK_TAGS]))&&((*htmp)->id == (uint64_t)rev.credentialId())) ? *htmp : (_RemoteCredential *)0; + if (!have) have = _newTag(rev.credentialId()); + if (rev.threshold() > have->revocationThreshold) { + have->lastReceived = now; + have->revocationThreshold = rev.threshold(); + return true; + } + return false; +} + +bool Membership::_revokeCoo(const Revocation &rev,const uint64_t now) +{ + _RemoteCredential *const *htmp = std::lower_bound(&(_remoteCoos[0]),&(_remoteCoos[ZT_MAX_CERTIFICATES_OF_OWNERSHIP]),(uint64_t)rev.credentialId(),_RemoteCredentialComp()); + _RemoteCredential *have = ((htmp != &(_remoteCoos[ZT_MAX_CERTIFICATES_OF_OWNERSHIP]))&&((*htmp)->id == (uint64_t)rev.credentialId())) ? *htmp : (_RemoteCredential *)0; + if (!have) have = _newCoo(rev.credentialId()); + if (rev.threshold() > have->revocationThreshold) { + have->lastReceived = now; + have->revocationThreshold = rev.threshold(); + return true; + } + return false; +} + +} // namespace ZeroTier diff --git a/zto/node/Membership.hpp b/zto/node/Membership.hpp new file mode 100644 index 0000000..c28d598 --- /dev/null +++ b/zto/node/Membership.hpp @@ -0,0 +1,300 @@ +/* + * 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 "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 +{ +private: + template + struct _RemoteCredential + { + _RemoteCredential() : id(ZT_MEMBERSHIP_CRED_ID_UNUSED),lastReceived(0),revocationThreshold(0) {} + uint64_t id; + uint64_t lastReceived; // last time we got this credential + uint64_t revocationThreshold; // credentials before this time are invalid + T credential; + inline bool operator<(const _RemoteCredential &c) const { return (id < c.id); } + }; + + template + struct _RemoteCredentialComp + { + inline bool operator()(const _RemoteCredential *a,const _RemoteCredential *b) const { return (a->id < b->id); } + inline bool operator()(const uint64_t a,const _RemoteCredential *b) const { return (a < b->id); } + inline bool operator()(const _RemoteCredential *a,const uint64_t b) const { return (a->id < b); } + inline bool operator()(const uint64_t a,const uint64_t b) const { return (a < b); } + }; + + // Used to track push state for network config tags[] and capabilities[] entries + struct _LocalCredentialPushState + { + _LocalCredentialPushState() : lastPushed(0),id(0) {} + uint64_t lastPushed; // last time we sent our own copy of this credential + uint64_t id; + }; + +public: + enum AddCredentialResult + { + ADD_REJECTED, + ADD_ACCEPTED_NEW, + ADD_ACCEPTED_REDUNDANT, + ADD_DEFERRED_FOR_WHOIS + }; + + /** + * Iterator to scan forward through capabilities in ascending order of ID + */ + class CapabilityIterator + { + public: + CapabilityIterator(const Membership &m,const NetworkConfig &nconf) : + _m(&m), + _c(&nconf), + _i(&(m._remoteCaps[0])) {} + + inline const Capability *next() + { + for(;;) { + if ((_i != &(_m->_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]))&&((*_i)->id != ZT_MEMBERSHIP_CRED_ID_UNUSED)) { + const Capability *tmp = &((*_i)->credential); + if (_m->_isCredentialTimestampValid(*_c,**_i)) { + ++_i; + return tmp; + } else ++_i; + } else { + return (const Capability *)0; + } + } + } + + private: + const Membership *_m; + const NetworkConfig *_c; + const _RemoteCredential *const *_i; + }; + friend class CapabilityIterator; + + /** + * Iterator to scan forward through tags in ascending order of ID + */ + class TagIterator + { + public: + TagIterator(const Membership &m,const NetworkConfig &nconf) : + _m(&m), + _c(&nconf), + _i(&(m._remoteTags[0])) {} + + inline const Tag *next() + { + for(;;) { + if ((_i != &(_m->_remoteTags[ZT_MAX_NETWORK_TAGS]))&&((*_i)->id != ZT_MEMBERSHIP_CRED_ID_UNUSED)) { + const Tag *tmp = &((*_i)->credential); + if (_m->_isCredentialTimestampValid(*_c,**_i)) { + ++_i; + return tmp; + } else ++_i; + } else { + return (const Tag *)0; + } + } + } + + private: + const Membership *_m; + const NetworkConfig *_c; + const _RemoteCredential *const *_i; + }; + friend class TagIterator; + + 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 + * + * @param now Current time + * @return True if we should update multicasts + */ + inline bool shouldLikeMulticasts(const uint64_t now) const { return ((now - _lastUpdatedMulticast) >= ZT_MULTICAST_ANNOUNCE_PERIOD); } + + /** + * Set time we last updated multicasts for this peer + * + * @param now Current time + */ + inline void likingMulticasts(const uint64_t now) { _lastUpdatedMulticast = now; } + + /** + * 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().first <= _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 + { + for(unsigned int i=0;iid == ZT_MEMBERSHIP_CRED_ID_UNUSED) + break; + if ((_isCredentialTimestampValid(nconf,*_remoteCoos[i]))&&(_remoteCoos[i]->credential.owns(r))) + return true; + } + return false; + } + + /** + * @param nconf Network configuration + * @param id Tag ID + * @return Pointer to tag or NULL if not found + */ + const Tag *getTag(const NetworkConfig &nconf,const uint32_t id) const; + + /** + * 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 Revocation &rev); + + /** + * 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); + +private: + _RemoteCredential *_newTag(const uint64_t id); + _RemoteCredential *_newCapability(const uint64_t id); + _RemoteCredential *_newCoo(const uint64_t id); + bool _revokeCom(const Revocation &rev); + bool _revokeCap(const Revocation &rev,const uint64_t now); + bool _revokeTag(const Revocation &rev,const uint64_t now); + bool _revokeCoo(const Revocation &rev,const uint64_t now); + + template + inline bool _isCredentialTimestampValid(const NetworkConfig &nconf,const _RemoteCredential &remoteCredential) const + { + if (!remoteCredential.lastReceived) + return false; + const uint64_t ts = remoteCredential.credential.timestamp(); + return ( (((ts >= nconf.timestamp) ? (ts - nconf.timestamp) : (nconf.timestamp - ts)) <= nconf.credentialTimeMaxDelta) && (ts > remoteCredential.revocationThreshold) ); + } + + // 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; + + // Sorted (in ascending order of ID) arrays of pointers to remote credentials + _RemoteCredential *_remoteTags[ZT_MAX_NETWORK_TAGS]; + _RemoteCredential *_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]; + _RemoteCredential *_remoteCoos[ZT_MAX_CERTIFICATES_OF_OWNERSHIP]; + + // This is the RAM allocated for remote credential cache objects + _RemoteCredential _tagMem[ZT_MAX_NETWORK_TAGS]; + _RemoteCredential _capMem[ZT_MAX_NETWORK_CAPABILITIES]; + _RemoteCredential _cooMem[ZT_MAX_CERTIFICATES_OF_OWNERSHIP]; + + // Local credential push state tracking + _LocalCredentialPushState _localTags[ZT_MAX_NETWORK_TAGS]; + _LocalCredentialPushState _localCaps[ZT_MAX_NETWORK_CAPABILITIES]; + _LocalCredentialPushState _localCoos[ZT_MAX_CERTIFICATES_OF_OWNERSHIP]; +}; + +} // namespace ZeroTier + +#endif diff --git a/zto/node/MulticastGroup.hpp b/zto/node/MulticastGroup.hpp new file mode 100644 index 0000000..be4e808 --- /dev/null +++ b/zto/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/zto/node/Multicaster.cpp b/zto/node/Multicaster.cpp new file mode 100644 index 0000000..8e534b5 --- /dev/null +++ b/zto/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/zto/node/Multicaster.hpp b/zto/node/Multicaster.hpp new file mode 100644 index 0000000..f646a5b --- /dev/null +++ b/zto/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/zto/node/Mutex.hpp b/zto/node/Mutex.hpp new file mode 100644 index 0000000..d451ede --- /dev/null +++ b/zto/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/zto/node/Network.cpp b/zto/node/Network.cpp new file mode 100644 index 0000000..0abfdf8 --- /dev/null +++ b/zto/node/Network.cpp @@ -0,0 +1,1615 @@ +/* + * 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) { + 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) +{ + 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) +{ + // _lock is NOT locked when this is called + try { + if ((nconf.issuedTo != RR->identity.address())||(nconf.networkId != _id)) + return 0; + if (_config == nconf) + return 1; // OK config, but duplicate of what we already have + + ZT_VirtualNetworkConfig ctmp; + bool oldPortInitialized; + { + Mutex::Lock _l(_lock); + _config = nconf; + _lastConfigUpdate = RR->node->now(); + _netconfFailure = NETCONF_FAILURE_NONE; + oldPortInitialized = _portInitialized; + _portInitialized = true; + _externalConfig(&ctmp); + } + _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) +{ + /* 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->shouldLikeMulticasts(now)) { + m->pushCredentials(RR,tPtr,now,peer->address(),_config,-1,false); + _announceMulticastGroupsTo(tPtr,peer->address(),_allMulticastGroups()); + m->likingMulticasts(now); + } + 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); + } + } +} + +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. Note that _membership() also does this but in a + // piecemeal on-demand fashion. + 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 ( ((newMulticastGroup)||(m->shouldLikeMulticasts(now))) && (m->isAllowedOnNetwork(_config)) ) { + if (!newMulticastGroup) + m->likingMulticasts(now); + _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/zto/node/Network.hpp b/zto/node/Network.hpp new file mode 100644 index 0000000..fccc267 --- /dev/null +++ b/zto/node/Network.hpp @@ -0,0 +1,423 @@ +/* + * 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 causes the network to disable itself, destroy its tap device, and on + * delete to delete all trace of itself on disk and remove any persistent tap + * device instances. Call this when a network is being removed from the system. + */ + 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() throw() { 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/zto/node/NetworkConfig.cpp b/zto/node/NetworkConfig.cpp new file mode 100644 index 0000000..fe7393e --- /dev/null +++ b/zto/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/zto/node/NetworkConfig.hpp b/zto/node/NetworkConfig.hpp new file mode 100644 index 0000000..85c2409 --- /dev/null +++ b/zto/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/zto/node/Node.cpp b/zto/node/Node.cpp new file mode 100644 index 0000000..e7dc637 --- /dev/null +++ b/zto/node/Node.cpp @@ -0,0 +1,1136 @@ +/* + * 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), + _prngStreamPtr(0), + _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)); + + _online = false; + + memset(_expectingRepliesToBucketPtr,0,sizeof(_expectingRepliesToBucketPtr)); + memset(_expectingRepliesTo,0,sizeof(_expectingRepliesTo)); + memset(_lastIdentityVerification,0,sizeof(_lastIdentityVerification)); + + // Use Salsa20 alone as a high-quality non-crypto PRNG + char foo[32]; + Utils::getSecureRandom(foo,32); + _prng.init(foo,256,foo); + memset(_prngStream,0,sizeof(_prngStream)); + _prng.crypt12(_prngStream,_prngStream,sizeof(_prngStream)); + + 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) + _networks.push_back(std::pair< uint64_t,SharedPtr >(nwid,SharedPtr(new Network(RR,tptr,nwid,uptr)))); + std::sort(_networks.begin(),_networks.end()); // will sort by nwid since it's the first in a pair<> + return ZT_RESULT_OK; +} + +ZT_ResultCode Node::leave(uint64_t nwid,void **uptr,void *tptr) +{ + std::vector< std::pair< uint64_t,SharedPtr > > newn; + 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(); + } + } + _networks.swap(newn); + 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< std::pair< SharedPtr,bool > > paths(pi->second->paths(_now)); + SharedPtr bestp(pi->second->getBestPath(_now,false)); + p->pathCount = 0; + for(std::vector< std::pair< SharedPtr,bool > >::iterator path(paths.begin());path!=paths.end();++path) { + memcpy(&(p->paths[p->pathCount].address),&(path->first->address()),sizeof(struct sockaddr_storage)); + p->paths[p->pathCount].lastSend = path->first->lastOut(); + p->paths[p->pathCount].lastReceive = path->first->lastIn(); + p->paths[p->pathCount].trustedPathId = RR->topology->getOutboundPathTrust(path->first->address()); + p->paths[p->pathCount].linkQuality = (int)path->first->linkQuality(); + p->paths[p->pathCount].expired = path->second; + p->paths[p->pathCount].preferred = (path->first == 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() +{ + unsigned int p = (++_prngStreamPtr % ZT_NODE_PRNG_BUF_SIZE); + if (!p) + _prng.crypt12(_prngStream,_prngStream,sizeof(_prngStream)); + return _prngStream[p]; +} + +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/zto/node/Node.hpp b/zto/node/Node.hpp new file mode 100644 index 0000000..03bd7a8 --- /dev/null +++ b/zto/node/Node.hpp @@ -0,0 +1,327 @@ +/* + * 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 + +// Size of PRNG stream buffer +#define ZT_NODE_PRNG_BUF_SIZE 64 + +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; + + unsigned int _prngStreamPtr; + Salsa20 _prng; + uint64_t _prngStream[ZT_NODE_PRNG_BUF_SIZE]; // repeatedly encrypted with _prng to yield a high-quality non-crypto PRNG stream + + uint64_t _now; + uint64_t _lastPingCheck; + uint64_t _lastHousekeepingRun; + bool _online; +}; + +} // namespace ZeroTier + +#endif diff --git a/zto/node/NonCopyable.hpp b/zto/node/NonCopyable.hpp new file mode 100644 index 0000000..6d4daa8 --- /dev/null +++ b/zto/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/zto/node/OutboundMulticast.cpp b/zto/node/OutboundMulticast.cpp new file mode 100644 index 0000000..285bfa5 --- /dev/null +++ b/zto/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/zto/node/OutboundMulticast.hpp b/zto/node/OutboundMulticast.hpp new file mode 100644 index 0000000..0ecf113 --- /dev/null +++ b/zto/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/zto/node/Packet.cpp b/zto/node/Packet.cpp new file mode 100644 index 0000000..756f314 --- /dev/null +++ b/zto/node/Packet.cpp @@ -0,0 +1,1169 @@ +/* + * 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 _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 { + +/************************************************************************** */ +/************************************************************************** */ + +/* 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],macKey[32],mac[16]; + 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); + Salsa20 s20(mangledKey,256,data + ZT_PACKET_IDX_IV); + + // MAC key is always the first 32 bytes of the Salsa20 key stream + // This is the same construction DJB's NaCl library uses + 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); + Poly1305::compute(mac,payload,payloadLen,macKey); + memcpy(data + ZT_PACKET_IDX_MAC,mac,8); +} + +bool Packet::dearmor(const void *key) +{ + uint8_t mangledKey[32],macKey[32],mac[16]; + 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); + Salsa20 s20(mangledKey,256,data + ZT_PACKET_IDX_IV); + + s20.crypt12(ZERO_KEY,macKey,sizeof(macKey)); + Poly1305::compute(mac,payload,payloadLen,macKey); + if (!Utils::secureEq(mac,data + ZT_PACKET_IDX_MAC,8)) + return false; // MAC failed, packet is corrupt, modified, or is not from the sender + + 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,256,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/zto/node/Packet.hpp b/zto/node/Packet.hpp new file mode 100644 index 0000000..8ad2c0f --- /dev/null +++ b/zto/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/zto/node/Path.cpp b/zto/node/Path.cpp new file mode 100644 index 0000000..7366b56 --- /dev/null +++ b/zto/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/zto/node/Path.hpp b/zto/node/Path.hpp new file mode 100644 index 0000000..aef628d --- /dev/null +++ b/zto/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/zto/node/Peer.cpp b/zto/node/Peer.cpp new file mode 100644 index 0000000..0795a6e --- /dev/null +++ b/zto/node/Peer.cpp @@ -0,0 +1,498 @@ +/* + * 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" + +#ifndef AF_MAX +#if AF_INET > AF_INET6 +#define AF_MAX AF_INET +#else +#define AF_MAX AF_INET6 +#endif +#endif + +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), + _remoteClusterOptimal4(0), + _vProto(0), + _vMajor(0), + _vMinor(0), + _vRevision(0), + _id(peerIdentity), + _numPaths(0), + _latency(0), + _directPathPushCutoffCount(0), + _credentialsCutoffCount(0) +{ + memset(_remoteClusterOptimal6,0,sizeof(_remoteClusterOptimal6)); + 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 suboptimalPath = 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); + } + suboptimalPath = 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 pathIsConfirmed = false; + { + Mutex::Lock _l(_paths_m); + for(unsigned int p=0;p<_numPaths;++p) { + if (_paths[p].path->address() == path->address()) { + _paths[p].lastReceive = now; + _paths[p].path = path; // local address may have changed! +#ifdef ZT_ENABLE_CLUSTER + _paths[p].localClusterSuboptimal = suboptimalPath; +#endif + pathIsConfirmed = true; + break; + } + } + } + + if ( (!pathIsConfirmed) && (RR->node->shouldUsePathForZeroTierTraffic(tPtr,_id.address(),path->localAddress(),path->address())) ) { + if (verb == Packet::VERB_OK) { + Mutex::Lock _l(_paths_m); + + // Since this is a new path, figure out where to put it (possibly replacing an old/dead one) + unsigned int slot; + if (_numPaths < ZT_MAX_PEER_NETWORK_PATHS) { + slot = _numPaths++; + } else { + // First try to replace the worst within the same address family, if possible + int worstSlot = -1; + uint64_t worstScore = 0xffffffffffffffffULL; + for(unsigned int p=0;p<_numPaths;++p) { + if (_paths[p].path->address().ss_family == path->address().ss_family) { + const uint64_t s = _pathScore(p,now); + if (s < worstScore) { + worstScore = s; + worstSlot = (int)p; + } + } + } + if (worstSlot >= 0) { + slot = (unsigned int)worstSlot; + } else { + // If we can't find one with the same family, replace the worst of any family + slot = ZT_MAX_PEER_NETWORK_PATHS - 1; + for(unsigned int p=0;p<_numPaths;++p) { + const uint64_t s = _pathScore(p,now); + if (s < worstScore) { + worstScore = s; + slot = p; + } + } + } + } + + _paths[slot].lastReceive = now; + _paths[slot].path = path; +#ifdef ZT_ENABLE_CLUSTER + _paths[slot].localClusterSuboptimal = suboptimalPath; + 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::hasActivePathTo(uint64_t now,const InetAddress &addr) const +{ + Mutex::Lock _l(_paths_m); + for(unsigned int p=0;p<_numPaths;++p) { + if ( (_paths[p].path->address() == addr) && ((now - _paths[p].lastReceive) <= ZT_PEER_PATH_EXPIRATION) && (_paths[p].path->alive(now)) ) + return true; + } + return false; +} + +bool Peer::sendDirect(void *tPtr,const void *data,unsigned int len,uint64_t now,bool forceEvenIfDead) +{ + Mutex::Lock _l(_paths_m); + + int bestp = -1; + uint64_t best = 0ULL; + for(unsigned int p=0;p<_numPaths;++p) { + if ( ((now - _paths[p].lastReceive) <= ZT_PEER_PATH_EXPIRATION) && (_paths[p].path->alive(now)||(forceEvenIfDead)) ) { + const uint64_t s = _pathScore(p,now); + if (s >= best) { + best = s; + bestp = (int)p; + } + } + } + + if (bestp >= 0) { + return _paths[bestp].path->send(RR,tPtr,data,len,now); + } else { + return false; + } +} + +SharedPtr Peer::getBestPath(uint64_t now,bool includeExpired) +{ + Mutex::Lock _l(_paths_m); + + int bestp = -1; + uint64_t best = 0ULL; + for(unsigned int p=0;p<_numPaths;++p) { + if ( ((now - _paths[p].lastReceive) <= ZT_PEER_PATH_EXPIRATION) || (includeExpired) ) { + const uint64_t s = _pathScore(p,now); + if (s >= best) { + best = s; + bestp = (int)p; + } + } + } + + if (bestp >= 0) { + return _paths[bestp].path; + } else { + 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); + + int bestp = -1; + uint64_t best = 0ULL; + for(unsigned int p=0;p<_numPaths;++p) { + if ( ((now - _paths[p].lastReceive) <= ZT_PEER_PATH_EXPIRATION) && ((inetAddressFamily < 0)||((int)_paths[p].path->address().ss_family == inetAddressFamily)) ) { + const uint64_t s = _pathScore(p,now); + if (s >= best) { + best = s; + bestp = (int)p; + } + } + } + + if (bestp >= 0) { + if ( ((now - _paths[bestp].lastReceive) >= ZT_PEER_PING_PERIOD) || (_paths[bestp].path->needsHeartbeat(now)) ) { + attemptToContactAt(tPtr,_paths[bestp].path->localAddress(),_paths[bestp].path->address(),now,false,_paths[bestp].path->nextOutgoingCounter()); + _paths[bestp].path->sent(now); + } + return true; + } else { + return false; + } +} + +bool Peer::hasActiveDirectPath(uint64_t now) const +{ + Mutex::Lock _l(_paths_m); + for(unsigned int p=0;p<_numPaths;++p) { + if (((now - _paths[p].lastReceive) <= ZT_PEER_PATH_EXPIRATION)&&(_paths[p].path->alive(now))) + return true; + } + return false; +} + +void Peer::resetWithinScope(void *tPtr,InetAddress::IpScope scope,int inetAddressFamily,uint64_t now) +{ + Mutex::Lock _l(_paths_m); + for(unsigned int p=0;p<_numPaths;++p) { + if ( (_paths[p].path->address().ss_family == inetAddressFamily) && (_paths[p].path->address().ipScope() == scope) ) { + attemptToContactAt(tPtr,_paths[p].path->localAddress(),_paths[p].path->address(),now,false,_paths[p].path->nextOutgoingCounter()); + _paths[p].path->sent(now); + _paths[p].lastReceive = 0; // path will not be used unless it speaks again + } + } +} + +void Peer::getRendezvousAddresses(uint64_t now,InetAddress &v4,InetAddress &v6) const +{ + Mutex::Lock _l(_paths_m); + + int bestp4 = -1,bestp6 = -1; + uint64_t best4 = 0ULL,best6 = 0ULL; + for(unsigned int p=0;p<_numPaths;++p) { + if ( ((now - _paths[p].lastReceive) <= ZT_PEER_PATH_EXPIRATION) && (_paths[p].path->alive(now)) ) { + if (_paths[p].path->address().ss_family == AF_INET) { + const uint64_t s = _pathScore(p,now); + if (s >= best4) { + best4 = s; + bestp4 = (int)p; + } + } else if (_paths[p].path->address().ss_family == AF_INET6) { + const uint64_t s = _pathScore(p,now); + if (s >= best6) { + best6 = s; + bestp6 = (int)p; + } + } + } + } + + if (bestp4 >= 0) + v4 = _paths[bestp4].path->address(); + if (bestp6 >= 0) + v6 = _paths[bestp6].path->address(); +} + +} // namespace ZeroTier diff --git a/zto/node/Peer.hpp b/zto/node/Peer.hpp new file mode 100644 index 0000000..4183641 --- /dev/null +++ b/zto/node/Peer.hpp @@ -0,0 +1,509 @@ +/* + * 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 + */ + bool hasActivePathTo(uint64_t now,const InetAddress &addr) const; + + /** + * Set which known path for an address family is optimal + * + * @param addr Address to make exclusive + */ + inline void setClusterOptimal(const InetAddress &addr) + { + if (addr.ss_family == AF_INET) { + _remoteClusterOptimal4 = (uint32_t)reinterpret_cast(&addr)->sin_addr.s_addr; + } else if (addr.ss_family == AF_INET6) { + memcpy(_remoteClusterOptimal6,reinterpret_cast(&addr)->sin6_addr.s6_addr,16); + } + } + + /** + * 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 forceEvenIfDead If true, send even if the 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 forceEvenIfDead); + + /** + * Get the best current direct path + * + * @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); + + /** + * @param now Current time + * @return True if this peer has at least one active and alive direct path + */ + bool hasActiveDirectPath(uint64_t now) const; + + /** + * 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 + */ + void resetWithinScope(void *tPtr,InetAddress::IpScope scope,int inetAddressFamily,uint64_t now); + + /** + * Get most recently active path addresses for IPv4 and/or IPv6 + * + * Note that v4 and v6 are not modified if they are not found, so + * initialize these to a NULL address to be able to check. + * + * @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 + */ + void getRendezvousAddresses(uint64_t now,InetAddress &v4,InetAddress &v6) const; + + /** + * @param now Current time + * @return All known direct paths to this peer and whether they are expired (true == expired) + */ + inline std::vector< std::pair< SharedPtr,bool > > paths(const uint64_t now) const + { + std::vector< std::pair< SharedPtr,bool > > pp; + Mutex::Lock _l(_paths_m); + for(unsigned int p=0,np=_numPaths;p,bool >(_paths[p].path,(now - _paths[p].lastReceive) > ZT_PEER_PATH_EXPIRATION)); + 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 + { + for(unsigned int p=0,np=_numPaths;palive(now)) && (!_paths[p].localClusterSuboptimal) ) + return true; + } + return false; + } +#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: + inline uint64_t _pathScore(const unsigned int p,const uint64_t now) const + { + uint64_t s = ZT_PEER_PING_PERIOD + _paths[p].lastReceive + (uint64_t)(_paths[p].path->preferenceRank() * (ZT_PEER_PING_PERIOD / ZT_PATH_MAX_PREFERENCE_RANK)); + + if (_paths[p].path->address().ss_family == AF_INET) { + s += (uint64_t)(ZT_PEER_PING_PERIOD * (unsigned long)(reinterpret_cast(&(_paths[p].path->address()))->sin_addr.s_addr == _remoteClusterOptimal4)); + } else if (_paths[p].path->address().ss_family == AF_INET6) { + uint64_t clusterWeight = ZT_PEER_PING_PERIOD; + const uint8_t *a = reinterpret_cast(reinterpret_cast(&(_paths[p].path->address()))->sin6_addr.s6_addr); + for(long i=0;i<16;++i) { + if (a[i] != _remoteClusterOptimal6[i]) { + clusterWeight = 0; + break; + } + } + s += clusterWeight; + } + + s += (ZT_PEER_PING_PERIOD / 2) * (uint64_t)_paths[p].path->alive(now); + +#ifdef ZT_ENABLE_CLUSTER + s -= ZT_PEER_PING_PERIOD * (uint64_t)_paths[p].localClusterSuboptimal; +#endif + + return s; + } + + 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; + + uint8_t _remoteClusterOptimal6[16]; + uint32_t _remoteClusterOptimal4; + + uint16_t _vProto; + uint16_t _vMajor; + uint16_t _vMinor; + uint16_t _vRevision; + + Identity _id; + + struct { + uint64_t lastReceive; + SharedPtr path; +#ifdef ZT_ENABLE_CLUSTER + bool localClusterSuboptimal; +#endif + } _paths[ZT_MAX_PEER_NETWORK_PATHS]; + Mutex _paths_m; + + unsigned int _numPaths; + 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/zto/node/Poly1305.cpp b/zto/node/Poly1305.cpp new file mode 100644 index 0000000..b78071f --- /dev/null +++ b/zto/node/Poly1305.cpp @@ -0,0 +1,628 @@ +/* +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__)) + +////////////////////////////////////////////////////////////////////////////// +// 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; + +/* interpret eight 8 bit unsigned integers as a 64 bit unsigned integer in 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)); +} + +/* store a 64 bit unsigned integer as eight 8 bit unsigned integers in 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; +} + +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/zto/node/Poly1305.hpp b/zto/node/Poly1305.hpp new file mode 100644 index 0000000..62d5754 --- /dev/null +++ b/zto/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/zto/node/README.md b/zto/node/README.md new file mode 100644 index 0000000..1728400 --- /dev/null +++ b/zto/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/zto/node/Revocation.cpp b/zto/node/Revocation.cpp new file mode 100644 index 0000000..bab5653 --- /dev/null +++ b/zto/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/zto/node/Revocation.hpp b/zto/node/Revocation.hpp new file mode 100644 index 0000000..8b9ce6d --- /dev/null +++ b/zto/node/Revocation.hpp @@ -0,0 +1,194 @@ +/* + * 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 "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 type being revoked + */ + enum CredentialType + { + CREDENTIAL_TYPE_NULL = 0, + CREDENTIAL_TYPE_COM = 1, // CertificateOfMembership + CREDENTIAL_TYPE_CAPABILITY = 2, + CREDENTIAL_TYPE_TAG = 3, + CREDENTIAL_TYPE_COO = 4 // CertificateOfOwnership + }; + + 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 uint64_t i,const uint64_t nwid,const uint64_t cid,const uint64_t thr,const uint64_t fl,const Address &tgt,const CredentialType ct) : + _id(i), + _networkId(nwid), + _credentialId(cid), + _threshold(thr), + _flags(fl), + _target(tgt), + _signedBy(), + _type(ct) {} + + inline uint64_t id() const { return _id; } + inline uint64_t networkId() const { return _networkId; } + inline uint64_t credentialId() const { return _credentialId; } + inline uint64_t threshold() const { return _threshold; } + inline const Address &target() const { return _target; } + inline const Address &signer() const { return _signedBy; } + inline CredentialType 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(_id); + b.append(_networkId); + 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; + + _id = b.template at(p); p += 8; + _networkId = b.template at(p); p += 8; + _credentialId = b.template at(p); p += 8; + _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 = (CredentialType)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: + uint64_t _id; + uint64_t _networkId; + uint64_t _credentialId; + uint64_t _threshold; + uint64_t _flags; + Address _target; + Address _signedBy; + CredentialType _type; + C25519::Signature _signature; +}; + +} // namespace ZeroTier + +#endif diff --git a/zto/node/RuntimeEnvironment.hpp b/zto/node/RuntimeEnvironment.hpp new file mode 100644 index 0000000..7ba1c98 --- /dev/null +++ b/zto/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/zto/node/SHA512.cpp b/zto/node/SHA512.cpp new file mode 100644 index 0000000..76737d3 --- /dev/null +++ b/zto/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/zto/node/SHA512.hpp b/zto/node/SHA512.hpp new file mode 100644 index 0000000..639a7df --- /dev/null +++ b/zto/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/zto/node/Salsa20.cpp b/zto/node/Salsa20.cpp new file mode 100644 index 0000000..1a4641f --- /dev/null +++ b/zto/node/Salsa20.cpp @@ -0,0 +1,1358 @@ +/* + * 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,unsigned int kbits,const void *iv) + throw() +{ +#ifdef ZT_SALSA20_SSE + const uint32_t *k = (const uint32_t *)key; + + _state.i[0] = 0x61707865; + _state.i[3] = 0x6b206574; + _state.i[13] = k[0]; + _state.i[10] = k[1]; + _state.i[7] = k[2]; + _state.i[4] = k[3]; + if (kbits == 256) { + k += 4; + _state.i[1] = 0x3320646e; + _state.i[2] = 0x79622d32; + } else { + _state.i[1] = 0x3120646e; + _state.i[2] = 0x79622d36; + } + _state.i[15] = k[0]; + _state.i[12] = k[1]; + _state.i[9] = k[2]; + _state.i[6] = k[3]; + _state.i[14] = ((const uint32_t *)iv)[0]; + _state.i[11] = ((const uint32_t *)iv)[1]; + _state.i[5] = 0; + _state.i[8] = 0; +#else + const char *constants; + const uint8_t *k = (const uint8_t *)key; + + _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); + if (kbits == 256) { /* recommended */ + k += 16; + constants = "expand 32-byte k"; + } else { /* kbits == 128 */ + constants = "expand 16-byte k"; + } + _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 + 0); + _state.i[12] = U8TO32_LITTLE(k + 4); + _state.i[13] = U8TO32_LITTLE(k + 8); + _state.i[14] = U8TO32_LITTLE(k + 12); + _state.i[15] = U8TO32_LITTLE(constants + 12); + _state.i[0] = U8TO32_LITTLE(constants + 0); +#endif +} + +void Salsa20::crypt12(const void *in,void *out,unsigned int bytes) + throw() +{ + 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) + throw() +{ + 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/zto/node/Salsa20.hpp b/zto/node/Salsa20.hpp new file mode 100644 index 0000000..6405d45 --- /dev/null +++ b/zto/node/Salsa20.hpp @@ -0,0 +1,89 @@ +/* + * 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 "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() throw() {} + + ~Salsa20() { Utils::burn(&_state,sizeof(_state)); } + + /** + * @param key Key bits + * @param kbits Number of key bits: 128 or 256 (recommended) + * @param iv 64-bit initialization vector + */ + Salsa20(const void *key,unsigned int kbits,const void *iv) + throw() + { + init(key,kbits,iv); + } + + /** + * Initialize cipher + * + * @param key Key bits + * @param kbits Number of key bits: 128 or 256 (recommended) + * @param iv 64-bit initialization vector + */ + void init(const void *key,unsigned int kbits,const void *iv) + throw(); + + /** + * 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) + throw(); + + /** + * 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) + throw(); + +private: + union { +#ifdef ZT_SALSA20_SSE + __m128i v[4]; +#endif // ZT_SALSA20_SSE + uint32_t i[16]; + } _state; +}; + +} // namespace ZeroTier + +#endif diff --git a/zto/node/SelfAwareness.cpp b/zto/node/SelfAwareness.cpp new file mode 100644 index 0000000..cba84cd --- /dev/null +++ b/zto/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/zto/node/SelfAwareness.hpp b/zto/node/SelfAwareness.hpp new file mode 100644 index 0000000..c1db0c8 --- /dev/null +++ b/zto/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/zto/node/SharedPtr.hpp b/zto/node/SharedPtr.hpp new file mode 100644 index 0000000..1dd3b43 --- /dev/null +++ b/zto/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/zto/node/Switch.cpp b/zto/node/Switch.cpp new file mode 100644 index 0000000..56299a9 --- /dev/null +++ b/zto/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/zto/node/Switch.hpp b/zto/node/Switch.hpp new file mode 100644 index 0000000..ff35093 --- /dev/null +++ b/zto/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/zto/node/Tag.cpp b/zto/node/Tag.cpp new file mode 100644 index 0000000..3f924da --- /dev/null +++ b/zto/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/zto/node/Tag.hpp b/zto/node/Tag.hpp new file mode 100644 index 0000000..3808590 --- /dev/null +++ b/zto/node/Tag.hpp @@ -0,0 +1,201 @@ +/* + * 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 "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: + 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) : + _networkId(nwid), + _ts(ts), + _id(id), + _value(value), + _issuedTo(issuedTo), + _signedBy() + { + } + + inline uint64_t networkId() const { return _networkId; } + inline uint64_t timestamp() const { return _ts; } + inline uint32_t id() const { return _id; } + inline const uint32_t &value() const { return _value; } + 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); + + // These are the same between Tag and Capability + 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: + uint64_t _networkId; + uint64_t _ts; + uint32_t _id; + uint32_t _value; + Address _issuedTo; + Address _signedBy; + C25519::Signature _signature; +}; + +} // namespace ZeroTier + +#endif diff --git a/zto/node/Topology.cpp b/zto/node/Topology.cpp new file mode 100644 index 0000000..a1d3733 --- /dev/null +++ b/zto/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/zto/node/Topology.hpp b/zto/node/Topology.hpp new file mode 100644 index 0000000..4870ab5 --- /dev/null +++ b/zto/node/Topology.hpp @@ -0,0 +1,456 @@ +/* + * 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)) { + cnt += (unsigned long)((*p)->hasActiveDirectPath(now)); + } + 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/zto/node/Utils.cpp b/zto/node/Utils.cpp new file mode 100644 index 0000000..fb448dd --- /dev/null +++ b/zto/node/Utils.cpp @@ -0,0 +1,255 @@ +/* + * 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,256,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)); + } + ((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)); + } + ((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/zto/node/Utils.hpp b/zto/node/Utils.hpp new file mode 100644 index 0000000..ceb29d7 --- /dev/null +++ b/zto/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/zto/node/World.hpp b/zto/node/World.hpp new file mode 100644 index 0000000..6e835be --- /dev/null +++ b/zto/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/zto/objects.mk b/zto/objects.mk new file mode 100644 index 0000000..74efc33 --- /dev/null +++ b/zto/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/zto/one.cpp b/zto/one.cpp new file mode 100644 index 0000000..edefe82 --- /dev/null +++ b/zto/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 _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/zto/osdep/Arp.cpp b/zto/osdep/Arp.cpp new file mode 100644 index 0000000..fcc122f --- /dev/null +++ b/zto/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/zto/osdep/Arp.hpp b/zto/osdep/Arp.hpp new file mode 100644 index 0000000..5f0d199 --- /dev/null +++ b/zto/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/zto/osdep/BSDEthernetTap.cpp b/zto/osdep/BSDEthernetTap.cpp new file mode 100644 index 0000000..62fabc4 --- /dev/null +++ b/zto/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/zto/osdep/BSDEthernetTap.hpp b/zto/osdep/BSDEthernetTap.hpp new file mode 100644 index 0000000..8c6314d --- /dev/null +++ b/zto/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/zto/osdep/Binder.hpp b/zto/osdep/Binder.hpp new file mode 100644 index 0000000..9829f17 --- /dev/null +++ b/zto/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/zto/osdep/BlockingQueue.hpp b/zto/osdep/BlockingQueue.hpp new file mode 100644 index 0000000..6172f4d --- /dev/null +++ b/zto/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/zto/osdep/Http.cpp b/zto/osdep/Http.cpp new file mode 100644 index 0000000..d4f43d1 --- /dev/null +++ b/zto/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, bool stack_invoked) + { + 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/zto/osdep/Http.hpp b/zto/osdep/Http.hpp new file mode 100644 index 0000000..1ecf4ee --- /dev/null +++ b/zto/osdep/Http.hpp @@ -0,0 +1,154 @@ +/* + * 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); + } + +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/zto/osdep/LinuxEthernetTap.cpp b/zto/osdep/LinuxEthernetTap.cpp new file mode 100644 index 0000000..c4b978e --- /dev/null +++ b/zto/osdep/LinuxEthernetTap.cpp @@ -0,0 +1,483 @@ +/* + * 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. + bool recalledDevice = false; + std::string devmapbuf; + Dictionary<8194> devmap; + if (OSUtils::readFile((_homePath + ZT_PATH_SEPARATOR_S + "devicemap").c_str(),devmapbuf)) { + devmap.load(devmapbuf.c_str()); + char desiredDevice[128]; + if (devmap.get(nwids,desiredDevice,sizeof(desiredDevice)) > 0) { + Utils::scopy(ifr.ifr_name,sizeof(ifr.ifr_name),desiredDevice); + 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); + + devmap.erase(nwids); + devmap.add(nwids,_dev.c_str()); + OSUtils::writeFile((_homePath + ZT_PATH_SEPARATOR_S + "devicemap").c_str(),(const void *)devmap.data(),devmap.sizeBytes()); + + _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/zto/osdep/LinuxEthernetTap.hpp b/zto/osdep/LinuxEthernetTap.hpp new file mode 100644 index 0000000..a2a00a7 --- /dev/null +++ b/zto/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/zto/osdep/ManagedRoute.cpp b/zto/osdep/ManagedRoute.cpp new file mode 100644 index 0000000..1fc6c78 --- /dev/null +++ b/zto/osdep/ManagedRoute.cpp @@ -0,0 +1,543 @@ +/* + * 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" + +// NOTE: BSD is mostly tested on Apple/Mac but is likely to work on other BSD too + +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; + } + } +} + +#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 + + // Generate two more specific routes than target with one extra bit + InetAddress leftt,rightt; + _forkTarget(_target,leftt,rightt); + +#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); + } + + // Create a device-bound default target if there is none in the system. This + // is to allow e.g. IPv6 default route to work even if there is no native + // IPv6 on your LAN. + /* + if (_target.isDefaultRoute()) { + if (_systemVia) { + if (_applied.count(_target)) { + _applied.erase(_target); + _routeCmd("delete",_target,_via,_device,(_via) ? (const char *)0 : _device); + } + } else { + if (!_applied.count(_target)) { + _applied[_target] = true; // ifscoped + _routeCmd("add",_target,_via,_device,(_via) ? (const char *)0 : _device); + _routeCmd("change",_target,_via,_device,(_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)) { + _applied[leftt] = false; // boolean unused + _winRoute(false,interfaceLuid,interfaceIndex,leftt,_via); + } + if ((rightt)&&(!_applied.count(rightt))) { + _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/zto/osdep/ManagedRoute.hpp b/zto/osdep/ManagedRoute.hpp new file mode 100644 index 0000000..fd77a79 --- /dev/null +++ b/zto/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/zto/osdep/NeighborDiscovery.cpp b/zto/osdep/NeighborDiscovery.cpp new file mode 100644 index 0000000..4f63631 --- /dev/null +++ b/zto/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/zto/osdep/NeighborDiscovery.hpp b/zto/osdep/NeighborDiscovery.hpp new file mode 100644 index 0000000..47831bd --- /dev/null +++ b/zto/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/zto/osdep/OSUtils.cpp b/zto/osdep/OSUtils.cpp new file mode 100644 index 0000000..33e143d --- /dev/null +++ b/zto/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) +{ + 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)) + 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)) + 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/zto/osdep/OSUtils.hpp b/zto/osdep/OSUtils.hpp new file mode 100644 index 0000000..2e007ef --- /dev/null +++ b/zto/osdep/OSUtils.hpp @@ -0,0 +1,287 @@ +/* + * 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 + * + * This returns only files, not sub-directories. + * + * @param path Path to list + * @return Names of files in directory (without path prepended) + */ + static std::vector listDirectory(const char *path); + + /** + * 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/zto/osdep/OSXEthernetTap.cpp b/zto/osdep/OSXEthernetTap.cpp new file mode 100644 index 0000000..35eac05 --- /dev/null +++ b/zto/osdep/OSXEthernetTap.cpp @@ -0,0 +1,659 @@ +/* + * 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. + bool recalledDevice = false; + std::string devmapbuf; + Dictionary<8194> devmap; + if (OSUtils::readFile((_homePath + ZT_PATH_SEPARATOR_S + "devicemap").c_str(),devmapbuf)) { + devmap.load(devmapbuf.c_str()); + char desiredDevice[128]; + if (devmap.get(nwids,desiredDevice,sizeof(desiredDevice)) > 0) { + Utils::snprintf(devpath,sizeof(devpath),"/dev/%s",desiredDevice); + 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)) + 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; + + devmap.erase(nwids); + devmap.add(nwids,_dev.c_str()); + OSUtils::writeFile((_homePath + ZT_PATH_SEPARATOR_S + "devicemap").c_str(),(const void *)devmap.data(),devmap.sizeBytes()); + + _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/zto/osdep/OSXEthernetTap.hpp b/zto/osdep/OSXEthernetTap.hpp new file mode 100644 index 0000000..5a96c21 --- /dev/null +++ b/zto/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/zto/osdep/Phy.hpp b/zto/osdep/Phy.hpp new file mode 100644 index 0000000..5201cff --- /dev/null +++ b/zto/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), false); + } 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/zto/osdep/PortMapper.cpp b/zto/osdep/PortMapper.cpp new file mode 100644 index 0000000..d3a1938 --- /dev/null +++ b/zto/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/zto/osdep/PortMapper.hpp b/zto/osdep/PortMapper.hpp new file mode 100644 index 0000000..0b8d15f --- /dev/null +++ b/zto/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/zto/osdep/README.md b/zto/osdep/README.md new file mode 100644 index 0000000..a77297a --- /dev/null +++ b/zto/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/zto/osdep/Thread.hpp b/zto/osdep/Thread.hpp new file mode 100644 index 0000000..227c2cf --- /dev/null +++ b/zto/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/zto/osdep/WindowsEthernetTap.cpp b/zto/osdep/WindowsEthernetTap.cpp new file mode 100644 index 0000000..79b9d35 --- /dev/null +++ b/zto/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/zto/osdep/WindowsEthernetTap.hpp b/zto/osdep/WindowsEthernetTap.hpp new file mode 100644 index 0000000..f2cf73f --- /dev/null +++ b/zto/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/zto/selftest.cpp b/zto/selftest.cpp new file mode 100644 index 0000000..48625d5 --- /dev/null +++ b/zto/selftest.cpp @@ -0,0 +1,1163 @@ +/* + * 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 __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 unsigned char fuzzbuf[1048576]; + +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; + } + + 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; + + std::cout << "[other] Testing hex encode/decode... "; std::cout.flush(); + for(unsigned int k=0;k<1000;++k) { + unsigned int flen = (rand() % 8194) + 1; + for(unsigned int i=0;i test; + char key[32][16]; + char value[32][128]; + for(unsigned int q=0;q<32;++q) { + Utils::snprintf(key[q],16,"%.8lx",(unsigned long)rand()); + int r = rand() % 128; + for(int x=0;x= 0) { + if (strcmp(value[r],tmp)) { + std::cout << "FAILED (invalid value)!" << std::endl; + return -1; + } + } else { + std::cout << "FAILED (can't find key '" << key[r] << "')!" << std::endl; + return -1; + } + } + for(unsigned int q=0;q<31;++q) { + char tmp[128]; + test.erase(key[q]); + if (test.get(key[q],tmp,sizeof(tmp)) >= 0) { + std::cout << "FAILED (key should have been erased)!" << std::endl; + return -1; + } + if (test.get(key[q+1],tmp,sizeof(tmp)) < 0) { + std::cout << "FAILED (key should NOT have been erased)!" << std::endl; + return -1; + } + } + } + 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[8194]; + for(int q=0;q test((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)); + } + } + std::cout << "PASS (junk value to prevent optimization-out of test: " << foo << ")" << std::endl; + + /* + std::cout << "[other] Testing controller/JSONDB..."; std::cout.flush(); + { + std::map db1data; + JSONDB db1("jsondb-test"); + for(unsigned int i=0;i<256;++i) { + std::string n; + for(unsigned int j=0,k=rand() % 4;j<=k;++j) { + if (j > 0) n.push_back('/'); + char foo[24]; + Utils::snprintf(foo,sizeof(foo),"%lx",rand()); + n.append(foo); + } + db1data[n] = {{"i",i}}; + db1.put(n,db1data[n]); + } + for(std::map::iterator i(db1data.begin());i!=db1data.end();++i) { + i->second["foo"] = "bar"; + db1.put(i->first,i->second); + } + JSONDB db2("jsondb-test"); + if (db1 != db2) { + std::cout << " FAILED (db1!=db2 #1)" << std::endl; + return -1; + } + for(std::map::iterator i(db1data.begin());i!=db1data.end();++i) { + db1.erase(i->first); + } + db2.reload(); + if (db1 != db2) { + std::cout << " FAILED (db1!=db2 #2)" << std::endl; + return -1; + } + } + std::cout << " PASS" << 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 _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/zto/service/ClusterDefinition.hpp b/zto/service/ClusterDefinition.hpp new file mode 100644 index 0000000..dda1a8c --- /dev/null +++ b/zto/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/zto/service/ClusterGeoIpService.cpp b/zto/service/ClusterGeoIpService.cpp new file mode 100644 index 0000000..89015c5 --- /dev/null +++ b/zto/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/zto/service/ClusterGeoIpService.hpp b/zto/service/ClusterGeoIpService.hpp new file mode 100644 index 0000000..ff2fcdb --- /dev/null +++ b/zto/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/zto/service/OneService.cpp b/zto/service/OneService.cpp new file mode 100644 index 0000000..f0de485 --- /dev/null +++ b/zto/service/OneService.cpp @@ -0,0 +1,2597 @@ +/* + * 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 "../node/Node.hpp" + +// Include the right tap device driver for this platform -- add new platforms here +#ifdef ZT_SDK + +// In network containers builds, use the virtual netcon endpoint instead of a tun/tap port driver +#include "../src/SocketTap.hpp" +namespace ZeroTier { typedef SocketTap EthernetTap; } + +#else // not ZT_SDK 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; + 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 : ".") + ,_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,(_homePath + ZT_PATH_SEPARATOR_S ZT_CONTROLLER_DB_PATH).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(); + } + +#ifdef ZT_SDK + virtual void leave(const char *hp) + { + _node->leave(Utils::hexStrToU64(hp),NULL,NULL); + } + + virtual void join(const char *hp) + { + _node->join(Utils::hexStrToU64(hp),NULL,NULL); + } + + virtual std::string givenHomePath() + { + return _homePath; + } + + virtual EthernetTap * getTap(uint64_t nwid) + { + Mutex::Lock _l(_nets_m); + std::map::const_iterator n(_nets.find(nwid)); + if (n == _nets.end()) + return NULL; + return n->second.tap; + } + + virtual Node * getNode() + { + return _node; + } +#endif // ZT_SDK + + 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; + memcpy(&settings,&(n->second.settings),sizeof(NetworkSettings)); + 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; + memcpy(&(n->second.settings),&settings,sizeof(NetworkSettings)); + + 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, bool stack_invoked) + { + 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/zto/service/OneService.hpp b/zto/service/OneService.hpp new file mode 100644 index 0000000..21804c4 --- /dev/null +++ b/zto/service/OneService.hpp @@ -0,0 +1,208 @@ +/* + * 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" +#include "../node/Node.hpp" + +// Include the right tap device driver for this platform -- add new platforms here +#ifdef ZT_SDK + // In network containers builds, use the virtual netcon endpoint instead of a tun/tap port driver + #include "../src/SocketTap.hpp" + namespace ZeroTier { typedef SocketTap EthernetTap; } +#endif // not ZT_SDK so pick a tap driver + +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; + +#ifdef ZT_SDK + /** + * Leaves a network + */ + virtual void leave(const char *hp) = 0; + + /** + * Joins a network + */ + virtual void join(const char *hp) = 0; + + /** + * Returns the homePath given by the client application + * - Used for SDK mode + */ + virtual std::string givenHomePath() = 0; + + /* + * + */ + virtual EthernetTap * getTap(uint64_t nwid) = 0; + + /* + * + */ + virtual Node * getNode() = 0; +#endif + + /** + * 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/zto/service/README.md b/zto/service/README.md new file mode 100644 index 0000000..f5223f2 --- /dev/null +++ b/zto/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/zto/service/SoftwareUpdater.cpp b/zto/service/SoftwareUpdater.cpp new file mode 100644 index 0000000..7ec377c --- /dev/null +++ b/zto/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/zto/service/SoftwareUpdater.hpp b/zto/service/SoftwareUpdater.hpp new file mode 100644 index 0000000..4bb0ef5 --- /dev/null +++ b/zto/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/zto/tcp-proxy/Makefile b/zto/tcp-proxy/Makefile new file mode 100644 index 0000000..af4e71e --- /dev/null +++ b/zto/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/zto/tcp-proxy/README.md b/zto/tcp-proxy/README.md new file mode 100644 index 0000000..6f347d6 --- /dev/null +++ b/zto/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/zto/tcp-proxy/tcp-proxy.cpp b/zto/tcp-proxy/tcp-proxy.cpp new file mode 100644 index 0000000..a7906aa --- /dev/null +++ b/zto/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/zto/version.h b/zto/version.h new file mode 100644 index 0000000..3d71f6d --- /dev/null +++ b/zto/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 3 + +/** + * 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/zto/zerotier-one.spec b/zto/zerotier-one.spec new file mode 100644 index 0000000..1003411 --- /dev/null +++ b/zto/zerotier-one.spec @@ -0,0 +1,170 @@ +Name: zerotier-one +Version: 1.2.2 +Release: 1%{?dist} +Summary: ZeroTier One network virtualization service + +License: GPLv3 +URL: https://www.zerotier.com + +%if 0%{?rhel} >= 7 +BuildRequires: systemd +%endif + +%if 0%{?fedora} >= 21 +BuildRequires: systemd +%endif + +Requires: iproute + +%if 0%{?rhel} >= 7 +Requires: systemd +Requires(pre): /usr/sbin/useradd, /usr/bin/getent +%endif + +%if 0%{?rhel} <= 6 +Requires: chkconfig +%endif + +%if 0%{?fedora} >= 21 +Requires: systemd +Requires(pre): /usr/sbin/useradd, /usr/bin/getent +%endif + +%description +ZeroTier is a software defined networking layer for Earth. + +It can be used for on-premise network virtualization, as a peer to peer VPN +for mobile teams, for hybrid or multi-data-center cloud deployments, or just +about anywhere else secure software defined virtual networking is useful. + +ZeroTier One is our OS-level client service. It allows Mac, Linux, Windows, +FreeBSD, and soon other types of clients to join ZeroTier virtual networks +like conventional VPNs or VLANs. It can run on native systems, VMs, or +containers (Docker, OpenVZ, etc.). + +%prep +#rm -rf * +#ln -s %{getenv:PWD} %{name}-%{version} +#tar --exclude=%{name}-%{version}/.git --exclude=%{name}-%{version}/%{name}-%{version} -czf %{_sourcedir}/%{name}-%{version}.tar.gz %{name}-%{version}/* +#rm -f %{name}-%{version} +#cp -a %{getenv:PWD}/* . + +%build +#%if 0%{?rhel} <= 7 +#make CFLAGS="`echo %{optflags} | sed s/stack-protector-strong/stack-protector/`" CXXFLAGS="`echo %{optflags} | sed s/stack-protector-strong/stack-protector/`" ZT_USE_MINIUPNPC=1 %{?_smp_mflags} one manpages selftest +#%else +#make CFLAGS="%{optflags}" CXXFLAGS="%{optflags}" ZT_USE_MINIUPNPC=1 %{?_smp_mflags} one manpages selftest +#%endif + +%pre +%if 0%{?rhel} >= 7 +/usr/bin/getent passwd zerotier-one || /usr/sbin/useradd -r -d /var/lib/zerotier-one -s /sbin/nologin zerotier-one +%endif +%if 0%{?fedora} >= 21 +/usr/bin/getent passwd zerotier-one || /usr/sbin/useradd -r -d /var/lib/zerotier-one -s /sbin/nologin zerotier-one +%endif + +%install +rm -rf $RPM_BUILD_ROOT +pushd %{getenv:PWD} +make install DESTDIR=$RPM_BUILD_ROOT +popd +%if 0%{?rhel} >= 7 +mkdir -p $RPM_BUILD_ROOT%{_unitdir} +cp %{getenv:PWD}/debian/zerotier-one.service $RPM_BUILD_ROOT%{_unitdir}/%{name}.service +%endif +%if 0%{?fedora} >= 21 +mkdir -p $RPM_BUILD_ROOT%{_unitdir} +cp ${getenv:PWD}/debian/zerotier-one.service $RPM_BUILD_ROOT%{_unitdir}/%{name}.service +%endif +%if 0%{?rhel} <= 6 +mkdir -p $RPM_BUILD_ROOT/etc/init.d +cp %{getenv:PWD}/ext/installfiles/linux/zerotier-one.init.rhel6 $RPM_BUILD_ROOT/etc/init.d/zerotier-one +chmod 0755 $RPM_BUILD_ROOT/etc/init.d/zerotier-one +%endif + +%files +%{_sbindir}/* +%{_mandir}/* +%{_localstatedir}/* +%if 0%{?rhel} >= 7 +%{_unitdir}/%{name}.service +%endif +%if 0%{?fedora} >= 21 +%{_unitdir}/%{name}.service +%endif +%if 0%{?rhel} <= 6 +/etc/init.d/zerotier-one +%endif + +%post +%if 0%{?rhel} >= 7 +%systemd_post zerotier-one.service +%endif +%if 0%{?fedora} >= 21 +%systemd_post zerotier-one.service +%endif +%if 0%{?rhel} <= 6 +case "$1" in + 1) + chkconfig --add zerotier-one + ;; + 2) + chkconfig --del newservice + chkconfig --add newservice + ;; +esac +%endif + +%preun +%if 0%{?rhel} >= 7 +%systemd_preun zerotier-one.service +%endif +%if 0%{?fedora} >= 21 +%systemd_preun zerotier-one.service +%endif +%if 0%{?rhel} <= 6 +case "$1" in + 0) + service zerotier-one stop + chkconfig --del zerotier-one + ;; + 1) + # This is an upgrade. + : + ;; +esac +%endif + +%postun +%if 0%{?rhel} >= 7 +%systemd_postun_with_restart zerotier-one.service +%endif +%if 0%{?fedora} >= 21 +%systemd_postun_with_restart zerotier-one.service +%endif + +%changelog +* Fri Mar 17 2017 Adam Ierymenko - 1.2.2-0.1 +- see https://github.com/zerotier/ZeroTierOne for release notes + +* Tue Mar 14 2017 Adam Ierymenko - 1.2.0-0.1 +- see https://github.com/zerotier/ZeroTierOne for release notes + +* Tue Jul 12 2016 Adam Ierymenko - 1.1.10-0.1 +- see https://github.com/zerotier/ZeroTierOne for release notes + +* Fri Jul 08 2016 Adam Ierymenko - 1.1.8-0.1 +- see https://github.com/zerotier/ZeroTierOne for release notes + +* Sat Jun 25 2016 Adam Ierymenko - 1.1.6-0.1 +- now builds on CentOS 6 as well as newer distros, and some cleanup + +* Wed Jun 08 2016 François Kooman - 1.1.5-0.3 +- include systemd unit file + +* Wed Jun 08 2016 François Kooman - 1.1.5-0.2 +- add libnatpmp as (build)dependency + +* Wed Jun 08 2016 François Kooman - 1.1.5-0.1 +- initial package