diff --git a/.gitignore b/.gitignore index 8bf3c55..bd884dc 100755 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +<<<<<<< HEAD # Main binaries created in *nix builds /zerotier-one /zerotier-idtool @@ -9,6 +10,7 @@ .DS_Store .Apple* Thumbs.db +@eaDir # Windows build droppings /windows/ZeroTierOne.sdf @@ -29,6 +31,15 @@ Thumbs.db /ext/installfiles/windows/Prerequisites /ext/installfiles/windows/*-cache /ZeroTier One.msi +/windows/.vs +*.vcxproj.backup +/windows/TapDriver6/Win7Debug +/windows/TapDriver6/win7Release +/windows/*.db +/windows/*.opendb +enc_temp_folder +/windows/copyutil/bin +/windows/copyutil/obj # *nix/Mac build droppings /build-* @@ -49,7 +60,7 @@ zt1-src.tar.gz *.pid *.pkg *.o -*.a +/*.a *.dylib *.so *.so.* @@ -59,16 +70,15 @@ zt1-src.tar.gz *.rpm *.autosave *.tmp -doc/*.1 -doc/*.2 -doc/*.8 .depend node_modules +zt1_update_* debian/files debian/zerotier-one debian/zerotier-one*.debhelper debian/*.log debian/zerotier-one.substvars +root-watcher/config.json # Java/Android/JNI build droppings java/obj/ @@ -83,3 +93,21 @@ windows/WinUI/obj/ windows/WinUI/bin/ windows/ZeroTierOne/Debug/ /ext/installfiles/windows/chocolatey/zerotier-one/*.nupkg + +# Miscellaneous mac/Xcode droppings +.DS_Store +.Trashes +*.swp +*~.nib +DerivedData/ +build/ +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 +*.xccheckout +xcuserdata/ diff --git a/AUTHORS.md b/AUTHORS.md index aa9e911..043ff00 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -25,13 +25,13 @@ ## Third-Party Code -These are included in ext/ for platforms that do not have them available in common repositories. Otherwise they may be linked and the package may ship with them as dependencies. +ZeroTier includes the following third party code, either in ext/ or incorporated into the ZeroTier core. * LZ4 compression algorithm by Yann Collet - * Files: ext/lz4/* + * Files: node/Packet.cpp (bundled within anonymous namespace) * Home page: http://code.google.com/p/lz4/ - * License grant: BSD attribution + * License grant: BSD 2-clause * http-parser by Joyent, Inc. (many authors) @@ -39,11 +39,11 @@ These are included in ext/ for platforms that do not have them available in comm * Home page: https://github.com/joyent/http-parser/ * License grant: MIT/Expat - * json-parser by James McLaughlin + * C++11 json (nlohmann/json) by Niels Lohmann - * Files: ext/json-parser/* - * Home page: https://github.com/udp/json-parser/ - * License grant: BSD attribution + * Files: ext/json/* + * Home page: https://github.com/nlohmann/json + * License grant: MIT * TunTapOSX by Mattias Nissler @@ -55,26 +55,19 @@ These are included in ext/ for platforms that do not have them available in comm * tap-windows6 by the OpenVPN project * Files: windows/TapDriver6/* - * Home page: - https://github.com/OpenVPN/tap-windows6/ + * 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 + * Salsa20 stream cipher, Curve25519 elliptic curve cipher, Ed25519 digital signature algorithm, and Poly1305 MAC algorithm, all by Daniel J. Bernstein - * Files: - node/Salsa20.hpp - node/C25519.hpp - node/Poly1305.hpp + * 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/* + * Files: ext/libnatpmp/* ext/miniupnpc/* * Home page: http://miniupnp.free.fr/ * License grant: BSD attribution no-endorsement diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..74c8624 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,82 @@ +#!/usr/bin/env groovy + +node('master') { + def changelog = getChangeLog currentBuild + + slackSend "Building ${env.JOB_NAME} #${env.BUILD_NUMBER} \n Change Log: \n ${changelog}" +} + +parallel 'centos7': { + node('centos7') { + try { + checkout scm + + stage('Build Centos 7') { + sh 'make -f make-linux.mk' + } + } + catch (err) { + currentBuild.result = "FAILURE" + slackSend color: '#ff0000', message: "${env.JOB_NAME} broken on Centos 7 (<${env.BUILD_URL}|Open>)" + + throw err + } + } +}, 'android-ndk': { + node('android-ndk') { + try { + checkout scm + + stage('Build Android NDK') { + sh "/android/android-ndk-r13b/ndk-build -C $WORKSPACE/java ZT1=${WORKSPACE}" + } + } + catch (err) { + currentBuild.result = "FAILURE" + slackSend color: '#ff0000', message: "${env.JOB_NAME} broken on Android NDK (<${env.BUILD_URL}|Open>)" + + throw err + } + } +}, 'macOS': { + node('macOS') { + try { + checkout scm + + stage('Build macOS') { + sh 'make -f make-mac.mk' + } + + stage('Build macOS UI') { + sh 'cd macui && xcodebuild -target "ZeroTier One" -configuration Debug' + } + } + catch (err) { + currentBuild.result = "FAILURE" + slackSend color: '#ff0000', message: "${env.JOB_NAME} broken on macOS (<${env.BUILD_URL}|Open>)" + + throw err + } + } +}, 'windows': { + node('windows') { + try { + checkout scm + + stage('Build Windows') { + bat '''CALL "C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat" amd64 +git clean -dfx +msbuild windows\\ZeroTierOne.sln +''' + } + } + catch (err) { + currentBuild.result = "FAILURE" + slackSend color: '#ff0000', message: "${env.JOB_NAME} broken on Windows (<${env.BUILD_URL}|Open>)" + + throw err + } + } +} + +slackSend color: "#00ff00", message: "${env.JOB_NAME} #${env.BUILD_NUMBER} Complete (<${env.BUILD_URL}|Show More...>)" diff --git a/Makefile b/Makefile index 5a5f660..9511862 100644 --- a/Makefile +++ b/Makefile @@ -11,8 +11,14 @@ ifeq ($(OSTYPE),Linux) endif ifeq ($(OSTYPE),FreeBSD) - include make-freebsd.mk + CC=clang + CXX=clang++ + ZT_BUILD_PLATFORM=7 + include make-bsd.mk endif ifeq ($(OSTYPE),OpenBSD) - include make-freebsd.mk + CC=egcc + CXX=eg++ + ZT_BUILD_PLATFORM=9 + include make-bsd.mk endif diff --git a/OFFICIAL-RELEASE-STEPS.md b/OFFICIAL-RELEASE-STEPS.md index 37e6791..d0f42e3 100644 --- a/OFFICIAL-RELEASE-STEPS.md +++ b/OFFICIAL-RELEASE-STEPS.md @@ -15,6 +15,7 @@ The version must be incremented in all of the following files: /ext/installfiles/mac/ZeroTier One.pkgproj /ext/installfiles/windows/chocolatey/zerotier-one.nuspec /ext/installfiles/windows/ZeroTier One.aip + /windows/WinUI/AboutView.xaml The final .AIP file can only be edited on Windows with [Advanced Installer Enterprise](http://www.advancedinstaller.com/). In addition to incrementing the version be sure that a new product code is generated. (The "upgrade code" GUID on the other hand must never change.) diff --git a/README.md b/README.md index a34a1a5..47bfc87 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,76 @@ ZeroTier - A Planetary Ethernet Switch ====== -ZeroTier is a software-based managed Ethernet switch for planet Earth. +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. -This repository contains ZeroTier One, a service that provides ZeroTier network connectivity to devices running Windows, Mac, Linux, iOS, Android, and FreeBSD and makes joining virtual networks as easy as joining IRC or Slack channels. It also contains the OS-independent core ZeroTier protocol implementation in [node/](node/). - 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" can be anything really: desktops, laptops, phones, servers, VMs/VPSes, containers, and even (soon) apps. +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 we provide a public virtual network called *Earth* with network ID `8056c2e21c000001`. On Linux and Mac you can do this with: +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. - -*(IPv4 addresses for Earth are assigned from the block 28.0.0.0/7, which is not a part of the public Internet but is non-standard for private networks. It's used to avoid IP conflicts during testing. Your networks can run any IP addressing scheme you want.)* - -If you don't want to belong to a giant Ethernet party line anymore, just type: +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. You can use [our hosted controller at my.zerotier.com](https://my.zerotier.com) which is free for up to 100 devices on an unlimited number of networks, or you can build your own controller and run it through its local JSON API. See [README.md in controller/](controller/) for more information. +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. -### Building from Source +### Project Layout -For Mac, Linux, and BSD, just type "make" (or "gmake" on BSD). You won't need much installed; here are the requirements for various platforms: + - `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. - * **Mac**: Xcode command line tools. It should build on OSX 10.7 or newer. - * **Linux**: gcc/g++ (4.9 or newer recommended) or clang/clang++ (3.4 or newer recommended) Makefile will use clang by default if available. The Linux build will auto-detect the presence of development headers for *json-parser*, *http-parser*, *li8bnatpmp*, and *libminiupnpc* and will link against the system libraries for these if they are present and recent enough. Otherwise the bundled versions in [ext/](ext/) will be used. Type `make install` to install the binaries and other files on the system, though this will not create init.d or systemd links. - * **FreeBSD**: C++ compiler (G++ usually) and GNU make (gmake). +The base path contains the ZeroTier One service main entry point (`one.cpp`), self test code, makefiles, etc. -Each supported platform has its own *make-XXX.mk* file that contains the actual make rules for the platform. The right .mk file is included by the main Makefile based on the GNU make *OSTYPE* variable. Take a look at the .mk file for your platform for other targets, debug build rules, 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. -Windows, of course, is special. We build for Windows with Microsoft Visual Studio 2012 on Windows 7. A solution file is located in the *windows/* subfolder. Newer versions of Visual Studio (and Windows) may work but haven't been tested. Older versions almost certainly will not, since they lack things like *stdint.h* and certain STL features. MinGW or other ports of gcc/clang to Windows should also work but haven't been tested. - -32 and 64 bit X86 and ARM (e.g. Raspberry Pi, Android) are officially supported. Community members have built for MIPS and Sparc without issues. - ### Running Running *zerotier-one* with -h will show help. @@ -62,7 +86,7 @@ The service is controlled via the JSON API, which by default is available at 127 Here's where home folders live (by default) on each OS: * **Linux**: `/var/lib/zerotier-one` - * **FreeBSD**: `/var/db/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.) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md new file mode 100644 index 0000000..195e888 --- /dev/null +++ b/RELEASE-NOTES.md @@ -0,0 +1,138 @@ +ZeroTier Release Notes +====== + +# 2017-04-20 -- Version 1.2.4 + + * Managed routes are now only bifurcated for the default route. This is a change in behavior, though few people will probably notice. Bifurcating all managed routes was causing more trouble than it was worth for most users. + * Up to 2X crypto speedup on x86-64 (except Windows, which will take some porting) and 32-bit ARM platforms due to integration of fast assembly language implementations of Salsa20/12 from the [supercop](http://bench.cr.yp.to/supercop.html) code base. These were written by Daniel J. Bernstein and are in the public domain. My Macbook Pro (Core i5 2.8ghz) now does almost 1.5GiB/sec Salsa20/12 per core and a Raspberry Pi got a 2X boost. 64-bit ARM support and Windows support will take some work but should not be too hard. + * Refactored code that manages credentials to greatly reduce memory use in most cases. This may also result in a small performance improvement. + * Reworked and simplified path selection and priority logic to fix path instability and dead path persistence edge cases. There have been some sporadic reports of persistent path instabilities and dead paths hanging around that take minutes to resolve. These have proven difficult to reproduce in house, but hopefully this will fix them. In any case it seems to speed up path establishment in our tests and it makes the code simpler and more readable. + * Eliminated some unused cruft from the code around path management and in the peer class. + * Fixed an issue causing build problems on some MIPS architecture systems. + * Fixed Windows forgetting routes on sleep/wake or in some other circumstances. (GitHub issue #465) + +# 2017-03-17 -- Version 1.2.2 + + * A bug causing unreliable multicast propagation (GitHub issue #461). + * A crash in ARM binaries due to a build chain and flags problem. + * A bug in the network controller preventing members from being listed (GitHub issue #460). + +# 2017-03-14 -- Version 1.2.0 + +Version 1.2.0 is a major milestone release representing almost nine months of work. It includes our rules engine for distributed network packet filtering and security monitoring, federated roots, and many other architectural and UI improvements and bug fixes. + +## New Features in 1.2.0 + +### The ZeroTier Rules Engine + +The largest new feature in 1.2.0, and the product of many months of work, is our advanced network rules engine. With this release we achieve traffic control, security monitoring, and micro-segmentation capability on par with many enterprise SDN solutions designed for use in advanced data centers and corporate networks. + +Rules allow you to filter packets on your network and vector traffic to security observers. Security observation can be performed in-band using REDIRECT or out of band using TEE. + +Tags and capabilites provide advanced methods for implementing fine grained permission structures and micro-segmentation schemes without bloating the size and complexity of your rules table. + +See the [rules engine announcement blog post](https://www.zerotier.com/blog/?p=927) for an in-depth discussion of theory and implementation. The [manual](https://www.zerotier.com/manual.shtml) contains detailed information on rule, tag, and capability use, and the `rule-compiler/` subfolder of the ZeroTier source tree contains a JavaScript function to compile rules in our human-readable rule definition language into rules suitable for import into a network controller. (ZeroTier Central uses this same script to compile rules on [my.zerotier.com](https://my.zerotier.com/).) + +### Root Server Federation + +It's now possible to create your own root servers and add them to the root server pool on your nodes. This is done by creating what's called a "moon," which is a signed enumeration of root servers and their stable points on the network. Refer to the [manual](https://www.zerotier.com/manual.shtml) for instructions. + +Federated roots achieve a number of things: + + * You can deploy your own infrastructure to reduce dependency on ours. + * You can deploy roots *inside your LAN* to ensure that network connectivity inside your facility still works if the Internet goes down. This is the first step toward making ZeroTier viable as an in-house SDN solution. + * Roots can be deployed inside national boundaries for countries with data residency laws or "great firewalls." (As of 1.2.0 there is still no way to force all traffic to use these roots, but that will be easy to do in a later version.) + * Last but not least this makes ZeroTier somewhat less centralized by eliminating any hard dependency on ZeroTier, Inc.'s infrastructure. + +Our roots will of course remain and continue to provide zero-configuration instant-on deployment, a secure global authority for identities, and free traffic relaying for those who can't establish peer to peer connections. + +### Local Configuration + +An element of our design philosophy is "features are bugs." This isn't an absolute dogma but more of a guiding principle. We try as hard as we can to avoid adding features, especially "knobs" that must be tweaked by a user. + +As of 1.2.0 we've decided that certain knobs are unavoidable, and so there is now a `local.conf` file that can be used to configure them. See the ZeroTier One documentation for these. They include: + + * Blacklisting interfaces you want to make sure ZeroTier doesn't use for network traffic, such as VPNs, slow links, or backplanes designated for only certain kinds of traffic. + * Turning uPnP/NAT-PMP on or off. + * Configuring software updates on Windows and Mac platforms. + * Defining trusted paths (the old trusted paths file is now deprecated) + * Setting the ZeroTier main port so it doesn't have to be changed on the command line, which is very inconvenient in many cases. + +### Improved In-Band Software Updates + +A good software update system for Windows and Mac clients has been a missing feature in previous versions. It does exist but we've been shy about using it so far due to its fragility in some environments. + +We've greatly improved this mechanism in 1.2.0. Not only does it now do a better job of actually invoking the update, but it also transfers updates in-band using the ZeroTier protocol. This means it can work in environments that do not allows http/https traffic or that force it through proxies. There's also now an update channel setting: `beta` or `release` (the default). + +Software updates are authenticated three ways: + + 1. ZeroTier's own signing key is used to sign all updates and this signature is checked prior to installation. ZeroTier, Inc.'s signatures are performed on an air-gapped machine. + + 2. Updates for Mac and Windows are signed using Apple and Microsoft (DigiCert EV) keys and will not install unless these signatures are also valid. + + 3. The new in-band update mechanism also authenticates the source of the update via ZeroTier's built-in security features. This provides transport security, while 1 and 2 provide security of the update at rest. + +Updates are now configurable via `local.conf`. There are three options: `disable`, `download`, and `apply`. The third (apply) is the default for official builds on Windows and Mac, making updates happen silently and automatically as they do for popular browsers like Chrome and Firefox. Updates are disabled by default on Linux and other Unix-type systems as these are typically updated through package managers. + +### Path Link Quality Awareness + +Version 1.2.0 is now aware of the link quality of direct paths with other 1.2.0 nodes. This information isn't used yet but is visible through the JSON API. (Quality always shows as 100% with pre-1.2.0 nodes.) Quality is measured passively with no additional overhead using a counter based packet loss detection algorithm. + +This information is visible from the command line via `listpeers`: + + 200 listpeers XXXXXXXXXX 199.XXX.XXX.XXX/9993;10574;15250;1.00 48 1.2.0 LEAF + 200 listpeers XXXXXXXXXX 195.XXX.XXX.XXX/45584;467;7608;0.44 290 1.2.0 LEAF + +The first peer's path is at 100% (1.00), while the second peer's path is suffering quite a bit of packet loss (0.44). + +Link quality awareness is a precursor to intelligent multi-path and QoS support, which will in future versions bring us to feature parity with SD-WAN products like Cisco iWAN. + +### Security Improvements + +Version 1.2.0 adds anti-DOS (denial of service) rate limits and other hardening for improved resiliency against a number of denial of service attack scenarios. + +It also adds a mechanism for instantaneous credential revocation. This can be used to revoke certificates of membership instantly to kick a node off a network (for private networks) and also to revoke capabilities and tags. The new controller sends revocations by default when a peer is de-authorized. + +Revocations propagate using a "rumor mill" peer to peer algorithm. This means that a controller need only successfully send a revocation to at least one member of a network with connections to other active members. At this point the revocation will flood through the network peer to peer very quickly. This helps make revocations more robust in the face of poor connectivity with the controller or attempts to incapacitate the controller with denial of service attacks, as well as making revocations faster on huge networks. + +### Windows and Macintosh UI Improvements (ZeroTier One) + +The Mac has a whole new UI built natively in Objective-C. It provides a pulldown similar in appearance and operation to the Mac WiFi task bar menu. + +The Windows UI has also been improved and now provides a task bar icon that can be right-clicked to manage networks. Both now expose managed route and IP permissions, allowing nodes to easily opt in to full tunnel operation if you have a router configured on your network. + +### Ad-Hoc Networks + +A special kind of public network called an ad-hoc network may be accessed by joining a network ID with the format: + + ffSSSSEEEE000000 + | | | | + | | | Reserved for future use, must be 0 + | | End of port range (hex) + | Start of port range (hex) + Reserved ZeroTier address prefix indicating a controller-less network + +Ad-hoc networks are public (no access control) networks that have no network controller. Instead their configuration and other credentials are generated locally. Ad-hoc networks permit only IPv6 UDP and TCP unicast traffic (no multicast or broadcast) using 6plane format NDP-emulated IPv6 addresses. In addition an ad-hoc network ID encodes an IP port range. UDP packets and TCP SYN (connection open) packets are only allowed to desintation ports within the encoded range. + +For example `ff00160016000000` is an ad-hoc network allowing only SSH, while `ff0000ffff000000` is an ad-hoc network allowing any UDP or TCP port. + +Keep in mind that these networks are public and anyone in the entire world can join them. Care must be taken to avoid exposing vulnerable services or sharing unwanted files or other resources. + +### Network Controller (Partial) Rewrite + +The network controller has been largely rewritten to use a simple in-filesystem JSON data store in place of SQLite, and it is now included by default in all Windows, Mac, Linux, and BSD builds. This means any desktop or server node running ZeroTier One can now be a controller with no recompilation needed. + +If you have data in an old SQLite3 controller we've included a NodeJS script in `controller/migrate-sqlite` to migrate data to the new format. If you don't migrate, members will start getting `NOT_FOUND` when they attempt to query for updates. + +## Major Bug Fixes in 1.2.0 + + * **The Windows HyperV 100% CPU bug is FINALLY DEAD**: This long-running problem turns out to have been an issue with Windows itself, but one we were triggering by placing invalid data into the Windows registry. Microsoft is aware of the issue but we've also fixed the triggering problem on our side. ZeroTier should now co-exist quite well with HyperV and should now be able to be bridged with a HyperV virtual switch. + * **Segmenation faults on musl-libc based Linux systems**: Alpine Linux and some embedded Linux systems that use musl libc (a minimal libc) experienced segmentation faults. These were due to a smaller default stack size. A work-around that sets the stack size for new threads has been added. + * **Windows firewall blocks local JSON API**: On some Windows systems the firewall likes to block 127.0.0.1:9993 for mysterious reasons. This is now fixed in the installer via the addition of another firewall exemption rule. + * **UI crash on embedded Windows due to missing fonts**: The MSI installer now ships fonts and will install them if they are not present, so this should be fixed. + +## Other Improvements in 1.2.0 + + * **Improved dead path detection**: ZeroTier is now more aggressive about expiring paths that do not seem to be active. If a path seems marginal it is re-confirmed before re-use. + * **Minor performance improvements**: We've reduced unnecessary memcpy's and made a few other performance improvements in the core. + * **Linux static binaries**: For our official packages (the ones in the download.zerotier.com apt and yum repositories) we now build Linux binaries with static linking. Hopefully this will stop all the bug reports relating to library inconsistencies, as well as allowing our deb packages to run on a wider variety of Debian-based distributions. (There are far too many of these to support officially!) The overhead for this is very small, especially since we built our static versions against musl-libc. Distribution maintainers are of course free to build dynamically linked versions for inclusion into distributions; this only affects our official binaries. diff --git a/attic/Filter.cpp b/attic/Filter.cpp deleted file mode 100644 index a701e8b..0000000 --- a/attic/Filter.cpp +++ /dev/null @@ -1,408 +0,0 @@ -/* - * 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 "RuntimeEnvironment.hpp" -#include "Logger.hpp" -#include "Filter.hpp" -#include "Utils.hpp" - -namespace ZeroTier { - -const char *const Filter::UNKNOWN_NAME = "(unknown)"; -const Range Filter::ANY; - -static inline Range __parseRange(char *r) - throw(std::invalid_argument) -{ - char *saveptr = (char *)0; - unsigned int a = 0; - unsigned int b = 0; - unsigned int fn = 0; - for(char *f=Utils::stok(r,"-",&saveptr);(f);f=Utils::stok((char *)0,"-",&saveptr)) { - if (*f) { - switch(fn++) { - case 0: - if (*f != '*') - a = b = (unsigned int)strtoul(f,(char **)0,10); - break; - case 1: - if (*f != '*') - b = (unsigned int)strtoul(f,(char **)0,10); - break; - default: - throw std::invalid_argument("rule range must be , -, or *"); - } - } - } - return Range(a,b); -} - -Filter::Rule::Rule(const char *s) - throw(std::invalid_argument) -{ - char *saveptr = (char *)0; - char tmp[256]; - if (!Utils::scopy(tmp,sizeof(tmp),s)) - throw std::invalid_argument("rule string too long"); - unsigned int fn = 0; - for(char *f=Utils::stok(tmp,";",&saveptr);(f);f=Utils::stok((char *)0,";",&saveptr)) { - if (*f) { - switch(fn++) { - case 0: - _etherType = __parseRange(f); - break; - case 1: - _protocol = __parseRange(f); - break; - case 2: - _port = __parseRange(f); - break; - default: - throw std::invalid_argument("rule string has unknown extra fields"); - } - } - } - if (fn != 3) - throw std::invalid_argument("rule string must contain 3 fields"); -} - -bool Filter::Rule::operator()(unsigned int etype,const void *data,unsigned int len) const - throw(std::invalid_argument) -{ - if ((!_etherType)||(_etherType(etype))) { // ethertype is ANY, or matches - // Ethertype determines meaning of protocol and port - switch(etype) { - case ZT_ETHERTYPE_IPV4: - if (len > 20) { - if ((!_protocol)||(_protocol(((const uint8_t *)data)[9]))) { // protocol is ANY or match - if (!_port) // port is ANY - return true; - - // Don't match on fragments beyond fragment 0. If we've blocked - // fragment 0, further fragments will fall on deaf ears anyway. - if ((Utils::ntoh(((const uint16_t *)data)[3]) & 0x1fff)) - return false; - - // Internet header length determines where data begins, in multiples of 32 bits - unsigned int ihl = 4 * (((const uint8_t *)data)[0] & 0x0f); - - switch(((const uint8_t *)data)[9]) { // port's meaning depends on IP protocol - case ZT_IPPROTO_ICMP: - // For ICMP, port is ICMP type - return _port(((const uint8_t *)data)[ihl]); - case ZT_IPPROTO_TCP: - case ZT_IPPROTO_UDP: - case ZT_IPPROTO_SCTP: - case ZT_IPPROTO_UDPLITE: - // For these, port is destination port. Protocol designers were - // nice enough to put the field in the same place. - return _port(((const uint16_t *)data)[(ihl / 2) + 1]); - default: - // port has no meaning for other IP types, so ignore it - return true; - } - - return false; // no match on port - } - } else throw std::invalid_argument("undersized IPv4 packet"); - break; - - case ZT_ETHERTYPE_IPV6: - if (len > 40) { - int nextHeader = ((const uint8_t *)data)[6]; - unsigned int pos = 40; - while ((pos < len)&&(nextHeader >= 0)&&(nextHeader != 59)) { // 59 == no next header - fprintf(stderr,"[rule] V6: start header parse, header %.2x pos %d\n",nextHeader,pos); - - switch(nextHeader) { - case 0: // hop-by-hop options - case 60: // destination options - case 43: // routing - case 135: // mobility (mobile IPv6 options) - if (_protocol((unsigned int)nextHeader)) - return true; // match if our goal was to match any of these - nextHeader = ((const uint8_t *)data)[pos]; - pos += 8 + (8 * ((const uint8_t *)data)[pos + 1]); - break; - case 44: // fragment - if (_protocol(44)) - return true; // match if our goal was to match fragments - nextHeader = ((const uint8_t *)data)[pos]; - pos += 8; - break; - case ZT_IPPROTO_AH: // AH - return _protocol(ZT_IPPROTO_AH); // true if AH is matched protocol, otherwise false since packet will be IPsec - case ZT_IPPROTO_ESP: // ESP - return _protocol(ZT_IPPROTO_ESP); // true if ESP is matched protocol, otherwise false since packet will be IPsec - case ZT_IPPROTO_ICMPV6: - // Only match ICMPv6 if we've selected it specifically - if (_protocol(ZT_IPPROTO_ICMPV6)) { - // Port is interpreted as ICMPv6 type - if ((!_port)||(_port(((const uint8_t *)data)[pos]))) - return true; - } - break; - case ZT_IPPROTO_TCP: - case ZT_IPPROTO_UDP: - case ZT_IPPROTO_SCTP: - case ZT_IPPROTO_UDPLITE: - // If we encounter any of these, match if protocol matches or is wildcard as - // we'll consider these the "real payload" if present. - if ((!_protocol)||(_protocol(nextHeader))) { - if ((!_port)||(_port(((const uint16_t *)data)[(pos / 2) + 1]))) - return true; // protocol matches or is ANY, port is ANY or matches - } - break; - default: { - char foo[128]; - Utils::snprintf(foo,sizeof(foo),"unrecognized IPv6 header type %d",(int)nextHeader); - throw std::invalid_argument(foo); - } - } - - fprintf(stderr,"[rule] V6: end header parse, next header %.2x, new pos %d\n",nextHeader,pos); - } - } else throw std::invalid_argument("undersized IPv6 packet"); - break; - - default: - // For other ethertypes, protocol and port are ignored. What would they mean? - return true; - } - } - - return false; -} - -std::string Filter::Rule::toString() const -{ - char buf[128]; - std::string s; - - switch(_etherType.magnitude()) { - case 0: - s.push_back('*'); - break; - case 1: - Utils::snprintf(buf,sizeof(buf),"%u",_etherType.start); - s.append(buf); - break; - default: - Utils::snprintf(buf,sizeof(buf),"%u-%u",_etherType.start,_etherType.end); - s.append(buf); - break; - } - s.push_back(';'); - switch(_protocol.magnitude()) { - case 0: - s.push_back('*'); - break; - case 1: - Utils::snprintf(buf,sizeof(buf),"%u",_protocol.start); - s.append(buf); - break; - default: - Utils::snprintf(buf,sizeof(buf),"%u-%u",_protocol.start,_protocol.end); - s.append(buf); - break; - } - s.push_back(';'); - switch(_port.magnitude()) { - case 0: - s.push_back('*'); - break; - case 1: - Utils::snprintf(buf,sizeof(buf),"%u",_port.start); - s.append(buf); - break; - default: - Utils::snprintf(buf,sizeof(buf),"%u-%u",_port.start,_port.end); - s.append(buf); - break; - } - - return s; -} - -Filter::Filter(const char *s) - throw(std::invalid_argument) -{ - char tmp[16384]; - if (!Utils::scopy(tmp,sizeof(tmp),s)) - throw std::invalid_argument("filter string too long"); - char *saveptr = (char *)0; - unsigned int fn = 0; - for(char *f=Utils::stok(tmp,",",&saveptr);(f);f=Utils::stok((char *)0,",",&saveptr)) { - try { - _rules.push_back(Rule(f)); - ++fn; - } catch (std::invalid_argument &exc) { - char tmp[256]; - Utils::snprintf(tmp,sizeof(tmp),"invalid rule at index %u: %s",fn,exc.what()); - throw std::invalid_argument(tmp); - } - } - std::sort(_rules.begin(),_rules.end()); -} - -std::string Filter::toString() const -{ - std::string s; - - for(std::vector::const_iterator r(_rules.begin());r!=_rules.end();++r) { - if (s.length() > 0) - s.push_back(','); - s.append(r->toString()); - } - - return s; -} - -void Filter::add(const Rule &r) -{ - for(std::vector::iterator rr(_rules.begin());rr!=_rules.end();++rr) { - if (r == *rr) - return; - } - _rules.push_back(r); - std::sort(_rules.begin(),_rules.end()); -} - -const char *Filter::etherTypeName(const unsigned int etherType) - throw() -{ - switch(etherType) { - case ZT_ETHERTYPE_IPV4: return "ETHERTYPE_IPV4"; - case ZT_ETHERTYPE_ARP: return "ETHERTYPE_ARP"; - case ZT_ETHERTYPE_RARP: return "ETHERTYPE_RARP"; - case ZT_ETHERTYPE_ATALK: return "ETHERTYPE_ATALK"; - case ZT_ETHERTYPE_AARP: return "ETHERTYPE_AARP"; - case ZT_ETHERTYPE_IPX_A: return "ETHERTYPE_IPX_A"; - case ZT_ETHERTYPE_IPX_B: return "ETHERTYPE_IPX_B"; - case ZT_ETHERTYPE_IPV6: return "ETHERTYPE_IPV6"; - } - return UNKNOWN_NAME; -} - -const char *Filter::ipProtocolName(const unsigned int ipp) - throw() -{ - switch(ipp) { - case ZT_IPPROTO_ICMP: return "IPPROTO_ICMP"; - case ZT_IPPROTO_IGMP: return "IPPROTO_IGMP"; - case ZT_IPPROTO_TCP: return "IPPROTO_TCP"; - case ZT_IPPROTO_UDP: return "IPPROTO_UDP"; - case ZT_IPPROTO_GRE: return "IPPROTO_GRE"; - case ZT_IPPROTO_ESP: return "IPPROTO_ESP"; - case ZT_IPPROTO_AH: return "IPPROTO_AH"; - case ZT_IPPROTO_ICMPV6: return "IPPROTO_ICMPV6"; - case ZT_IPPROTO_OSPF: return "IPPROTO_OSPF"; - case ZT_IPPROTO_IPIP: return "IPPROTO_IPIP"; - case ZT_IPPROTO_IPCOMP: return "IPPROTO_IPCOMP"; - case ZT_IPPROTO_L2TP: return "IPPROTO_L2TP"; - case ZT_IPPROTO_SCTP: return "IPPROTO_SCTP"; - case ZT_IPPROTO_FC: return "IPPROTO_FC"; - case ZT_IPPROTO_UDPLITE: return "IPPROTO_UDPLITE"; - case ZT_IPPROTO_HIP: return "IPPROTO_HIP"; - } - return UNKNOWN_NAME; -} - -const char *Filter::icmpTypeName(const unsigned int icmpType) - throw() -{ - switch(icmpType) { - case ZT_ICMP_ECHO_REPLY: return "ICMP_ECHO_REPLY"; - case ZT_ICMP_DESTINATION_UNREACHABLE: return "ICMP_DESTINATION_UNREACHABLE"; - case ZT_ICMP_SOURCE_QUENCH: return "ICMP_SOURCE_QUENCH"; - case ZT_ICMP_REDIRECT: return "ICMP_REDIRECT"; - case ZT_ICMP_ALTERNATE_HOST_ADDRESS: return "ICMP_ALTERNATE_HOST_ADDRESS"; - case ZT_ICMP_ECHO_REQUEST: return "ICMP_ECHO_REQUEST"; - case ZT_ICMP_ROUTER_ADVERTISEMENT: return "ICMP_ROUTER_ADVERTISEMENT"; - case ZT_ICMP_ROUTER_SOLICITATION: return "ICMP_ROUTER_SOLICITATION"; - case ZT_ICMP_TIME_EXCEEDED: return "ICMP_TIME_EXCEEDED"; - case ZT_ICMP_BAD_IP_HEADER: return "ICMP_BAD_IP_HEADER"; - case ZT_ICMP_TIMESTAMP: return "ICMP_TIMESTAMP"; - case ZT_ICMP_TIMESTAMP_REPLY: return "ICMP_TIMESTAMP_REPLY"; - case ZT_ICMP_INFORMATION_REQUEST: return "ICMP_INFORMATION_REQUEST"; - case ZT_ICMP_INFORMATION_REPLY: return "ICMP_INFORMATION_REPLY"; - case ZT_ICMP_ADDRESS_MASK_REQUEST: return "ICMP_ADDRESS_MASK_REQUEST"; - case ZT_ICMP_ADDRESS_MASK_REPLY: return "ICMP_ADDRESS_MASK_REPLY"; - case ZT_ICMP_TRACEROUTE: return "ICMP_TRACEROUTE"; - case ZT_ICMP_MOBILE_HOST_REDIRECT: return "ICMP_MOBILE_HOST_REDIRECT"; - case ZT_ICMP_MOBILE_REGISTRATION_REQUEST: return "ICMP_MOBILE_REGISTRATION_REQUEST"; - case ZT_ICMP_MOBILE_REGISTRATION_REPLY: return "ICMP_MOBILE_REGISTRATION_REPLY"; - } - return UNKNOWN_NAME; -} - -const char *Filter::icmp6TypeName(const unsigned int icmp6Type) - throw() -{ - switch(icmp6Type) { - case ZT_ICMP6_DESTINATION_UNREACHABLE: return "ICMP6_DESTINATION_UNREACHABLE"; - case ZT_ICMP6_PACKET_TOO_BIG: return "ICMP6_PACKET_TOO_BIG"; - case ZT_ICMP6_TIME_EXCEEDED: return "ICMP6_TIME_EXCEEDED"; - case ZT_ICMP6_PARAMETER_PROBLEM: return "ICMP6_PARAMETER_PROBLEM"; - case ZT_ICMP6_ECHO_REQUEST: return "ICMP6_ECHO_REQUEST"; - case ZT_ICMP6_ECHO_REPLY: return "ICMP6_ECHO_REPLY"; - case ZT_ICMP6_MULTICAST_LISTENER_QUERY: return "ICMP6_MULTICAST_LISTENER_QUERY"; - case ZT_ICMP6_MULTICAST_LISTENER_REPORT: return "ICMP6_MULTICAST_LISTENER_REPORT"; - case ZT_ICMP6_MULTICAST_LISTENER_DONE: return "ICMP6_MULTICAST_LISTENER_DONE"; - case ZT_ICMP6_ROUTER_SOLICITATION: return "ICMP6_ROUTER_SOLICITATION"; - case ZT_ICMP6_ROUTER_ADVERTISEMENT: return "ICMP6_ROUTER_ADVERTISEMENT"; - case ZT_ICMP6_NEIGHBOR_SOLICITATION: return "ICMP6_NEIGHBOR_SOLICITATION"; - case ZT_ICMP6_NEIGHBOR_ADVERTISEMENT: return "ICMP6_NEIGHBOR_ADVERTISEMENT"; - case ZT_ICMP6_REDIRECT_MESSAGE: return "ICMP6_REDIRECT_MESSAGE"; - case ZT_ICMP6_ROUTER_RENUMBERING: return "ICMP6_ROUTER_RENUMBERING"; - case ZT_ICMP6_NODE_INFORMATION_QUERY: return "ICMP6_NODE_INFORMATION_QUERY"; - case ZT_ICMP6_NODE_INFORMATION_RESPONSE: return "ICMP6_NODE_INFORMATION_RESPONSE"; - case ZT_ICMP6_INV_NEIGHBOR_SOLICITATION: return "ICMP6_INV_NEIGHBOR_SOLICITATION"; - case ZT_ICMP6_INV_NEIGHBOR_ADVERTISEMENT: return "ICMP6_INV_NEIGHBOR_ADVERTISEMENT"; - case ZT_ICMP6_MLDV2: return "ICMP6_MLDV2"; - case ZT_ICMP6_HOME_AGENT_ADDRESS_DISCOVERY_REQUEST: return "ICMP6_HOME_AGENT_ADDRESS_DISCOVERY_REQUEST"; - case ZT_ICMP6_HOME_AGENT_ADDRESS_DISCOVERY_REPLY: return "ICMP6_HOME_AGENT_ADDRESS_DISCOVERY_REPLY"; - case ZT_ICMP6_MOBILE_PREFIX_SOLICITATION: return "ICMP6_MOBILE_PREFIX_SOLICITATION"; - case ZT_ICMP6_MOBILE_PREFIX_ADVERTISEMENT: return "ICMP6_MOBILE_PREFIX_ADVERTISEMENT"; - case ZT_ICMP6_CERTIFICATION_PATH_SOLICITATION: return "ICMP6_CERTIFICATION_PATH_SOLICITATION"; - case ZT_ICMP6_CERTIFICATION_PATH_ADVERTISEMENT: return "ICMP6_CERTIFICATION_PATH_ADVERTISEMENT"; - case ZT_ICMP6_MULTICAST_ROUTER_ADVERTISEMENT: return "ICMP6_MULTICAST_ROUTER_ADVERTISEMENT"; - case ZT_ICMP6_MULTICAST_ROUTER_SOLICITATION: return "ICMP6_MULTICAST_ROUTER_SOLICITATION"; - case ZT_ICMP6_MULTICAST_ROUTER_TERMINATION: return "ICMP6_MULTICAST_ROUTER_TERMINATION"; - case ZT_ICMP6_RPL_CONTROL_MESSAGE: return "ICMP6_RPL_CONTROL_MESSAGE"; - } - return UNKNOWN_NAME; -} - -} // namespace ZeroTier diff --git a/attic/Filter.hpp b/attic/Filter.hpp deleted file mode 100644 index 4bea371..0000000 --- a/attic/Filter.hpp +++ /dev/null @@ -1,284 +0,0 @@ -/* - * 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_FILTER_HPP -#define _ZT_FILTER_HPP - -#include -#include -#include - -#include -#include -#include -#include - -#include "Range.hpp" - -/* 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 - -/* IP protocols we might care about */ -#define ZT_IPPROTO_ICMP 0x01 -#define ZT_IPPROTO_IGMP 0x02 -#define ZT_IPPROTO_TCP 0x06 -#define ZT_IPPROTO_UDP 0x11 -#define ZT_IPPROTO_GRE 0x2f -#define ZT_IPPROTO_ESP 0x32 -#define ZT_IPPROTO_AH 0x33 -#define ZT_IPPROTO_ICMPV6 0x3a -#define ZT_IPPROTO_OSPF 0x59 -#define ZT_IPPROTO_IPIP 0x5e -#define ZT_IPPROTO_IPCOMP 0x6c -#define ZT_IPPROTO_L2TP 0x73 -#define ZT_IPPROTO_SCTP 0x84 -#define ZT_IPPROTO_FC 0x85 -#define ZT_IPPROTO_UDPLITE 0x88 -#define ZT_IPPROTO_HIP 0x8b - -/* IPv4 ICMP types */ -#define ZT_ICMP_ECHO_REPLY 0 -#define ZT_ICMP_DESTINATION_UNREACHABLE 3 -#define ZT_ICMP_SOURCE_QUENCH 4 -#define ZT_ICMP_REDIRECT 5 -#define ZT_ICMP_ALTERNATE_HOST_ADDRESS 6 -#define ZT_ICMP_ECHO_REQUEST 8 -#define ZT_ICMP_ROUTER_ADVERTISEMENT 9 -#define ZT_ICMP_ROUTER_SOLICITATION 10 -#define ZT_ICMP_TIME_EXCEEDED 11 -#define ZT_ICMP_BAD_IP_HEADER 12 -#define ZT_ICMP_TIMESTAMP 13 -#define ZT_ICMP_TIMESTAMP_REPLY 14 -#define ZT_ICMP_INFORMATION_REQUEST 15 -#define ZT_ICMP_INFORMATION_REPLY 16 -#define ZT_ICMP_ADDRESS_MASK_REQUEST 17 -#define ZT_ICMP_ADDRESS_MASK_REPLY 18 -#define ZT_ICMP_TRACEROUTE 30 -#define ZT_ICMP_MOBILE_HOST_REDIRECT 32 -#define ZT_ICMP_MOBILE_REGISTRATION_REQUEST 35 -#define ZT_ICMP_MOBILE_REGISTRATION_REPLY 36 - -/* IPv6 ICMP types */ -#define ZT_ICMP6_DESTINATION_UNREACHABLE 1 -#define ZT_ICMP6_PACKET_TOO_BIG 2 -#define ZT_ICMP6_TIME_EXCEEDED 3 -#define ZT_ICMP6_PARAMETER_PROBLEM 4 -#define ZT_ICMP6_ECHO_REQUEST 128 -#define ZT_ICMP6_ECHO_REPLY 129 -#define ZT_ICMP6_MULTICAST_LISTENER_QUERY 130 -#define ZT_ICMP6_MULTICAST_LISTENER_REPORT 131 -#define ZT_ICMP6_MULTICAST_LISTENER_DONE 132 -#define ZT_ICMP6_ROUTER_SOLICITATION 133 -#define ZT_ICMP6_ROUTER_ADVERTISEMENT 134 -#define ZT_ICMP6_NEIGHBOR_SOLICITATION 135 -#define ZT_ICMP6_NEIGHBOR_ADVERTISEMENT 136 -#define ZT_ICMP6_REDIRECT_MESSAGE 137 -#define ZT_ICMP6_ROUTER_RENUMBERING 138 -#define ZT_ICMP6_NODE_INFORMATION_QUERY 139 -#define ZT_ICMP6_NODE_INFORMATION_RESPONSE 140 -#define ZT_ICMP6_INV_NEIGHBOR_SOLICITATION 141 -#define ZT_ICMP6_INV_NEIGHBOR_ADVERTISEMENT 142 -#define ZT_ICMP6_MLDV2 143 -#define ZT_ICMP6_HOME_AGENT_ADDRESS_DISCOVERY_REQUEST 144 -#define ZT_ICMP6_HOME_AGENT_ADDRESS_DISCOVERY_REPLY 145 -#define ZT_ICMP6_MOBILE_PREFIX_SOLICITATION 146 -#define ZT_ICMP6_MOBILE_PREFIX_ADVERTISEMENT 147 -#define ZT_ICMP6_CERTIFICATION_PATH_SOLICITATION 148 -#define ZT_ICMP6_CERTIFICATION_PATH_ADVERTISEMENT 149 -#define ZT_ICMP6_MULTICAST_ROUTER_ADVERTISEMENT 151 -#define ZT_ICMP6_MULTICAST_ROUTER_SOLICITATION 152 -#define ZT_ICMP6_MULTICAST_ROUTER_TERMINATION 153 -#define ZT_ICMP6_RPL_CONTROL_MESSAGE 155 - -namespace ZeroTier { - -class RuntimeEnvironment; - -/** - * A simple Ethernet frame level filter - * - * This doesn't specify actions, since it's used as a deny filter. The rule - * in ZT1 is "that which is not explicitly prohibited is allowed." (Except for - * ethertypes, which are handled by a whitelist.) - */ -class Filter -{ -public: - /** - * Value returned by etherTypeName, etc. on unknown - * - * These static methods return precisely this, so a pointer equality - * check will work. - */ - static const char *const UNKNOWN_NAME; - - /** - * An empty range as a more idiomatic way of specifying a wildcard match - */ - static const Range ANY; - - /** - * A filter rule - */ - class Rule - { - public: - Rule() - throw() : - _etherType(), - _protocol(), - _port() - { - } - - /** - * Construct a rule from a string-serialized value - * - * @param s String formatted rule, such as returned by toString() - * @throws std::invalid_argument String formatted rule is not valid - */ - Rule(const char *s) - throw(std::invalid_argument); - - /** - * Construct a new rule - * - * @param etype Ethernet type or empty range for ANY - * @param prot Protocol or empty range for ANY (meaning depends on ethertype, e.g. IP protocol numbers) - * @param prt Port or empty range for ANY (only applies to some protocols) - */ - Rule(const Range &etype,const Range &prot,const Range &prt) - throw() : - _etherType(etype), - _protocol(prot), - _port(prt) - { - } - - inline const Range ðerType() const throw() { return _etherType; } - inline const Range &protocol() const throw() { return _protocol; } - inline const Range &port() const throw() { return _port; } - - /** - * Test this rule against a frame - * - * @param etype Type of ethernet frame - * @param data Ethernet frame data - * @param len Length of ethernet frame - * @return True if rule matches - * @throws std::invalid_argument Frame invalid or not parseable - */ - bool operator()(unsigned int etype,const void *data,unsigned int len) const - throw(std::invalid_argument); - - /** - * Serialize rule as string - * - * @return Human readable representation of rule - */ - std::string toString() const; - - inline bool operator==(const Rule &r) const throw() { return ((_etherType == r._etherType)&&(_protocol == r._protocol)&&(_port == r._port)); } - inline bool operator!=(const Rule &r) const throw() { return !(*this == r); } - inline bool operator<(const Rule &r) const - throw() - { - if (_etherType < r._etherType) - return true; - else if (_etherType == r._etherType) { - if (_protocol < r._protocol) - return true; - else if (_protocol == r._protocol) { - if (_port < r._port) - return true; - } - } - return false; - } - inline bool operator>(const Rule &r) const throw() { return (r < *this); } - inline bool operator<=(const Rule &r) const throw() { return !(r < *this); } - inline bool operator>=(const Rule &r) const throw() { return !(*this < r); } - - private: - Range _etherType; - Range _protocol; - Range _port; - }; - - Filter() {} - - /** - * @param s String-serialized filter representation - */ - Filter(const char *s) - throw(std::invalid_argument); - - /** - * @return Comma-delimited list of string-format rules - */ - std::string toString() const; - - /** - * Add a rule to this filter - * - * @param r Rule to add to filter - */ - void add(const Rule &r); - - inline bool operator()(unsigned int etype,const void *data,unsigned int len) const - throw(std::invalid_argument) - { - for(std::vector::const_iterator r(_rules.begin());r!=_rules.end();++r) { - if ((*r)(etype,data,len)) - return true; - } - return false; - } - - static const char *etherTypeName(const unsigned int etherType) - throw(); - static const char *ipProtocolName(const unsigned int ipp) - throw(); - static const char *icmpTypeName(const unsigned int icmpType) - throw(); - static const char *icmp6TypeName(const unsigned int icmp6Type) - throw(); - -private: - std::vector _rules; -}; - -} // namespace ZeroTier - -#endif diff --git a/attic/SECURITY.md b/attic/SECURITY.md deleted file mode 100644 index 5ca125e..0000000 --- a/attic/SECURITY.md +++ /dev/null @@ -1,84 +0,0 @@ -ZeroTier Security -====== - -## Summary - - -## Using ZeroTier Securely - -### Overall Recommendations - -*TL;DR: same as anything else: defense in depth defense in depth defense in depth.* - -We encourage our users to treat private ZeroTier networks as being rougly equivalent in security to WPA2-enterprise securied WiFi or on-premise wired Ethernet. (Public networks on the other hand are open by design.) That means they're networks with perimeters, but like all networks the compromise of any participating device or network controller allows an attacker to breach this perimeter. - -**Never trust the network.** Many modern security professionals discourage reliance on network perimeters as major components in any security strategy, and we strongly agree regardless of whether your network is physical or virtual. - -As part of a defense in depth approach **we specifically encourage the use of other secure protocols and authentication systems over ZeroTier networks**. While the use of secure encrypted protocols like SSH and SSL over ZeroTier adds a bit more overhead, it greatly reduces the chance of total compromise. - -Imagine that the per-day probability of a major "0-day" security flaw in ZeroTier and OpenSSH are both roughly 0.001 or one per thousand days. Using both at the same time gives you a cumulative 0-day risk of roughly 0.000001 or one per one million days. - -Those are made-up numbers. In reality these probabilities can't be known ahead of time. History shows that a 0-day could be found in anything tomorrow, next week, or never. But layers of security give you an overall posture that is the product -- more than the sum -- of its parts. That's how defense in depth works. - -### ZeroTier Specifics - -#### Protect Your Identity - -Each ZeroTier device has an identity. The secret portion of this identity is stored in a file called "identity.secret." *Protect this file.* If it's stolen your device's identity (as represented by its 10-digit ZeroTier address) can easily be stolen or impersonated and your traffic can be decrypted or man-in-the-middle'd. - -#### Protect Your Controller - -The second major component of ZeroTier network security is the network controller. It's responsible for issuing certificates and configuration information to all network members. That makes it a certificate authority. Compromise of the controller allows an attacker to join or disrupt any network the controller controls. It does *not*, however, allow an attacker to decrypt peer to peer unicast traffic. - -If you are using our controller-as-a-service at [my.zerotier.com](https://my.zerotier.com), you are delegating this responsibility to us. - -## Security Priorities - -These are our security "must-haves." If the system fails in any of these objectives it is broken. - -* ZeroTier must be secure against remote vulnerabilities. This includes things like unauthorized remote control, remote penetration of the device using ZeroTier as a vector, or remote injection of malware. - -* The content (but not meta-data) of communication must be secure against eavesdropping on the wire by any known means. (We can't warrant against secret vulnerabilities against ciphers, etc., or anything else we don't know about.) - -* Communication must be secure against man-in-the-middle attacks and remote device impersonation. - -## Security Non-Priorities - -There are a few aspects of security we knowingly do not address, since doing so would be beyond scope or would conflict too greatly with other priorities. - -* ZeroTier makes no effort to conceal communication meta-data such as source and destination addresses and the amount of information transferred between peers. To do this more or less requires onion routing or other "heavy" approaches to anonymity, and this is beyond scope. - -* ZeroTier does not implement complex certificate chains, X.509, or other feature-rich (some would say feature-laden) cryptographic stuff. We only implement the crypto we need to get the job done. - -* We don't take extraordinary measures to preserve security under conditions in which an endpoint device has been penetrated by other means (e.g. "rooted" by third party malware) or physicall compromised. If someone steals your keys they've stolen your keys, and if they've "pwned" your device they can easily eavesdrop on everything directly. - -## Insecurities and Areas for Improvement - -The only perfectly secure system is one that is off. All real world systems have potential security weaknesses. If possible, we like to know what these are and acknowledge their existence. - -In some cases we plan to improve these. In other cases we have deliberately decided to "punt" on them in favor of some other priority (see philosophy). We may or may not revisit this decision in the future. - -* We don't implement forward secrecy / ephemeral keys. A [discussion of this can be found at the closed GitHub issue for this feature](https://github.com/zerotier/ZeroTierOne/issues/204). In short: we've decided to "punt" on this feature because it introduces complexity and state negotiation. One of the design goals of ZeroTier is "reliability convergence" -- the reliability of ZeroTier virtual networks should rapidly converge with that of the underlying physical wire. Any state that must be negotiated prior to communication multiplies the probability of delay or failure due to packet loss. We *may* revisit this decision at a later date. - -## Secure Coding Practices - -The first line of defense employed against remote vulnerabilities and other major security flaws is the use of secure coding practices. These are, in no particular order: - -* All parsing of remote messages is performed via higher level safe bounds-checked data structures and interfaces. See node/Buffer.hpp for one of the core elements of this. - -* C++ exceptions are used to ensure that any unhandled failure or error condition (such as a bounds checking violation) results in the safe and complete termination of message processing. Invalid messages are dropped and ignored. - -* Minimalism is a secure coding practice. There is an exponential relationship between complexity and the probability of bugs, and complex designs are much harder to audit and reason about. - -* Our build scripts try to enable any OS and compiler level security features such as ASLR and "stack canaries" on non-debug builds. - -## Cryptographic Security Practices - -* We use [boring crypto](https://cr.yp.to/talks/2015.10.05/slides-djb-20151005-a4.pdf). A single symmetric algorithm (Salsa20/12), a single asymmetric algorithm (Curve25519 ECDH-256), and a single MAC (Poly1305). The way these algorithms are used is identical to how they're used in the NaCl reference implementation. The protocol supports selection of alternative algorithms but only for "future proofing" in the case that a serious flaw is discovered in any of these. Avoding algorithm bloat and cryptographic state negotiation helps guard against down-grade, "oracle," and other protocol level attacks. - -* Authenticated encryption is employed with authentication being performed prior to any other operations on received messages. See also: [the cryptographic doom principle](https://moxie.org/blog/the-cryptographic-doom-principle/). - -* "Never branch on anything secret" -- deterministic-time comparisons and other operations are used in cryptographic operations. See Utils::secureEq() in node/Utils.hpp. - -* OS-derived crypographic random numbers (/dev/urandom or Windows CryptGenRandom) are further randomized using encryption by a secondary key with a secondary source of entropy to guard against CSPRNG bugs. Such OS-level CSPRNG bugs have been found in the past. See Utils::getSecureRandom() in node/Utils.hpp. - diff --git a/attic/cli/README.md b/attic/cli/README.md new file mode 100644 index 0000000..595df07 --- /dev/null +++ b/attic/cli/README.md @@ -0,0 +1,57 @@ +The new ZeroTier CLI! +==== + +With this update we've expanded upon the previous CLI's functionality, so things should seem pretty familiar. Here are some of the new features we've introduced: + + - Create and administer networks on ZeroTier Central directly from the console. + - Service configurations, allows you to control local/remote instances of ZeroTier One + - Identity generation and management is now part of the same CLI tool + +*** +## Configurations + +Configurations are a way for you to nickname and logically organize the control of ZeroTier services running locally or remotely (this includes ZeroTier Central!). They're merely groupings of service API url's and auth tokens. The CLI's settings data is contained within `.zerotierCliSettings`. + +For instance, you can control your local instance of ZeroTier One via the `@local` config. By default it is represented as follows: + +``` +"local": { + "auth": "7tyqRoFytajf21j2l2t9QPm5", + "type": "one", + "url": "http://127.0.0.1:9993/" +} +``` + +As an example, if you issue the command `zerotier ls` is it implicitly stating `zerotier @local ls`. + +With the same line of thinking, you could create a `@my.zerotier.com` which would allow for something like `zerotier @my.zerotier.com net-create` which talks to our hosted ZeroTier Central to create a new network. + + + +## Command families + +- `cli-` is for configuring the settings data for the CLI itself, such as adding/removing `@thing` configurations, variables, etc. +- `net-` is for operating on a *ZeroTier Central* service such as `https://my.zerotier.com` +- `id-` is for handling ZeroTier identities. + +And those commands with no prefix are there to allow you to operate ZeroTier One instances either local or remote. + +*** +## Useful command examples + +*Add a ZeroTier One configuration:* + + - `zerotier cli-add-zt MyLocalConfigName https://127.0.0.1:9993/ ` + +*Add a ZeroTier Central configuration:* + + - `zerotier cli-add-central MyZTCentralConfigName https://my.zerotier.com/ ` + +*Set a default ZeroTier One instance:* + + - `zerotier cli-set defaultOne MyLocalConfigName` + +*Set a default ZeroTier Central:* + + - `zerotier cli-set defaultCentral MyZTCentralConfigName` + diff --git a/attic/cli/zerotier.cpp b/attic/cli/zerotier.cpp new file mode 100644 index 0000000..e75268d --- /dev/null +++ b/attic/cli/zerotier.cpp @@ -0,0 +1,957 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +// Note: unlike the rest of ZT's code base, this requires C++11 due to +// the JSON library it uses and other things. + +#include +#include +#include +#include + +#include "../node/Constants.hpp" +#include "../node/Identity.hpp" +#include "../version.h" +#include "../osdep/OSUtils.hpp" +#include "../ext/offbase/json/json.hpp" + +#ifdef __WINDOWS__ +#include +#include +#include +#include +#else +#include +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include + +using json = nlohmann::json; +using namespace ZeroTier; + +#define ZT_CLI_FLAG_VERBOSE 'v' +#define ZT_CLI_FLAG_UNSAFE_SSL 'X' + +#define REQ_GET 0 +#define REQ_POST 1 +#define REQ_DEL 2 + +#define OK_STR "[OK ]: " +#define FAIL_STR "[FAIL]: " +#define WARN_STR "[WARN]: " +#define INVALID_ARGS_STR "Invalid args. Usage: " + +struct CLIState +{ + std::string atname; + std::string command; + std::string url; + std::map reqHeaders; + std::vector args; + std::map opts; + json settings; +}; + +namespace { + +static Identity getIdFromArg(char *arg) +{ + Identity id; + if ((strlen(arg) > 32)&&(arg[10] == ':')) { // identity is a literal on the command line + if (id.fromString(arg)) + return id; + } else { // identity is to be read from a file + std::string idser; + if (OSUtils::readFile(arg,idser)) { + if (id.fromString(idser)) + return id; + } + } + return Identity(); +} + +static std::string trimString(const std::string &s) +{ + unsigned long end = (unsigned long)s.length(); + while (end) { + char c = s[end - 1]; + if ((c == ' ')||(c == '\r')||(c == '\n')||(!c)||(c == '\t')) + --end; + else break; + } + unsigned long start = 0; + while (start < end) { + char c = s[start]; + if ((c == ' ')||(c == '\r')||(c == '\n')||(!c)||(c == '\t')) + ++start; + else break; + } + return s.substr(start,end - start); +} + +static inline std::string getSettingsFilePath() +{ +#ifdef __WINDOWS__ +#else + const char *home = getenv("HOME"); + if (!home) + home = "/"; + return (std::string(home) + "/.zerotierCliSettings"); +#endif +} + +static bool saveSettingsBackup(CLIState &state) +{ + std::string sfp(getSettingsFilePath().c_str()); + if(state.settings.find("generateBackupConfig") != state.settings.end() + && state.settings["generateBackupConfig"].get() == "true") { + std::string backup_file = getSettingsFilePath() + ".bak"; + if(!OSUtils::writeFile(sfp.c_str(), state.settings.dump(2))) { + OSUtils::lockDownFile(sfp.c_str(),false); + std::cout << WARN_STR << "unable to write backup config file" << std::endl; + return false; + } + return true; + } + return false; +} + +static bool saveSettings(CLIState &state) +{ + std::string sfp(getSettingsFilePath().c_str()); + if(OSUtils::writeFile(sfp.c_str(), state.settings.dump(2))) { + OSUtils::lockDownFile(sfp.c_str(),false); + std::cout << OK_STR << "changes saved." << std::endl; + return true; + } + std::cout << FAIL_STR << "unable to write to " << sfp << std::endl; + return false; +} + +static void dumpHelp() +{ + std::cout << "ZeroTier Newer-Spiffier CLI " << ZEROTIER_ONE_VERSION_MAJOR << "." << ZEROTIER_ONE_VERSION_MINOR << "." << ZEROTIER_ONE_VERSION_REVISION << std::endl; + std::cout << "(c)2016 ZeroTier, Inc. / Licensed under the GNU GPL v3" << std::endl; + std::cout << std::endl; + std::cout << "Configuration path: " << getSettingsFilePath() << std::endl; + std::cout << std::endl; + std::cout << "Usage: zerotier [-option] [@name] []" << std::endl; + std::cout << std::endl; + std::cout << "Options:" << std::endl; + std::cout << " -verbose - Verbose JSON output" << std::endl; + std::cout << " -X - Do not check SSL certs (CAUTION!)" << std::endl; + std::cout << std::endl; + std::cout << "CLI Configuration Commands:" << std::endl; + std::cout << " cli-set - Set a CLI option ('cli-set help')" << std::endl; + std::cout << " cli-unset - Un-sets a CLI option ('cli-unset help')" << std::endl; + std::cout << " cli-ls - List configured @things" << std::endl; + std::cout << " cli-rm @name - Remove a configured @thing" << std::endl; + std::cout << " cli-add-zt @name - Add a ZeroTier service" << std::endl; + std::cout << " cli-add-central @name - Add ZeroTier Central instance" << std::endl; + std::cout << std::endl; + std::cout << "ZeroTier One Service Commands:" << std::endl; + std::cout << " -v / -version - Displays default local instance's version'" << std::endl; + std::cout << " ls - List currently joined networks" << std::endl; + std::cout << " join [opt=value ...] - Join a network" << std::endl; + std::cout << " leave - Leave a network" << std::endl; + std::cout << " peers - List ZeroTier VL1 peers" << std::endl; + std::cout << " show [] - Get info about self or object" << std::endl; + std::cout << std::endl; + std::cout << "Network Controller Commands:" << std::endl; + std::cout << " net-create - Create a new network" << std::endl; + std::cout << " net-rm - Delete a network (CAUTION!)" << std::endl; + std::cout << " net-ls - List administered networks" << std::endl; + std::cout << " net-members - List members of a network" << std::endl; + std::cout << " net-show [
] - Get network or member info" << std::endl; + std::cout << " net-auth
- Authorize a member" << std::endl; + std::cout << " net-unauth
- De-authorize a member" << std::endl; + std::cout << " net-set - See 'net-set help'" << std::endl; + std::cout << std::endl; + std::cout << "Identity Commands:" << std::endl; + std::cout << " id-generate [] - Generate a ZeroTier identity" << std::endl; + std::cout << " id-validate - Locally validate an identity" << std::endl; + std::cout << " id-sign - Sign a file" << std::endl; + std::cout << " id-verify - Verify a file's signature" << std::endl; + std::cout << " id-getpublic - Get full identity's public portion" << std::endl; + std::cout << std::endl; +} + +static size_t _curlStringAppendCallback(void *contents,size_t size,size_t nmemb,void *stdstring) +{ + size_t totalSize = size * nmemb; + reinterpret_cast(stdstring)->append((const char *)contents,totalSize); + return totalSize; +} + +static std::tuple REQUEST(int requestType, CLIState &state, const std::map &headers, const std::string &postfield, const std::string &url) +{ + std::string body; + char errbuf[CURL_ERROR_SIZE]; + char urlbuf[4096]; + + CURL *curl; + curl = curl_easy_init(); + if (!curl) { + std::cerr << "FATAL: curl_easy_init() failed" << std::endl; + exit(-1); + } + + Utils::scopy(urlbuf,sizeof(urlbuf),url.c_str()); + curl_easy_setopt(curl,CURLOPT_URL,urlbuf); + + struct curl_slist *hdrs = (struct curl_slist *)0; + for(std::map::const_iterator i(headers.begin());i!=headers.end();++i) { + std::string htmp(i->first); + htmp.append(": "); + htmp.append(i->second); + hdrs = curl_slist_append(hdrs,htmp.c_str()); + } + if (hdrs) + curl_easy_setopt(curl,CURLOPT_HTTPHEADER,hdrs); + + //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); + curl_easy_setopt(curl,CURLOPT_WRITEDATA,(void *)&body); + curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,_curlStringAppendCallback); + + if(std::find(state.args.begin(), state.args.end(), "-X") == state.args.end()) + curl_easy_setopt(curl,CURLOPT_SSL_VERIFYPEER,(state.opts.count(ZT_CLI_FLAG_UNSAFE_SSL) > 0) ? 0L : 1L); + + if(requestType == REQ_POST) { + curl_easy_setopt(curl, CURLOPT_POST, 1); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postfield.c_str()); + } + if(requestType == REQ_DEL) + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); + if(requestType == REQ_GET) { + curl_easy_setopt(curl,CURLOPT_ERRORBUFFER,errbuf); + curl_easy_setopt(curl,CURLOPT_FOLLOWLOCATION,0L); + } + + curl_easy_setopt(curl,CURLOPT_USERAGENT,"ZeroTier-CLI"); + CURLcode res = curl_easy_perform(curl); + + errbuf[CURL_ERROR_SIZE-1] = (char)0; // sanity check + + if (res != CURLE_OK) + return std::make_tuple(-1,std::string(errbuf)); + + long response_code; + int rc = (int)curl_easy_getinfo(curl,CURLINFO_RESPONSE_CODE, &response_code); + + if(response_code == 401) { std::cout << FAIL_STR << response_code << "Unauthorized." << std::endl; exit(0); } + else if(response_code == 403) { std::cout << FAIL_STR << response_code << "Forbidden." << std::endl; exit(0); } + else if(response_code == 404) { std::cout << FAIL_STR << response_code << "Not found." << std::endl; exit(0); } + else if(response_code == 408) { std::cout << FAIL_STR << response_code << "Request timed out." << std::endl; exit(0); } + else if(response_code != 200) { std::cout << FAIL_STR << response_code << "Unable to process request." << std::endl; exit(0); } + + curl_easy_cleanup(curl); + if (hdrs) + curl_slist_free_all(hdrs); + return std::make_tuple(response_code,body); +} + +} // anonymous namespace + +////////////////////////////////////////////////////////////////////////////// + +// Check for user-specified @thing config +// Make sure it @thing makes sense +// Apply appropriate request headers +static void checkForThing(CLIState &state, std::string thingType, bool warnNoThingProvided) +{ + std::string configName; + if(state.atname.length()) { + configName = state.atname.erase(0,1); + // make sure specified @thing makes sense in the context of the command + if(thingType == "one" && state.settings["things"][configName]["type"].get() != "one") { + std::cout << FAIL_STR << "A ZeroTier Central config was specified for a ZeroTier One command." << std::endl; + exit(0); + } + if(thingType == "central" && state.settings["things"][configName]["type"].get() != "central") { + std::cout << FAIL_STR << "A ZeroTier One config was specified for a ZeroTier Central command." << std::endl; + exit(0); + } + } + else { // no @thing specified, check for defaults depending on type + if(thingType == "one") { + if(state.settings.find("defaultOne") != state.settings.end()) { + if(warnNoThingProvided) + std::cout << WARN_STR << "No @thing specified, assuming default for ZeroTier One command: " << state.settings["defaultOne"].get().c_str() << std::endl; + configName = state.settings["defaultOne"].get().erase(0,1); // get default + } + else { + std::cout << WARN_STR << "No @thing specified, and no default is known." << std::endl; + std::cout << "HELP: To set a default: zerotier cli-set defaultOne @my_default_thing" << std::endl; + exit(0); + } + } + if(thingType == "central") { + if(state.settings.find("defaultCentral") != state.settings.end()) { + if(warnNoThingProvided) + std::cout << WARN_STR << "No @thing specified, assuming default for ZeroTier Central command: " << state.settings["defaultCentral"].get().c_str() << std::endl; + configName = state.settings["defaultCentral"].get().erase(0,1); // get default + } + else { + std::cout << WARN_STR << "No @thing specified, and no default is known." << std::endl; + std::cout << "HELP: To set a default: zerotier cli-set defaultCentral @my_default_thing" << std::endl; + exit(0); + } + } + } + // Apply headers + if(thingType == "one") { + state.reqHeaders["X-ZT1-Auth"] = state.settings["things"][configName]["auth"]; + } + if(thingType == "central"){ + state.reqHeaders["Content-Type"] = "application/json"; + state.reqHeaders["Authorization"] = "Bearer " + state.settings["things"][configName]["auth"].get(); + state.reqHeaders["Accept"] = "application/json"; + } + state.url = state.settings["things"][configName]["url"]; +} + +static bool checkURL(std::string url) +{ + // TODO + return true; +} + +static std::string getLocalVersion(CLIState &state) +{ + json result; + std::tuple res; + checkForThing(state,"one",false); + res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "/status"); + if(std::get<0>(res) == 200) { + result = json::parse(std::get<1>(res)); + return result["version"].get(); + } + return "---"; +} + +#ifdef __WINDOWS__ +int _tmain(int argc, _TCHAR* argv[]) +#else +int main(int argc,char **argv) +#endif +{ +#ifdef __WINDOWS__ + { + WSADATA wsaData; + WSAStartup(MAKEWORD(2,2),&wsaData); + } +#endif + + curl_global_init(CURL_GLOBAL_DEFAULT); + CLIState state; + std::string arg1, arg2, authToken; + + for(int i=1;i 0)&&(port < 65536))&&(authToken.length() > 0)) { + state.settings["things"]["local"]["url"] = (std::string("http://127.0.0.1:") + portStr + "/"); + state.settings["things"]["local"]["auth"] = authToken; + initSuccess = true; + } + } + + if (!saveSettings(state)) { + std::cerr << "FATAL: unable to write " << getSettingsFilePath() << std::endl; + exit(-1); + } + + if (initSuccess) { + std::cerr << "INFO: initialized new config at " << getSettingsFilePath() << std::endl; + } else { + std::cerr << "INFO: initialized new config at " << getSettingsFilePath() << " but could not auto-init local ZeroTier One service config from " << oneHome << " -- you will need to set local service URL and port manually if you want to control a local instance of ZeroTier One. (This happens if you are not root/administrator.)" << std::endl; + } + } + } + + // PRE-REQUEST SETUP + + json result; + std::tuple res; + std::string url = ""; + + // META + + if ((state.command.length() == 0)||(state.command == "help")) { + dumpHelp(); + return -1; + } + + // zerotier version + else if (state.command == "v" || state.command == "version") { + std::cout << getLocalVersion(state) << std::endl; + return 1; + } + + // zerotier cli-set + else if (state.command == "cli-set") { + if(argc != 4) { + std::cerr << INVALID_ARGS_STR << "zerotier cli-set " << std::endl; + return 1; + } + std::string settingName, settingValue; + if(state.atname.length()) { // User provided @thing erroneously, we will ignore it and adjust argument indices + settingName = argv[3]; + settingValue = argv[4]; + } + else { + settingName = argv[2]; + settingValue = argv[3]; + } + saveSettingsBackup(state); + state.settings[settingName] = settingValue; // changes + saveSettings(state); + } + + // zerotier cli-unset + else if (state.command == "cli-unset") { + if(argc != 3) { + std::cerr << INVALID_ARGS_STR << "zerotier cli-unset " << std::endl; + return 1; + } + std::string settingName; + if(state.atname.length()) // User provided @thing erroneously, we will ignore it and adjust argument indices + settingName = argv[3]; + else + settingName = argv[2]; + saveSettingsBackup(state); + state.settings.erase(settingName); // changes + saveSettings(state); + } + + // zerotier @thing_to_remove cli-rm --- removes the configuration + else if (state.command == "cli-rm") { + if(argc != 3) { + std::cerr << INVALID_ARGS_STR << "zerotier cli-rm <@thing>" << std::endl; + return 1; + } + if(state.settings["things"].find(state.atname) != state.settings["things"].end()) { + if(state.settings["defaultOne"] == state.atname) { + std::cout << "WARNING: The config you're trying to remove is currently set as your default. Set a new default first!" << std::endl; + std::cout << " | Usage: zerotier set defaultOne @your_other_thing" << std::endl; + } + else { + state.settings["things"].erase(state.atname.c_str()); + saveSettings(state); + } + } + } + + // zerotier cli-add-zt + // TODO: Check for malformed urls/auth + else if (state.command == "cli-add-zt") { + if(argc != 5) { + std::cerr << INVALID_ARGS_STR << "zerotier cli-add-zt " << std::endl; + return 1; + } + std::string thing_name = argv[2], url = argv[3], auth = argv[4]; + if(!checkURL(url)) { + std::cout << FAIL_STR << "Malformed URL" << std::endl; + return 1; + } + if(state.settings.find(thing_name) != state.settings.end()) { + std::cout << "WARNING: A @thing with the shortname " << thing_name.c_str() + << " already exists. Choose another name or rename the old @thing" << std::endl; + std::cout << " | Usage: To rename a @thing: zerotier cli-rename @old_thing_name @new_thing_name" << std::endl; + } + else { + result = json::parse("{ \"auth\": \"" + auth + "\", \"type\": \"" + "one" + "\", \"url\": \"" + url + "\" }"); + saveSettingsBackup(state); + // TODO: Handle cases where user may or may not prepend an @ + state.settings["things"][thing_name] = result; // changes + saveSettings(state); + } + } + + // zerotier cli-add-central + // TODO: Check for malformed urls/auth + else if (state.command == "cli-add-central") { + if(argc != 5) { + std::cerr << INVALID_ARGS_STR << "zerotier cli-add-central " << std::endl; + return 1; + } + std::string thing_name = argv[2], url = argv[3], auth = argv[4]; + if(!checkURL(url)) { + std::cout << FAIL_STR << "Malformed URL" << std::endl; + return 1; + } + if(state.settings.find(thing_name) != state.settings.end()) { + std::cout << "WARNING: A @thing with the shortname " << thing_name.c_str() + << " already exists. Choose another name or rename the old @thing" << std::endl; + std::cout << " | Usage: To rename a @thing: zerotier cli-rename @old_thing_name @new_thing_name" << std::endl; + } + else { + result = json::parse("{ \"auth\": \"" + auth + "\", \"type\": \"" + "central" + "\", \"url\": \"" + url + "\" }"); + saveSettingsBackup(state); + // TODO: Handle cases where user may or may not prepend an @ + state.settings["things"]["@" + thing_name] = result; // changes + saveSettings(state); + } + } + + // ONE SERVICE + + // zerotier ls --- display all networks currently joined + else if (state.command == "ls" || state.command == "listnetworks") { + if(argc != 2) { + std::cerr << INVALID_ARGS_STR << "zerotier ls" << std::endl; + return 1; + } + checkForThing(state,"one",true); + url = state.url + "network"; + res = REQUEST(REQ_GET,state,state.reqHeaders,"",(const std::string)url); + if(std::get<0>(res) == 200) { + std::cout << "listnetworks " << std::endl; + auto j = json::parse(std::get<1>(res).c_str()); + if (j.type() == json::value_t::array) { + for(int i=0;i(); + std::string name = j[i]["name"].get(); + std::string mac = j[i]["mac"].get(); + std::string status = j[i]["status"].get(); + std::string type = j[i]["type"].get(); + std::string addrs; + for(int m=0; m() + " "; + } + std::string dev = j[i]["portDeviceName"].get(); + std::cout << "listnetworks " << nwid << " " << name << " " << mac << " " << status << " " << type << " " << dev << " " << addrs << std::endl; + } + } + } + } + + // zerotier join --- joins a network + else if (state.command == "join") { + if(argc != 3) { + std::cerr << INVALID_ARGS_STR << "zerotier join " << std::endl; + return 1; + } + checkForThing(state,"one",true); + res = REQUEST(REQ_POST,state,state.reqHeaders,"{}",state.url + "/network/" + state.args[0]); + if(std::get<0>(res) == 200) { + std::cout << OK_STR << "connected to " << state.args[0] << std::endl; + } + } + + // zerotier leave --- leaves a network + else if (state.command == "leave") { + if(argc != 3) { + std::cerr << INVALID_ARGS_STR << "zerotier leave " << std::endl; + return 1; + } + checkForThing(state,"one",true); + res = REQUEST(REQ_DEL,state,state.reqHeaders,"{}",state.url + "/network/" + state.args[0]); + if(std::get<0>(res) == 200) { + std::cout << OK_STR << "disconnected from " << state.args[0] << std::endl; + } + } + + // zerotier peers --- display address and role of all peers + else if (state.command == "peers") { + if(argc != 2) { + std::cerr << INVALID_ARGS_STR << "zerotier peers" << std::endl; + return 1; + } + checkForThing(state,"one",true); + res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "/peer"); + if(std::get<0>(res) == 200) { + json result = json::parse(std::get<1>(res)); + for(int i=0; i(res) == 200) { + result = json::parse(std::get<1>(res)); + std::string status_str = result["online"].get() ? "ONLINE" : "OFFLINE"; + std::cout << "info " << result["address"].get() + << " " << status_str << " " << result["version"].get() << std::endl; + } + } + + // REMOTE + + // zerotier @thing net-create --- creates a new network + else if (state.command == "net-create") { + if(argc > 3 || (argc == 3 && !state.atname.length())) { + std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-create" << std::endl; + return 1; + } + checkForThing(state,"central",true); + res = REQUEST(REQ_POST,state,state.reqHeaders,"",state.url + "api/network"); + if(std::get<0>(res) == 200) { + json result = json::parse(std::get<1>(res)); + std::cout << OK_STR << "created network " << result["config"]["nwid"].get() << std::endl; + } + } + + // zerotier @thing net-rm --- deletes a network + else if (state.command == "net-rm") { + if(argc > 4 || (argc == 4 && !state.atname.length())) { + std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-rm " << std::endl; + return 1; + } + checkForThing(state,"central",true); + if(!state.args.size()) { + std::cout << "Argument error: No network specified." << std::endl; + std::cout << " | Usage: zerotier net-rm " << std::endl; + } + else { + std::string nwid = state.args[0]; + res = REQUEST(REQ_DEL,state,state.reqHeaders,"",state.url + "api/network/" + nwid); + if(std::get<0>(res) == 200) { + std::cout << "deleted network " << nwid << std::endl; + } + } + } + + // zerotier @thing net-ls --- lists all networks + else if (state.command == "net-ls") { + if(argc > 3 || (argc == 3 && !state.atname.length())) { + std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-ls" << std::endl; + return 1; + } + checkForThing(state,"central",true); + res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "api/network"); + if(std::get<0>(res) == 200) { + json result = json::parse(std::get<1>(res)); + for(int m=0;m() << std::endl; + } + } + } + + // zerotier @thing net-members --- show all members of a network + else if (state.command == "net-members") { + if(argc > 4 || (argc == 4 && !state.atname.length())) { + std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-members " << std::endl; + return 1; + } + checkForThing(state,"central",true); + if(!state.args.size()) { + std::cout << FAIL_STR << "Argument error: No network specified." << std::endl; + std::cout << " | Usage: zerotier net-members " << std::endl; + } + else { + std::string nwid = state.args[0]; + res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "api/network/" + nwid + "/member"); + json result = json::parse(std::get<1>(res)); + std::cout << "Members of " << nwid << ":" << std::endl; + for (json::iterator it = result.begin(); it != result.end(); ++it) { + std::cout << it.key() << std::endl; + } + } + } + + // zerotier @thing net-show --- show info about a device on a specific network + else if (state.command == "net-show") { + if(argc > 5 || (argc == 5 && !state.atname.length())) { + std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-show " << std::endl; + return 1; + } + checkForThing(state,"central",true); + if(state.args.size() < 2) { + std::cout << FAIL_STR << "Argument error: Too few arguments." << std::endl; + std::cout << " | Usage: zerotier net-show " << std::endl; + } + else { + std::string nwid = state.args[0]; + std::string devid = state.args[1]; + res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "api/network/" + nwid + "/member/" + devid); + // TODO: More info, what would we like to show exactly? + if(std::get<0>(res) == 200) { + json result = json::parse(std::get<1>(res)); + std::cout << "Assigned IP: " << std::endl; + for(int m=0; m() << std::endl; + } + } + } + } + + // zerotier @thing net-auth --- authorize a device on a network + else if (state.command == "net-auth") { + if(argc > 5 || (argc == 5 && !state.atname.length())) { + std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-auth " << std::endl; + return 1; + } + checkForThing(state,"central",true); + if(state.args.size() != 2) { + std::cout << FAIL_STR << "Argument error: Network and/or device ID not specified." << std::endl; + std::cout << " | Usage: zerotier net-auth " << std::endl; + } + std::string nwid = state.args[0]; + std::string devid = state.args[1]; + url = state.url + "api/network/" + nwid + "/member/" + devid; + // Add device to network + res = REQUEST(REQ_POST,state,state.reqHeaders,"",(const std::string)url); + if(std::get<0>(res) == 200) { + result = json::parse(std::get<1>(res)); + res = REQUEST(REQ_GET,state,state.reqHeaders,"",(const std::string)url); + result = json::parse(std::get<1>(res)); + result["config"]["authorized"] = "true"; + std::string newconfig = result.dump(); + res = REQUEST(REQ_POST,state,state.reqHeaders,newconfig,(const std::string)url); + if(std::get<0>(res) == 200) + std::cout << OK_STR << devid << " authorized on " << nwid << std::endl; + else + std::cout << FAIL_STR << "There was a problem authorizing that device." << std::endl; + } + } + + // zerotier @thing net-unauth + else if (state.command == "net-unauth") { + if(argc > 5 || (argc == 5 && !state.atname.length())) { + std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-unauth " << std::endl; + return 1; + } + checkForThing(state,"central",true); + if(state.args.size() != 2) { + std::cout << FAIL_STR << "Bad argument. No network and/or device ID specified." << std::endl; + std::cout << " | Usage: zerotier net-unauth " << std::endl; + } + std::string nwid = state.args[0]; + std::string devid = state.args[1]; + // If successful, get member config + res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "api/network/" + nwid + "/member/" + devid); + result = json::parse(std::get<1>(res)); + // modify auth field and re-POST + result["config"]["authorized"] = "false"; + std::string newconfig = result.dump(); + res = REQUEST(REQ_POST,state,state.reqHeaders,newconfig,state.url + "api/network/" + nwid + "/member/" + devid); + if(std::get<0>(res) == 200) + std::cout << OK_STR << devid << " de-authorized from " << nwid << std::endl; + else + std::cout << FAIL_STR << "There was a problem de-authorizing that device." << std::endl; + } + + // zerotier @thing net-set + else if (state.command == "net-set") { + } + + // ID + + // zerotier id-generate [] + else if (state.command == "id-generate") { + if(argc != 3) { + std::cerr << INVALID_ARGS_STR << "zerotier id-generate []" << std::endl; + return 1; + } + uint64_t vanity = 0; + int vanityBits = 0; + if (argc >= 5) { + vanity = Utils::hexStrToU64(argv[4]) & 0xffffffffffULL; + vanityBits = 4 * strlen(argv[4]); + if (vanityBits > 40) + vanityBits = 40; + } + + ZeroTier::Identity id; + for(;;) { + id.generate(); + if ((id.address().toInt() >> (40 - vanityBits)) == vanity) { + if (vanityBits > 0) { + fprintf(stderr,"vanity address: found %.10llx !\n",(unsigned long long)id.address().toInt()); + } + break; + } else { + fprintf(stderr,"vanity address: tried %.10llx looking for first %d bits of %.10llx\n",(unsigned long long)id.address().toInt(),vanityBits,(unsigned long long)(vanity << (40 - vanityBits))); + } + } + + std::string idser = id.toString(true); + if (argc >= 3) { + if (!OSUtils::writeFile(argv[2],idser)) { + std::cerr << "Error writing to " << argv[2] << std::endl; + return 1; + } else std::cout << argv[2] << " written" << std::endl; + if (argc >= 4) { + idser = id.toString(false); + if (!OSUtils::writeFile(argv[3],idser)) { + std::cerr << "Error writing to " << argv[3] << std::endl; + return 1; + } else std::cout << argv[3] << " written" << std::endl; + } + } else std::cout << idser << std::endl; + } + + // zerotier id-validate + else if (state.command == "id-validate") { + if(argc != 3) { + std::cerr << INVALID_ARGS_STR << "zerotier id-validate " << std::endl; + return 1; + } + Identity id = getIdFromArg(argv[2]); + if (!id) { + std::cerr << "Identity argument invalid or file unreadable: " << argv[2] << std::endl; + return 1; + } + if (!id.locallyValidate()) { + std::cerr << argv[2] << " FAILED validation." << std::endl; + return 1; + } else std::cout << argv[2] << "is a valid identity" << std::endl; + } + + // zerotier id-sign + else if (state.command == "id-sign") { + if(argc != 4) { + std::cerr << INVALID_ARGS_STR << "zerotier id-sign " << std::endl; + return 1; + } + Identity id = getIdFromArg(argv[2]); + if (!id) { + std::cerr << "Identity argument invalid or file unreadable: " << argv[2] << std::endl; + return 1; + } + if (!id.hasPrivate()) { + std::cerr << argv[2] << " does not contain a private key (must use private to sign)" << std::endl; + return 1; + } + std::string inf; + if (!OSUtils::readFile(argv[3],inf)) { + std::cerr << argv[3] << " is not readable" << std::endl; + return 1; + } + C25519::Signature signature = id.sign(inf.data(),(unsigned int)inf.length()); + std::cout << Utils::hex(signature.data,(unsigned int)signature.size()) << std::endl; + } + + // zerotier id-verify + else if (state.command == "id-verify") { + if(argc != 4) { + std::cerr << INVALID_ARGS_STR << "zerotier id-verify " << std::endl; + return 1; + } + Identity id = getIdFromArg(argv[2]); + if (!id) { + std::cerr << "Identity argument invalid or file unreadable: " << argv[2] << std::endl; + return 1; + } + std::string inf; + if (!OSUtils::readFile(argv[3],inf)) { + std::cerr << argv[3] << " is not readable" << std::endl; + return 1; + } + std::string signature(Utils::unhex(argv[4])); + if ((signature.length() > ZT_ADDRESS_LENGTH)&&(id.verify(inf.data(),(unsigned int)inf.length(),signature.data(),(unsigned int)signature.length()))) { + std::cout << argv[3] << " signature valid" << std::endl; + } else { + std::cerr << argv[3] << " signature check FAILED" << std::endl; + return 1; + } + } + + // zerotier id-getpublic + else if (state.command == "id-getpublic") { + if(argc != 3) { + std::cerr << INVALID_ARGS_STR << "zerotier id-getpublic " << std::endl; + return 1; + } + Identity id = getIdFromArg(argv[2]); + if (!id) { + std::cerr << "Identity argument invalid or file unreadable: " << argv[2] << std::endl; + return 1; + } + std::cerr << id.toString(false) << std::endl; + } + // + else { + dumpHelp(); + return -1; + } + if(std::find(state.args.begin(), state.args.end(), "-verbose") != state.args.end()) + std::cout << "\n\nAPI response = " << std::get<1>(res) << std::endl; + curl_global_cleanup(); + return 0; +} diff --git a/linux-build-farm/README.md b/attic/linux-build-farm/README.md similarity index 100% rename from linux-build-farm/README.md rename to attic/linux-build-farm/README.md diff --git a/linux-build-farm/amazon-2016.03/x64/Dockerfile b/attic/linux-build-farm/amazon-2016.03/x64/Dockerfile similarity index 100% rename from linux-build-farm/amazon-2016.03/x64/Dockerfile rename to attic/linux-build-farm/amazon-2016.03/x64/Dockerfile diff --git a/linux-build-farm/build.sh b/attic/linux-build-farm/build.sh similarity index 100% rename from linux-build-farm/build.sh rename to attic/linux-build-farm/build.sh diff --git a/linux-build-farm/centos-6/x64/Dockerfile b/attic/linux-build-farm/centos-6/x64/Dockerfile similarity index 100% rename from linux-build-farm/centos-6/x64/Dockerfile rename to attic/linux-build-farm/centos-6/x64/Dockerfile diff --git a/linux-build-farm/centos-6/x86/Dockerfile b/attic/linux-build-farm/centos-6/x86/Dockerfile similarity index 100% rename from linux-build-farm/centos-6/x86/Dockerfile rename to attic/linux-build-farm/centos-6/x86/Dockerfile diff --git a/linux-build-farm/centos-7/x64/Dockerfile b/attic/linux-build-farm/centos-7/x64/Dockerfile similarity index 100% rename from linux-build-farm/centos-7/x64/Dockerfile rename to attic/linux-build-farm/centos-7/x64/Dockerfile diff --git a/linux-build-farm/centos-7/x86/Dockerfile b/attic/linux-build-farm/centos-7/x86/Dockerfile similarity index 100% rename from linux-build-farm/centos-7/x86/Dockerfile rename to attic/linux-build-farm/centos-7/x86/Dockerfile diff --git a/linux-build-farm/debian-jessie/x64/Dockerfile b/attic/linux-build-farm/debian-jessie/x64/Dockerfile similarity index 100% rename from linux-build-farm/debian-jessie/x64/Dockerfile rename to attic/linux-build-farm/debian-jessie/x64/Dockerfile diff --git a/linux-build-farm/debian-jessie/x86/Dockerfile b/attic/linux-build-farm/debian-jessie/x86/Dockerfile similarity index 100% rename from linux-build-farm/debian-jessie/x86/Dockerfile rename to attic/linux-build-farm/debian-jessie/x86/Dockerfile diff --git a/linux-build-farm/debian-stretch/x64/Dockerfile b/attic/linux-build-farm/debian-stretch/x64/Dockerfile similarity index 100% rename from linux-build-farm/debian-stretch/x64/Dockerfile rename to attic/linux-build-farm/debian-stretch/x64/Dockerfile diff --git a/linux-build-farm/debian-stretch/x86/Dockerfile b/attic/linux-build-farm/debian-stretch/x86/Dockerfile similarity index 100% rename from linux-build-farm/debian-stretch/x86/Dockerfile rename to attic/linux-build-farm/debian-stretch/x86/Dockerfile diff --git a/linux-build-farm/debian-wheezy/x64/Dockerfile b/attic/linux-build-farm/debian-wheezy/x64/Dockerfile similarity index 100% rename from linux-build-farm/debian-wheezy/x64/Dockerfile rename to attic/linux-build-farm/debian-wheezy/x64/Dockerfile diff --git a/linux-build-farm/debian-wheezy/x86/Dockerfile b/attic/linux-build-farm/debian-wheezy/x86/Dockerfile similarity index 100% rename from linux-build-farm/debian-wheezy/x86/Dockerfile rename to attic/linux-build-farm/debian-wheezy/x86/Dockerfile diff --git a/linux-build-farm/fedora-22/x64/Dockerfile b/attic/linux-build-farm/fedora-22/x64/Dockerfile similarity index 100% rename from linux-build-farm/fedora-22/x64/Dockerfile rename to attic/linux-build-farm/fedora-22/x64/Dockerfile diff --git a/linux-build-farm/fedora-22/x86/Dockerfile b/attic/linux-build-farm/fedora-22/x86/Dockerfile similarity index 100% rename from linux-build-farm/fedora-22/x86/Dockerfile rename to attic/linux-build-farm/fedora-22/x86/Dockerfile diff --git a/linux-build-farm/make-apt-repos.sh b/attic/linux-build-farm/make-apt-repos.sh similarity index 100% rename from linux-build-farm/make-apt-repos.sh rename to attic/linux-build-farm/make-apt-repos.sh diff --git a/linux-build-farm/make-rpm-repos.sh b/attic/linux-build-farm/make-rpm-repos.sh similarity index 100% rename from linux-build-farm/make-rpm-repos.sh rename to attic/linux-build-farm/make-rpm-repos.sh diff --git a/linux-build-farm/ubuntu-trusty/x64/Dockerfile b/attic/linux-build-farm/ubuntu-trusty/x64/Dockerfile similarity index 100% rename from linux-build-farm/ubuntu-trusty/x64/Dockerfile rename to attic/linux-build-farm/ubuntu-trusty/x64/Dockerfile diff --git a/linux-build-farm/ubuntu-trusty/x86/Dockerfile b/attic/linux-build-farm/ubuntu-trusty/x86/Dockerfile similarity index 100% rename from linux-build-farm/ubuntu-trusty/x86/Dockerfile rename to attic/linux-build-farm/ubuntu-trusty/x86/Dockerfile diff --git a/linux-build-farm/ubuntu-wily/x64/Dockerfile b/attic/linux-build-farm/ubuntu-wily/x64/Dockerfile similarity index 100% rename from linux-build-farm/ubuntu-wily/x64/Dockerfile rename to attic/linux-build-farm/ubuntu-wily/x64/Dockerfile diff --git a/linux-build-farm/ubuntu-wily/x86/Dockerfile b/attic/linux-build-farm/ubuntu-wily/x86/Dockerfile similarity index 100% rename from linux-build-farm/ubuntu-wily/x86/Dockerfile rename to attic/linux-build-farm/ubuntu-wily/x86/Dockerfile diff --git a/linux-build-farm/ubuntu-xenial/x64/Dockerfile b/attic/linux-build-farm/ubuntu-xenial/x64/Dockerfile similarity index 100% rename from linux-build-farm/ubuntu-xenial/x64/Dockerfile rename to attic/linux-build-farm/ubuntu-xenial/x64/Dockerfile diff --git a/linux-build-farm/ubuntu-xenial/x86/Dockerfile b/attic/linux-build-farm/ubuntu-xenial/x86/Dockerfile similarity index 100% rename from linux-build-farm/ubuntu-xenial/x86/Dockerfile rename to attic/linux-build-farm/ubuntu-xenial/x86/Dockerfile diff --git a/controller/schema.sql b/attic/old-controller-schema.sql similarity index 83% rename from controller/schema.sql rename to attic/old-controller-schema.sql index 105db92..d1daf8d 100644 --- a/controller/schema.sql +++ b/attic/old-controller-schema.sql @@ -86,34 +86,27 @@ CREATE TABLE Route ( CREATE INDEX Route_networkId ON Route (networkId); -CREATE TABLE Relay ( - networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE, - address char(10) NOT NULL, - phyAddress varchar(64) NOT NULL -); - -CREATE UNIQUE INDEX Relay_networkId_address ON Relay (networkId,address); - CREATE TABLE Rule ( networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE, + capId integer, ruleNo integer NOT NULL, - nodeId char(10) REFERENCES Node(id), - sourcePort char(10), - destPort char(10), - vlanId integer, - vlanPcp integer, - etherType integer, - macSource char(12), - macDest char(12), - ipSource varchar(64), - ipDest varchar(64), - ipTos integer, - ipProtocol integer, - ipSourcePort integer, - ipDestPort integer, - flags integer, - invFlags integer, - "action" varchar(4096) NOT NULL DEFAULT('accept') + ruleType integer NOT NULL DEFAULT(0), + "addr" blob(16), + "int1" integer, + "int2" integer, + "int3" integer, + "int4" integer ); -CREATE UNIQUE INDEX Rule_networkId_ruleNo ON Rule (networkId, ruleNo); +CREATE INDEX Rule_networkId_capId ON Rule (networkId,capId); + +CREATE TABLE MemberTC ( + networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE, + nodeId char(10) NOT NULL REFERENCES Node(id) ON DELETE CASCADE, + tagId integer, + tagValue integer, + capId integer, + capMaxCustodyChainLength integer NOT NULL DEFAULT(1) +); + +CREATE INDEX MemberTC_networkId_nodeId ON MemberTC (networkId,nodeId); diff --git a/world/README.md b/attic/world/README.md similarity index 100% rename from world/README.md rename to attic/world/README.md diff --git a/world/build.sh b/attic/world/build.sh similarity index 100% rename from world/build.sh rename to attic/world/build.sh diff --git a/world/earth-2016-01-13.bin b/attic/world/earth-2016-01-13.bin similarity index 100% rename from world/earth-2016-01-13.bin rename to attic/world/earth-2016-01-13.bin diff --git a/world/mkworld.cpp b/attic/world/mkworld.cpp similarity index 93% rename from world/mkworld.cpp rename to attic/world/mkworld.cpp index 061d634..e0f477b 100644 --- a/world/mkworld.cpp +++ b/attic/world/mkworld.cpp @@ -50,25 +50,6 @@ using namespace ZeroTier; -class WorldMaker : public World -{ -public: - static inline World make(uint64_t id,uint64_t ts,const C25519::Public &sk,const std::vector &roots,const C25519::Pair &signWith) - { - WorldMaker w; - w._id = id; - w._ts = ts; - w._updateSigningKey = sk; - w._roots = roots; - - Buffer tmp; - w.serialize(tmp,true); - w._signature = C25519::sign(signWith,tmp.data(),tmp.size()); - - return w; - } -}; - int main(int argc,char **argv) { std::string previous,current; @@ -139,7 +120,7 @@ int main(int argc,char **argv) fprintf(stderr,"INFO: generating and signing id==%llu ts==%llu"ZT_EOL_S,(unsigned long long)id,(unsigned long long)ts); - World nw = WorldMaker::make(id,ts,currentKP.pub,roots,previousKP); + World nw = World::make(World::TYPE_PLANET,id,ts,currentKP.pub,roots,previousKP); Buffer outtmp; nw.serialize(outtmp,false); diff --git a/world/old/earth-2015-11-16.bin b/attic/world/old/earth-2015-11-16.bin similarity index 100% rename from world/old/earth-2015-11-16.bin rename to attic/world/old/earth-2015-11-16.bin diff --git a/world/old/earth-2015-11-20.bin b/attic/world/old/earth-2015-11-20.bin similarity index 100% rename from world/old/earth-2015-11-20.bin rename to attic/world/old/earth-2015-11-20.bin diff --git a/world/old/earth-2015-12-17.bin b/attic/world/old/earth-2015-12-17.bin similarity index 100% rename from world/old/earth-2015-12-17.bin rename to attic/world/old/earth-2015-12-17.bin diff --git a/cli/README.md b/cli/README.md deleted file mode 100644 index dabbd30..0000000 --- a/cli/README.md +++ /dev/null @@ -1,6 +0,0 @@ -ZeroTier Newer-Spiffier Command Line Interface -====== - -This will be the future home of our new unified CLI for ZeroTier One, controllers, and Central (my.zerotier.com etc.). - -IT IS NOT DONE AND DOES NOT WORK EVEN A LITTLE BIT. GO AWAY. diff --git a/cli/zerotier.cpp b/cli/zerotier.cpp deleted file mode 100644 index f9eec5d..0000000 --- a/cli/zerotier.cpp +++ /dev/null @@ -1,335 +0,0 @@ -/* - * ZeroTier One - Network Virtualization Everywhere - * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -// Note: unlike the rest of ZT's code base, this requires C++11 due to -// the JSON library it uses and other things. - -#include -#include -#include -#include - -#include "../node/Constants.hpp" -#include "../version.h" -#include "../osdep/OSUtils.hpp" -#include "../ext/json/json.hpp" - -#ifdef __WINDOWS__ -#include -#include -#include -#include -#else -#include -#include -#endif - -#include -#include -#include -#include -#include - -#include - -using json = nlohmann::json; -using namespace ZeroTier; - -#define ZT_CLI_FLAG_VERBOSE 'v' -#define ZT_CLI_FLAG_UNSAFE_SSL 'X' - -struct CLIState -{ - std::string atname; - std::string command; - std::vector args; - std::map opts; - json settings; -}; - -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 inline std::string getSettingsFilePath() -{ -#ifdef __WINDOWS__ -#else - const char *home = getenv("HOME"); - if (!home) - home = "/"; - return (std::string(home) + "/.zerotierCliSettings"); -#endif -} - -static bool saveSettings(const json &settings) -{ - std::string sfp(getSettingsFilePath().c_str()); - std::string buf(settings.dump(2)); - if (OSUtils::writeFile(sfp.c_str(),buf)) { - OSUtils::lockDownFile(sfp.c_str(),false); - return true; - } - return false; -} - -static void dumpHelp() -{ - std::cout << "ZeroTier Newer-Spiffier CLI " << ZEROTIER_ONE_VERSION_MAJOR << "." << ZEROTIER_ONE_VERSION_MINOR << "." << ZEROTIER_ONE_VERSION_REVISION << std::endl; - std::cout << "(c)2016 ZeroTier, Inc. / Licensed under the GNU GPL v3" << std::endl; - std::cout << std::endl; - std::cout << "Configuration path: " << getSettingsFilePath() << std::endl; - std::cout << std::endl; - std::cout << "Usage: zerotier [-option] [@name] []" << std::endl; - std::cout << std::endl; - std::cout << "Options:" << std::endl; - std::cout << " -v - Verbose JSON output" << std::endl; - std::cout << " -X - Do not check SSL certs (CAUTION!)" << std::endl; - std::cout << std::endl; - std::cout << "CLI Configuration Commands:" << std::endl; - std::cout << " cli-set - Set a CLI option ('cli-set help')" << std::endl; - std::cout << " cli-ls - List configured @things" << std::endl; - std::cout << " cli-rm @name - Remove a configured @thing" << std::endl; - std::cout << " cli-add-zt @name - Add a ZeroTier service" << std::endl; - std::cout << " cli-add-central @name - Add ZeroTier Central instance" << std::endl; - std::cout << std::endl; - std::cout << "ZeroTier One Service Commands:" << std::endl; - std::cout << " ls - List currently joined networks" << std::endl; - std::cout << " join [opt=value ...] - Join a network" << std::endl; - std::cout << " leave - Leave a network" << std::endl; - std::cout << " peers - List ZeroTier VL1 peers" << std::endl; - std::cout << " show [] - Get info about self or object" << std::endl; - std::cout << std::endl; - std::cout << "Network Controller Commands:" << std::endl; - std::cout << " net-create - Create a new network" << std::endl; - std::cout << " net-rm - Delete a network (CAUTION!)" << std::endl; - std::cout << " net-ls - List administered networks" << std::endl; - std::cout << " net-members - List members of a network" << std::endl; - std::cout << " net-show [
] - Get network or member info" << std::endl; - std::cout << " net-auth
- Authorize a member" << std::endl; - std::cout << " net-set - See 'net-set help'" << std::endl; - std::cout << std::endl; - std::cout << "Identity Commands:" << std::endl; - std::cout << " id-generate [] - Generate a ZeroTier identity" << std::endl; - std::cout << " id-validate - Locally validate an identity" << std::endl; - std::cout << " id-sign - Sign a file" << std::endl; - std::cout << " id-verify - Verify a file's signature" << std::endl; - std::cout << " id-getpublic - Get full identity's public portion" << std::endl; - std::cout << std::endl; -} - -static size_t _curlStringAppendCallback(void *contents,size_t size,size_t nmemb,void *stdstring) -{ - size_t totalSize = size * nmemb; - reinterpret_cast(stdstring)->append((const char *)contents,totalSize); - return totalSize; -} - -static std::tuple GET(const CLIState &state,const std::map &headers,const std::string &url) -{ - std::string body; - char errbuf[CURL_ERROR_SIZE]; - char urlbuf[4096]; - - CURL *curl = curl_easy_init(); - if (!curl) { - std::cerr << "FATAL: curl_easy_init() failed" << std::endl; - exit(-1); - } - - curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,_curlStringAppendCallback); - curl_easy_setopt(curl,CURLOPT_WRITEDATA,(void *)&body); - curl_easy_setopt(curl,CURLOPT_USERAGENT,"ZeroTier-CLI"); - curl_easy_setopt(curl,CURLOPT_SSL_VERIFYPEER,(state.opts.count(ZT_CLI_FLAG_UNSAFE_SSL) > 0) ? 0L : 1L); - curl_easy_setopt(curl,CURLOPT_ERRORBUFFER,errbuf); - curl_easy_setopt(curl,CURLOPT_FOLLOWLOCATION,0L); - - Utils::scopy(urlbuf,sizeof(urlbuf),url.c_str()); - curl_easy_setopt(curl,CURLOPT_URL,urlbuf); - - struct curl_slist *hdrs = (struct curl_slist *)0; - for(std::map::const_iterator i(headers.begin());i!=headers.end();++i) { - std::string htmp(i->first); - htmp.append(": "); - htmp.append(i->second); - hdrs = curl_slist_append(hdrs,htmp.c_str()); - } - if (hdrs) - curl_easy_setopt(curl,CURLOPT_HTTPHEADER,hdrs); - - memset(errbuf,0,sizeof(errbuf)); - CURLcode res = curl_easy_perform(curl); - errbuf[CURL_ERROR_SIZE-1] = (char)0; // sanity check - - if (res != CURLE_OK) - return std::make_tuple(-1,std::string(errbuf)); - - int rc = (int)curl_easy_getinfo(curl,CURLINFO_RESPONSE_CODE); - - curl_easy_cleanup(curl); - if (hdrs) - curl_slist_free_all(hdrs); - - return std::make_tuple(rc,body); -} - -} // anonymous namespace - -////////////////////////////////////////////////////////////////////////////// - -#ifdef __WINDOWS__ -int _tmain(int argc, _TCHAR* argv[]) -#else -int main(int argc,char **argv) -#endif -{ -#ifdef __WINDOWS__ - { - WSADATA wsaData; - WSAStartup(MAKEWORD(2,2),&wsaData); - } -#endif - - curl_global_init(CURL_GLOBAL_DEFAULT); - - CLIState state; - - for(int i=1;i 0)&&(port < 65536))&&(authToken.length() > 0)) { - state.settings["things"]["local"]["url"] = (std::string("http://127.0.0.1:") + portStr + "/"); - state.settings["things"]["local"]["auth"] = authToken; - initSuccess = true; - } - } - - if (!saveSettings(state.settings)) { - std::cerr << "FATAL: unable to write " << getSettingsFilePath() << std::endl; - exit(-1); - } - - if (initSuccess) { - std::cerr << "INFO: initialized new config at " << getSettingsFilePath() << std::endl; - } else { - std::cerr << "INFO: initialized new config at " << getSettingsFilePath() << " but could not auto-init local ZeroTier One service config from " << oneHome << " -- you will need to set local service URL and port manually if you want to control a local instance of ZeroTier One. (This happens if you are not root/administrator.)" << std::endl; - } - } - } - - if ((state.command.length() == 0)||(state.command == "help")) { - dumpHelp(); - return -1; - } else if (state.command == "cli-set") { - } else if (state.command == "cli-ls") { - } else if (state.command == "cli-rm") { - } else if (state.command == "cli-add-zt") { - } else if (state.command == "cli-add-central") { - } else if (state.command == "ls") { - } else if (state.command == "join") { - } else if (state.command == "leave") { - } else if (state.command == "peers") { - } else if (state.command == "show") { - } else if (state.command == "net-create") { - } else if (state.command == "net-rm") { - } else if (state.command == "net-ls") { - } else if (state.command == "net-members") { - } else if (state.command == "net-show") { - } else if (state.command == "net-auth") { - } else if (state.command == "net-set") { - } else if (state.command == "id-generate") { - } else if (state.command == "id-validate") { - } else if (state.command == "id-sign") { - } else if (state.command == "id-verify") { - } else if (state.command == "id-getpublic") { - } else { - dumpHelp(); - return -1; - } - - curl_global_cleanup(); - - return 0; -} diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp new file mode 100644 index 0000000..597bc9c --- /dev/null +++ b/controller/EmbeddedNetworkController.cpp @@ -0,0 +1,1854 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#endif +#include + +#include +#include +#include +#include +#include + +#include "../include/ZeroTierOne.h" +#include "../node/Constants.hpp" + +#include "EmbeddedNetworkController.hpp" + +#include "../node/Node.hpp" +#include "../node/Utils.hpp" +#include "../node/CertificateOfMembership.hpp" +#include "../node/NetworkConfig.hpp" +#include "../node/Dictionary.hpp" +#include "../node/InetAddress.hpp" +#include "../node/MAC.hpp" +#include "../node/Address.hpp" + +using json = nlohmann::json; + +// API version reported via JSON control plane +#define ZT_NETCONF_CONTROLLER_API_VERSION 3 + +// Number of requests to remember in member history +#define ZT_NETCONF_DB_MEMBER_HISTORY_LENGTH 2 + +// Min duration between requests for an address/nwid combo to prevent floods +#define ZT_NETCONF_MIN_REQUEST_PERIOD 1000 + +// Nodes are considered active if they've queried in less than this long +#define ZT_NETCONF_NODE_ACTIVE_THRESHOLD (ZT_NETWORK_AUTOCONF_DELAY * 2) + +namespace ZeroTier { + +static json _renderRule(ZT_VirtualNetworkRule &rule) +{ + char tmp[128]; + json r = json::object(); + const ZT_VirtualNetworkRuleType rt = (ZT_VirtualNetworkRuleType)(rule.t & 0x3f); + + switch(rt) { + case ZT_NETWORK_RULE_ACTION_DROP: + r["type"] = "ACTION_DROP"; + break; + case ZT_NETWORK_RULE_ACTION_ACCEPT: + r["type"] = "ACTION_ACCEPT"; + break; + case ZT_NETWORK_RULE_ACTION_TEE: + r["type"] = "ACTION_TEE"; + r["address"] = Address(rule.v.fwd.address).toString(); + r["flags"] = (unsigned int)rule.v.fwd.flags; + r["length"] = (unsigned int)rule.v.fwd.length; + break; + case ZT_NETWORK_RULE_ACTION_WATCH: + r["type"] = "ACTION_WATCH"; + r["address"] = Address(rule.v.fwd.address).toString(); + r["flags"] = (unsigned int)rule.v.fwd.flags; + r["length"] = (unsigned int)rule.v.fwd.length; + break; + case ZT_NETWORK_RULE_ACTION_REDIRECT: + r["type"] = "ACTION_REDIRECT"; + r["address"] = Address(rule.v.fwd.address).toString(); + r["flags"] = (unsigned int)rule.v.fwd.flags; + break; + case ZT_NETWORK_RULE_ACTION_BREAK: + r["type"] = "ACTION_BREAK"; + break; + default: + break; + } + + if (r.size() == 0) { + switch(rt) { + case ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS: + r["type"] = "MATCH_SOURCE_ZEROTIER_ADDRESS"; + r["zt"] = Address(rule.v.zt).toString(); + break; + case ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS: + r["type"] = "MATCH_DEST_ZEROTIER_ADDRESS"; + r["zt"] = Address(rule.v.zt).toString(); + break; + case ZT_NETWORK_RULE_MATCH_VLAN_ID: + r["type"] = "MATCH_VLAN_ID"; + r["vlanId"] = (unsigned int)rule.v.vlanId; + break; + case ZT_NETWORK_RULE_MATCH_VLAN_PCP: + r["type"] = "MATCH_VLAN_PCP"; + r["vlanPcp"] = (unsigned int)rule.v.vlanPcp; + break; + case ZT_NETWORK_RULE_MATCH_VLAN_DEI: + r["type"] = "MATCH_VLAN_DEI"; + r["vlanDei"] = (unsigned int)rule.v.vlanDei; + break; + case ZT_NETWORK_RULE_MATCH_MAC_SOURCE: + r["type"] = "MATCH_MAC_SOURCE"; + Utils::snprintf(tmp,sizeof(tmp),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(unsigned int)rule.v.mac[0],(unsigned int)rule.v.mac[1],(unsigned int)rule.v.mac[2],(unsigned int)rule.v.mac[3],(unsigned int)rule.v.mac[4],(unsigned int)rule.v.mac[5]); + r["mac"] = tmp; + break; + case ZT_NETWORK_RULE_MATCH_MAC_DEST: + r["type"] = "MATCH_MAC_DEST"; + Utils::snprintf(tmp,sizeof(tmp),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(unsigned int)rule.v.mac[0],(unsigned int)rule.v.mac[1],(unsigned int)rule.v.mac[2],(unsigned int)rule.v.mac[3],(unsigned int)rule.v.mac[4],(unsigned int)rule.v.mac[5]); + r["mac"] = tmp; + break; + case ZT_NETWORK_RULE_MATCH_IPV4_SOURCE: + r["type"] = "MATCH_IPV4_SOURCE"; + r["ip"] = InetAddress(&(rule.v.ipv4.ip),4,(unsigned int)rule.v.ipv4.mask).toString(); + break; + case ZT_NETWORK_RULE_MATCH_IPV4_DEST: + r["type"] = "MATCH_IPV4_DEST"; + r["ip"] = InetAddress(&(rule.v.ipv4.ip),4,(unsigned int)rule.v.ipv4.mask).toString(); + break; + case ZT_NETWORK_RULE_MATCH_IPV6_SOURCE: + r["type"] = "MATCH_IPV6_SOURCE"; + r["ip"] = InetAddress(rule.v.ipv6.ip,16,(unsigned int)rule.v.ipv6.mask).toString(); + break; + case ZT_NETWORK_RULE_MATCH_IPV6_DEST: + r["type"] = "MATCH_IPV6_DEST"; + r["ip"] = InetAddress(rule.v.ipv6.ip,16,(unsigned int)rule.v.ipv6.mask).toString(); + break; + case ZT_NETWORK_RULE_MATCH_IP_TOS: + r["type"] = "MATCH_IP_TOS"; + r["mask"] = (unsigned int)rule.v.ipTos.mask; + r["start"] = (unsigned int)rule.v.ipTos.value[0]; + r["end"] = (unsigned int)rule.v.ipTos.value[1]; + break; + case ZT_NETWORK_RULE_MATCH_IP_PROTOCOL: + r["type"] = "MATCH_IP_PROTOCOL"; + r["ipProtocol"] = (unsigned int)rule.v.ipProtocol; + break; + case ZT_NETWORK_RULE_MATCH_ETHERTYPE: + r["type"] = "MATCH_ETHERTYPE"; + r["etherType"] = (unsigned int)rule.v.etherType; + break; + case ZT_NETWORK_RULE_MATCH_ICMP: + r["type"] = "MATCH_ICMP"; + r["icmpType"] = (unsigned int)rule.v.icmp.type; + if ((rule.v.icmp.flags & 0x01) != 0) + r["icmpCode"] = (unsigned int)rule.v.icmp.code; + else r["icmpCode"] = json(); + break; + case ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE: + r["type"] = "MATCH_IP_SOURCE_PORT_RANGE"; + r["start"] = (unsigned int)rule.v.port[0]; + r["end"] = (unsigned int)rule.v.port[1]; + break; + case ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE: + r["type"] = "MATCH_IP_DEST_PORT_RANGE"; + r["start"] = (unsigned int)rule.v.port[0]; + r["end"] = (unsigned int)rule.v.port[1]; + break; + case ZT_NETWORK_RULE_MATCH_CHARACTERISTICS: + r["type"] = "MATCH_CHARACTERISTICS"; + Utils::snprintf(tmp,sizeof(tmp),"%.16llx",rule.v.characteristics); + r["mask"] = tmp; + break; + case ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE: + r["type"] = "MATCH_FRAME_SIZE_RANGE"; + r["start"] = (unsigned int)rule.v.frameSize[0]; + r["end"] = (unsigned int)rule.v.frameSize[1]; + break; + case ZT_NETWORK_RULE_MATCH_RANDOM: + r["type"] = "MATCH_RANDOM"; + r["probability"] = (unsigned long)rule.v.randomProbability; + break; + case ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE: + r["type"] = "MATCH_TAGS_DIFFERENCE"; + r["id"] = rule.v.tag.id; + r["value"] = rule.v.tag.value; + break; + case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND: + r["type"] = "MATCH_TAGS_BITWISE_AND"; + r["id"] = rule.v.tag.id; + r["value"] = rule.v.tag.value; + break; + case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR: + r["type"] = "MATCH_TAGS_BITWISE_OR"; + r["id"] = rule.v.tag.id; + r["value"] = rule.v.tag.value; + break; + case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR: + r["type"] = "MATCH_TAGS_BITWISE_XOR"; + r["id"] = rule.v.tag.id; + r["value"] = rule.v.tag.value; + break; + case ZT_NETWORK_RULE_MATCH_TAGS_EQUAL: + r["type"] = "MATCH_TAGS_EQUAL"; + r["id"] = rule.v.tag.id; + r["value"] = rule.v.tag.value; + break; + case ZT_NETWORK_RULE_MATCH_TAG_SENDER: + r["type"] = "MATCH_TAG_SENDER"; + r["id"] = rule.v.tag.id; + r["value"] = rule.v.tag.value; + break; + case ZT_NETWORK_RULE_MATCH_TAG_RECEIVER: + r["type"] = "MATCH_TAG_RECEIVER"; + r["id"] = rule.v.tag.id; + r["value"] = rule.v.tag.value; + break; + default: + break; + } + + if (r.size() > 0) { + r["not"] = ((rule.t & 0x80) != 0); + r["or"] = ((rule.t & 0x40) != 0); + } + } + + return r; +} + +static bool _parseRule(json &r,ZT_VirtualNetworkRule &rule) +{ + if (!r.is_object()) + return false; + + const std::string t(OSUtils::jsonString(r["type"],"")); + memset(&rule,0,sizeof(ZT_VirtualNetworkRule)); + + if (OSUtils::jsonBool(r["not"],false)) + rule.t = 0x80; + else rule.t = 0x00; + if (OSUtils::jsonBool(r["or"],false)) + rule.t |= 0x40; + + bool tag = false; + if (t == "ACTION_DROP") { + rule.t |= ZT_NETWORK_RULE_ACTION_DROP; + return true; + } else if (t == "ACTION_ACCEPT") { + rule.t |= ZT_NETWORK_RULE_ACTION_ACCEPT; + return true; + } else if (t == "ACTION_TEE") { + rule.t |= ZT_NETWORK_RULE_ACTION_TEE; + rule.v.fwd.address = Utils::hexStrToU64(OSUtils::jsonString(r["address"],"0").c_str()) & 0xffffffffffULL; + rule.v.fwd.flags = (uint32_t)(OSUtils::jsonInt(r["flags"],0ULL) & 0xffffffffULL); + rule.v.fwd.length = (uint16_t)(OSUtils::jsonInt(r["length"],0ULL) & 0xffffULL); + return true; + } else if (t == "ACTION_WATCH") { + rule.t |= ZT_NETWORK_RULE_ACTION_WATCH; + rule.v.fwd.address = Utils::hexStrToU64(OSUtils::jsonString(r["address"],"0").c_str()) & 0xffffffffffULL; + rule.v.fwd.flags = (uint32_t)(OSUtils::jsonInt(r["flags"],0ULL) & 0xffffffffULL); + rule.v.fwd.length = (uint16_t)(OSUtils::jsonInt(r["length"],0ULL) & 0xffffULL); + return true; + } else if (t == "ACTION_REDIRECT") { + rule.t |= ZT_NETWORK_RULE_ACTION_REDIRECT; + rule.v.fwd.address = Utils::hexStrToU64(OSUtils::jsonString(r["address"],"0").c_str()) & 0xffffffffffULL; + rule.v.fwd.flags = (uint32_t)(OSUtils::jsonInt(r["flags"],0ULL) & 0xffffffffULL); + return true; + } else if (t == "ACTION_BREAK") { + rule.t |= ZT_NETWORK_RULE_ACTION_BREAK; + return true; + } else if (t == "MATCH_SOURCE_ZEROTIER_ADDRESS") { + rule.t |= ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS; + rule.v.zt = Utils::hexStrToU64(OSUtils::jsonString(r["zt"],"0").c_str()) & 0xffffffffffULL; + return true; + } else if (t == "MATCH_DEST_ZEROTIER_ADDRESS") { + rule.t |= ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS; + rule.v.zt = Utils::hexStrToU64(OSUtils::jsonString(r["zt"],"0").c_str()) & 0xffffffffffULL; + return true; + } else if (t == "MATCH_VLAN_ID") { + rule.t |= ZT_NETWORK_RULE_MATCH_VLAN_ID; + rule.v.vlanId = (uint16_t)(OSUtils::jsonInt(r["vlanId"],0ULL) & 0xffffULL); + return true; + } else if (t == "MATCH_VLAN_PCP") { + rule.t |= ZT_NETWORK_RULE_MATCH_VLAN_PCP; + rule.v.vlanPcp = (uint8_t)(OSUtils::jsonInt(r["vlanPcp"],0ULL) & 0xffULL); + return true; + } else if (t == "MATCH_VLAN_DEI") { + rule.t |= ZT_NETWORK_RULE_MATCH_VLAN_DEI; + rule.v.vlanDei = (uint8_t)(OSUtils::jsonInt(r["vlanDei"],0ULL) & 0xffULL); + return true; + } else if (t == "MATCH_MAC_SOURCE") { + rule.t |= ZT_NETWORK_RULE_MATCH_MAC_SOURCE; + const std::string mac(OSUtils::jsonString(r["mac"],"0")); + Utils::unhex(mac.c_str(),(unsigned int)mac.length(),rule.v.mac,6); + return true; + } else if (t == "MATCH_MAC_DEST") { + rule.t |= ZT_NETWORK_RULE_MATCH_MAC_DEST; + const std::string mac(OSUtils::jsonString(r["mac"],"0")); + Utils::unhex(mac.c_str(),(unsigned int)mac.length(),rule.v.mac,6); + return true; + } else if (t == "MATCH_IPV4_SOURCE") { + rule.t |= ZT_NETWORK_RULE_MATCH_IPV4_SOURCE; + InetAddress ip(OSUtils::jsonString(r["ip"],"0.0.0.0")); + rule.v.ipv4.ip = reinterpret_cast(&ip)->sin_addr.s_addr; + rule.v.ipv4.mask = Utils::ntoh(reinterpret_cast(&ip)->sin_port) & 0xff; + if (rule.v.ipv4.mask > 32) rule.v.ipv4.mask = 32; + return true; + } else if (t == "MATCH_IPV4_DEST") { + rule.t |= ZT_NETWORK_RULE_MATCH_IPV4_DEST; + InetAddress ip(OSUtils::jsonString(r["ip"],"0.0.0.0")); + rule.v.ipv4.ip = reinterpret_cast(&ip)->sin_addr.s_addr; + rule.v.ipv4.mask = Utils::ntoh(reinterpret_cast(&ip)->sin_port) & 0xff; + if (rule.v.ipv4.mask > 32) rule.v.ipv4.mask = 32; + return true; + } else if (t == "MATCH_IPV6_SOURCE") { + rule.t |= ZT_NETWORK_RULE_MATCH_IPV6_SOURCE; + InetAddress ip(OSUtils::jsonString(r["ip"],"::0")); + memcpy(rule.v.ipv6.ip,reinterpret_cast(&ip)->sin6_addr.s6_addr,16); + rule.v.ipv6.mask = Utils::ntoh(reinterpret_cast(&ip)->sin6_port) & 0xff; + if (rule.v.ipv6.mask > 128) rule.v.ipv6.mask = 128; + return true; + } else if (t == "MATCH_IPV6_DEST") { + rule.t |= ZT_NETWORK_RULE_MATCH_IPV6_DEST; + InetAddress ip(OSUtils::jsonString(r["ip"],"::0")); + memcpy(rule.v.ipv6.ip,reinterpret_cast(&ip)->sin6_addr.s6_addr,16); + rule.v.ipv6.mask = Utils::ntoh(reinterpret_cast(&ip)->sin6_port) & 0xff; + if (rule.v.ipv6.mask > 128) rule.v.ipv6.mask = 128; + return true; + } else if (t == "MATCH_IP_TOS") { + rule.t |= ZT_NETWORK_RULE_MATCH_IP_TOS; + rule.v.ipTos.mask = (uint8_t)(OSUtils::jsonInt(r["mask"],0ULL) & 0xffULL); + rule.v.ipTos.value[0] = (uint8_t)(OSUtils::jsonInt(r["start"],0ULL) & 0xffULL); + rule.v.ipTos.value[1] = (uint8_t)(OSUtils::jsonInt(r["end"],0ULL) & 0xffULL); + return true; + } else if (t == "MATCH_IP_PROTOCOL") { + rule.t |= ZT_NETWORK_RULE_MATCH_IP_PROTOCOL; + rule.v.ipProtocol = (uint8_t)(OSUtils::jsonInt(r["ipProtocol"],0ULL) & 0xffULL); + return true; + } else if (t == "MATCH_ETHERTYPE") { + rule.t |= ZT_NETWORK_RULE_MATCH_ETHERTYPE; + rule.v.etherType = (uint16_t)(OSUtils::jsonInt(r["etherType"],0ULL) & 0xffffULL); + return true; + } else if (t == "MATCH_ICMP") { + rule.t |= ZT_NETWORK_RULE_MATCH_ICMP; + rule.v.icmp.type = (uint8_t)(OSUtils::jsonInt(r["icmpType"],0ULL) & 0xffULL); + json &code = r["icmpCode"]; + if (code.is_null()) { + rule.v.icmp.code = 0; + rule.v.icmp.flags = 0x00; + } else { + rule.v.icmp.code = (uint8_t)(OSUtils::jsonInt(code,0ULL) & 0xffULL); + rule.v.icmp.flags = 0x01; + } + return true; + } else if (t == "MATCH_IP_SOURCE_PORT_RANGE") { + rule.t |= ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE; + rule.v.port[0] = (uint16_t)(OSUtils::jsonInt(r["start"],0ULL) & 0xffffULL); + rule.v.port[1] = (uint16_t)(OSUtils::jsonInt(r["end"],(uint64_t)rule.v.port[0]) & 0xffffULL); + return true; + } else if (t == "MATCH_IP_DEST_PORT_RANGE") { + rule.t |= ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE; + rule.v.port[0] = (uint16_t)(OSUtils::jsonInt(r["start"],0ULL) & 0xffffULL); + rule.v.port[1] = (uint16_t)(OSUtils::jsonInt(r["end"],(uint64_t)rule.v.port[0]) & 0xffffULL); + return true; + } else if (t == "MATCH_CHARACTERISTICS") { + rule.t |= ZT_NETWORK_RULE_MATCH_CHARACTERISTICS; + if (r.count("mask")) { + json &v = r["mask"]; + if (v.is_number()) { + rule.v.characteristics = v; + } else { + std::string tmp = v; + rule.v.characteristics = Utils::hexStrToU64(tmp.c_str()); + } + } + return true; + } else if (t == "MATCH_FRAME_SIZE_RANGE") { + rule.t |= ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE; + rule.v.frameSize[0] = (uint16_t)(OSUtils::jsonInt(r["start"],0ULL) & 0xffffULL); + rule.v.frameSize[1] = (uint16_t)(OSUtils::jsonInt(r["end"],(uint64_t)rule.v.frameSize[0]) & 0xffffULL); + return true; + } else if (t == "MATCH_RANDOM") { + rule.t |= ZT_NETWORK_RULE_MATCH_RANDOM; + rule.v.randomProbability = (uint32_t)(OSUtils::jsonInt(r["probability"],0ULL) & 0xffffffffULL); + return true; + } else if (t == "MATCH_TAGS_DIFFERENCE") { + rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE; + tag = true; + } else if (t == "MATCH_TAGS_BITWISE_AND") { + rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND; + tag = true; + } else if (t == "MATCH_TAGS_BITWISE_OR") { + rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR; + tag = true; + } else if (t == "MATCH_TAGS_BITWISE_XOR") { + rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR; + tag = true; + } else if (t == "MATCH_TAGS_EQUAL") { + rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_EQUAL; + tag = true; + } else if (t == "MATCH_TAG_SENDER") { + rule.t |= ZT_NETWORK_RULE_MATCH_TAG_SENDER; + tag = true; + } else if (t == "MATCH_TAG_RECEIVER") { + rule.t |= ZT_NETWORK_RULE_MATCH_TAG_RECEIVER; + tag = true; + } + if (tag) { + rule.v.tag.id = (uint32_t)(OSUtils::jsonInt(r["id"],0ULL) & 0xffffffffULL); + rule.v.tag.value = (uint32_t)(OSUtils::jsonInt(r["value"],0ULL) & 0xffffffffULL); + return true; + } + + return false; +} + +EmbeddedNetworkController::EmbeddedNetworkController(Node *node,const char *dbPath) : + _startTime(OSUtils::now()), + _threadsStarted(false), + _db(dbPath), + _node(node) +{ +} + +EmbeddedNetworkController::~EmbeddedNetworkController() +{ + Mutex::Lock _l(_threads_m); + if (_threadsStarted) { + for(int i=0;i<(ZT_EMBEDDEDNETWORKCONTROLLER_BACKGROUND_THREAD_COUNT*2);++i) + _queue.post((_RQEntry *)0); + for(int i=0;i_sender = sender; + this->_signingId = signingId; +} + +void EmbeddedNetworkController::request( + uint64_t nwid, + const InetAddress &fromAddr, + uint64_t requestPacketId, + const Identity &identity, + const Dictionary &metaData) +{ + if (((!_signingId)||(!_signingId.hasPrivate()))||(_signingId.address().toInt() != (nwid >> 24))||(!_sender)) + return; + + { + Mutex::Lock _l(_threads_m); + if (!_threadsStarted) { + for(int i=0;inwid = nwid; + qe->requestPacketId = requestPacketId; + qe->fromAddr = fromAddr; + qe->identity = identity; + qe->metaData = metaData; + _queue.post(qe); +} + +unsigned int EmbeddedNetworkController::handleControlPlaneHttpGET( + const std::vector &path, + const std::map &urlArgs, + const std::map &headers, + const std::string &body, + std::string &responseBody, + std::string &responseContentType) +{ + if ((path.size() > 0)&&(path[0] == "network")) { + + if ((path.size() >= 2)&&(path[1].length() == 16)) { + const uint64_t nwid = Utils::hexStrToU64(path[1].c_str()); + char nwids[24]; + Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid); + + json network; + { + Mutex::Lock _l(_db_m); + network = _db.get("network",nwids); + } + if (!network.size()) + return 404; + + if (path.size() >= 3) { + + if (path[2] == "member") { + + if (path.size() >= 4) { + const uint64_t address = Utils::hexStrToU64(path[3].c_str()); + + json member; + { + Mutex::Lock _l(_db_m); + member = _db.get("network",nwids,"member",Address(address).toString()); + } + if (!member.size()) + return 404; + + _addMemberNonPersistedFields(member,OSUtils::now()); + responseBody = OSUtils::jsonDump(member); + responseContentType = "application/json"; + + return 200; + } else { + + Mutex::Lock _l(_db_m); + + responseBody = "{"; + _db.filter((std::string("network/") + nwids + "/member/"),[&responseBody](const std::string &n,const json &member) { + if ((member.is_object())&&(member.size() > 0)) { + responseBody.append((responseBody.length() == 1) ? "\"" : ",\""); + responseBody.append(OSUtils::jsonString(member["id"],"0")); + responseBody.append("\":"); + responseBody.append(OSUtils::jsonString(member["revision"],"0")); + } + return true; // never delete + }); + responseBody.push_back('}'); + responseContentType = "application/json"; + + return 200; + } + + } // else 404 + + } else { + + const uint64_t now = OSUtils::now(); + _NetworkMemberInfo nmi; + _getNetworkMemberInfo(now,nwid,nmi); + _addNetworkNonPersistedFields(network,now,nmi); + responseBody = OSUtils::jsonDump(network); + responseContentType = "application/json"; + return 200; + + } + } else if (path.size() == 1) { + + std::set networkIds; + { + Mutex::Lock _l(_db_m); + _db.filter("network/",[&networkIds](const std::string &n,const json &obj) { + if (n.length() == (16 + 8)) + networkIds.insert(n.substr(8)); + return true; // do not delete + }); + } + + responseBody.push_back('['); + for(std::set::iterator i(networkIds.begin());i!=networkIds.end();++i) { + responseBody.append((responseBody.length() == 1) ? "\"" : ",\""); + responseBody.append(*i); + responseBody.append("\""); + } + responseBody.push_back(']'); + responseContentType = "application/json"; + return 200; + + } // else 404 + + } else { + + char tmp[4096]; + Utils::snprintf(tmp,sizeof(tmp),"{\n\t\"controller\": true,\n\t\"apiVersion\": %d,\n\t\"clock\": %llu\n}\n",ZT_NETCONF_CONTROLLER_API_VERSION,(unsigned long long)OSUtils::now()); + responseBody = tmp; + responseContentType = "application/json"; + return 200; + + } + + return 404; +} + +unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST( + const std::vector &path, + const std::map &urlArgs, + const std::map &headers, + const std::string &body, + std::string &responseBody, + std::string &responseContentType) +{ + if (path.empty()) + return 404; + + json b; + try { + b = OSUtils::jsonParse(body); + if (!b.is_object()) { + responseBody = "{ \"message\": \"body is not a JSON object\" }"; + responseContentType = "application/json"; + return 400; + } + } catch ( ... ) { + responseBody = "{ \"message\": \"body JSON is invalid\" }"; + responseContentType = "application/json"; + return 400; + } + const uint64_t now = OSUtils::now(); + + if (path[0] == "network") { + + if ((path.size() >= 2)&&(path[1].length() == 16)) { + uint64_t nwid = Utils::hexStrToU64(path[1].c_str()); + char nwids[24]; + Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid); + + if (path.size() >= 3) { + + if ((path.size() == 4)&&(path[2] == "member")&&(path[3].length() == 10)) { + uint64_t address = Utils::hexStrToU64(path[3].c_str()); + char addrs[24]; + Utils::snprintf(addrs,sizeof(addrs),"%.10llx",(unsigned long long)address); + + json member; + { + Mutex::Lock _l(_db_m); + member = _db.get("network",nwids,"member",Address(address).toString()); + } + json origMember(member); // for detecting changes + _initMember(member); + + try { + if (b.count("activeBridge")) member["activeBridge"] = OSUtils::jsonBool(b["activeBridge"],false); + if (b.count("noAutoAssignIps")) member["noAutoAssignIps"] = OSUtils::jsonBool(b["noAutoAssignIps"],false); + + if (b.count("authorized")) { + const bool newAuth = OSUtils::jsonBool(b["authorized"],false); + if (newAuth != OSUtils::jsonBool(member["authorized"],false)) { + member["authorized"] = newAuth; + member[((newAuth) ? "lastAuthorizedTime" : "lastDeauthorizedTime")] = now; + + json ah; + ah["a"] = newAuth; + ah["by"] = "api"; + ah["ts"] = now; + ah["ct"] = json(); + ah["c"] = json(); + member["authHistory"].push_back(ah); + + // Member is being de-authorized, so spray Revocation objects to all online members + if (!newAuth) { + _clearNetworkMemberInfoCache(nwid); + Revocation rev((uint32_t)_node->prng(),nwid,0,now,ZT_REVOCATION_FLAG_FAST_PROPAGATE,Address(address),Revocation::CREDENTIAL_TYPE_COM); + rev.sign(_signingId); + Mutex::Lock _l(_lastRequestTime_m); + for(std::map< std::pair,uint64_t >::iterator i(_lastRequestTime.begin());i!=_lastRequestTime.end();++i) { + if ((now - i->second) < ZT_NETWORK_AUTOCONF_DELAY) + _node->ncSendRevocation(Address(i->first.first),rev); + } + } + } + } + + if (b.count("ipAssignments")) { + json &ipa = b["ipAssignments"]; + if (ipa.is_array()) { + json mipa(json::array()); + for(unsigned long i=0;i mtags; + for(unsigned long i=0;i::iterator t(mtags.begin());t!=mtags.end();++t) { + json ta = json::array(); + ta.push_back(t->first); + ta.push_back(t->second); + mtagsa.push_back(ta); + } + member["tags"] = mtagsa; + } + } + + if (b.count("capabilities")) { + json &capabilities = b["capabilities"]; + if (capabilities.is_array()) { + json mcaps = json::array(); + for(unsigned long i=0;itestId),sizeof(test->testId)); + test->credentialNetworkId = nwid; + test->ptr = (void *)this; + json hops = b["hops"]; + if (hops.is_array()) { + for(unsigned long i=0;ihops[test->hopCount].addresses[test->hops[test->hopCount].breadth++] = Utils::hexStrToU64(s.c_str()) & 0xffffffffffULL; + } + ++test->hopCount; + } else if (hops2.is_string()) { + std::string s = hops2; + test->hops[test->hopCount].addresses[test->hops[test->hopCount].breadth++] = Utils::hexStrToU64(s.c_str()) & 0xffffffffffULL; + ++test->hopCount; + } + } + } + test->reportAtEveryHop = (OSUtils::jsonBool(b["reportAtEveryHop"],true) ? 1 : 0); + + if (!test->hopCount) { + _tests.pop_back(); + responseBody = "{ \"message\": \"a test must contain at least one hop\" }"; + responseContentType = "application/json"; + return 400; + } + + test->timestamp = OSUtils::now(); + + if (_node) { + _node->circuitTestBegin((void *)0,test,&(EmbeddedNetworkController::_circuitTestCallback)); + } else { + _tests.pop_back(); + return 500; + } + + char json[512]; + Utils::snprintf(json,sizeof(json),"{\"testId\":\"%.16llx\",\"timestamp\":%llu}",test->testId,test->timestamp); + responseBody = json; + responseContentType = "application/json"; + + return 200; + + } // else 404 + + } else { + // POST to network ID + + json network; + { + Mutex::Lock _l(_db_m); + + // Magic ID ending with ______ picks a random unused network ID + if (path[1].substr(10) == "______") { + nwid = 0; + uint64_t nwidPrefix = (Utils::hexStrToU64(path[1].substr(0,10).c_str()) << 24) & 0xffffffffff000000ULL; + uint64_t nwidPostfix = 0; + for(unsigned long k=0;k<100000;++k) { // sanity limit on trials + Utils::getSecureRandom(&nwidPostfix,sizeof(nwidPostfix)); + uint64_t tryNwid = nwidPrefix | (nwidPostfix & 0xffffffULL); + if ((tryNwid & 0xffffffULL) == 0ULL) tryNwid |= 1ULL; + Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)tryNwid); + if (_db.get("network",nwids).size() <= 0) { + nwid = tryNwid; + break; + } + } + if (!nwid) + return 503; + } + + network = _db.get("network",nwids); + } + json origNetwork(network); // for detecting changes + _initNetwork(network); + + try { + if (b.count("name")) network["name"] = OSUtils::jsonString(b["name"],""); + if (b.count("private")) network["private"] = OSUtils::jsonBool(b["private"],true); + if (b.count("enableBroadcast")) network["enableBroadcast"] = OSUtils::jsonBool(b["enableBroadcast"],false); + if (b.count("allowPassiveBridging")) network["allowPassiveBridging"] = OSUtils::jsonBool(b["allowPassiveBridging"],false); + if (b.count("multicastLimit")) network["multicastLimit"] = OSUtils::jsonInt(b["multicastLimit"],32ULL); + + if (b.count("v4AssignMode")) { + json nv4m; + json &v4m = b["v4AssignMode"]; + if (v4m.is_string()) { // backward compatibility + nv4m["zt"] = (OSUtils::jsonString(v4m,"") == "zt"); + } else if (v4m.is_object()) { + nv4m["zt"] = OSUtils::jsonBool(v4m["zt"],false); + } else nv4m["zt"] = false; + network["v4AssignMode"] = nv4m; + } + + if (b.count("v6AssignMode")) { + json nv6m; + json &v6m = b["v6AssignMode"]; + if (!nv6m.is_object()) nv6m = json::object(); + if (v6m.is_string()) { // backward compatibility + std::vector v6ms(OSUtils::split(OSUtils::jsonString(v6m,"").c_str(),",","","")); + std::sort(v6ms.begin(),v6ms.end()); + v6ms.erase(std::unique(v6ms.begin(),v6ms.end()),v6ms.end()); + nv6m["rfc4193"] = false; + nv6m["zt"] = false; + nv6m["6plane"] = false; + for(std::vector::iterator i(v6ms.begin());i!=v6ms.end();++i) { + if (*i == "rfc4193") + nv6m["rfc4193"] = true; + else if (*i == "zt") + nv6m["zt"] = true; + else if (*i == "6plane") + nv6m["6plane"] = true; + } + } else if (v6m.is_object()) { + if (v6m.count("rfc4193")) nv6m["rfc4193"] = OSUtils::jsonBool(v6m["rfc4193"],false); + if (v6m.count("zt")) nv6m["zt"] = OSUtils::jsonBool(v6m["zt"],false); + if (v6m.count("6plane")) nv6m["6plane"] = OSUtils::jsonBool(v6m["6plane"],false); + } else { + nv6m["rfc4193"] = false; + nv6m["zt"] = false; + nv6m["6plane"] = false; + } + network["v6AssignMode"] = nv6m; + } + + if (b.count("routes")) { + json &rts = b["routes"]; + if (rts.is_array()) { + json nrts = json::array(); + for(unsigned long i=0;i()); + InetAddress v; + if (via.is_string()) v.fromString(via.get()); + if ( ((t.ss_family == AF_INET)||(t.ss_family == AF_INET6)) && (t.netmaskBitsValid()) ) { + json tmp; + tmp["target"] = t.toString(); + if (v.ss_family == t.ss_family) + tmp["via"] = v.toIpString(); + else tmp["via"] = json(); + nrts.push_back(tmp); + } + } + } + } + network["routes"] = nrts; + } + } + + if (b.count("ipAssignmentPools")) { + json &ipp = b["ipAssignmentPools"]; + if (ipp.is_array()) { + json nipp = json::array(); + for(unsigned long i=0;i 0) { + json t = json::object(); + t["token"] = tstr; + t["expires"] = OSUtils::jsonInt(token["expires"],0ULL); + t["maxUsesPerMember"] = OSUtils::jsonInt(token["maxUsesPerMember"],0ULL); + nat.push_back(t); + } + } + } + network["authTokens"] = nat; + } + } + + if (b.count("capabilities")) { + json &capabilities = b["capabilities"]; + if (capabilities.is_array()) { + std::map< uint64_t,json > ncaps; + for(unsigned long i=0;i::iterator c(ncaps.begin());c!=ncaps.end();++c) + ncapsa.push_back(c->second); + network["capabilities"] = ncapsa; + } + } + + if (b.count("tags")) { + json &tags = b["tags"]; + if (tags.is_array()) { + std::map< uint64_t,json > ntags; + for(unsigned long i=0;i::iterator t(ntags.begin());t!=ntags.end();++t) + ntagsa.push_back(t->second); + network["tags"] = ntagsa; + } + } + + } catch ( ... ) { + responseBody = "{ \"message\": \"exception occurred while parsing body variables\" }"; + responseContentType = "application/json"; + return 400; + } + + network["id"] = nwids; + network["nwid"] = nwids; // legacy + + if (network != origNetwork) { + json &revj = network["revision"]; + network["revision"] = (revj.is_number() ? ((uint64_t)revj + 1ULL) : 1ULL); + network["lastModified"] = now; + { + Mutex::Lock _l(_db_m); + _db.put("network",nwids,network); + } + + // Send an update to all members of the network + _db.filter((std::string("network/") + nwids + "/member/"),[this,&now,&nwid](const std::string &n,const json &obj) { + _pushMemberUpdate(now,nwid,obj); + return true; // do not delete + }); + } + + _NetworkMemberInfo nmi; + _getNetworkMemberInfo(now,nwid,nmi); + _addNetworkNonPersistedFields(network,now,nmi); + + responseBody = OSUtils::jsonDump(network); + responseContentType = "application/json"; + return 200; + } // else 404 + + } // else 404 + + } else if (path[0] == "ping") { + + json testRec; + const uint64_t now = OSUtils::now(); + testRec["clock"] = now; + testRec["uptime"] = (now - _startTime); + testRec["content"] = b; + responseBody = OSUtils::jsonDump(testRec); + _db.writeRaw("pong",responseBody); + responseContentType = "application/json"; + return 200; + + } + + return 404; +} + +unsigned int EmbeddedNetworkController::handleControlPlaneHttpDELETE( + const std::vector &path, + const std::map &urlArgs, + const std::map &headers, + const std::string &body, + std::string &responseBody, + std::string &responseContentType) +{ + if (path.empty()) + return 404; + + if (path[0] == "network") { + if ((path.size() >= 2)&&(path[1].length() == 16)) { + const uint64_t nwid = Utils::hexStrToU64(path[1].c_str()); + + char nwids[24]; + Utils::snprintf(nwids,sizeof(nwids),"%.16llx",nwid); + json network; + { + Mutex::Lock _l(_db_m); + network = _db.get("network",nwids); + } + if (!network.size()) + return 404; + + if (path.size() >= 3) { + if ((path.size() == 4)&&(path[2] == "member")&&(path[3].length() == 10)) { + const uint64_t address = Utils::hexStrToU64(path[3].c_str()); + + Mutex::Lock _l(_db_m); + + json member = _db.get("network",nwids,"member",Address(address).toString()); + _db.erase("network",nwids,"member",Address(address).toString()); + + if (!member.size()) + return 404; + responseBody = OSUtils::jsonDump(member); + responseContentType = "application/json"; + return 200; + } + } else { + Mutex::Lock _l(_db_m); + + std::string pfx("network/"); + pfx.append(nwids); + _db.filter(pfx,[](const std::string &n,const json &obj) { + return false; // delete + }); + + Mutex::Lock _l2(_nmiCache_m); + _nmiCache.erase(nwid); + + responseBody = OSUtils::jsonDump(network); + responseContentType = "application/json"; + return 200; + } + } // else 404 + + } // else 404 + + return 404; +} + +void EmbeddedNetworkController::threadMain() + throw() +{ + uint64_t lastCircuitTestCheck = 0; + for(;;) { + _RQEntry *const qe = _queue.get(); // waits on next request + if (!qe) break; // enqueue a NULL to terminate threads + try { + _request(qe->nwid,qe->fromAddr,qe->requestPacketId,qe->identity,qe->metaData); + } catch ( ... ) {} + delete qe; + + uint64_t now = OSUtils::now(); + if ((now - lastCircuitTestCheck) > ZT_EMBEDDEDNETWORKCONTROLLER_CIRCUIT_TEST_EXPIRATION) { + lastCircuitTestCheck = now; + Mutex::Lock _l(_tests_m); + for(std::list< ZT_CircuitTest >::iterator i(_tests.begin());i!=_tests.end();) { + if ((now - i->timestamp) > ZT_EMBEDDEDNETWORKCONTROLLER_CIRCUIT_TEST_EXPIRATION) { + _node->circuitTestEnd(&(*i)); + _tests.erase(i++); + } else ++i; + } + } + } +} + +void EmbeddedNetworkController::_circuitTestCallback(ZT_Node *node,ZT_CircuitTest *test,const ZT_CircuitTestReport *report) +{ + char tmp[1024],id[128]; + EmbeddedNetworkController *const self = reinterpret_cast(test->ptr); + + if ((!test)||(!report)||(!test->credentialNetworkId)) return; // sanity check + + const uint64_t now = OSUtils::now(); + Utils::snprintf(id,sizeof(id),"network/%.16llx/test/%.16llx-%.16llx-%.10llx-%.10llx",test->credentialNetworkId,test->testId,now,report->upstream,report->current); + Utils::snprintf(tmp,sizeof(tmp), + "{\"id\": \"%s\"," + "\"timestamp\": %llu," + "\"networkId\": \"%.16llx\"," + "\"testId\": \"%.16llx\"," + "\"upstream\": \"%.10llx\"," + "\"current\": \"%.10llx\"," + "\"receivedTimestamp\": %llu," + "\"sourcePacketId\": \"%.16llx\"," + "\"flags\": %llu," + "\"sourcePacketHopCount\": %u," + "\"errorCode\": %u," + "\"vendor\": %d," + "\"protocolVersion\": %u," + "\"majorVersion\": %u," + "\"minorVersion\": %u," + "\"revision\": %u," + "\"platform\": %d," + "\"architecture\": %d," + "\"receivedOnLocalAddress\": \"%s\"," + "\"receivedFromRemoteAddress\": \"%s\"," + "\"receivedFromLinkQuality\": %f}", + id + 30, // last bit only, not leading path + (unsigned long long)test->timestamp, + (unsigned long long)test->credentialNetworkId, + (unsigned long long)test->testId, + (unsigned long long)report->upstream, + (unsigned long long)report->current, + (unsigned long long)now, + (unsigned long long)report->sourcePacketId, + (unsigned long long)report->flags, + report->sourcePacketHopCount, + report->errorCode, + (int)report->vendor, + report->protocolVersion, + report->majorVersion, + report->minorVersion, + report->revision, + (int)report->platform, + (int)report->architecture, + reinterpret_cast(&(report->receivedOnLocalAddress))->toString().c_str(), + reinterpret_cast(&(report->receivedFromRemoteAddress))->toString().c_str(), + ((double)report->receivedFromLinkQuality / (double)ZT_PATH_LINK_QUALITY_MAX)); + + Mutex::Lock _l(self->_db_m); + self->_db.writeRaw(id,std::string(tmp)); +} + +void EmbeddedNetworkController::_request( + uint64_t nwid, + const InetAddress &fromAddr, + uint64_t requestPacketId, + const Identity &identity, + const Dictionary &metaData) +{ + if (((!_signingId)||(!_signingId.hasPrivate()))||(_signingId.address().toInt() != (nwid >> 24))||(!_sender)) + return; + + const uint64_t now = OSUtils::now(); + + if (requestPacketId) { + Mutex::Lock _l(_lastRequestTime_m); + uint64_t &lrt = _lastRequestTime[std::pair(identity.address().toInt(),nwid)]; + if ((now - lrt) <= ZT_NETCONF_MIN_REQUEST_PERIOD) + return; + lrt = now; + } + + char nwids[24]; + Utils::snprintf(nwids,sizeof(nwids),"%.16llx",nwid); + json network; + json member; + { + Mutex::Lock _l(_db_m); + network = _db.get("network",nwids); + member = _db.get("network",nwids,"member",identity.address().toString()); + } + + if (!network.size()) { + _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_OBJECT_NOT_FOUND); + return; + } + + const bool newMember = (member.size() == 0); + + json origMember(member); // for detecting modification later + _initMember(member); + + { + std::string haveIdStr(OSUtils::jsonString(member["identity"],"")); + if (haveIdStr.length() > 0) { + // If we already know this member's identity perform a full compare. This prevents + // a "collision" from being able to auth onto our network in place of an already + // known member. + try { + if (Identity(haveIdStr.c_str()) != identity) { + _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_ACCESS_DENIED); + return; + } + } catch ( ... ) { + _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_ACCESS_DENIED); + return; + } + } else { + // If we do not yet know this member's identity, learn it. + member["identity"] = identity.toString(false); + } + } + + // These are always the same, but make sure they are set + member["id"] = identity.address().toString(); + member["address"] = member["id"]; + member["nwid"] = nwids; + + // Determine whether and how member is authorized + const char *authorizedBy = (const char *)0; + bool autoAuthorized = false; + json autoAuthCredentialType,autoAuthCredential; + if (OSUtils::jsonBool(member["authorized"],false)) { + authorizedBy = "memberIsAuthorized"; + } else if (!OSUtils::jsonBool(network["private"],true)) { + authorizedBy = "networkIsPublic"; + json &ahist = member["authHistory"]; + if ((!ahist.is_array())||(ahist.size() == 0)) + autoAuthorized = true; + } else { + char presentedAuth[512]; + if (metaData.get(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_AUTH,presentedAuth,sizeof(presentedAuth)) > 0) { + presentedAuth[511] = (char)0; // sanity check + + // Check for bearer token presented by member + if ((strlen(presentedAuth) > 6)&&(!strncmp(presentedAuth,"token:",6))) { + const char *const presentedToken = presentedAuth + 6; + + json &authTokens = network["authTokens"]; + if (authTokens.is_array()) { + for(unsigned long i=0;i now))&&(tstr == presentedToken)) { + bool usable = (maxUses == 0); + if (!usable) { + uint64_t useCount = 0; + json &ahist = member["authHistory"]; + if (ahist.is_array()) { + for(unsigned long j=0;j= ZT_NETCONF_DB_MEMBER_HISTORY_LENGTH) + break; + } + } + member["recentLog"] = recentLog; + + // Also only do this on real requests + member["lastRequestMetaData"] = metaData.data(); + } + + // If they are not authorized, STOP! + if (!authorizedBy) { + if (origMember != member) { + member["lastModified"] = now; + Mutex::Lock _l(_db_m); + _db.put("network",nwids,"member",identity.address().toString(),member); + } + _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_ACCESS_DENIED); + return; + } + + // ------------------------------------------------------------------------- + // If we made it this far, they are authorized. + // ------------------------------------------------------------------------- + + NetworkConfig nc; + _NetworkMemberInfo nmi; + _getNetworkMemberInfo(now,nwid,nmi); + + uint64_t credentialtmd = ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MAX_MAX_DELTA; + if (now > nmi.mostRecentDeauthTime) { + // If we recently de-authorized a member, shrink credential TTL/max delta to + // be below the threshold required to exclude it. Cap this to a min/max to + // prevent jitter or absurdly large values. + const uint64_t deauthWindow = now - nmi.mostRecentDeauthTime; + if (deauthWindow < ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MIN_MAX_DELTA) { + credentialtmd = ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MIN_MAX_DELTA; + } else if (deauthWindow < (ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MAX_MAX_DELTA + 5000ULL)) { + credentialtmd = deauthWindow - 5000ULL; + } + } + + nc.networkId = nwid; + nc.type = OSUtils::jsonBool(network["private"],true) ? ZT_NETWORK_TYPE_PRIVATE : ZT_NETWORK_TYPE_PUBLIC; + nc.timestamp = now; + nc.credentialTimeMaxDelta = credentialtmd; + nc.revision = OSUtils::jsonInt(network["revision"],0ULL); + nc.issuedTo = identity.address(); + if (OSUtils::jsonBool(network["enableBroadcast"],true)) nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST; + if (OSUtils::jsonBool(network["allowPassiveBridging"],false)) nc.flags |= ZT_NETWORKCONFIG_FLAG_ALLOW_PASSIVE_BRIDGING; + Utils::scopy(nc.name,sizeof(nc.name),OSUtils::jsonString(network["name"],"").c_str()); + nc.multicastLimit = (unsigned int)OSUtils::jsonInt(network["multicastLimit"],32ULL); + + for(std::set
::const_iterator ab(nmi.activeBridges.begin());ab!=nmi.activeBridges.end();++ab) { + nc.addSpecialist(*ab,ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE); + } + + json &v4AssignMode = network["v4AssignMode"]; + json &v6AssignMode = network["v6AssignMode"]; + json &ipAssignmentPools = network["ipAssignmentPools"]; + json &routes = network["routes"]; + json &rules = network["rules"]; + json &capabilities = network["capabilities"]; + json &tags = network["tags"]; + json &memberCapabilities = member["capabilities"]; + json &memberTags = member["tags"]; + + if (metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_RULES_ENGINE_REV,0) <= 0) { + // Old versions with no rules engine support get an allow everything rule. + // Since rules are enforced bidirectionally, newer versions *will* still + // enforce rules on the inbound side. + nc.ruleCount = 1; + nc.rules[0].t = ZT_NETWORK_RULE_ACTION_ACCEPT; + } else { + if (rules.is_array()) { + for(unsigned long i=0;i= ZT_MAX_NETWORK_RULES) + break; + if (_parseRule(rules[i],nc.rules[nc.ruleCount])) + ++nc.ruleCount; + } + } + + std::map< uint64_t,json * > capsById; + if (!memberCapabilities.is_array()) + memberCapabilities = json::array(); + if (capabilities.is_array()) { + for(unsigned long i=0;i::const_iterator ctmp = capsById.find(capId); + if (ctmp != capsById.end()) { + json *cap = ctmp->second; + if ((cap)&&(cap->is_object())&&(cap->size() > 0)) { + ZT_VirtualNetworkRule capr[ZT_MAX_CAPABILITY_RULES]; + unsigned int caprc = 0; + json &caprj = (*cap)["rules"]; + if ((caprj.is_array())&&(caprj.size() > 0)) { + for(unsigned long j=0;j= ZT_MAX_CAPABILITY_RULES) + break; + if (_parseRule(caprj[j],capr[caprc])) + ++caprc; + } + } + nc.capabilities[nc.capabilityCount] = Capability((uint32_t)capId,nwid,now,1,capr,caprc); + if (nc.capabilities[nc.capabilityCount].sign(_signingId,identity.address())) + ++nc.capabilityCount; + if (nc.capabilityCount >= ZT_MAX_NETWORK_CAPABILITIES) + break; + } + } + } + + std::map< uint32_t,uint32_t > memberTagsById; + if (memberTags.is_array()) { + for(unsigned long i=0;i::const_iterator t(memberTagsById.begin());t!=memberTagsById.end();++t) { + if (nc.tagCount >= ZT_MAX_NETWORK_TAGS) + break; + nc.tags[nc.tagCount] = Tag(nwid,now,identity.address(),t->first,t->second); + if (nc.tags[nc.tagCount].sign(_signingId)) + ++nc.tagCount; + } + } + + if (routes.is_array()) { + for(unsigned long i=0;i= ZT_MAX_NETWORK_ROUTES) + break; + json &route = routes[i]; + json &target = route["target"]; + json &via = route["via"]; + if (target.is_string()) { + const InetAddress t(target.get()); + InetAddress v; + if (via.is_string()) v.fromString(via.get()); + if ((t.ss_family == AF_INET)||(t.ss_family == AF_INET6)) { + ZT_VirtualNetworkRoute *r = &(nc.routes[nc.routeCount]); + *(reinterpret_cast(&(r->target))) = t; + if (v.ss_family == t.ss_family) + *(reinterpret_cast(&(r->via))) = v; + ++nc.routeCount; + } + } + } + } + + const bool noAutoAssignIps = OSUtils::jsonBool(member["noAutoAssignIps"],false); + + if ((v6AssignMode.is_object())&&(!noAutoAssignIps)) { + if ((OSUtils::jsonBool(v6AssignMode["rfc4193"],false))&&(nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)) { + nc.staticIps[nc.staticIpCount++] = InetAddress::makeIpv6rfc4193(nwid,identity.address().toInt()); + nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION; + } + if ((OSUtils::jsonBool(v6AssignMode["6plane"],false))&&(nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)) { + nc.staticIps[nc.staticIpCount++] = InetAddress::makeIpv66plane(nwid,identity.address().toInt()); + nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION; + } + } + + bool haveManagedIpv4AutoAssignment = false; + bool haveManagedIpv6AutoAssignment = false; // "special" NDP-emulated address types do not count + json ipAssignments = member["ipAssignments"]; // we want to make a copy + if (ipAssignments.is_array()) { + for(unsigned long i=0;i(&(nc.routes[rk].target))->containsAddress(ip)) ) + routedNetmaskBits = reinterpret_cast(&(nc.routes[rk].target))->netmaskBits(); + } + + if (routedNetmaskBits > 0) { + if (nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES) { + ip.setPort(routedNetmaskBits); + nc.staticIps[nc.staticIpCount++] = ip; + } + if (ip.ss_family == AF_INET) + haveManagedIpv4AutoAssignment = true; + else if (ip.ss_family == AF_INET6) + haveManagedIpv6AutoAssignment = true; + } + } + } else { + ipAssignments = json::array(); + } + + if ( (ipAssignmentPools.is_array()) && ((v6AssignMode.is_object())&&(OSUtils::jsonBool(v6AssignMode["zt"],false))) && (!haveManagedIpv6AutoAssignment) && (!noAutoAssignIps) ) { + for(unsigned long p=0;((p s[1])&&((e[1] - s[1]) >= 0xffffffffffULL)) { + // First see if we can just cram a ZeroTier ID into the higher 64 bits. If so do that. + xx[0] = Utils::hton(x[0]); + xx[1] = Utils::hton(x[1] + identity.address().toInt()); + } else { + // Otherwise pick random addresses -- this technically doesn't explore the whole range if the lower 64 bit range is >= 1 but that won't matter since that would be huge anyway + Utils::getSecureRandom((void *)xx,16); + if ((e[0] > s[0])) + xx[0] %= (e[0] - s[0]); + else xx[0] = 0; + if ((e[1] > s[1])) + xx[1] %= (e[1] - s[1]); + else xx[1] = 0; + xx[0] = Utils::hton(x[0] + xx[0]); + xx[1] = Utils::hton(x[1] + xx[1]); + } + + InetAddress ip6((const void *)xx,16,0); + + // Check if this IP is within a local-to-Ethernet routed network + int routedNetmaskBits = 0; + for(unsigned int rk=0;rk(&(nc.routes[rk].target))->containsAddress(ip6)) ) + routedNetmaskBits = reinterpret_cast(&(nc.routes[rk].target))->netmaskBits(); + } + + // If it's routed, then try to claim and assign it and if successful end loop + if ((routedNetmaskBits > 0)&&(!nmi.allocatedIps.count(ip6))) { + ipAssignments.push_back(ip6.toIpString()); + member["ipAssignments"] = ipAssignments; + ip6.setPort((unsigned int)routedNetmaskBits); + if (nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES) + nc.staticIps[nc.staticIpCount++] = ip6; + haveManagedIpv6AutoAssignment = true; + _clearNetworkMemberInfoCache(nwid); // clear cache to prevent IP assignment duplication on many rapid assigns + break; + } + } + } + } + } + } + + if ( (ipAssignmentPools.is_array()) && ((v4AssignMode.is_object())&&(OSUtils::jsonBool(v4AssignMode["zt"],false))) && (!haveManagedIpv4AutoAssignment) && (!noAutoAssignIps) ) { + for(unsigned long p=0;((p(&ipRangeStartIA)->sin_addr.s_addr)); + uint32_t ipRangeEnd = Utils::ntoh((uint32_t)(reinterpret_cast(&ipRangeEndIA)->sin_addr.s_addr)); + if ((ipRangeEnd < ipRangeStart)||(ipRangeStart == 0)) + continue; + uint32_t ipRangeLen = ipRangeEnd - ipRangeStart; + + // Start with the LSB of the member's address + uint32_t ipTrialCounter = (uint32_t)(identity.address().toInt() & 0xffffffff); + + for(uint32_t k=ipRangeStart,trialCount=0;((k<=ipRangeEnd)&&(trialCount < 1000));++k,++trialCount) { + uint32_t ip = (ipRangeLen > 0) ? (ipRangeStart + (ipTrialCounter % ipRangeLen)) : ipRangeStart; + ++ipTrialCounter; + if ((ip & 0x000000ff) == 0x000000ff) + continue; // don't allow addresses that end in .255 + + // Check if this IP is within a local-to-Ethernet routed network + int routedNetmaskBits = -1; + for(unsigned int rk=0;rk(&(nc.routes[rk].target))->sin_addr.s_addr)); + int targetBits = Utils::ntoh((uint16_t)(reinterpret_cast(&(nc.routes[rk].target))->sin_port)); + if ((ip & (0xffffffff << (32 - targetBits))) == targetIp) { + routedNetmaskBits = targetBits; + break; + } + } + } + + // If it's routed, then try to claim and assign it and if successful end loop + const InetAddress ip4(Utils::hton(ip),0); + if ((routedNetmaskBits > 0)&&(!nmi.allocatedIps.count(ip4))) { + ipAssignments.push_back(ip4.toIpString()); + member["ipAssignments"] = ipAssignments; + if (nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES) { + struct sockaddr_in *const v4ip = reinterpret_cast(&(nc.staticIps[nc.staticIpCount++])); + v4ip->sin_family = AF_INET; + v4ip->sin_port = Utils::hton((uint16_t)routedNetmaskBits); + v4ip->sin_addr.s_addr = Utils::hton(ip); + } + haveManagedIpv4AutoAssignment = true; + _clearNetworkMemberInfoCache(nwid); // clear cache to prevent IP assignment duplication on many rapid assigns + break; + } + } + } + } + } + } + + // Issue a certificate of ownership for all static IPs + if (nc.staticIpCount) { + nc.certificatesOfOwnership[0] = CertificateOfOwnership(nwid,now,identity.address(),1); + for(unsigned int i=0;incSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_INTERNAL_SERVER_ERROR); + return; + } + + if (member != origMember) { + member["lastModified"] = now; + Mutex::Lock _l(_db_m); + _db.put("network",nwids,"member",identity.address().toString(),member); + } + + _sender->ncSendConfig(nwid,requestPacketId,identity.address(),nc,metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_VERSION,0) < 6); +} + +void EmbeddedNetworkController::_getNetworkMemberInfo(uint64_t now,uint64_t nwid,_NetworkMemberInfo &nmi) +{ + char pfx[256]; + Utils::snprintf(pfx,sizeof(pfx),"network/%.16llx/member",nwid); + + { + Mutex::Lock _l(_nmiCache_m); + std::map::iterator c(_nmiCache.find(nwid)); + if ((c != _nmiCache.end())&&((now - c->second.nmiTimestamp) < 1000)) { // a short duration cache but limits CPU use on big networks + nmi = c->second; + return; + } + } + + { + Mutex::Lock _l(_db_m); + _db.filter(pfx,[&nmi,&now](const std::string &n,const json &member) { + try { + if (OSUtils::jsonBool(member["authorized"],false)) { + ++nmi.authorizedMemberCount; + + if (member.count("recentLog")) { + const json &mlog = member["recentLog"]; + if ((mlog.is_array())&&(mlog.size() > 0)) { + const json &mlog1 = mlog[0]; + if (mlog1.is_object()) { + if ((now - OSUtils::jsonInt(mlog1["ts"],0ULL)) < ZT_NETCONF_NODE_ACTIVE_THRESHOLD) + ++nmi.activeMemberCount; + } + } + } + + if (OSUtils::jsonBool(member["activeBridge"],false)) { + nmi.activeBridges.insert(Address(Utils::hexStrToU64(OSUtils::jsonString(member["id"],"0000000000").c_str()))); + } + + if (member.count("ipAssignments")) { + const json &mips = member["ipAssignments"]; + if (mips.is_array()) { + for(unsigned long i=0;i 0)&&(mdstr.length() > 0)) { + const Identity id(idstr); + bool online; + { + Mutex::Lock _l(_lastRequestTime_m); + std::map< std::pair,uint64_t >::iterator lrt(_lastRequestTime.find(std::pair(id.address().toInt(),nwid))); + online = ( (lrt != _lastRequestTime.end()) && ((now - lrt->second) < ZT_NETWORK_AUTOCONF_DELAY) ); + } + if (online) { + Dictionary *metaData = new Dictionary(mdstr.c_str()); + try { + this->request(nwid,InetAddress(),0,id,*metaData); + } catch ( ... ) {} + delete metaData; + } + } + } catch ( ... ) {} +} + +} // namespace ZeroTier diff --git a/controller/EmbeddedNetworkController.hpp b/controller/EmbeddedNetworkController.hpp new file mode 100644 index 0000000..0ae2f3b --- /dev/null +++ b/controller/EmbeddedNetworkController.hpp @@ -0,0 +1,210 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ZT_SQLITENETWORKCONTROLLER_HPP +#define ZT_SQLITENETWORKCONTROLLER_HPP + +#include + +#include +#include +#include +#include +#include + +#include "../node/Constants.hpp" + +#include "../node/NetworkController.hpp" +#include "../node/Mutex.hpp" +#include "../node/Utils.hpp" +#include "../node/Address.hpp" +#include "../node/InetAddress.hpp" + +#include "../osdep/OSUtils.hpp" +#include "../osdep/Thread.hpp" +#include "../osdep/BlockingQueue.hpp" + +#include "../ext/json/json.hpp" + +#include "JSONDB.hpp" + +// Number of background threads to start -- not actually started until needed +#define ZT_EMBEDDEDNETWORKCONTROLLER_BACKGROUND_THREAD_COUNT 4 + +// TTL for circuit tests +#define ZT_EMBEDDEDNETWORKCONTROLLER_CIRCUIT_TEST_EXPIRATION 120000 + +namespace ZeroTier { + +class Node; + +class EmbeddedNetworkController : public NetworkController +{ +public: + /** + * @param node Parent node + * @param dbPath Path to store data + */ + EmbeddedNetworkController(Node *node,const char *dbPath); + virtual ~EmbeddedNetworkController(); + + virtual void init(const Identity &signingId,Sender *sender); + + virtual void request( + uint64_t nwid, + const InetAddress &fromAddr, + uint64_t requestPacketId, + const Identity &identity, + const Dictionary &metaData); + + unsigned int handleControlPlaneHttpGET( + const std::vector &path, + const std::map &urlArgs, + const std::map &headers, + const std::string &body, + std::string &responseBody, + std::string &responseContentType); + unsigned int handleControlPlaneHttpPOST( + const std::vector &path, + const std::map &urlArgs, + const std::map &headers, + const std::string &body, + std::string &responseBody, + std::string &responseContentType); + unsigned int handleControlPlaneHttpDELETE( + const std::vector &path, + const std::map &urlArgs, + const std::map &headers, + const std::string &body, + std::string &responseBody, + std::string &responseContentType); + + void threadMain() + throw(); + +private: + struct _RQEntry + { + uint64_t nwid; + uint64_t requestPacketId; + InetAddress fromAddr; + Identity identity; + Dictionary metaData; + }; + + // Gathers a bunch of statistics about members of a network, IP assignments, etc. that we need in various places + struct _NetworkMemberInfo + { + _NetworkMemberInfo() : authorizedMemberCount(0),activeMemberCount(0),totalMemberCount(0),mostRecentDeauthTime(0) {} + std::set
activeBridges; + std::set allocatedIps; + unsigned long authorizedMemberCount; + unsigned long activeMemberCount; + unsigned long totalMemberCount; + uint64_t mostRecentDeauthTime; + uint64_t nmiTimestamp; // time this NMI structure was computed + }; + + static void _circuitTestCallback(ZT_Node *node,ZT_CircuitTest *test,const ZT_CircuitTestReport *report); + void _request(uint64_t nwid,const InetAddress &fromAddr,uint64_t requestPacketId,const Identity &identity,const Dictionary &metaData); + void _getNetworkMemberInfo(uint64_t now,uint64_t nwid,_NetworkMemberInfo &nmi); + inline void _clearNetworkMemberInfoCache(const uint64_t nwid) { Mutex::Lock _l(_nmiCache_m); _nmiCache.erase(nwid); } + void _pushMemberUpdate(uint64_t now,uint64_t nwid,const nlohmann::json &member); + + // These init objects with default and static/informational fields + inline void _initMember(nlohmann::json &member) + { + if (!member.count("authorized")) member["authorized"] = false; + if (!member.count("authHistory")) member["authHistory"] = nlohmann::json::array(); + if (!member.count("ipAssignments")) member["ipAssignments"] = nlohmann::json::array(); + if (!member.count("recentLog")) member["recentLog"] = nlohmann::json::array(); + if (!member.count("activeBridge")) member["activeBridge"] = false; + if (!member.count("tags")) member["tags"] = nlohmann::json::array(); + if (!member.count("capabilities")) member["capabilities"] = nlohmann::json::array(); + if (!member.count("creationTime")) member["creationTime"] = OSUtils::now(); + if (!member.count("noAutoAssignIps")) member["noAutoAssignIps"] = false; + if (!member.count("revision")) member["revision"] = 0ULL; + if (!member.count("lastDeauthorizedTime")) member["lastDeauthorizedTime"] = 0ULL; + if (!member.count("lastAuthorizedTime")) member["lastAuthorizedTime"] = 0ULL; + member["objtype"] = "member"; + } + inline void _initNetwork(nlohmann::json &network) + { + if (!network.count("private")) network["private"] = true; + if (!network.count("creationTime")) network["creationTime"] = OSUtils::now(); + if (!network.count("name")) network["name"] = ""; + if (!network.count("multicastLimit")) network["multicastLimit"] = (uint64_t)32; + if (!network.count("enableBroadcast")) network["enableBroadcast"] = true; + if (!network.count("v4AssignMode")) network["v4AssignMode"] = {{"zt",false}}; + if (!network.count("v6AssignMode")) network["v6AssignMode"] = {{"rfc4193",false},{"zt",false},{"6plane",false}}; + if (!network.count("authTokens")) network["authTokens"] = nlohmann::json::array(); + if (!network.count("capabilities")) network["capabilities"] = nlohmann::json::array(); + if (!network.count("tags")) network["tags"] = nlohmann::json::array(); + if (!network.count("routes")) network["routes"] = nlohmann::json::array(); + if (!network.count("ipAssignmentPools")) network["ipAssignmentPools"] = nlohmann::json::array(); + if (!network.count("rules")) { + // If unspecified, rules are set to allow anything and behave like a flat L2 segment + network["rules"] = {{ + { "not",false }, + { "or", false }, + { "type","ACTION_ACCEPT" } + }}; + } + network["objtype"] = "network"; + } + inline void _addNetworkNonPersistedFields(nlohmann::json &network,uint64_t now,const _NetworkMemberInfo &nmi) + { + network["clock"] = now; + network["authorizedMemberCount"] = nmi.authorizedMemberCount; + network["activeMemberCount"] = nmi.activeMemberCount; + network["totalMemberCount"] = nmi.totalMemberCount; + } + inline void _addMemberNonPersistedFields(nlohmann::json &member,uint64_t now) + { + member["clock"] = now; + } + + const uint64_t _startTime; + + BlockingQueue<_RQEntry *> _queue; + Thread _threads[ZT_EMBEDDEDNETWORKCONTROLLER_BACKGROUND_THREAD_COUNT]; + bool _threadsStarted; + Mutex _threads_m; + + std::map _nmiCache; + Mutex _nmiCache_m; + + JSONDB _db; + Mutex _db_m; + + Node *const _node; + std::string _path; + + NetworkController::Sender *_sender; + Identity _signingId; + + std::list< ZT_CircuitTest > _tests; + Mutex _tests_m; + + std::map< std::pair,uint64_t > _lastRequestTime; // last request time by + Mutex _lastRequestTime_m; +}; + +} // namespace ZeroTier + +#endif diff --git a/controller/JSONDB.cpp b/controller/JSONDB.cpp new file mode 100644 index 0000000..d3e76fc --- /dev/null +++ b/controller/JSONDB.cpp @@ -0,0 +1,219 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "JSONDB.hpp" + +#define ZT_JSONDB_HTTP_TIMEOUT 60000 + +namespace ZeroTier { + +static const nlohmann::json _EMPTY_JSON(nlohmann::json::object()); +static const std::map _ZT_JSONDB_GET_HEADERS; + +JSONDB::JSONDB(const std::string &basePath) : + _basePath(basePath), + _ready(false) +{ + if ((_basePath.length() > 7)&&(_basePath.substr(0,7) == "http://")) { + // TODO: this doesn't yet support IPv6 since bracketed address notiation isn't supported. + // Typically it's used with 127.0.0.1 anyway. + std::string hn = _basePath.substr(7); + std::size_t hnend = hn.find_first_of('/'); + if (hnend != std::string::npos) + hn = hn.substr(0,hnend); + std::size_t hnsep = hn.find_last_of(':'); + if (hnsep != std::string::npos) + hn[hnsep] = '/'; + _httpAddr.fromString(hn); + if (hnend != std::string::npos) + _basePath = _basePath.substr(7 + hnend); + if (_basePath.length() == 0) + _basePath = "/"; + if (_basePath[0] != '/') + _basePath = std::string("/") + _basePath; + } else { + OSUtils::mkdir(_basePath.c_str()); + OSUtils::lockDownFile(_basePath.c_str(),true); // networks might contain auth tokens, etc., so restrict directory permissions + } + _ready = _reload(_basePath,std::string()); +} + +bool JSONDB::writeRaw(const std::string &n,const std::string &obj) +{ + if (!_isValidObjectName(n)) + return false; + if (_httpAddr) { + std::map headers; + std::string body; + std::map reqHeaders; + char tmp[64]; + Utils::snprintf(tmp,sizeof(tmp),"%lu",(unsigned long)obj.length()); + reqHeaders["Content-Length"] = tmp; + reqHeaders["Content-Type"] = "application/json"; + const unsigned int sc = Http::PUT(1048576,ZT_JSONDB_HTTP_TIMEOUT,reinterpret_cast(&_httpAddr),(_basePath+"/"+n).c_str(),reqHeaders,obj.data(),(unsigned long)obj.length(),headers,body); + return (sc == 200); + } else { + const std::string path(_genPath(n,true)); + if (!path.length()) + return false; + return OSUtils::writeFile(path.c_str(),obj); + } +} + +bool JSONDB::put(const std::string &n,const nlohmann::json &obj) +{ + const bool r = writeRaw(n,OSUtils::jsonDump(obj)); + _db[n].obj = obj; + return r; +} + +const nlohmann::json &JSONDB::get(const std::string &n) +{ + while (!_ready) { + Thread::sleep(250); + _ready = _reload(_basePath,std::string()); + } + + if (!_isValidObjectName(n)) + return _EMPTY_JSON; + std::map::iterator e(_db.find(n)); + if (e != _db.end()) + return e->second.obj; + + std::string buf; + if (_httpAddr) { + std::map headers; + const unsigned int sc = Http::GET(1048576,ZT_JSONDB_HTTP_TIMEOUT,reinterpret_cast(&_httpAddr),(_basePath+"/"+n).c_str(),_ZT_JSONDB_GET_HEADERS,headers,buf); + if (sc != 200) + return _EMPTY_JSON; + } else { + const std::string path(_genPath(n,false)); + if (!path.length()) + return _EMPTY_JSON; + if (!OSUtils::readFile(path.c_str(),buf)) + return _EMPTY_JSON; + } + + try { + _E &e2 = _db[n]; + e2.obj = OSUtils::jsonParse(buf); + return e2.obj; + } catch ( ... ) { + _db.erase(n); + return _EMPTY_JSON; + } +} + +void JSONDB::erase(const std::string &n) +{ + if (!_isValidObjectName(n)) + return; + + if (_httpAddr) { + std::string body; + std::map headers; + Http::DEL(1048576,ZT_JSONDB_HTTP_TIMEOUT,reinterpret_cast(&_httpAddr),(_basePath+"/"+n).c_str(),_ZT_JSONDB_GET_HEADERS,headers,body); + } else { + std::string path(_genPath(n,true)); + if (!path.length()) + return; + OSUtils::rm(path.c_str()); + } + + _db.erase(n); +} + +bool JSONDB::_reload(const std::string &p,const std::string &b) +{ + if (_httpAddr) { + std::string body; + std::map headers; + const unsigned int sc = Http::GET(2147483647,ZT_JSONDB_HTTP_TIMEOUT,reinterpret_cast(&_httpAddr),_basePath.c_str(),_ZT_JSONDB_GET_HEADERS,headers,body); + if (sc == 200) { + try { + nlohmann::json dbImg(OSUtils::jsonParse(body)); + std::string tmp; + if (dbImg.is_object()) { + for(nlohmann::json::iterator i(dbImg.begin());i!=dbImg.end();++i) { + if (i.value().is_object()) { + tmp = i.key(); + _db[tmp].obj = i.value(); + } + } + return true; + } + } catch ( ... ) {} // invalid JSON, so maybe incomplete request + } + return false; + } else { + std::vector dl(OSUtils::listDirectory(p.c_str(),true)); + for(std::vector::const_iterator di(dl.begin());di!=dl.end();++di) { + if ((di->length() > 5)&&(di->substr(di->length() - 5) == ".json")) { + this->get(b + di->substr(0,di->length() - 5)); + } else { + this->_reload((p + ZT_PATH_SEPARATOR + *di),(b + *di + ZT_PATH_SEPARATOR)); + } + } + return true; + } +} + +bool JSONDB::_isValidObjectName(const std::string &n) +{ + if (n.length() == 0) + return false; + const char *p = n.c_str(); + char c; + // For security reasons we should not allow dots, backslashes, or other path characters or potential path characters. + while ((c = *(p++))) { + if (!( ((c >= 'a')&&(c <= 'z')) || ((c >= 'A')&&(c <= 'Z')) || ((c >= '0')&&(c <= '9')) || (c == '/') || (c == '_') || (c == '~') || (c == '-') )) + return false; + } + return true; +} + +std::string JSONDB::_genPath(const std::string &n,bool create) +{ + std::vector pt(OSUtils::split(n.c_str(),"/","","")); + if (pt.size() == 0) + return std::string(); + + char sep; + if (_httpAddr) { + sep = '/'; + create = false; + } else { + sep = ZT_PATH_SEPARATOR; + } + + std::string p(_basePath); + if (create) OSUtils::mkdir(p.c_str()); + for(unsigned long i=0,j=(unsigned long)(pt.size()-1);i. + */ + +#ifndef ZT_JSONDB_HPP +#define ZT_JSONDB_HPP + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "../node/Constants.hpp" +#include "../node/Utils.hpp" +#include "../node/InetAddress.hpp" +#include "../node/Mutex.hpp" +#include "../ext/json/json.hpp" +#include "../osdep/OSUtils.hpp" +#include "../osdep/Http.hpp" +#include "../osdep/Thread.hpp" + +namespace ZeroTier { + +/** + * Hierarchical JSON store that persists into the filesystem or via HTTP + */ +class JSONDB +{ +public: + JSONDB(const std::string &basePath); + + bool writeRaw(const std::string &n,const std::string &obj); + + bool put(const std::string &n,const nlohmann::json &obj); + + inline bool put(const std::string &n1,const std::string &n2,const nlohmann::json &obj) { return this->put((n1 + "/" + n2),obj); } + inline bool put(const std::string &n1,const std::string &n2,const std::string &n3,const nlohmann::json &obj) { return this->put((n1 + "/" + n2 + "/" + n3),obj); } + inline bool put(const std::string &n1,const std::string &n2,const std::string &n3,const std::string &n4,const nlohmann::json &obj) { return this->put((n1 + "/" + n2 + "/" + n3 + "/" + n4),obj); } + inline bool put(const std::string &n1,const std::string &n2,const std::string &n3,const std::string &n4,const std::string &n5,const nlohmann::json &obj) { return this->put((n1 + "/" + n2 + "/" + n3 + "/" + n4 + "/" + n5),obj); } + + const nlohmann::json &get(const std::string &n); + + inline const nlohmann::json &get(const std::string &n1,const std::string &n2) { return this->get((n1 + "/" + n2)); } + inline const nlohmann::json &get(const std::string &n1,const std::string &n2,const std::string &n3) { return this->get((n1 + "/" + n2 + "/" + n3)); } + inline const nlohmann::json &get(const std::string &n1,const std::string &n2,const std::string &n3,const std::string &n4) { return this->get((n1 + "/" + n2 + "/" + n3 + "/" + n4)); } + inline const nlohmann::json &get(const std::string &n1,const std::string &n2,const std::string &n3,const std::string &n4,const std::string &n5) { return this->get((n1 + "/" + n2 + "/" + n3 + "/" + n4 + "/" + n5)); } + + void erase(const std::string &n); + + inline void erase(const std::string &n1,const std::string &n2) { this->erase(n1 + "/" + n2); } + inline void erase(const std::string &n1,const std::string &n2,const std::string &n3) { this->erase(n1 + "/" + n2 + "/" + n3); } + inline void erase(const std::string &n1,const std::string &n2,const std::string &n3,const std::string &n4) { this->erase(n1 + "/" + n2 + "/" + n3 + "/" + n4); } + inline void erase(const std::string &n1,const std::string &n2,const std::string &n3,const std::string &n4,const std::string &n5) { this->erase(n1 + "/" + n2 + "/" + n3 + "/" + n4 + "/" + n5); } + + template + inline void filter(const std::string &prefix,F func) + { + while (!_ready) { + Thread::sleep(250); + _ready = _reload(_basePath,std::string()); + } + + for(std::map::iterator i(_db.lower_bound(prefix));i!=_db.end();) { + if ((i->first.length() >= prefix.length())&&(!memcmp(i->first.data(),prefix.data(),prefix.length()))) { + if (!func(i->first,get(i->first))) { + std::map::iterator i2(i); ++i2; + this->erase(i->first); + i = i2; + } else ++i; + } else break; + } + } + + inline bool operator==(const JSONDB &db) const { return ((_basePath == db._basePath)&&(_db == db._db)); } + inline bool operator!=(const JSONDB &db) const { return (!(*this == db)); } + +private: + bool _reload(const std::string &p,const std::string &b); + bool _isValidObjectName(const std::string &n); + std::string _genPath(const std::string &n,bool create); + + struct _E + { + nlohmann::json obj; + inline bool operator==(const _E &e) const { return (obj == e.obj); } + inline bool operator!=(const _E &e) const { return (obj != e.obj); } + }; + + InetAddress _httpAddr; + std::string _basePath; + std::map _db; + volatile bool _ready; +}; + +} // namespace ZeroTier + +#endif diff --git a/controller/README.md b/controller/README.md index 8b789a3..db8d015 100644 --- a/controller/README.md +++ b/controller/README.md @@ -1,66 +1,43 @@ Network Controller Microservice ====== -ZeroTier's 16-digit network IDs are really just a concatenation of the 10-digit ZeroTier address of a network controller followed by a 6-digit (24-bit) network number on that controller. Fans of software defined networking will recognize this as a variation of the familiar [separation of data plane and control plane](http://sdntutorials.com/difference-between-control-plane-and-data-plane/) SDN design pattern. +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. -This code implements the *node/NetworkController.hpp* interface and provides a SQLite3-backed network controller microservice. Including it in the build allows ZeroTier One to act as a controller and create/manage networks. +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. -This is the same code we use to run [my.zerotier.com](https://my.zerotier.com/), which is a web UI and API that runs in front of a pool of controllers. +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. -### Building +### Upgrading from Older (1.1.14 or earlier) Versions -On Linux, Mac, or BSD you can create a controller-enabled build with: +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. - make ZT_ENABLE_NETWORK_CONTROLLER=1 +The migration tool is written in nodeJS and can be used like this: -You will need the development headers and libraries for SQLite3 installed. + cd migrate-sqlite + npm install + node migrate.js -### Running +Very old versions of nodeJS may have issues. We tested it with version 7. -After building and installing (`make install`) a controller-enabled build of ZeroTier One, start it and try: +### Scalability and Reliability - sudo zerotier-cli /controller +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. -You should see something like: - - { - "controller": true, - "apiVersion": 2, - "clock": 1468002975497, - "instanceId": "8ab354604debe1da27ee627c9ef94a48" - } - -When started, a controller-enabled build of ZeroTier One will automatically create and initialize a `controller.db` file in its home folder. This is where all the controller's data and persistent state lives. If you're upgrading an old controller it will upgrade its database schema automatically on first launch. Make a backup of the old controller's database first since you can't go backward. - -Controllers periodically make backups of their database as `controller.db.backup`. This is done so that this file can be more easily copied/rsync'ed to other systems without worrying about corruption. SQLite3 supports multiple processes accessing the same database file, so `sqlite3 /path/to/controller.db .dump` also works but can be slow on a busy controller. - -Controllers can in theory host up to 2^24 networks and serve many millions of devices (or more), but we recommend running multiple controllers for a lot of networks to spread load and be more fault tolerant. +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. -### Implementing High Availability Fail-Over - -ZeroTier network controllers are not single points of failure for networks-- in the sense that if a controller goes down *existing* members of a network can continue to communicate. But new members (or those that have been offline for a while) can't join, existing members can't be de-authorized, and other changes to the network's configuration can't be made. This means that short "glitches" in controller availability are not a major problem but long periods of unavailability can be. - -Because controllers are just regular ZeroTier nodes and controller queries are in-band, controllers can trivially be moved without worrying about changes to underlying physical IPs. This makes high-availability fail-over very easy to implement. - -Just set up two cloud hosts, preferably in different data centers (e.g. two different AWS regions or Digital Ocean SF and NYC). Now set up the hot spare controller to constantly mirror `controller.db.backup` from its active sibling. - -If the active controller goes down, rename `controller.db.backup` to `controller.db` on the hot spare and start the ZeroTier One service there. The spare will take over and has now become the active controller. If the original active node comes back, it should take on the role of spare and should not start its service. Instead it should start mirroring the active controller's backup and wait until it is needed. - -The details of actually implementing this kind of HA fail-over on Linux or other OSes are beyond the scope of these docs and there are many ways to do it. Docker orchestration tools like Kubernetes can also be used to accomplish this if you've dockerized your controller. - ### 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. +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.) -Also note that the API is *very* sensitive about types. Integers must be integers and strings strings, etc. Incorrectly typed and unrecognized fields are just ignored. +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` @@ -71,11 +48,8 @@ Also note that the API is *very* sensitive about types. Integers must be integer | Field | Type | Description | Writable | | ------------------ | ----------- | ------------------------------------------------- | -------- | | controller | boolean | Always 'true' | no | -| apiVersion | integer | Controller API version, currently 2 | no | +| apiVersion | integer | Controller API version, currently 3 | no | | clock | integer | Current clock on controller, ms since epoch | no | -| instanceId | string | A random ID generated on first controller DB init | no | - -The instance ID can be used to check whether a controller's database has been reset or otherwise switched. #### `/controller/network` @@ -97,52 +71,50 @@ When POSTing new networks take care that their IDs are not in use, otherwise you | Field | Type | Description | Writable | | --------------------- | ------------- | ------------------------------------------------- | -------- | -| nwid | string | 16-digit network ID | no | -| controllerInstanceId | string | Controller database instance ID | no | +| 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 | string | If 'zt', auto-assign IPv4 from pool(s) | YES | -| v6AssignMode | string | IPv6 address auto-assign modes; see below | 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 | -| memberRevisionCounter | integer | Network member revision counter | no | | authorizedMemberCount | integer | Number of authorized members (for private nets) | no | -| relays | array[object] | Alternative relays; see below | YES | +| 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 | -(The `ipLocalRoutes` field appeared in older versions but is no longer present. Routes will now show up in `routes`.) +Recent changes: -Two important things to know about networks: + * 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"). - - Networks without rules won't carry any traffic. See below for an example with rules to permit IPv4 and IPv6. - - 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 not common in ordinary use. +Other important points: -**IPv6 Auto-Assign Modes:** + * 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. -This field is (for legacy reasons) a comma-delimited list of strings. These can be `rfc4193`, `6plane`, and `zt`. RFC4193 and 6PLANE are special addressing modes that deterministically assign IPv6 addresses based on the network ID and the ZeroTier address of each member. The `zt` mode enables IPv6 auto-assignment from arbitrary IPv6 IP ranges configured in `ipAssignmentPools`. +**Auto-Assign Modes:** -**Relay object format:** +Auto assign modes (`v4AssignMode` and `v6AssignMode`) contain objects that map assignment modes to booleans. -Relay objects define network-specific preferred relay nodes. Traffic to peers on this network will preferentially use these relays if they are available, and otherwise will fall back to the global rootserver infrastructure. +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.) -| Field | Type | Description | Writable | -| --------------------- | ------------- | ------------------------------------------------- | -------- | -| address | string | 10-digit ZeroTier address of relay | YES | -| phyAddress | string | Optional IP/port suggestion for finding relay | YES | +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 | Writable | -| --------------------- | ------------- | ------------------------------------------------- | -------- | -| ipRangeStart | string | Starting IP address in range | YES | -| ipRangeEnd | string | Ending IP address in range (inclusive) | YES | +| 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. @@ -159,57 +131,68 @@ That defines a range within network `fd00:feed:feed:beef::/64` that contains up **Rule object format:** -Rules are matched in order of ruleNo. If no rules match, the default action is `drop`. To allow all traffic, create a single rule with all *null* fields and an action of `accept`. +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`. -In the future there will be many, many more types of rules. As of today only filtering by Ethernet packet type is supported. +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. -| Field | Type | Description | Writable | -| --------------------- | ------------- | ------------------------------------------------- | -------- | -| ruleNo | integer | Rule sorting key | YES | -| etherType | integer | Ethernet frame type (e.g. 34525 for IPv6) | YES | -| action | string | Currently either `allow` or `drop` | YES | +Each rule table entry has two common fields. -**An Example: The Configuration for Earth** +| Field | Type | Description | +| --------------------- | ------------- | ------------------------------------------------- | +| type | string | Entry type (all caps, case sensitive) | +| not | boolean | If true, MATCHes match if they don't match | -Here is an example of a correctly configured ZeroTier network with IPv4 auto-assigned addresses from 28.0.0.0/7 (a "de-facto private" space) and RFC4193 IPv6 addressing. Users might recognize this as *Earth*, our public "global LAN party" that's used for demos and testing and occasionally gaming. +The following fields may or may not be present depending on rule type: -For your own networks you'll probably want to change `private` to `true` unless you like company. These rules on the other hand probably are what you want. These allow IPv4, IPv4 ARP, and IPv6 Ethernet frames. To allow only IPv4 omit the one for Ethernet type 34525 (IPv6). +| 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) | - { - "nwid": "8056c2e21c000001", - "controllerInstanceId": "8ab354604debe1da27ee627c9ef94a48", - "clock": 1468004857100, - "name": "earth.zerotier.net", - "private": false, - "enableBroadcast": false, - "allowPassiveBridging": false, - "v4AssignMode": "zt", - "v6AssignMode": "rfc4193", - "multicastLimit": 64, - "creationTime": 1442292573165, - "revision": 234, - "memberRevisionCounter": 3326, - "authorizedMemberCount": 2873, - "relays": [], - "routes": [ - {"target":"28.0.0.0/7","via":null,"flags":0,"metric":0}], - "ipAssignmentPools": [ - {"ipRangeStart":"28.0.0.1","ipRangeEnd":"29.255.255.254"}], - "rules": [ - { - "ruleNo": 20, - "etherType": 2048, - "action": "accept" - },{ - "ruleNo": 21, - "etherType": 2054, - "action": "accept" - },{ - "ruleNo": 30, - "etherType": 34525, - "action": "accept" - }] - } +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` @@ -235,10 +218,12 @@ This returns an object containing all currently online members and the most rece | 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 | -| address | string | Member's 10-digit ZeroTier address | 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 | @@ -252,10 +237,12 @@ Note that managed IP assignments are only used if they fall within a managed rou | Field | Type | Description | | --------------------- | ------------- | ------------------------------------------------- | | ts | integer | Time of request, ms since epoch | -| authorized | boolean | Was member authorized? | -| clientMajorVersion | integer | Client major version or -1 if unknown | -| clientMinorVersion | integer | Client minor version or -1 if unknown | -| clientRevision | integer | Client revision or -1 if unknown | +| auth | boolean | Was member authorized? | +| authBy | string | How was member authorized? | +| vMajor | integer | Client major version or -1 if unknown | +| vMinor | integer | Client minor version or -1 if unknown | +| vRev | integer | Client revision or -1 if unknown | +| vProto | integer | ZeroTier protocol version reported by client | | fromAddr | string | Physical address if known | The controller can only know a member's `fromAddr` if it's able to establish a direct path to it. Members behind very restrictive firewalls may not have this information since the controller will be receiving the member's requests by way of a relay. ZeroTier does not back-trace IP paths as packets are relayed since this would add a lot of protocol overhead. diff --git a/controller/SqliteNetworkController.cpp b/controller/SqliteNetworkController.cpp deleted file mode 100644 index 6505174..0000000 --- a/controller/SqliteNetworkController.cpp +++ /dev/null @@ -1,2195 +0,0 @@ -/* - * ZeroTier One - Network Virtualization Everywhere - * Copyright (C) 2011-2015 ZeroTier, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * -- - * - * ZeroTier may be used and distributed under the terms of the GPLv3, which - * are available at: http://www.gnu.org/licenses/gpl-3.0.html - * - * If you would like to embed ZeroTier into a commercial application or - * redistribute it in a modified binary form, please contact ZeroTier Networks - * LLC. Start here: http://www.zerotier.com/ - */ - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "../include/ZeroTierOne.h" -#include "../node/Constants.hpp" - -#ifdef ZT_USE_SYSTEM_JSON_PARSER -#include -#else -#include "../ext/json-parser/json.h" -#endif - -#include "SqliteNetworkController.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" - -#include "../osdep/OSUtils.hpp" - -// Include ZT_NETCONF_SCHEMA_SQL constant to init database -#include "schema.sql.c" - -// Stored in database as schemaVersion key in Config. -// If not present, database is assumed to be empty and at the current schema version -// and this key/value is added automatically. -#define ZT_NETCONF_SQLITE_SCHEMA_VERSION 4 -#define ZT_NETCONF_SQLITE_SCHEMA_VERSION_STR "4" - -// API version reported via JSON control plane -#define ZT_NETCONF_CONTROLLER_API_VERSION 2 - -// Number of requests to remember in member history -#define ZT_NETCONF_DB_MEMBER_HISTORY_LENGTH 8 - -// Min duration between requests for an address/nwid combo to prevent floods -#define ZT_NETCONF_MIN_REQUEST_PERIOD 1000 - -// Delay between backups in milliseconds -#define ZT_NETCONF_BACKUP_PERIOD 300000 - -// Nodes are considered active if they've queried in less than this long -#define ZT_NETCONF_NODE_ACTIVE_THRESHOLD ((ZT_NETWORK_AUTOCONF_DELAY * 2) + 5000) - -// Flags for Network 'flags' field in table -#define ZT_DB_NETWORK_FLAG_ZT_MANAGED_V4_AUTO_ASSIGN 1 -#define ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_RFC4193 2 -#define ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_6PLANE 4 -#define ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_AUTO_ASSIGN 8 - -// Flags with all V6 managed mode flags flipped off -- for masking in update operation and in string form for SQL building -#define ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_MASK_S "268435441" - -// Uncomment to trace Sqlite for debugging -//#define ZT_NETCONF_SQLITE_TRACE 1 - -namespace ZeroTier { - -namespace { - -static std::string _jsonEscape(const char *s) -{ - if (!s) - return std::string(); - std::string buf; - for(const char *p=s;(*p);++p) { - switch(*p) { - case '\t': buf.append("\\t"); break; - case '\b': buf.append("\\b"); break; - case '\r': buf.append("\\r"); break; - case '\n': buf.append("\\n"); break; - case '\f': buf.append("\\f"); break; - case '"': buf.append("\\\""); break; - case '\\': buf.append("\\\\"); break; - case '/': buf.append("\\/"); break; - default: buf.push_back(*p); break; - } - } - return buf; -} -static std::string _jsonEscape(const std::string &s) { return _jsonEscape(s.c_str()); } - -// Converts an InetAddress to a blob and an int for storage in database -static void _ipToBlob(const InetAddress &a,char *ipBlob,int &ipVersion) /* blob[16] */ -{ - switch(a.ss_family) { - case AF_INET: - memset(ipBlob,0,12); - memcpy(ipBlob + 12,a.rawIpData(),4); - ipVersion = 4; - break; - case AF_INET6: - memcpy(ipBlob,a.rawIpData(),16); - ipVersion = 6; - break; - } -} - -// Member.recentHistory is stored in a BLOB as an array of strings containing JSON objects. -// This is kind of hacky but efficient and quick to parse and send to the client. -class MemberRecentHistory : public std::list -{ -public: - inline std::string toBlob() const - { - std::string b; - for(const_iterator i(begin());i!=end();++i) { - b.append(*i); - b.push_back((char)0); - } - return b; - } - - inline void fromBlob(const char *blob,unsigned int len) - { - for(unsigned int i=0,k=0;i 0) FROM Network WHERE id = ?",-1,&_sGetNetworkById,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"SELECT revision FROM Network WHERE id = ?",-1,&_sGetNetworkRevision,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"UPDATE Network SET revision = ? WHERE id = ?",-1,&_sSetNetworkRevision,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"INSERT INTO Network (id,name,creationTime,revision) VALUES (?,?,?,1)",-1,&_sCreateNetwork,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"DELETE FROM Network WHERE id = ?",-1,&_sDeleteNetwork,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"SELECT id FROM Network ORDER BY id ASC",-1,&_sListNetworks,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"UPDATE Network SET memberRevisionCounter = (memberRevisionCounter + 1) WHERE id = ?",-1,&_sIncrementMemberRevisionCounter,(const char **)0) != SQLITE_OK) - - /* Node */ - ||(sqlite3_prepare_v2(_db,"SELECT identity FROM Node WHERE id = ?",-1,&_sGetNodeIdentity,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"INSERT OR REPLACE INTO Node (id,identity) VALUES (?,?)",-1,&_sCreateOrReplaceNode,(const char **)0) != SQLITE_OK) - - /* Rule */ - ||(sqlite3_prepare_v2(_db,"SELECT etherType FROM Rule WHERE networkId = ? AND \"action\" = 'accept'",-1,&_sGetEtherTypesFromRuleTable,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"INSERT INTO Rule (networkId,ruleNo,nodeId,sourcePort,destPort,vlanId,vlanPcP,etherType,macSource,macDest,ipSource,ipDest,ipTos,ipProtocol,ipSourcePort,ipDestPort,flags,invFlags,\"action\") VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",-1,&_sCreateRule,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"SELECT ruleNo,nodeId,sourcePort,destPort,vlanId,vlanPcp,etherType,macSource,macDest,ipSource,ipDest,ipTos,ipProtocol,ipSourcePort,ipDestPort,\"flags\",invFlags,\"action\" FROM Rule WHERE networkId = ? ORDER BY ruleNo ASC",-1,&_sListRules,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"DELETE FROM Rule WHERE networkId = ?",-1,&_sDeleteRulesForNetwork,(const char **)0) != SQLITE_OK) - - /* IpAssignmentPool */ - ||(sqlite3_prepare_v2(_db,"SELECT ipRangeStart,ipRangeEnd FROM IpAssignmentPool WHERE networkId = ? AND ipVersion = ?",-1,&_sGetIpAssignmentPools,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"SELECT ipRangeStart,ipRangeEnd,ipVersion FROM IpAssignmentPool WHERE networkId = ? ORDER BY ipRangeStart ASC",-1,&_sGetIpAssignmentPools2,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"INSERT INTO IpAssignmentPool (networkId,ipRangeStart,ipRangeEnd,ipVersion) VALUES (?,?,?,?)",-1,&_sCreateIpAssignmentPool,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"DELETE FROM IpAssignmentPool WHERE networkId = ?",-1,&_sDeleteIpAssignmentPoolsForNetwork,(const char **)0) != SQLITE_OK) - - /* IpAssignment */ - ||(sqlite3_prepare_v2(_db,"SELECT ip,ipNetmaskBits,ipVersion FROM IpAssignment WHERE networkId = ? AND nodeId = ? AND \"type\" = 0 ORDER BY ip ASC",-1,&_sGetIpAssignmentsForNode,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"SELECT 1 FROM IpAssignment WHERE networkId = ? AND ip = ? AND ipVersion = ? AND \"type\" = ?",-1,&_sCheckIfIpIsAllocated,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"INSERT INTO IpAssignment (networkId,nodeId,\"type\",ip,ipNetmaskBits,ipVersion) VALUES (?,?,?,?,?,?)",-1,&_sAllocateIp,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"DELETE FROM IpAssignment WHERE networkId = ? AND nodeId = ? AND \"type\" = ?",-1,&_sDeleteIpAllocations,(const char **)0) != SQLITE_OK) - - /* Relay */ - ||(sqlite3_prepare_v2(_db,"SELECT \"address\",\"phyAddress\" FROM Relay WHERE \"networkId\" = ? ORDER BY \"address\" ASC",-1,&_sGetRelays,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"DELETE FROM Relay WHERE networkId = ?",-1,&_sDeleteRelaysForNetwork,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"INSERT INTO Relay (\"networkId\",\"address\",\"phyAddress\") VALUES (?,?,?)",-1,&_sCreateRelay,(const char **)0) != SQLITE_OK) - - /* Member */ - ||(sqlite3_prepare_v2(_db,"SELECT rowid,authorized,activeBridge,memberRevision,\"flags\",lastRequestTime,recentHistory FROM Member WHERE networkId = ? AND nodeId = ?",-1,&_sGetMember,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"SELECT m.authorized,m.activeBridge,m.memberRevision,n.identity,m.flags,m.lastRequestTime,m.recentHistory FROM Member AS m LEFT OUTER JOIN Node AS n ON n.id = m.nodeId WHERE m.networkId = ? AND m.nodeId = ?",-1,&_sGetMember2,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"INSERT INTO Member (networkId,nodeId,authorized,activeBridge,memberRevision) VALUES (?,?,?,0,(SELECT memberRevisionCounter FROM Network WHERE id = ?))",-1,&_sCreateMember,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"SELECT nodeId FROM Member WHERE networkId = ? AND activeBridge > 0 AND authorized > 0",-1,&_sGetActiveBridges,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"SELECT m.nodeId,m.memberRevision FROM Member AS m WHERE m.networkId = ? ORDER BY m.nodeId ASC",-1,&_sListNetworkMembers,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"UPDATE Member SET authorized = ?,memberRevision = (SELECT memberRevisionCounter FROM Network WHERE id = ?) WHERE rowid = ?",-1,&_sUpdateMemberAuthorized,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"UPDATE Member SET activeBridge = ?,memberRevision = (SELECT memberRevisionCounter FROM Network WHERE id = ?) WHERE rowid = ?",-1,&_sUpdateMemberActiveBridge,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"UPDATE Member SET \"lastRequestTime\" = ?, \"recentHistory\" = ? WHERE rowid = ?",-1,&_sUpdateMemberHistory,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"DELETE FROM Member WHERE networkId = ? AND nodeId = ?",-1,&_sDeleteMember,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"DELETE FROM Member WHERE networkId = ?",-1,&_sDeleteAllNetworkMembers,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"SELECT nodeId,recentHistory FROM Member WHERE networkId = ? AND lastRequestTime >= ?",-1,&_sGetActiveNodesOnNetwork,(const char **)0) != SQLITE_OK) - - /* Route */ - ||(sqlite3_prepare_v2(_db,"INSERT INTO Route (networkId,target,via,targetNetmaskBits,ipVersion,flags,metric) VALUES (?,?,?,?,?,?,?)",-1,&_sCreateRoute,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"SELECT DISTINCT target,via,targetNetmaskBits,ipVersion,flags,metric FROM \"Route\" WHERE networkId = ? ORDER BY ipVersion,target,via",-1,&_sGetRoutes,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"DELETE FROM \"Route\" WHERE networkId = ?",-1,&_sDeleteRoutes,(const char **)0) != SQLITE_OK) - - /* Config */ - ||(sqlite3_prepare_v2(_db,"SELECT \"v\" FROM \"Config\" WHERE \"k\" = ?",-1,&_sGetConfig,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"INSERT OR REPLACE INTO \"Config\" (\"k\",\"v\") VALUES (?,?)",-1,&_sSetConfig,(const char **)0) != SQLITE_OK) - - ) { - std::string err(std::string("SqliteNetworkController unable to initialize one or more prepared statements: ") + sqlite3_errmsg(_db)); - sqlite3_close(_db); - throw std::runtime_error(err); - } - - /* Generate a 128-bit / 32-character "instance ID" if one isn't already - * defined. Clients can use this to determine if this is the same controller - * database they know and love. */ - sqlite3_reset(_sGetConfig); - sqlite3_bind_text(_sGetConfig,1,"instanceId",10,SQLITE_STATIC); - if (sqlite3_step(_sGetConfig) != SQLITE_ROW) { - unsigned char sr[32]; - Utils::getSecureRandom(sr,32); - for(unsigned int i=0;i<32;++i) - _instanceId.push_back("0123456789abcdef"[(unsigned int)sr[i] & 0xf]); - - sqlite3_reset(_sSetConfig); - sqlite3_bind_text(_sSetConfig,1,"instanceId",10,SQLITE_STATIC); - sqlite3_bind_text(_sSetConfig,2,_instanceId.c_str(),-1,SQLITE_STATIC); - if (sqlite3_step(_sSetConfig) != SQLITE_DONE) - throw std::runtime_error("SqliteNetworkController unable to read or initialize instanceId"); - } else { - const char *iid = reinterpret_cast(sqlite3_column_text(_sGetConfig,0)); - if (!iid) - throw std::runtime_error("SqliteNetworkController unable to read instanceId (it's NULL)"); - _instanceId = iid; - } - -#ifdef ZT_NETCONF_SQLITE_TRACE - sqlite3_trace(_db,sqliteTraceFunc,(void *)0); -#endif - - _backupThread = Thread::start(this); -} - -SqliteNetworkController::~SqliteNetworkController() -{ - _backupThreadRun = false; - Thread::join(_backupThread); - - Mutex::Lock _l(_lock); - if (_db) { - sqlite3_finalize(_sGetNetworkById); - sqlite3_finalize(_sGetMember); - sqlite3_finalize(_sCreateMember); - sqlite3_finalize(_sGetNodeIdentity); - sqlite3_finalize(_sCreateOrReplaceNode); - sqlite3_finalize(_sGetEtherTypesFromRuleTable); - sqlite3_finalize(_sGetActiveBridges); - sqlite3_finalize(_sGetIpAssignmentsForNode); - sqlite3_finalize(_sGetIpAssignmentPools); - sqlite3_finalize(_sCheckIfIpIsAllocated); - sqlite3_finalize(_sAllocateIp); - sqlite3_finalize(_sDeleteIpAllocations); - sqlite3_finalize(_sGetRelays); - sqlite3_finalize(_sListNetworks); - sqlite3_finalize(_sListNetworkMembers); - sqlite3_finalize(_sGetMember2); - sqlite3_finalize(_sGetIpAssignmentPools2); - sqlite3_finalize(_sListRules); - sqlite3_finalize(_sCreateRule); - sqlite3_finalize(_sCreateNetwork); - sqlite3_finalize(_sGetNetworkRevision); - sqlite3_finalize(_sSetNetworkRevision); - sqlite3_finalize(_sDeleteRelaysForNetwork); - sqlite3_finalize(_sCreateRelay); - sqlite3_finalize(_sDeleteIpAssignmentPoolsForNetwork); - sqlite3_finalize(_sDeleteRulesForNetwork); - sqlite3_finalize(_sCreateIpAssignmentPool); - sqlite3_finalize(_sUpdateMemberAuthorized); - sqlite3_finalize(_sUpdateMemberActiveBridge); - sqlite3_finalize(_sUpdateMemberHistory); - sqlite3_finalize(_sDeleteMember); - sqlite3_finalize(_sDeleteAllNetworkMembers); - sqlite3_finalize(_sGetActiveNodesOnNetwork); - sqlite3_finalize(_sDeleteNetwork); - sqlite3_finalize(_sCreateRoute); - sqlite3_finalize(_sGetRoutes); - sqlite3_finalize(_sDeleteRoutes); - sqlite3_finalize(_sIncrementMemberRevisionCounter); - sqlite3_finalize(_sGetConfig); - sqlite3_finalize(_sSetConfig); - sqlite3_close(_db); - } -} - -NetworkController::ResultCode SqliteNetworkController::doNetworkConfigRequest(const InetAddress &fromAddr,const Identity &signingId,const Identity &identity,uint64_t nwid,const Dictionary &metaData,NetworkConfig &nc) -{ - if (((!signingId)||(!signingId.hasPrivate()))||(signingId.address().toInt() != (nwid >> 24))) { - return NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR; - } - - const uint64_t now = OSUtils::now(); - - NetworkRecord network; - Utils::snprintf(network.id,sizeof(network.id),"%.16llx",(unsigned long long)nwid); - - MemberRecord member; - Utils::snprintf(member.nodeId,sizeof(member.nodeId),"%.10llx",(unsigned long long)identity.address().toInt()); - - { // begin lock - Mutex::Lock _l(_lock); - - // Check rate limit circuit breaker to prevent flooding - { - uint64_t &lrt = _lastRequestTime[std::pair(identity.address().toInt(),nwid)]; - if ((now - lrt) <= ZT_NETCONF_MIN_REQUEST_PERIOD) - return NetworkController::NETCONF_QUERY_IGNORE; - lrt = now; - } - - _backupNeeded = true; - - // Create Node record or do full identity check if we already have one - - sqlite3_reset(_sGetNodeIdentity); - sqlite3_bind_text(_sGetNodeIdentity,1,member.nodeId,10,SQLITE_STATIC); - if (sqlite3_step(_sGetNodeIdentity) == SQLITE_ROW) { - try { - Identity alreadyKnownIdentity((const char *)sqlite3_column_text(_sGetNodeIdentity,0)); - if (alreadyKnownIdentity != identity) - return NetworkController::NETCONF_QUERY_ACCESS_DENIED; - } catch ( ... ) { // identity stored in database is not valid or is NULL - return NetworkController::NETCONF_QUERY_ACCESS_DENIED; - } - } else { - std::string idstr(identity.toString(false)); - sqlite3_reset(_sCreateOrReplaceNode); - sqlite3_bind_text(_sCreateOrReplaceNode,1,member.nodeId,10,SQLITE_STATIC); - sqlite3_bind_text(_sCreateOrReplaceNode,2,idstr.c_str(),-1,SQLITE_STATIC); - if (sqlite3_step(_sCreateOrReplaceNode) != SQLITE_DONE) { - return NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR; - } - } - - // Fetch Network record - - sqlite3_reset(_sGetNetworkById); - sqlite3_bind_text(_sGetNetworkById,1,network.id,16,SQLITE_STATIC); - if (sqlite3_step(_sGetNetworkById) == SQLITE_ROW) { - network.name = (const char *)sqlite3_column_text(_sGetNetworkById,0); - network.isPrivate = (sqlite3_column_int(_sGetNetworkById,1) > 0); - network.enableBroadcast = (sqlite3_column_int(_sGetNetworkById,2) > 0); - network.allowPassiveBridging = (sqlite3_column_int(_sGetNetworkById,3) > 0); - network.flags = sqlite3_column_int(_sGetNetworkById,4); - network.multicastLimit = sqlite3_column_int(_sGetNetworkById,5); - network.creationTime = (uint64_t)sqlite3_column_int64(_sGetNetworkById,6); - network.revision = (uint64_t)sqlite3_column_int64(_sGetNetworkById,7); - network.memberRevisionCounter = (uint64_t)sqlite3_column_int64(_sGetNetworkById,8); - } else { - return NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND; - } - - // Fetch or create Member record - - sqlite3_reset(_sGetMember); - sqlite3_bind_text(_sGetMember,1,network.id,16,SQLITE_STATIC); - sqlite3_bind_text(_sGetMember,2,member.nodeId,10,SQLITE_STATIC); - if (sqlite3_step(_sGetMember) == SQLITE_ROW) { - member.rowid = sqlite3_column_int64(_sGetMember,0); - member.authorized = (sqlite3_column_int(_sGetMember,1) > 0); - member.activeBridge = (sqlite3_column_int(_sGetMember,2) > 0); - member.lastRequestTime = (uint64_t)sqlite3_column_int64(_sGetMember,5); - const char *rhblob = (const char *)sqlite3_column_blob(_sGetMember,6); - if (rhblob) - member.recentHistory.fromBlob(rhblob,(unsigned int)sqlite3_column_bytes(_sGetMember,6)); - } else { - member.authorized = (network.isPrivate ? false : true); - member.activeBridge = false; - sqlite3_reset(_sCreateMember); - sqlite3_bind_text(_sCreateMember,1,network.id,16,SQLITE_STATIC); - sqlite3_bind_text(_sCreateMember,2,member.nodeId,10,SQLITE_STATIC); - sqlite3_bind_int(_sCreateMember,3,(member.authorized ? 1 : 0)); - sqlite3_bind_text(_sCreateMember,4,network.id,16,SQLITE_STATIC); - if (sqlite3_step(_sCreateMember) != SQLITE_DONE) { - return NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR; - } - member.rowid = sqlite3_last_insert_rowid(_db); - - sqlite3_reset(_sIncrementMemberRevisionCounter); - sqlite3_bind_text(_sIncrementMemberRevisionCounter,1,network.id,16,SQLITE_STATIC); - sqlite3_step(_sIncrementMemberRevisionCounter); - } - - // Update Member.history - - { - char mh[1024]; - Utils::snprintf(mh,sizeof(mh), - "{\"ts\":%llu,\"authorized\":%s,\"clientMajorVersion\":%u,\"clientMinorVersion\":%u,\"clientRevision\":%u,\"fromAddr\":", - (unsigned long long)now, - ((member.authorized) ? "true" : "false"), - metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION,0), - metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MINOR_VERSION,0), - metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_REVISION,0)); - member.recentHistory.push_front(std::string(mh)); - if (fromAddr) { - member.recentHistory.front().push_back('"'); - member.recentHistory.front().append(_jsonEscape(fromAddr.toString())); - member.recentHistory.front().append("\"}"); - } else { - member.recentHistory.front().append("null}"); - } - - while (member.recentHistory.size() > ZT_NETCONF_DB_MEMBER_HISTORY_LENGTH) - member.recentHistory.pop_back(); - std::string rhblob(member.recentHistory.toBlob()); - - sqlite3_reset(_sUpdateMemberHistory); - sqlite3_clear_bindings(_sUpdateMemberHistory); - sqlite3_bind_int64(_sUpdateMemberHistory,1,(sqlite3_int64)now); - sqlite3_bind_blob(_sUpdateMemberHistory,2,(const void *)rhblob.data(),(int)rhblob.length(),SQLITE_STATIC); - sqlite3_bind_int64(_sUpdateMemberHistory,3,member.rowid); - sqlite3_step(_sUpdateMemberHistory); - } - - // Don't proceed if member is not authorized! --------------------------- - - if (!member.authorized) - return NetworkController::NETCONF_QUERY_ACCESS_DENIED; - - // Create network configuration -- we create both legacy and new types and send both for backward compatibility - - // New network config structure - nc.networkId = Utils::hexStrToU64(network.id); - nc.type = network.isPrivate ? ZT_NETWORK_TYPE_PRIVATE : ZT_NETWORK_TYPE_PUBLIC; - nc.timestamp = now; - nc.revision = network.revision; - nc.issuedTo = member.nodeId; - if (network.enableBroadcast) nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST; - if (network.allowPassiveBridging) nc.flags |= ZT_NETWORKCONFIG_FLAG_ALLOW_PASSIVE_BRIDGING; - memcpy(nc.name,network.name,std::min((unsigned int)ZT_MAX_NETWORK_SHORT_NAME_LENGTH,(unsigned int)strlen(network.name))); - - { // TODO: right now only etherTypes are supported in rules - std::vector allowedEtherTypes; - sqlite3_reset(_sGetEtherTypesFromRuleTable); - sqlite3_bind_text(_sGetEtherTypesFromRuleTable,1,network.id,16,SQLITE_STATIC); - while (sqlite3_step(_sGetEtherTypesFromRuleTable) == SQLITE_ROW) { - if (sqlite3_column_type(_sGetEtherTypesFromRuleTable,0) == SQLITE_NULL) { - allowedEtherTypes.clear(); - allowedEtherTypes.push_back(0); // NULL 'allow' matches ANY - break; - } else { - int et = sqlite3_column_int(_sGetEtherTypesFromRuleTable,0); - if ((et >= 0)&&(et <= 0xffff)) - allowedEtherTypes.push_back(et); - } - } - std::sort(allowedEtherTypes.begin(),allowedEtherTypes.end()); - allowedEtherTypes.erase(std::unique(allowedEtherTypes.begin(),allowedEtherTypes.end()),allowedEtherTypes.end()); - - for(long i=0;i<(long)allowedEtherTypes.size();++i) { - if ((nc.ruleCount + 2) > ZT_MAX_NETWORK_RULES) - break; - if (allowedEtherTypes[i] > 0) { - nc.rules[nc.ruleCount].t = ZT_NETWORK_RULE_MATCH_ETHERTYPE; - nc.rules[nc.ruleCount].v.etherType = (uint16_t)allowedEtherTypes[i]; - ++nc.ruleCount; - } - nc.rules[nc.ruleCount++].t = ZT_NETWORK_RULE_ACTION_ACCEPT; - } - } - - nc.multicastLimit = network.multicastLimit; - - bool amActiveBridge = false; - { - sqlite3_reset(_sGetActiveBridges); - sqlite3_bind_text(_sGetActiveBridges,1,network.id,16,SQLITE_STATIC); - while (sqlite3_step(_sGetActiveBridges) == SQLITE_ROW) { - const char *ab = (const char *)sqlite3_column_text(_sGetActiveBridges,0); - if ((ab)&&(strlen(ab) == 10)) { - const uint64_t ab2 = Utils::hexStrToU64(ab); - nc.addSpecialist(Address(ab2),ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE); - if (!strcmp(member.nodeId,ab)) - amActiveBridge = true; - } - } - } - - // Do not send relays to 1.1.0 since it had a serious bug in using them - // 1.1.0 will still work, it'll just fall back to roots instead of using network preferred relays - if (!((metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION,0) == 1)&&(metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MINOR_VERSION,0) == 1)&&(metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_REVISION,0) == 0))) { - sqlite3_reset(_sGetRelays); - sqlite3_bind_text(_sGetRelays,1,network.id,16,SQLITE_STATIC); - while (sqlite3_step(_sGetRelays) == SQLITE_ROW) { - const char *n = (const char *)sqlite3_column_text(_sGetRelays,0); - const char *a = (const char *)sqlite3_column_text(_sGetRelays,1); - if ((n)&&(a)) { - Address node(n); - InetAddress addr(a); - if (node) - nc.addSpecialist(node,ZT_NETWORKCONFIG_SPECIALIST_TYPE_NETWORK_PREFERRED_RELAY); - } - } - } - - sqlite3_reset(_sGetRoutes); - sqlite3_bind_text(_sGetRoutes,1,network.id,16,SQLITE_STATIC); - while ((sqlite3_step(_sGetRoutes) == SQLITE_ROW)&&(nc.routeCount < ZT_MAX_NETWORK_ROUTES)) { - ZT_VirtualNetworkRoute *r = &(nc.routes[nc.routeCount]); - memset(r,0,sizeof(ZT_VirtualNetworkRoute)); - switch(sqlite3_column_int(_sGetRoutes,3)) { // ipVersion - case 4: - *(reinterpret_cast(&(r->target))) = InetAddress((const void *)((const char *)sqlite3_column_blob(_sGetRoutes,0) + 12),4,(unsigned int)sqlite3_column_int(_sGetRoutes,2)); - break; - case 6: - *(reinterpret_cast(&(r->target))) = InetAddress((const void *)sqlite3_column_blob(_sGetRoutes,0),16,(unsigned int)sqlite3_column_int(_sGetRoutes,2)); - break; - default: - continue; - } - if (sqlite3_column_type(_sGetRoutes,1) != SQLITE_NULL) { - switch(sqlite3_column_int(_sGetRoutes,3)) { // ipVersion - case 4: - *(reinterpret_cast(&(r->via))) = InetAddress((const void *)((const char *)sqlite3_column_blob(_sGetRoutes,1) + 12),4,0); - break; - case 6: - *(reinterpret_cast(&(r->via))) = InetAddress((const void *)sqlite3_column_blob(_sGetRoutes,1),16,0); - break; - default: - continue; - } - } - r->flags = (uint16_t)sqlite3_column_int(_sGetRoutes,4); - r->metric = (uint16_t)sqlite3_column_int(_sGetRoutes,5); - ++nc.routeCount; - } - - // Assign special IPv6 addresses if these are enabled - if (((network.flags & ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_RFC4193) != 0)&&(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 (((network.flags & ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_6PLANE) != 0)&&(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; - } - - // Get managed addresses that are assigned to this member - bool haveManagedIpv4AutoAssignment = false; - bool haveManagedIpv6AutoAssignment = false; // "special" NDP-emulated address types do not count - sqlite3_reset(_sGetIpAssignmentsForNode); - sqlite3_bind_text(_sGetIpAssignmentsForNode,1,network.id,16,SQLITE_STATIC); - sqlite3_bind_text(_sGetIpAssignmentsForNode,2,member.nodeId,10,SQLITE_STATIC); - while (sqlite3_step(_sGetIpAssignmentsForNode) == SQLITE_ROW) { - const unsigned char *const ipbytes = (const unsigned char *)sqlite3_column_blob(_sGetIpAssignmentsForNode,0); - if ((!ipbytes)||(sqlite3_column_bytes(_sGetIpAssignmentsForNode,0) != 16)) - continue; - //const int ipNetmaskBits = sqlite3_column_int(_sGetIpAssignmentsForNode,1); - const int ipVersion = sqlite3_column_int(_sGetIpAssignmentsForNode,2); - - InetAddress ip; - if (ipVersion == 4) - ip = InetAddress(ipbytes + 12,4,0); - else if (ipVersion == 6) - ip = InetAddress(ipbytes,16,0); - else continue; - - // IP assignments are only pushed if there is a corresponding local route. We also now get the netmask bits from - // this route, ignoring the netmask bits field of the assigned IP itself. Using that was worthless and a source - // of user error / poor UX. - int routedNetmaskBits = 0; - for(unsigned int rk=0;rk(&(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 (ipVersion == 4) - haveManagedIpv4AutoAssignment = true; - else if (ipVersion == 6) - haveManagedIpv6AutoAssignment = true; - } - } - - // Auto-assign IPv6 address if auto-assignment is enabled and it's needed - if ( ((network.flags & ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_AUTO_ASSIGN) != 0) && (!haveManagedIpv6AutoAssignment) && (!amActiveBridge) ) { - sqlite3_reset(_sGetIpAssignmentPools); - sqlite3_bind_text(_sGetIpAssignmentPools,1,network.id,16,SQLITE_STATIC); - sqlite3_bind_int(_sGetIpAssignmentPools,2,6); // 6 == IPv6 - while (sqlite3_step(_sGetIpAssignmentPools) == SQLITE_ROW) { - const uint8_t *const ipRangeStartB = reinterpret_cast(sqlite3_column_blob(_sGetIpAssignmentPools,0)); - const uint8_t *const ipRangeEndB = reinterpret_cast(sqlite3_column_blob(_sGetIpAssignmentPools,1)); - if ((!ipRangeStartB)||(!ipRangeEndB)||(sqlite3_column_bytes(_sGetIpAssignmentPools,0) != 16)||(sqlite3_column_bytes(_sGetIpAssignmentPools,1) != 16)) - continue; - - uint64_t s[2],e[2],x[2],xx[2]; - memcpy(s,ipRangeStartB,16); - memcpy(e,ipRangeEndB,16); - s[0] = Utils::ntoh(s[0]); - s[1] = Utils::ntoh(s[1]); - e[0] = Utils::ntoh(e[0]); - e[1] = Utils::ntoh(e[1]); - x[0] = s[0]; - x[1] = s[1]; - - for(unsigned int trialCount=0;trialCount<1000;++trialCount) { - if ((trialCount == 0)&&(e[1] > 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) { - sqlite3_reset(_sCheckIfIpIsAllocated); - sqlite3_bind_text(_sCheckIfIpIsAllocated,1,network.id,16,SQLITE_STATIC); - sqlite3_bind_blob(_sCheckIfIpIsAllocated,2,(const void *)ip6.rawIpData(),16,SQLITE_STATIC); - sqlite3_bind_int(_sCheckIfIpIsAllocated,3,6); // 6 == IPv6 - sqlite3_bind_int(_sCheckIfIpIsAllocated,4,(int)0 /*ZT_IP_ASSIGNMENT_TYPE_ADDRESS*/); - if (sqlite3_step(_sCheckIfIpIsAllocated) != SQLITE_ROW) { - // No rows returned, so the IP is available - sqlite3_reset(_sAllocateIp); - sqlite3_bind_text(_sAllocateIp,1,network.id,16,SQLITE_STATIC); - sqlite3_bind_text(_sAllocateIp,2,member.nodeId,10,SQLITE_STATIC); - sqlite3_bind_int(_sAllocateIp,3,(int)0 /*ZT_IP_ASSIGNMENT_TYPE_ADDRESS*/); - sqlite3_bind_blob(_sAllocateIp,4,(const void *)ip6.rawIpData(),16,SQLITE_STATIC); - sqlite3_bind_int(_sAllocateIp,5,routedNetmaskBits); // IP netmask bits from matching route - sqlite3_bind_int(_sAllocateIp,6,6); // 6 == IPv6 - if (sqlite3_step(_sAllocateIp) == SQLITE_DONE) { - ip6.setPort(routedNetmaskBits); - if (nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES) - nc.staticIps[nc.staticIpCount++] = ip6; - break; - } - } - } - } - } - } - - // Auto-assign IPv4 address if auto-assignment is enabled and it's needed - if ( ((network.flags & ZT_DB_NETWORK_FLAG_ZT_MANAGED_V4_AUTO_ASSIGN) != 0) && (!haveManagedIpv4AutoAssignment) && (!amActiveBridge) ) { - sqlite3_reset(_sGetIpAssignmentPools); - sqlite3_bind_text(_sGetIpAssignmentPools,1,network.id,16,SQLITE_STATIC); - sqlite3_bind_int(_sGetIpAssignmentPools,2,4); // 4 == IPv4 - while (sqlite3_step(_sGetIpAssignmentPools) == SQLITE_ROW) { - const unsigned char *ipRangeStartB = reinterpret_cast(sqlite3_column_blob(_sGetIpAssignmentPools,0)); - const unsigned char *ipRangeEndB = reinterpret_cast(sqlite3_column_blob(_sGetIpAssignmentPools,1)); - if ((!ipRangeStartB)||(!ipRangeEndB)||(sqlite3_column_bytes(_sGetIpAssignmentPools,0) != 16)||(sqlite3_column_bytes(_sGetIpAssignmentPools,1) != 16)) - continue; - - uint32_t ipRangeStart = Utils::ntoh(*(reinterpret_cast(ipRangeStartB + 12))); - uint32_t ipRangeEnd = Utils::ntoh(*(reinterpret_cast(ipRangeEndB + 12))); - 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 = 0; - 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 - if (routedNetmaskBits > 0) { - uint32_t ipBlob[4]; // actually a 16-byte blob, we put IPv4s in the last 4 bytes - ipBlob[0] = 0; ipBlob[1] = 0; ipBlob[2] = 0; ipBlob[3] = Utils::hton(ip); - sqlite3_reset(_sCheckIfIpIsAllocated); - sqlite3_bind_text(_sCheckIfIpIsAllocated,1,network.id,16,SQLITE_STATIC); - sqlite3_bind_blob(_sCheckIfIpIsAllocated,2,(const void *)ipBlob,16,SQLITE_STATIC); - sqlite3_bind_int(_sCheckIfIpIsAllocated,3,4); // 4 == IPv4 - sqlite3_bind_int(_sCheckIfIpIsAllocated,4,(int)0 /*ZT_IP_ASSIGNMENT_TYPE_ADDRESS*/); - if (sqlite3_step(_sCheckIfIpIsAllocated) != SQLITE_ROW) { - // No rows returned, so the IP is available - sqlite3_reset(_sAllocateIp); - sqlite3_bind_text(_sAllocateIp,1,network.id,16,SQLITE_STATIC); - sqlite3_bind_text(_sAllocateIp,2,member.nodeId,10,SQLITE_STATIC); - sqlite3_bind_int(_sAllocateIp,3,(int)0 /*ZT_IP_ASSIGNMENT_TYPE_ADDRESS*/); - sqlite3_bind_blob(_sAllocateIp,4,(const void *)ipBlob,16,SQLITE_STATIC); - sqlite3_bind_int(_sAllocateIp,5,routedNetmaskBits); // IP netmask bits from matching route - sqlite3_bind_int(_sAllocateIp,6,4); // 4 == IPv4 - if (sqlite3_step(_sAllocateIp) == SQLITE_DONE) { - 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); - } - break; - } - } - } - } - } - } - } // end lock - - // Perform signing outside lock to enable concurrency - if (network.isPrivate) { - CertificateOfMembership com(now,ZT_NETWORK_COM_DEFAULT_REVISION_MAX_DELTA,nwid,identity.address()); - if (com.sign(signingId)) { - nc.com = com; - } else { - return NETCONF_QUERY_INTERNAL_SERVER_ERROR; - } - } - - return NetworkController::NETCONF_QUERY_OK; -} - -unsigned int SqliteNetworkController::handleControlPlaneHttpGET( - const std::vector &path, - const std::map &urlArgs, - const std::map &headers, - const std::string &body, - std::string &responseBody, - std::string &responseContentType) -{ - Mutex::Lock _l(_lock); - return _doCPGet(path,urlArgs,headers,body,responseBody,responseContentType); -} - -unsigned int SqliteNetworkController::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; - Mutex::Lock _l(_lock); - - _backupNeeded = true; - - 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); - - int64_t revision = 0; - sqlite3_reset(_sGetNetworkRevision); - sqlite3_bind_text(_sGetNetworkRevision,1,nwids,16,SQLITE_STATIC); - bool networkExists = false; - if (sqlite3_step(_sGetNetworkRevision) == SQLITE_ROW) { - networkExists = true; - revision = sqlite3_column_int64(_sGetNetworkRevision,0); - } - - if (path.size() >= 3) { - - if (!networkExists) - return 404; - - 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",address); - - int64_t addToNetworkRevision = 0; - - int64_t memberRowId = 0; - sqlite3_reset(_sGetMember); - sqlite3_bind_text(_sGetMember,1,nwids,16,SQLITE_STATIC); - sqlite3_bind_text(_sGetMember,2,addrs,10,SQLITE_STATIC); - bool memberExists = false; - if (sqlite3_step(_sGetMember) == SQLITE_ROW) { - memberExists = true; - memberRowId = sqlite3_column_int64(_sGetMember,0); - } - - if (!memberExists) { - sqlite3_reset(_sCreateMember); - sqlite3_bind_text(_sCreateMember,1,nwids,16,SQLITE_STATIC); - sqlite3_bind_text(_sCreateMember,2,addrs,10,SQLITE_STATIC); - sqlite3_bind_int(_sCreateMember,3,0); - sqlite3_bind_text(_sCreateMember,4,nwids,16,SQLITE_STATIC); - if (sqlite3_step(_sCreateMember) != SQLITE_DONE) - return 500; - memberRowId = (int64_t)sqlite3_last_insert_rowid(_db); - - sqlite3_reset(_sIncrementMemberRevisionCounter); - sqlite3_bind_text(_sIncrementMemberRevisionCounter,1,nwids,16,SQLITE_STATIC); - sqlite3_step(_sIncrementMemberRevisionCounter); - addToNetworkRevision = 1; - } - - json_value *j = json_parse(body.c_str(),body.length()); - if (j) { - if (j->type == json_object) { - for(unsigned int k=0;ku.object.length;++k) { - - if (!strcmp(j->u.object.values[k].name,"authorized")) { - if (j->u.object.values[k].value->type == json_boolean) { - sqlite3_reset(_sUpdateMemberAuthorized); - sqlite3_bind_int(_sUpdateMemberAuthorized,1,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1); - sqlite3_bind_text(_sUpdateMemberAuthorized,2,nwids,16,SQLITE_STATIC); - sqlite3_bind_int64(_sUpdateMemberAuthorized,3,memberRowId); - if (sqlite3_step(_sUpdateMemberAuthorized) != SQLITE_DONE) - return 500; - - sqlite3_reset(_sIncrementMemberRevisionCounter); - sqlite3_bind_text(_sIncrementMemberRevisionCounter,1,nwids,16,SQLITE_STATIC); - sqlite3_step(_sIncrementMemberRevisionCounter); - addToNetworkRevision = 1; - } - } else if (!strcmp(j->u.object.values[k].name,"activeBridge")) { - if (j->u.object.values[k].value->type == json_boolean) { - sqlite3_reset(_sUpdateMemberActiveBridge); - sqlite3_bind_int(_sUpdateMemberActiveBridge,1,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1); - sqlite3_bind_text(_sUpdateMemberActiveBridge,2,nwids,16,SQLITE_STATIC); - sqlite3_bind_int64(_sUpdateMemberActiveBridge,3,memberRowId); - if (sqlite3_step(_sUpdateMemberActiveBridge) != SQLITE_DONE) - return 500; - - sqlite3_reset(_sIncrementMemberRevisionCounter); - sqlite3_bind_text(_sIncrementMemberRevisionCounter,1,nwids,16,SQLITE_STATIC); - sqlite3_step(_sIncrementMemberRevisionCounter); - addToNetworkRevision = 1; - } - } else if (!strcmp(j->u.object.values[k].name,"ipAssignments")) { - if (j->u.object.values[k].value->type == json_array) { - sqlite3_reset(_sDeleteIpAllocations); - sqlite3_bind_text(_sDeleteIpAllocations,1,nwids,16,SQLITE_STATIC); - sqlite3_bind_text(_sDeleteIpAllocations,2,addrs,10,SQLITE_STATIC); - sqlite3_bind_int(_sDeleteIpAllocations,3,(int)0 /*ZT_IP_ASSIGNMENT_TYPE_ADDRESS*/); - if (sqlite3_step(_sDeleteIpAllocations) != SQLITE_DONE) - return 500; - for(unsigned int kk=0;kku.object.values[k].value->u.array.length;++kk) { - json_value *ipalloc = j->u.object.values[k].value->u.array.values[kk]; - if (ipalloc->type == json_string) { - InetAddress a(ipalloc->u.string.ptr); - char ipBlob[16]; - int ipVersion = 0; - _ipToBlob(a,ipBlob,ipVersion); - if (ipVersion > 0) { - sqlite3_reset(_sAllocateIp); - sqlite3_bind_text(_sAllocateIp,1,nwids,16,SQLITE_STATIC); - sqlite3_bind_text(_sAllocateIp,2,addrs,10,SQLITE_STATIC); - sqlite3_bind_int(_sAllocateIp,3,(int)0 /*ZT_IP_ASSIGNMENT_TYPE_ADDRESS*/); - sqlite3_bind_blob(_sAllocateIp,4,(const void *)ipBlob,16,SQLITE_STATIC); - sqlite3_bind_int(_sAllocateIp,5,(int)a.netmaskBits()); // NOTE: this field is now ignored but set it anyway - sqlite3_bind_int(_sAllocateIp,6,ipVersion); - if (sqlite3_step(_sAllocateIp) != SQLITE_DONE) - return 500; - } - } - } - addToNetworkRevision = 1; - } - } else if (!strcmp(j->u.object.values[k].name,"identity")) { - // Identity is technically an immutable field, but if the member's Node has - // no identity we allow it to be populated. This is primarily for migrating - // node data from another controller. - json_value *idstr = j->u.object.values[k].value; - if (idstr->type == json_string) { - bool alreadyHaveIdentity = false; - - sqlite3_reset(_sGetNodeIdentity); - sqlite3_bind_text(_sGetNodeIdentity,1,addrs,10,SQLITE_STATIC); - if (sqlite3_step(_sGetNodeIdentity) == SQLITE_ROW) { - const char *tmp2 = (const char *)sqlite3_column_text(_sGetNodeIdentity,0); - if ((tmp2)&&(tmp2[0])) - alreadyHaveIdentity = true; - } - - if (!alreadyHaveIdentity) { - try { - Identity id2(idstr->u.string.ptr); - if (id2) { - std::string idstr2(id2.toString(false)); // object must persist until after sqlite3_step() for SQLITE_STATIC - sqlite3_reset(_sCreateOrReplaceNode); - sqlite3_bind_text(_sCreateOrReplaceNode,1,addrs,10,SQLITE_STATIC); - sqlite3_bind_text(_sCreateOrReplaceNode,2,idstr2.c_str(),-1,SQLITE_STATIC); - sqlite3_step(_sCreateOrReplaceNode); - } - } catch ( ... ) {} // ignore invalid identities - } - } - } - - } - } - json_value_free(j); - } - - if ((addToNetworkRevision > 0)&&(revision > 0)) { - sqlite3_reset(_sSetNetworkRevision); - sqlite3_bind_int64(_sSetNetworkRevision,1,revision + addToNetworkRevision); - sqlite3_bind_text(_sSetNetworkRevision,2,nwids,16,SQLITE_STATIC); - sqlite3_step(_sSetNetworkRevision); - } - - return _doCPGet(path,urlArgs,headers,body,responseBody,responseContentType); - } else if ((path.size() == 3)&&(path[2] == "test")) { - ZT_CircuitTest *test = (ZT_CircuitTest *)malloc(sizeof(ZT_CircuitTest)); - memset(test,0,sizeof(ZT_CircuitTest)); - - Utils::getSecureRandom(&(test->testId),sizeof(test->testId)); - test->credentialNetworkId = nwid; - test->ptr = (void *)this; - - json_value *j = json_parse(body.c_str(),body.length()); - if (j) { - if (j->type == json_object) { - for(unsigned int k=0;ku.object.length;++k) { - - if (!strcmp(j->u.object.values[k].name,"hops")) { - if (j->u.object.values[k].value->type == json_array) { - for(unsigned int kk=0;kku.object.values[k].value->u.array.length;++kk) { - json_value *hop = j->u.object.values[k].value->u.array.values[kk]; - if (hop->type == json_array) { - for(unsigned int kkk=0;kkku.array.length;++kkk) { - if (hop->u.array.values[kkk]->type == json_string) { - test->hops[test->hopCount].addresses[test->hops[test->hopCount].breadth++] = Utils::hexStrToU64(hop->u.array.values[kkk]->u.string.ptr) & 0xffffffffffULL; - } - } - ++test->hopCount; - } - } - } - } else if (!strcmp(j->u.object.values[k].name,"reportAtEveryHop")) { - if (j->u.object.values[k].value->type == json_boolean) - test->reportAtEveryHop = (j->u.object.values[k].value->u.boolean == 0) ? 0 : 1; - } - - } - } - json_value_free(j); - } - - if (!test->hopCount) { - ::free((void *)test); - return 500; - } - - test->timestamp = OSUtils::now(); - - _CircuitTestEntry &te = _circuitTests[test->testId]; - te.test = test; - te.jsonResults = ""; - - _node->circuitTestBegin(test,&(SqliteNetworkController::_circuitTestCallback)); - - char json[1024]; - Utils::snprintf(json,sizeof(json),"{\"testId\":\"%.16llx\"}",test->testId); - responseBody = json; - responseContentType = "application/json"; - - return 200; - } // else 404 - - } else { - std::vector path_copy(path); - - if (!networkExists) { - if (path[1].substr(10) == "______") { - // A special POST /network/##########______ feature lets users create a network - // with an arbitrary unused network number at this controller. - nwid = 0; - - uint64_t nwidPrefix = (Utils::hexStrToU64(path[1].substr(0,10).c_str()) << 24) & 0xffffffffff000000ULL; - uint64_t nwidPostfix = 0; - Utils::getSecureRandom(&nwidPostfix,sizeof(nwidPostfix)); - uint64_t nwidOriginalPostfix = nwidPostfix; - do { - uint64_t tryNwid = nwidPrefix | (nwidPostfix & 0xffffffULL); - if (!nwidPostfix) - tryNwid |= 1; - Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)tryNwid); - - sqlite3_reset(_sGetNetworkRevision); - sqlite3_bind_text(_sGetNetworkRevision,1,nwids,16,SQLITE_STATIC); - if (sqlite3_step(_sGetNetworkRevision) != SQLITE_ROW) { - nwid = tryNwid; - break; - } - - ++nwidPostfix; - } while (nwidPostfix != nwidOriginalPostfix); - - // 503 means we have no more free IDs for this prefix. You shouldn't host anywhere - // near 16 million networks on the same controller, so shouldn't happen. - if (!nwid) - return 503; - } - - sqlite3_reset(_sCreateNetwork); - sqlite3_bind_text(_sCreateNetwork,1,nwids,16,SQLITE_STATIC); - sqlite3_bind_text(_sCreateNetwork,2,"",0,SQLITE_STATIC); - sqlite3_bind_int64(_sCreateNetwork,3,(long long)OSUtils::now()); - if (sqlite3_step(_sCreateNetwork) != SQLITE_DONE) - return 500; - path_copy[1].assign(nwids); - } - - json_value *j = json_parse(body.c_str(),body.length()); - if (j) { - if (j->type == json_object) { - for(unsigned int k=0;ku.object.length;++k) { - sqlite3_stmt *stmt = (sqlite3_stmt *)0; - - if (!strcmp(j->u.object.values[k].name,"name")) { - if ((j->u.object.values[k].value->type == json_string)&&(j->u.object.values[k].value->u.string.ptr[0])) { - if (sqlite3_prepare_v2(_db,"UPDATE Network SET \"name\" = ? WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK) - sqlite3_bind_text(stmt,1,j->u.object.values[k].value->u.string.ptr,-1,SQLITE_STATIC); - } - } else if (!strcmp(j->u.object.values[k].name,"private")) { - if (j->u.object.values[k].value->type == json_boolean) { - if (sqlite3_prepare_v2(_db,"UPDATE Network SET \"private\" = ? WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK) - sqlite3_bind_int(stmt,1,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1); - } - } else if (!strcmp(j->u.object.values[k].name,"enableBroadcast")) { - if (j->u.object.values[k].value->type == json_boolean) { - if (sqlite3_prepare_v2(_db,"UPDATE Network SET enableBroadcast = ? WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK) - sqlite3_bind_int(stmt,1,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1); - } - } else if (!strcmp(j->u.object.values[k].name,"allowPassiveBridging")) { - if (j->u.object.values[k].value->type == json_boolean) { - if (sqlite3_prepare_v2(_db,"UPDATE Network SET allowPassiveBridging = ? WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK) - sqlite3_bind_int(stmt,1,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1); - } - } else if (!strcmp(j->u.object.values[k].name,"v4AssignMode")) { - if ((j->u.object.values[k].value->type == json_string)&&(!strcmp(j->u.object.values[k].value->u.string.ptr,"zt"))) { - if (sqlite3_prepare_v2(_db,"UPDATE Network SET \"flags\" = (\"flags\" | ?) WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK) - sqlite3_bind_int(stmt,1,(int)ZT_DB_NETWORK_FLAG_ZT_MANAGED_V4_AUTO_ASSIGN); - } else { - if (sqlite3_prepare_v2(_db,"UPDATE Network SET \"flags\" = (\"flags\" & ?) WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK) - sqlite3_bind_int(stmt,1,(int)(ZT_DB_NETWORK_FLAG_ZT_MANAGED_V4_AUTO_ASSIGN ^ 0xfffffff)); - } - } else if (!strcmp(j->u.object.values[k].name,"v6AssignMode")) { - int fl = 0; - if (j->u.object.values[k].value->type == json_string) { - char *saveptr = (char *)0; - for(char *f=Utils::stok(j->u.object.values[k].value->u.string.ptr,",",&saveptr);(f);f=Utils::stok((char *)0,",",&saveptr)) { - if (!strcmp(f,"rfc4193")) - fl |= ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_RFC4193; - else if (!strcmp(f,"6plane")) - fl |= ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_6PLANE; - else if (!strcmp(f,"zt")) - fl |= ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_AUTO_ASSIGN; - } - } - if (sqlite3_prepare_v2(_db,"UPDATE Network SET \"flags\" = ((\"flags\" & " ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_MASK_S ") | ?) WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK) - sqlite3_bind_int(stmt,1,fl); - } else if (!strcmp(j->u.object.values[k].name,"multicastLimit")) { - if (j->u.object.values[k].value->type == json_integer) { - if (sqlite3_prepare_v2(_db,"UPDATE Network SET multicastLimit = ? WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK) - sqlite3_bind_int(stmt,1,(int)j->u.object.values[k].value->u.integer); - } - } else if (!strcmp(j->u.object.values[k].name,"relays")) { - if (j->u.object.values[k].value->type == json_array) { - std::map nodeIdToPhyAddress; - for(unsigned int kk=0;kku.object.values[k].value->u.array.length;++kk) { - json_value *relay = j->u.object.values[k].value->u.array.values[kk]; - const char *address = (const char *)0; - const char *phyAddress = (const char *)0; - if ((relay)&&(relay->type == json_object)) { - for(unsigned int rk=0;rku.object.length;++rk) { - if ((!strcmp(relay->u.object.values[rk].name,"address"))&&(relay->u.object.values[rk].value->type == json_string)) - address = relay->u.object.values[rk].value->u.string.ptr; - else if ((!strcmp(relay->u.object.values[rk].name,"phyAddress"))&&(relay->u.object.values[rk].value->type == json_string)) - phyAddress = relay->u.object.values[rk].value->u.string.ptr; - } - } - if ((address)&&(phyAddress)) - nodeIdToPhyAddress[Address(address)] = InetAddress(phyAddress); - } - - sqlite3_reset(_sDeleteRelaysForNetwork); - sqlite3_bind_text(_sDeleteRelaysForNetwork,1,nwids,16,SQLITE_STATIC); - sqlite3_step(_sDeleteRelaysForNetwork); - - for(std::map::iterator rl(nodeIdToPhyAddress.begin());rl!=nodeIdToPhyAddress.end();++rl) { - sqlite3_reset(_sCreateRelay); - sqlite3_bind_text(_sCreateRelay,1,nwids,16,SQLITE_STATIC); - std::string a(rl->first.toString()),b(rl->second.toString()); // don't destroy strings until sqlite3_step() - sqlite3_bind_text(_sCreateRelay,2,a.c_str(),-1,SQLITE_STATIC); - sqlite3_bind_text(_sCreateRelay,3,b.c_str(),-1,SQLITE_STATIC); - sqlite3_step(_sCreateRelay); - } - } - } else if (!strcmp(j->u.object.values[k].name,"routes")) { - sqlite3_reset(_sDeleteRoutes); - sqlite3_bind_text(_sDeleteRoutes,1,nwids,16,SQLITE_STATIC); - sqlite3_step(_sDeleteRoutes); - if (j->u.object.values[k].value->type == json_array) { - for(unsigned int kk=0;kku.object.values[k].value->u.array.length;++kk) { - json_value *r = j->u.object.values[k].value->u.array.values[kk]; - if ((r)&&(r->type == json_object)) { - InetAddress r_target,r_via; - int r_flags = 0; - int r_metric = 0; - for(unsigned int rk=0;rku.object.length;++rk) { - if ((!strcmp(r->u.object.values[rk].name,"target"))&&(r->u.object.values[rk].value->type == json_string)) - r_target = InetAddress(std::string(r->u.object.values[rk].value->u.string.ptr)); - else if ((!strcmp(r->u.object.values[rk].name,"via"))&&(r->u.object.values[rk].value->type == json_string)) - r_via = InetAddress(std::string(r->u.object.values[rk].value->u.string.ptr),0); - else if ((!strcmp(r->u.object.values[rk].name,"flags"))&&(r->u.object.values[rk].value->type == json_integer)) - r_flags = (int)(r->u.object.values[rk].value->u.integer & 0xffff); - else if ((!strcmp(r->u.object.values[rk].name,"metric"))&&(r->u.object.values[rk].value->type == json_integer)) - r_metric = (int)(r->u.object.values[rk].value->u.integer & 0xffff); - } - if ((r_target)&&((!r_via)||(r_via.ss_family == r_target.ss_family))) { - int r_ipVersion = 0; - char r_targetBlob[16]; - char r_viaBlob[16]; - _ipToBlob(r_target,r_targetBlob,r_ipVersion); - if (r_ipVersion) { - int r_targetNetmaskBits = r_target.netmaskBits(); - if ((r_ipVersion == 4)&&(r_targetNetmaskBits > 32)) r_targetNetmaskBits = 32; - else if ((r_ipVersion == 6)&&(r_targetNetmaskBits > 128)) r_targetNetmaskBits = 128; - sqlite3_reset(_sCreateRoute); - sqlite3_bind_text(_sCreateRoute,1,nwids,16,SQLITE_STATIC); - sqlite3_bind_blob(_sCreateRoute,2,(const void *)r_targetBlob,16,SQLITE_STATIC); - if (r_via) { - _ipToBlob(r_via,r_viaBlob,r_ipVersion); - sqlite3_bind_blob(_sCreateRoute,3,(const void *)r_viaBlob,16,SQLITE_STATIC); - } else { - sqlite3_bind_null(_sCreateRoute,3); - } - sqlite3_bind_int(_sCreateRoute,4,r_targetNetmaskBits); - sqlite3_bind_int(_sCreateRoute,5,r_ipVersion); - sqlite3_bind_int(_sCreateRoute,6,r_flags); - sqlite3_bind_int(_sCreateRoute,7,r_metric); - sqlite3_step(_sCreateRoute); - } - } - } - } - } - } else if (!strcmp(j->u.object.values[k].name,"ipAssignmentPools")) { - if (j->u.object.values[k].value->type == json_array) { - std::vector< std::pair > pools; - for(unsigned int kk=0;kku.object.values[k].value->u.array.length;++kk) { - json_value *pool = j->u.object.values[k].value->u.array.values[kk]; - const char *iprs = (const char *)0; - const char *ipre = (const char *)0; - if ((pool)&&(pool->type == json_object)) { - for(unsigned int rk=0;rku.object.length;++rk) { - if ((!strcmp(pool->u.object.values[rk].name,"ipRangeStart"))&&(pool->u.object.values[rk].value->type == json_string)) - iprs = pool->u.object.values[rk].value->u.string.ptr; - else if ((!strcmp(pool->u.object.values[rk].name,"ipRangeEnd"))&&(pool->u.object.values[rk].value->type == json_string)) - ipre = pool->u.object.values[rk].value->u.string.ptr; - } - } - if ((iprs)&&(ipre)) { - InetAddress iprs2(iprs); - InetAddress ipre2(ipre); - if (iprs2.ss_family == ipre2.ss_family) { - iprs2.setPort(0); - ipre2.setPort(0); - pools.push_back(std::pair(iprs2,ipre2)); - } - } - } - std::sort(pools.begin(),pools.end()); - pools.erase(std::unique(pools.begin(),pools.end()),pools.end()); - - sqlite3_reset(_sDeleteIpAssignmentPoolsForNetwork); - sqlite3_bind_text(_sDeleteIpAssignmentPoolsForNetwork,1,nwids,16,SQLITE_STATIC); - sqlite3_step(_sDeleteIpAssignmentPoolsForNetwork); - - for(std::vector< std::pair >::const_iterator p(pools.begin());p!=pools.end();++p) { - char ipBlob1[16],ipBlob2[16]; - sqlite3_reset(_sCreateIpAssignmentPool); - sqlite3_bind_text(_sCreateIpAssignmentPool,1,nwids,16,SQLITE_STATIC); - if (p->first.ss_family == AF_INET) { - memset(ipBlob1,0,12); - memcpy(ipBlob1 + 12,p->first.rawIpData(),4); - memset(ipBlob2,0,12); - memcpy(ipBlob2 + 12,p->second.rawIpData(),4); - sqlite3_bind_blob(_sCreateIpAssignmentPool,2,(const void *)ipBlob1,16,SQLITE_STATIC); - sqlite3_bind_blob(_sCreateIpAssignmentPool,3,(const void *)ipBlob2,16,SQLITE_STATIC); - sqlite3_bind_int(_sCreateIpAssignmentPool,4,4); - } else if (p->first.ss_family == AF_INET6) { - sqlite3_bind_blob(_sCreateIpAssignmentPool,2,p->first.rawIpData(),16,SQLITE_STATIC); - sqlite3_bind_blob(_sCreateIpAssignmentPool,3,p->second.rawIpData(),16,SQLITE_STATIC); - sqlite3_bind_int(_sCreateIpAssignmentPool,4,6); - } else continue; - sqlite3_step(_sCreateIpAssignmentPool); - } - } - } else if (!strcmp(j->u.object.values[k].name,"rules")) { - if (j->u.object.values[k].value->type == json_array) { - sqlite3_reset(_sDeleteRulesForNetwork); - sqlite3_bind_text(_sDeleteRulesForNetwork,1,nwids,16,SQLITE_STATIC); - sqlite3_step(_sDeleteRulesForNetwork); - - for(unsigned int kk=0;kku.object.values[k].value->u.array.length;++kk) { - json_value *rj = j->u.object.values[k].value->u.array.values[kk]; - if ((rj)&&(rj->type == json_object)) { - struct { // NULL pointers indicate missing or NULL -- wildcards - const json_int_t *ruleNo; - const char *nodeId; - const char *sourcePort; - const char *destPort; - const json_int_t *vlanId; - const json_int_t *vlanPcp; - const json_int_t *etherType; - const char *macSource; - const char *macDest; - const char *ipSource; - const char *ipDest; - const json_int_t *ipTos; - const json_int_t *ipProtocol; - const json_int_t *ipSourcePort; - const json_int_t *ipDestPort; - const json_int_t *flags; - const json_int_t *invFlags; - const char *action; - } rule; - memset(&rule,0,sizeof(rule)); - - for(unsigned int rk=0;rku.object.length;++rk) { - if ((!strcmp(rj->u.object.values[rk].name,"ruleNo"))&&(rj->u.object.values[rk].value->type == json_integer)) - rule.ruleNo = &(rj->u.object.values[rk].value->u.integer); - else if ((!strcmp(rj->u.object.values[rk].name,"nodeId"))&&(rj->u.object.values[rk].value->type == json_string)) - rule.nodeId = rj->u.object.values[rk].value->u.string.ptr; - else if ((!strcmp(rj->u.object.values[rk].name,"sourcePort"))&&(rj->u.object.values[rk].value->type == json_string)) - rule.sourcePort = rj->u.object.values[rk].value->u.string.ptr; - else if ((!strcmp(rj->u.object.values[rk].name,"destPort"))&&(rj->u.object.values[rk].value->type == json_string)) - rule.destPort = rj->u.object.values[rk].value->u.string.ptr; - else if ((!strcmp(rj->u.object.values[rk].name,"vlanId"))&&(rj->u.object.values[rk].value->type == json_integer)) - rule.vlanId = &(rj->u.object.values[rk].value->u.integer); - else if ((!strcmp(rj->u.object.values[rk].name,"vlanPcp"))&&(rj->u.object.values[rk].value->type == json_integer)) - rule.vlanPcp = &(rj->u.object.values[rk].value->u.integer); - else if ((!strcmp(rj->u.object.values[rk].name,"etherType"))&&(rj->u.object.values[rk].value->type == json_integer)) - rule.etherType = &(rj->u.object.values[rk].value->u.integer); - else if ((!strcmp(rj->u.object.values[rk].name,"macSource"))&&(rj->u.object.values[rk].value->type == json_string)) - rule.macSource = rj->u.object.values[rk].value->u.string.ptr; - else if ((!strcmp(rj->u.object.values[rk].name,"macDest"))&&(rj->u.object.values[rk].value->type == json_string)) - rule.macDest = rj->u.object.values[rk].value->u.string.ptr; - else if ((!strcmp(rj->u.object.values[rk].name,"ipSource"))&&(rj->u.object.values[rk].value->type == json_string)) - rule.ipSource = rj->u.object.values[rk].value->u.string.ptr; - else if ((!strcmp(rj->u.object.values[rk].name,"ipDest"))&&(rj->u.object.values[rk].value->type == json_string)) - rule.ipDest = rj->u.object.values[rk].value->u.string.ptr; - else if ((!strcmp(rj->u.object.values[rk].name,"ipTos"))&&(rj->u.object.values[rk].value->type == json_integer)) - rule.ipTos = &(rj->u.object.values[rk].value->u.integer); - else if ((!strcmp(rj->u.object.values[rk].name,"ipProtocol"))&&(rj->u.object.values[rk].value->type == json_integer)) - rule.ipProtocol = &(rj->u.object.values[rk].value->u.integer); - else if ((!strcmp(rj->u.object.values[rk].name,"ipSourcePort"))&&(rj->u.object.values[rk].value->type == json_integer)) - rule.ipSourcePort = &(rj->u.object.values[rk].value->u.integer); - else if ((!strcmp(rj->u.object.values[rk].name,"ipDestPort"))&&(rj->u.object.values[rk].value->type == json_integer)) - rule.ipDestPort = &(rj->u.object.values[rk].value->u.integer); - else if ((!strcmp(rj->u.object.values[rk].name,"flags"))&&(rj->u.object.values[rk].value->type == json_integer)) - rule.flags = &(rj->u.object.values[rk].value->u.integer); - else if ((!strcmp(rj->u.object.values[rk].name,"invFlags"))&&(rj->u.object.values[rk].value->type == json_integer)) - rule.invFlags = &(rj->u.object.values[rk].value->u.integer); - else if ((!strcmp(rj->u.object.values[rk].name,"action"))&&(rj->u.object.values[rk].value->type == json_string)) - rule.action = rj->u.object.values[rk].value->u.string.ptr; - } - - if ((rule.ruleNo)&&(rule.action)&&(rule.action[0])) { - char mactmp1[16],mactmp2[16]; - sqlite3_reset(_sCreateRule); - sqlite3_bind_text(_sCreateRule,1,nwids,16,SQLITE_STATIC); - sqlite3_bind_int64(_sCreateRule,2,*rule.ruleNo); - - // Optional values: null by default - for(int i=3;i<=18;++i) - sqlite3_bind_null(_sCreateRule,i); - if ((rule.nodeId)&&(strlen(rule.nodeId) == 10)) sqlite3_bind_text(_sCreateRule,3,rule.nodeId,10,SQLITE_STATIC); - if ((rule.sourcePort)&&(strlen(rule.sourcePort) == 10)) sqlite3_bind_text(_sCreateRule,4,rule.sourcePort,10,SQLITE_STATIC); - if ((rule.destPort)&&(strlen(rule.destPort) == 10)) sqlite3_bind_text(_sCreateRule,5,rule.destPort,10,SQLITE_STATIC); - if (rule.vlanId) sqlite3_bind_int(_sCreateRule,6,(int)*rule.vlanId); - if (rule.vlanPcp) sqlite3_bind_int(_sCreateRule,7,(int)*rule.vlanPcp); - if (rule.etherType) sqlite3_bind_int(_sCreateRule,8,(int)*rule.etherType & (int)0xffff); - if (rule.macSource) { - MAC m(rule.macSource); - Utils::snprintf(mactmp1,sizeof(mactmp1),"%.12llx",(unsigned long long)m.toInt()); - sqlite3_bind_text(_sCreateRule,9,mactmp1,-1,SQLITE_STATIC); - } - if (rule.macDest) { - MAC m(rule.macDest); - Utils::snprintf(mactmp2,sizeof(mactmp2),"%.12llx",(unsigned long long)m.toInt()); - sqlite3_bind_text(_sCreateRule,10,mactmp2,-1,SQLITE_STATIC); - } - if (rule.ipSource) sqlite3_bind_text(_sCreateRule,11,rule.ipSource,-1,SQLITE_STATIC); - if (rule.ipDest) sqlite3_bind_text(_sCreateRule,12,rule.ipDest,-1,SQLITE_STATIC); - if (rule.ipTos) sqlite3_bind_int(_sCreateRule,13,(int)*rule.ipTos); - if (rule.ipProtocol) sqlite3_bind_int(_sCreateRule,14,(int)*rule.ipProtocol); - if (rule.ipSourcePort) sqlite3_bind_int(_sCreateRule,15,(int)*rule.ipSourcePort & (int)0xffff); - if (rule.ipDestPort) sqlite3_bind_int(_sCreateRule,16,(int)*rule.ipDestPort & (int)0xffff); - if (rule.flags) sqlite3_bind_int64(_sCreateRule,17,(int64_t)*rule.flags); - if (rule.invFlags) sqlite3_bind_int64(_sCreateRule,18,(int64_t)*rule.invFlags); - - sqlite3_bind_text(_sCreateRule,19,rule.action,-1,SQLITE_STATIC); - sqlite3_step(_sCreateRule); - } - } - } - } - } - - if (stmt) { - sqlite3_bind_text(stmt,2,nwids,16,SQLITE_STATIC); - sqlite3_step(stmt); - sqlite3_finalize(stmt); - } - } - } - json_value_free(j); - } - - sqlite3_reset(_sSetNetworkRevision); - sqlite3_bind_int64(_sSetNetworkRevision,1,revision += 1); - sqlite3_bind_text(_sSetNetworkRevision,2,nwids,16,SQLITE_STATIC); - sqlite3_step(_sSetNetworkRevision); - - return _doCPGet(path_copy,urlArgs,headers,body,responseBody,responseContentType); - } - - } // else 404 - - } // else 404 - - return 404; -} - -unsigned int SqliteNetworkController::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; - Mutex::Lock _l(_lock); - - _backupNeeded = true; - - 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); - - sqlite3_reset(_sGetNetworkById); - sqlite3_bind_text(_sGetNetworkById,1,nwids,16,SQLITE_STATIC); - if (sqlite3_step(_sGetNetworkById) != SQLITE_ROW) - return 404; - - 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",address); - - sqlite3_reset(_sGetMember); - sqlite3_bind_text(_sGetMember,1,nwids,16,SQLITE_STATIC); - sqlite3_bind_text(_sGetMember,2,addrs,10,SQLITE_STATIC); - if (sqlite3_step(_sGetMember) != SQLITE_ROW) - return 404; - - sqlite3_reset(_sDeleteIpAllocations); - sqlite3_bind_text(_sDeleteIpAllocations,1,nwids,16,SQLITE_STATIC); - sqlite3_bind_text(_sDeleteIpAllocations,2,addrs,10,SQLITE_STATIC); - sqlite3_bind_int(_sDeleteIpAllocations,3,(int)0 /*ZT_IP_ASSIGNMENT_TYPE_ADDRESS*/); - if (sqlite3_step(_sDeleteIpAllocations) == SQLITE_DONE) { - sqlite3_reset(_sDeleteMember); - sqlite3_bind_text(_sDeleteMember,1,nwids,16,SQLITE_STATIC); - sqlite3_bind_text(_sDeleteMember,2,addrs,10,SQLITE_STATIC); - if (sqlite3_step(_sDeleteMember) != SQLITE_DONE) - return 500; - } else return 500; - - return 200; - } - - } else { - - sqlite3_reset(_sDeleteNetwork); - sqlite3_bind_text(_sDeleteNetwork,1,nwids,16,SQLITE_STATIC); - if (sqlite3_step(_sDeleteNetwork) == SQLITE_DONE) { - sqlite3_reset(_sDeleteAllNetworkMembers); - sqlite3_bind_text(_sDeleteAllNetworkMembers,1,nwids,16,SQLITE_STATIC); - sqlite3_step(_sDeleteAllNetworkMembers); - return 200; - } else return 500; - - } - } // else 404 - - } // else 404 - - return 404; -} - -void SqliteNetworkController::threadMain() - throw() -{ - uint64_t lastBackupTime = OSUtils::now(); - uint64_t lastCleanupTime = OSUtils::now(); - - while (_backupThreadRun) { - if ((OSUtils::now() - lastCleanupTime) >= 5000) { - const uint64_t now = OSUtils::now(); - lastCleanupTime = now; - - Mutex::Lock _l(_lock); - - // Clean out really old circuit tests to prevent memory build-up - for(std::map< uint64_t,_CircuitTestEntry >::iterator ct(_circuitTests.begin());ct!=_circuitTests.end();) { - if (!ct->second.test) { - _circuitTests.erase(ct++); - } else if ((now - ct->second.test->timestamp) >= ZT_SQLITENETWORKCONTROLLER_CIRCUIT_TEST_TIMEOUT) { - _node->circuitTestEnd(ct->second.test); - ::free((void *)ct->second.test); - _circuitTests.erase(ct++); - } else ++ct; - } - } - - if (((OSUtils::now() - lastBackupTime) >= ZT_NETCONF_BACKUP_PERIOD)&&(_backupNeeded)) { - lastBackupTime = OSUtils::now(); - - char backupPath[4096],backupPath2[4096]; - Utils::snprintf(backupPath,sizeof(backupPath),"%s.backupInProgress",_dbPath.c_str()); - Utils::snprintf(backupPath2,sizeof(backupPath),"%s.backup",_dbPath.c_str()); - OSUtils::rm(backupPath); // delete any unfinished backups - - sqlite3 *bakdb = (sqlite3 *)0; - sqlite3_backup *bak = (sqlite3_backup *)0; - if (sqlite3_open_v2(backupPath,&bakdb,SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE,(const char *)0) != SQLITE_OK) { - fprintf(stderr,"SqliteNetworkController: CRITICAL: backup failed on sqlite3_open_v2()"ZT_EOL_S); - continue; - } - bak = sqlite3_backup_init(bakdb,"main",_db,"main"); - if (!bak) { - sqlite3_close(bakdb); - OSUtils::rm(backupPath); // delete any unfinished backups - fprintf(stderr,"SqliteNetworkController: CRITICAL: backup failed on sqlite3_backup_init()"ZT_EOL_S); - continue; - } - - int rc = SQLITE_OK; - for(;;) { - if (!_backupThreadRun) { - sqlite3_backup_finish(bak); - sqlite3_close(bakdb); - OSUtils::rm(backupPath); - return; - } - _lock.lock(); - rc = sqlite3_backup_step(bak,64); - _lock.unlock(); - if ((rc == SQLITE_OK)||(rc == SQLITE_LOCKED)||(rc == SQLITE_BUSY)) - Thread::sleep(50); - else break; - } - - sqlite3_backup_finish(bak); - sqlite3_close(bakdb); - - OSUtils::rm(backupPath2); - ::rename(backupPath,backupPath2); - - _backupNeeded = false; - } - - Thread::sleep(250); - } -} - -unsigned int SqliteNetworkController::_doCPGet( - const std::vector &path, - const std::map &urlArgs, - const std::map &headers, - const std::string &body, - std::string &responseBody, - std::string &responseContentType) -{ - // Assumes _lock is locked - char json[65536]; - - if ((path.size() > 0)&&(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) { - // /network//... - - if (path[2] == "member") { - - if (path.size() >= 4) { - // Get specific member info - - uint64_t address = Utils::hexStrToU64(path[3].c_str()); - char addrs[24]; - Utils::snprintf(addrs,sizeof(addrs),"%.10llx",address); - - sqlite3_reset(_sGetMember2); - sqlite3_bind_text(_sGetMember2,1,nwids,16,SQLITE_STATIC); - sqlite3_bind_text(_sGetMember2,2,addrs,10,SQLITE_STATIC); - if (sqlite3_step(_sGetMember2) == SQLITE_ROW) { - const char *memberIdStr = (const char *)sqlite3_column_text(_sGetMember2,3); - - Utils::snprintf(json,sizeof(json), - "{\n" - "\t\"nwid\": \"%s\",\n" - "\t\"address\": \"%s\",\n" - "\t\"controllerInstanceId\": \"%s\",\n" - "\t\"authorized\": %s,\n" - "\t\"activeBridge\": %s,\n" - "\t\"memberRevision\": %llu,\n" - "\t\"clock\": %llu,\n" - "\t\"identity\": \"%s\",\n" - "\t\"ipAssignments\": [", - nwids, - addrs, - _instanceId.c_str(), - (sqlite3_column_int(_sGetMember2,0) > 0) ? "true" : "false", - (sqlite3_column_int(_sGetMember2,1) > 0) ? "true" : "false", - (unsigned long long)sqlite3_column_int64(_sGetMember2,2), - (unsigned long long)OSUtils::now(), - _jsonEscape(memberIdStr).c_str()); - responseBody = json; - - sqlite3_reset(_sGetIpAssignmentsForNode); - sqlite3_bind_text(_sGetIpAssignmentsForNode,1,nwids,16,SQLITE_STATIC); - sqlite3_bind_text(_sGetIpAssignmentsForNode,2,addrs,10,SQLITE_STATIC); - bool firstIp = true; - while (sqlite3_step(_sGetIpAssignmentsForNode) == SQLITE_ROW) { - int ipversion = sqlite3_column_int(_sGetIpAssignmentsForNode,2); - char ipBlob[16]; - memcpy(ipBlob,(const void *)sqlite3_column_blob(_sGetIpAssignmentsForNode,0),16); - InetAddress ip( - (const void *)(ipversion == 6 ? ipBlob : &ipBlob[12]), - (ipversion == 6 ? 16 : 4), - (unsigned int)sqlite3_column_int(_sGetIpAssignmentsForNode,1) - ); - responseBody.append(firstIp ? "\"" : ",\""); - responseBody.append(_jsonEscape(ip.toIpString())); - responseBody.push_back('"'); - firstIp = false; - } - - responseBody.append("],\n\t\"recentLog\": ["); - - const void *histb = sqlite3_column_blob(_sGetMember2,6); - if (histb) { - MemberRecentHistory rh; - rh.fromBlob((const char *)histb,sqlite3_column_bytes(_sGetMember2,6)); - for(MemberRecentHistory::const_iterator i(rh.begin());i!=rh.end();++i) { - if (i != rh.begin()) - responseBody.push_back(','); - responseBody.append(*i); - } - } - - responseBody.append("]\n}\n"); - - responseContentType = "application/json"; - return 200; - } // else 404 - - } else { - // List members - - sqlite3_reset(_sListNetworkMembers); - sqlite3_bind_text(_sListNetworkMembers,1,nwids,16,SQLITE_STATIC); - responseBody.push_back('{'); - bool firstMember = true; - while (sqlite3_step(_sListNetworkMembers) == SQLITE_ROW) { - responseBody.append(firstMember ? "\"" : ",\""); - firstMember = false; - responseBody.append((const char *)sqlite3_column_text(_sListNetworkMembers,0)); - responseBody.append("\":"); - responseBody.append((const char *)sqlite3_column_text(_sListNetworkMembers,1)); - } - responseBody.push_back('}'); - responseContentType = "application/json"; - return 200; - - } - - } else if ((path[2] == "active")&&(path.size() == 3)) { - - sqlite3_reset(_sGetActiveNodesOnNetwork); - sqlite3_bind_text(_sGetActiveNodesOnNetwork,1,nwids,16,SQLITE_STATIC); - sqlite3_bind_int64(_sGetActiveNodesOnNetwork,2,(int64_t)(OSUtils::now() - ZT_NETCONF_NODE_ACTIVE_THRESHOLD)); - - responseBody.push_back('{'); - bool firstActiveMember = true; - while (sqlite3_step(_sGetActiveNodesOnNetwork) == SQLITE_ROW) { - const char *nodeId = (const char *)sqlite3_column_text(_sGetActiveNodesOnNetwork,0); - const char *rhblob = (const char *)sqlite3_column_blob(_sGetActiveNodesOnNetwork,1); - if ((nodeId)&&(rhblob)) { - MemberRecentHistory rh; - rh.fromBlob(rhblob,sqlite3_column_bytes(_sGetActiveNodesOnNetwork,1)); - if (rh.size() > 0) { - if (firstActiveMember) { - firstActiveMember = false; - } else { - responseBody.push_back(','); - } - responseBody.push_back('"'); - responseBody.append(nodeId); - responseBody.append("\":"); - responseBody.append(rh.front()); - } - } - } - responseBody.push_back('}'); - - responseContentType = "application/json"; - return 200; - - } else if ((path[2] == "test")&&(path.size() >= 4)) { - - std::map< uint64_t,_CircuitTestEntry >::iterator cte(_circuitTests.find(Utils::hexStrToU64(path[3].c_str()))); - if ((cte != _circuitTests.end())&&(cte->second.test)) { - - responseBody = "["; - responseBody.append(cte->second.jsonResults); - responseBody.push_back(']'); - responseContentType = "application/json"; - - return 200; - - } // else 404 - - } // else 404 - - } else { - - sqlite3_reset(_sGetNetworkById); - sqlite3_bind_text(_sGetNetworkById,1,nwids,16,SQLITE_STATIC); - if (sqlite3_step(_sGetNetworkById) == SQLITE_ROW) { - unsigned int fl = (unsigned int)sqlite3_column_int(_sGetNetworkById,4); - std::string v6modes; - if ((fl & ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_RFC4193) != 0) - v6modes.append("rfc4193"); - if ((fl & ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_6PLANE) != 0) { - if (v6modes.length() > 0) - v6modes.push_back(','); - v6modes.append("6plane"); - } - if ((fl & ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_AUTO_ASSIGN) != 0) { - if (v6modes.length() > 0) - v6modes.push_back(','); - v6modes.append("zt"); - } - - Utils::snprintf(json,sizeof(json), - "{\n" - "\t\"nwid\": \"%s\",\n" - "\t\"controllerInstanceId\": \"%s\",\n" - "\t\"clock\": %llu,\n" - "\t\"name\": \"%s\",\n" - "\t\"private\": %s,\n" - "\t\"enableBroadcast\": %s,\n" - "\t\"allowPassiveBridging\": %s,\n" - "\t\"v4AssignMode\": \"%s\",\n" - "\t\"v6AssignMode\": \"%s\",\n" - "\t\"multicastLimit\": %d,\n" - "\t\"creationTime\": %llu,\n" - "\t\"revision\": %llu,\n" - "\t\"memberRevisionCounter\": %llu,\n" - "\t\"authorizedMemberCount\": %llu,\n" - "\t\"relays\": [", - nwids, - _instanceId.c_str(), - (unsigned long long)OSUtils::now(), - _jsonEscape((const char *)sqlite3_column_text(_sGetNetworkById,0)).c_str(), - (sqlite3_column_int(_sGetNetworkById,1) > 0) ? "true" : "false", - (sqlite3_column_int(_sGetNetworkById,2) > 0) ? "true" : "false", - (sqlite3_column_int(_sGetNetworkById,3) > 0) ? "true" : "false", - (((fl & ZT_DB_NETWORK_FLAG_ZT_MANAGED_V4_AUTO_ASSIGN) != 0) ? "zt" : ""), - v6modes.c_str(), - sqlite3_column_int(_sGetNetworkById,5), - (unsigned long long)sqlite3_column_int64(_sGetNetworkById,6), - (unsigned long long)sqlite3_column_int64(_sGetNetworkById,7), - (unsigned long long)sqlite3_column_int64(_sGetNetworkById,8), - (unsigned long long)sqlite3_column_int64(_sGetNetworkById,9)); - responseBody = json; - - sqlite3_reset(_sGetRelays); - sqlite3_bind_text(_sGetRelays,1,nwids,16,SQLITE_STATIC); - bool firstRelay = true; - while (sqlite3_step(_sGetRelays) == SQLITE_ROW) { - responseBody.append(firstRelay ? "\n\t\t" : ",\n\t\t"); - firstRelay = false; - responseBody.append("{\"address\":\""); - responseBody.append((const char *)sqlite3_column_text(_sGetRelays,0)); - responseBody.append("\",\"phyAddress\":\""); - responseBody.append(_jsonEscape((const char *)sqlite3_column_text(_sGetRelays,1))); - responseBody.append("\"}"); - } - - responseBody.append("],\n\t\"routes\": ["); - - sqlite3_reset(_sGetRoutes); - sqlite3_bind_text(_sGetRoutes,1,nwids,16,SQLITE_STATIC); - bool firstRoute = true; - while (sqlite3_step(_sGetRoutes) == SQLITE_ROW) { - responseBody.append(firstRoute ? "\n\t\t" : ",\n\t\t"); - firstRoute = false; - responseBody.append("{\"target\":"); - char tmp[128]; - const unsigned char *ip = (const unsigned char *)sqlite3_column_blob(_sGetRoutes,0); - switch(sqlite3_column_int(_sGetRoutes,3)) { // ipVersion - case 4: - Utils::snprintf(tmp,sizeof(tmp),"\"%d.%d.%d.%d/%d\"",(int)ip[12],(int)ip[13],(int)ip[14],(int)ip[15],sqlite3_column_int(_sGetRoutes,2)); - break; - case 6: - Utils::snprintf(tmp,sizeof(tmp),"\"%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x/%d\"",(int)ip[0],(int)ip[1],(int)ip[2],(int)ip[3],(int)ip[4],(int)ip[5],(int)ip[6],(int)ip[7],(int)ip[8],(int)ip[9],(int)ip[10],(int)ip[11],(int)ip[12],(int)ip[13],(int)ip[14],(int)ip[15],sqlite3_column_int(_sGetRoutes,2)); - break; - } - responseBody.append(tmp); - if (sqlite3_column_type(_sGetRoutes,1) == SQLITE_NULL) { - responseBody.append(",\"via\":null"); - } else { - responseBody.append(",\"via\":"); - ip = (const unsigned char *)sqlite3_column_blob(_sGetRoutes,1); - switch(sqlite3_column_int(_sGetRoutes,3)) { // ipVersion - case 4: - Utils::snprintf(tmp,sizeof(tmp),"\"%d.%d.%d.%d\"",(int)ip[12],(int)ip[13],(int)ip[14],(int)ip[15]); - break; - case 6: - Utils::snprintf(tmp,sizeof(tmp),"\"%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x\"",(int)ip[0],(int)ip[1],(int)ip[2],(int)ip[3],(int)ip[4],(int)ip[5],(int)ip[6],(int)ip[7],(int)ip[8],(int)ip[9],(int)ip[10],(int)ip[11],(int)ip[12],(int)ip[13],(int)ip[14],(int)ip[15]); - break; - } - responseBody.append(tmp); - } - responseBody.append(",\"flags\":"); - responseBody.append((const char *)sqlite3_column_text(_sGetRoutes,4)); - responseBody.append(",\"metric\":"); - responseBody.append((const char *)sqlite3_column_text(_sGetRoutes,5)); - responseBody.push_back('}'); - } - - responseBody.append("],\n\t\"ipAssignmentPools\": ["); - - sqlite3_reset(_sGetIpAssignmentPools2); - sqlite3_bind_text(_sGetIpAssignmentPools2,1,nwids,16,SQLITE_STATIC); - bool firstIpAssignmentPool = true; - while (sqlite3_step(_sGetIpAssignmentPools2) == SQLITE_ROW) { - const char *ipRangeStartB = reinterpret_cast(sqlite3_column_blob(_sGetIpAssignmentPools2,0)); - const char *ipRangeEndB = reinterpret_cast(sqlite3_column_blob(_sGetIpAssignmentPools2,1)); - if ((ipRangeStartB)&&(ipRangeEndB)) { - InetAddress ipps,ippe; - int ipVersion = sqlite3_column_int(_sGetIpAssignmentPools2,2); - if (ipVersion == 4) { - ipps.set((const void *)(ipRangeStartB + 12),4,0); - ippe.set((const void *)(ipRangeEndB + 12),4,0); - } else if (ipVersion == 6) { - ipps.set((const void *)ipRangeStartB,16,0); - ippe.set((const void *)ipRangeEndB,16,0); - } - if (ipps) { - responseBody.append(firstIpAssignmentPool ? "\n\t\t" : ",\n\t\t"); - firstIpAssignmentPool = false; - Utils::snprintf(json,sizeof(json),"{\"ipRangeStart\":\"%s\",\"ipRangeEnd\":\"%s\"}", - _jsonEscape(ipps.toIpString()).c_str(), - _jsonEscape(ippe.toIpString()).c_str()); - responseBody.append(json); - } - } - } - - responseBody.append("],\n\t\"rules\": ["); - - sqlite3_reset(_sListRules); - sqlite3_bind_text(_sListRules,1,nwids,16,SQLITE_STATIC); - bool firstRule = true; - while (sqlite3_step(_sListRules) == SQLITE_ROW) { - responseBody.append(firstRule ? "\n\t{\n" : ",{\n"); - firstRule = false; - Utils::snprintf(json,sizeof(json),"\t\t\"ruleNo\": %lld,\n",sqlite3_column_int64(_sListRules,0)); - responseBody.append(json); - if (sqlite3_column_type(_sListRules,1) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\t\"nodeId\": \"%s\",\n",(const char *)sqlite3_column_text(_sListRules,1)); - responseBody.append(json); - } - if (sqlite3_column_type(_sListRules,2) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\t\"sourcePort\": \"%s\",\n",(const char *)sqlite3_column_text(_sListRules,2)); - responseBody.append(json); - } - if (sqlite3_column_type(_sListRules,3) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\t\"destPort\": \"%s\",\n",(const char *)sqlite3_column_text(_sListRules,3)); - responseBody.append(json); - } - if (sqlite3_column_type(_sListRules,4) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\t\"vlanId\": %d,\n",sqlite3_column_int(_sListRules,4)); - responseBody.append(json); - } - if (sqlite3_column_type(_sListRules,5) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\t\"vlanPcp\": %d,\n",sqlite3_column_int(_sListRules,5)); - responseBody.append(json); - } - if (sqlite3_column_type(_sListRules,6) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\t\"etherType\": %d,\n",sqlite3_column_int(_sListRules,6)); - responseBody.append(json); - } - if (sqlite3_column_type(_sListRules,7) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\t\"macSource\": \"%s\",\n",MAC((const char *)sqlite3_column_text(_sListRules,7)).toString().c_str()); - responseBody.append(json); - } - if (sqlite3_column_type(_sListRules,8) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\t\"macDest\": \"%s\",\n",MAC((const char *)sqlite3_column_text(_sListRules,8)).toString().c_str()); - responseBody.append(json); - } - if (sqlite3_column_type(_sListRules,9) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\t\"ipSource\": \"%s\",\n",_jsonEscape((const char *)sqlite3_column_text(_sListRules,9)).c_str()); - responseBody.append(json); - } - if (sqlite3_column_type(_sListRules,10) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\t\"ipDest\": \"%s\",\n",_jsonEscape((const char *)sqlite3_column_text(_sListRules,10)).c_str()); - responseBody.append(json); - } - if (sqlite3_column_type(_sListRules,11) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\t\"ipTos\": %d,\n",sqlite3_column_int(_sListRules,11)); - responseBody.append(json); - } - if (sqlite3_column_type(_sListRules,12) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\t\"ipProtocol\": %d,\n",sqlite3_column_int(_sListRules,12)); - responseBody.append(json); - } - if (sqlite3_column_type(_sListRules,13) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\t\"ipSourcePort\": %d,\n",sqlite3_column_int(_sListRules,13)); - responseBody.append(json); - } - if (sqlite3_column_type(_sListRules,14) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\t\"ipDestPort\": %d,\n",sqlite3_column_int(_sListRules,14)); - responseBody.append(json); - } - if (sqlite3_column_type(_sListRules,15) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\t\"flags\": %lu,\n",(unsigned long)sqlite3_column_int64(_sListRules,15)); - responseBody.append(json); - } - if (sqlite3_column_type(_sListRules,16) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\t\"invFlags\": %lu,\n",(unsigned long)sqlite3_column_int64(_sListRules,16)); - responseBody.append(json); - } - responseBody.append("\t\t\"action\": \""); - responseBody.append(_jsonEscape( (sqlite3_column_type(_sListRules,17) == SQLITE_NULL) ? "drop" : (const char *)sqlite3_column_text(_sListRules,17) )); - responseBody.append("\"\n\t}"); - } - - responseBody.append("]\n}\n"); - responseContentType = "application/json"; - return 200; - } // else 404 - } - } else if (path.size() == 1) { - // list networks - sqlite3_reset(_sListNetworks); - responseContentType = "application/json"; - responseBody = "["; - bool first = true; - while (sqlite3_step(_sListNetworks) == SQLITE_ROW) { - if (first) { - first = false; - responseBody.push_back('"'); - } else responseBody.append(",\""); - responseBody.append((const char *)sqlite3_column_text(_sListNetworks,0)); - responseBody.push_back('"'); - } - responseBody.push_back(']'); - return 200; - } // else 404 - - } else { - // GET /controller returns status and API version if controller is supported - Utils::snprintf(json,sizeof(json),"{\n\t\"controller\": true,\n\t\"apiVersion\": %d,\n\t\"clock\": %llu,\n\t\"instanceId\": \"%s\"\n}\n",ZT_NETCONF_CONTROLLER_API_VERSION,(unsigned long long)OSUtils::now(),_instanceId.c_str()); - responseBody = json; - responseContentType = "application/json"; - return 200; - } - - return 404; -} - -void SqliteNetworkController::_circuitTestCallback(ZT_Node *node,ZT_CircuitTest *test,const ZT_CircuitTestReport *report) -{ - char tmp[65535]; - SqliteNetworkController *const self = reinterpret_cast(test->ptr); - - if (!test) - return; - if (!report) - return; - - Mutex::Lock _l(self->_lock); - std::map< uint64_t,_CircuitTestEntry >::iterator cte(self->_circuitTests.find(test->testId)); - - if (cte == self->_circuitTests.end()) { // sanity check: a circuit test we didn't launch? - self->_node->circuitTestEnd(test); - ::free((void *)test); - return; - } - - Utils::snprintf(tmp,sizeof(tmp), - "%s{\n" - "\t\"timestamp\": %llu,"ZT_EOL_S - "\t\"testId\": \"%.16llx\","ZT_EOL_S - "\t\"upstream\": \"%.10llx\","ZT_EOL_S - "\t\"current\": \"%.10llx\","ZT_EOL_S - "\t\"receivedTimestamp\": %llu,"ZT_EOL_S - "\t\"remoteTimestamp\": %llu,"ZT_EOL_S - "\t\"sourcePacketId\": \"%.16llx\","ZT_EOL_S - "\t\"flags\": %llu,"ZT_EOL_S - "\t\"sourcePacketHopCount\": %u,"ZT_EOL_S - "\t\"errorCode\": %u,"ZT_EOL_S - "\t\"vendor\": %d,"ZT_EOL_S - "\t\"protocolVersion\": %u,"ZT_EOL_S - "\t\"majorVersion\": %u,"ZT_EOL_S - "\t\"minorVersion\": %u,"ZT_EOL_S - "\t\"revision\": %u,"ZT_EOL_S - "\t\"platform\": %d,"ZT_EOL_S - "\t\"architecture\": %d,"ZT_EOL_S - "\t\"receivedOnLocalAddress\": \"%s\","ZT_EOL_S - "\t\"receivedFromRemoteAddress\": \"%s\""ZT_EOL_S - "}", - ((cte->second.jsonResults.length() > 0) ? ",\n" : ""), - (unsigned long long)report->timestamp, - (unsigned long long)test->testId, - (unsigned long long)report->upstream, - (unsigned long long)report->current, - (unsigned long long)OSUtils::now(), - (unsigned long long)report->remoteTimestamp, - (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()); - - cte->second.jsonResults.append(tmp); -} - -} // namespace ZeroTier diff --git a/controller/SqliteNetworkController.hpp b/controller/SqliteNetworkController.hpp deleted file mode 100644 index 145788c..0000000 --- a/controller/SqliteNetworkController.hpp +++ /dev/null @@ -1,181 +0,0 @@ -/* - * 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_SQLITENETWORKCONTROLLER_HPP -#define ZT_SQLITENETWORKCONTROLLER_HPP - -#include - -#include - -#include -#include -#include - -#include "../node/Constants.hpp" -#include "../node/NetworkController.hpp" -#include "../node/Mutex.hpp" -#include "../osdep/Thread.hpp" - -// Number of in-memory last log entries to maintain per user -#define ZT_SQLITENETWORKCONTROLLER_IN_MEMORY_LOG_SIZE 32 - -// How long do circuit tests last before they're forgotten? -#define ZT_SQLITENETWORKCONTROLLER_CIRCUIT_TEST_TIMEOUT 60000 - -namespace ZeroTier { - -class Node; - -class SqliteNetworkController : public NetworkController -{ -public: - SqliteNetworkController(Node *node,const char *dbPath,const char *circuitTestPath); - virtual ~SqliteNetworkController(); - - virtual NetworkController::ResultCode doNetworkConfigRequest( - const InetAddress &fromAddr, - const Identity &signingId, - const Identity &identity, - uint64_t nwid, - const Dictionary &metaData, - NetworkConfig &nc); - - 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); - - // threadMain() for backup thread -- do not call directly - void threadMain() - throw(); - -private: - /* deprecated - enum IpAssignmentType { - // IP assignment is a static IP address - ZT_IP_ASSIGNMENT_TYPE_ADDRESS = 0, - // IP assignment is a network -- a route via this interface, not an address - ZT_IP_ASSIGNMENT_TYPE_NETWORK = 1 - }; - */ - - unsigned int _doCPGet( - const std::vector &path, - const std::map &urlArgs, - const std::map &headers, - const std::string &body, - std::string &responseBody, - std::string &responseContentType); - - static void _circuitTestCallback(ZT_Node *node,ZT_CircuitTest *test,const ZT_CircuitTestReport *report); - - Node *_node; - Thread _backupThread; - volatile bool _backupThreadRun; - volatile bool _backupNeeded; - std::string _dbPath; - std::string _circuitTestPath; - std::string _instanceId; - - // Circuit tests outstanding - struct _CircuitTestEntry - { - ZT_CircuitTest *test; - std::string jsonResults; - }; - std::map< uint64_t,_CircuitTestEntry > _circuitTests; - - // Last request time by address, for rate limitation - std::map< std::pair,uint64_t > _lastRequestTime; - - sqlite3 *_db; - - sqlite3_stmt *_sGetNetworkById; - sqlite3_stmt *_sGetMember; - sqlite3_stmt *_sCreateMember; - sqlite3_stmt *_sGetNodeIdentity; - sqlite3_stmt *_sCreateOrReplaceNode; - sqlite3_stmt *_sGetEtherTypesFromRuleTable; - sqlite3_stmt *_sGetActiveBridges; - sqlite3_stmt *_sGetIpAssignmentsForNode; - sqlite3_stmt *_sGetIpAssignmentPools; - sqlite3_stmt *_sCheckIfIpIsAllocated; - sqlite3_stmt *_sAllocateIp; - sqlite3_stmt *_sDeleteIpAllocations; - sqlite3_stmt *_sGetRelays; - sqlite3_stmt *_sListNetworks; - sqlite3_stmt *_sListNetworkMembers; - sqlite3_stmt *_sGetMember2; - sqlite3_stmt *_sGetIpAssignmentPools2; - sqlite3_stmt *_sListRules; - sqlite3_stmt *_sCreateRule; - sqlite3_stmt *_sCreateNetwork; - sqlite3_stmt *_sGetNetworkRevision; - sqlite3_stmt *_sSetNetworkRevision; - sqlite3_stmt *_sDeleteRelaysForNetwork; - sqlite3_stmt *_sCreateRelay; - sqlite3_stmt *_sDeleteIpAssignmentPoolsForNetwork; - sqlite3_stmt *_sDeleteRulesForNetwork; - sqlite3_stmt *_sCreateIpAssignmentPool; - sqlite3_stmt *_sUpdateMemberAuthorized; - sqlite3_stmt *_sUpdateMemberActiveBridge; - sqlite3_stmt *_sUpdateMemberHistory; - sqlite3_stmt *_sDeleteMember; - sqlite3_stmt *_sDeleteAllNetworkMembers; - sqlite3_stmt *_sGetActiveNodesOnNetwork; - sqlite3_stmt *_sDeleteNetwork; - sqlite3_stmt *_sCreateRoute; - sqlite3_stmt *_sGetRoutes; - sqlite3_stmt *_sDeleteRoutes; - sqlite3_stmt *_sIncrementMemberRevisionCounter; - sqlite3_stmt *_sGetConfig; - sqlite3_stmt *_sSetConfig; - - Mutex _lock; -}; - -} // namespace ZeroTier - -#endif diff --git a/controller/migrate-sqlite/migrate.js b/controller/migrate-sqlite/migrate.js new file mode 100644 index 0000000..ac9678a --- /dev/null +++ b/controller/migrate-sqlite/migrate.js @@ -0,0 +1,320 @@ +'use strict'; + +var sqlite3 = require('sqlite3').verbose(); +var fs = require('fs'); +var async = require('async'); + +function blobToIPv4(b) +{ + if (!b) + return null; + if (b.length !== 16) + return null; + return b.readUInt8(12).toString()+'.'+b.readUInt8(13).toString()+'.'+b.readUInt8(14).toString()+'.'+b.readUInt8(15).toString(); +} +function blobToIPv6(b) +{ + if (!b) + return null; + if (b.length !== 16) + return null; + var s = ''; + for(var i=0;i<16;++i) { + var x = b.readUInt8(i).toString(16); + if (x.length === 1) + s += '0'; + s += x; + if ((((i+1) & 1) === 0)&&(i !== 15)) + s += ':'; + } + return s; +} + +if (process.argv.length !== 4) { + console.log('ZeroTier Old Sqlite3 Controller DB Migration Utility'); + console.log('(c)2017 ZeroTier, Inc. [GPL3]'); + console.log(''); + console.log('Usage: node migrate.js '); + console.log(''); + console.log('The first argument must be the path to the old Sqlite3 controller.db'); + console.log('file. The second must be the path to the EMPTY controller.d database'); + console.log('directory for a new (1.1.17 or newer) controller. If this path does'); + console.log('not exist it will be created.'); + console.log(''); + console.log('WARNING: this will ONLY work correctly on a 1.1.14 controller database.'); + console.log('If your controller is old you should first upgrade to 1.1.14 and run the'); + console.log('controller so that it will brings its Sqlite3 database up to the latest'); + console.log('version before running this migration.'); + console.log(''); + process.exit(1); +} + +var oldDbPath = process.argv[2]; +var newDbPath = process.argv[3]; + +console.log('Starting migrate of "'+oldDbPath+'" to "'+newDbPath+'"...'); +console.log(''); + +var old = new sqlite3.Database(oldDbPath); + +var networks = {}; + +var nodeIdentities = {}; +var networkCount = 0; +var memberCount = 0; +var routeCount = 0; +var ipAssignmentPoolCount = 0; +var ipAssignmentCount = 0; +var ruleCount = 0; +var oldSchemaVersion = -1; + +async.series([function(nextStep) { + + old.each('SELECT v from Config WHERE k = \'schemaVersion\'',function(err,row) { + oldSchemaVersion = parseInt(row.v)||-1; + },nextStep); + +},function(nextStep) { + + if (oldSchemaVersion !== 4) { + console.log('FATAL: this MUST be run on a 1.1.14 controller.db! Upgrade your old'); + console.log('controller to 1.1.14 first and run it once to bring its DB up to date.'); + return process.exit(1); + } + + console.log('Reading networks...'); + old.each('SELECT * FROM Network',function(err,row) { + if ((typeof row.id === 'string')&&(row.id.length === 16)) { + var flags = parseInt(row.flags)||0; + networks[row.id] = { + id: row.id, + nwid: row.id, + objtype: 'network', + authTokens: [], + capabilities: [], + creationTime: parseInt(row.creationTime)||0, + enableBroadcast: !!row.enableBroadcast, + ipAssignmentPools: [], + lastModified: Date.now(), + multicastLimit: row.multicastLimit||32, + name: row.name||'', + private: !!row.private, + revision: parseInt(row.revision)||1, + rules: [{ 'type': 'ACTION_ACCEPT' }], // populated later if there are defined rules, otherwise default is allow all + routes: [], + v4AssignMode: { + 'zt': ((flags & 1) !== 0) + }, + v6AssignMode: { + '6plane': ((flags & 4) !== 0), + 'rfc4193': ((flags & 2) !== 0), + 'zt': ((flags & 8) !== 0) + }, + _members: {} // temporary + }; + ++networkCount; + //console.log(networks[row.id]); + } + },nextStep); + +},function(nextStep) { + + console.log(' '+networkCount+' networks.'); + console.log('Reading network route definitions...'); + old.each('SELECT * from Route WHERE ipVersion = 4 OR ipVersion = 6',function(err,row) { + var network = networks[row.networkId]; + if (network) { + var rt = { + target: (((row.ipVersion == 4) ? blobToIPv4(row.target) : blobToIPv6(row.target))+'/'+row.targetNetmaskBits), + via: ((row.via) ? ((row.ipVersion == 4) ? blobToIPv4(row.via) : blobToIPv6(row.via)) : null) + }; + network.routes.push(rt); + ++routeCount; + } + },nextStep); + +},function(nextStep) { + + console.log(' '+routeCount+' routes in '+networkCount+' networks.'); + console.log('Reading IP assignment pools...'); + old.each('SELECT * FROM IpAssignmentPool WHERE ipVersion = 4 OR ipVersion = 6',function(err,row) { + var network = networks[row.networkId]; + if (network) { + var p = { + ipRangeStart: ((row.ipVersion == 4) ? blobToIPv4(row.ipRangeStart) : blobToIPv6(row.ipRangeStart)), + ipRangeEnd: ((row.ipVersion == 4) ? blobToIPv4(row.ipRangeEnd) : blobToIPv6(row.ipRangeEnd)) + }; + network.ipAssignmentPools.push(p); + ++ipAssignmentPoolCount; + } + },nextStep); + +},function(nextStep) { + + console.log(' '+ipAssignmentPoolCount+' IP assignment pools in '+networkCount+' networks.'); + console.log('Reading known node identities...'); + old.each('SELECT * FROM Node',function(err,row) { + nodeIdentities[row.id] = row.identity; + },nextStep); + +},function(nextStep) { + + console.log(' '+Object.keys(nodeIdentities).length+' known identities.'); + console.log('Reading network members...'); + old.each('SELECT * FROM Member',function(err,row) { + var network = networks[row.networkId]; + if (network) { + network._members[row.nodeId] = { + id: row.nodeId, + address: row.nodeId, + objtype: 'member', + authorized: !!row.authorized, + activeBridge: !!row.activeBridge, + authHistory: [], + capabilities: [], + creationTime: 0, + identity: nodeIdentities[row.nodeId]||null, + ipAssignments: [], + lastAuthorizedTime: (row.authorized) ? Date.now() : 0, + lastDeauthorizedTime: (row.authorized) ? 0 : Date.now(), + lastModified: Date.now(), + lastRequestMetaData: '', + noAutoAssignIps: false, + nwid: row.networkId, + revision: parseInt(row.memberRevision)||1, + tags: [], + recentLog: [] + }; + ++memberCount; + //console.log(network._members[row.nodeId]); + } + },nextStep); + +},function(nextStep) { + + console.log(' '+memberCount+' members of '+networkCount+' networks.'); + console.log('Reading static IP assignments...'); + old.each('SELECT * FROM IpAssignment WHERE ipVersion = 4 OR ipVersion = 6',function(err,row) { + var network = networks[row.networkId]; + if (network) { + var member = network._members[row.nodeId]; + if ((member)&&((member.authorized)||(!network['private']))) { // don't mirror assignments to unauthorized members to avoid conflicts + if (row.ipVersion == 4) { + member.ipAssignments.push(blobToIPv4(row.ip)); + ++ipAssignmentCount; + } else if (row.ipVersion == 6) { + member.ipAssignments.push(blobToIPv6(row.ip)); + ++ipAssignmentCount; + } + } + } + },nextStep); + +},function(nextStep) { + + // Old versions only supported Ethertype whitelisting, so that's + // all we mirror forward. The other fields were always unused. + + console.log(' '+ipAssignmentCount+' IP assignments for '+memberCount+' authorized members of '+networkCount+' networks.'); + console.log('Reading allowed Ethernet types (old basic rules)...'); + var etherTypesByNetwork = {}; + old.each('SELECT DISTINCT networkId,ruleNo,etherType FROM Rule WHERE "action" = \'accept\'',function(err,row) { + if (row.networkId in networks) { + var et = parseInt(row.etherType)||0; + var ets = etherTypesByNetwork[row.networkId]; + if (!ets) + etherTypesByNetwork[row.networkId] = [ et ]; + else ets.push(et); + } + },function(err) { + if (err) return nextStep(err); + for(var nwid in etherTypesByNetwork) { + var ets = etherTypesByNetwork[nwid].sort(); + var network = networks[nwid]; + if (network) { + var rules = []; + if (ets.indexOf(0) >= 0) { + // If 0 is in the list, all Ethernet types are allowed so we accept all. + rules.push({ 'type': 'ACTION_ACCEPT' }); + } else { + // Otherwise we whitelist. + for(var i=0;i 0) { + try { + fs.mkdirSync(nwBase+network.id); + } catch (e) {} + var mbase = nwBase+network.id+'/member'; + try { + fs.mkdirSync(mbase,0o700); + } catch (e) {} + mbase = mbase + '/'; + + for(var mi=0;mi", + "license": "GPL-3.0", + "dependencies": { + "async": "^2.1.4", + "sqlite3": "^3.1.8" + } +} diff --git a/controller/schema.sql.c b/controller/schema.sql.c deleted file mode 100644 index dab3413..0000000 --- a/controller/schema.sql.c +++ /dev/null @@ -1,121 +0,0 @@ -#define ZT_NETCONF_SCHEMA_SQL \ -"CREATE TABLE Config (\n"\ -" k varchar(16) PRIMARY KEY NOT NULL,\n"\ -" v varchar(1024) NOT NULL\n"\ -");\n"\ -"\n"\ -"CREATE TABLE Network (\n"\ -" id char(16) PRIMARY KEY NOT NULL,\n"\ -" name varchar(128) NOT NULL,\n"\ -" private integer NOT NULL DEFAULT(1),\n"\ -" enableBroadcast integer NOT NULL DEFAULT(1),\n"\ -" allowPassiveBridging integer NOT NULL DEFAULT(0),\n"\ -" multicastLimit integer NOT NULL DEFAULT(32),\n"\ -" creationTime integer NOT NULL DEFAULT(0),\n"\ -" revision integer NOT NULL DEFAULT(1),\n"\ -" memberRevisionCounter integer NOT NULL DEFAULT(1),\n"\ -" flags integer NOT NULL DEFAULT(0)\n"\ -");\n"\ -"\n"\ -"CREATE TABLE AuthToken (\n"\ -" id integer PRIMARY KEY NOT NULL,\n"\ -" networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,\n"\ -" authMode integer NOT NULL DEFAULT(1),\n"\ -" useCount integer NOT NULL DEFAULT(0),\n"\ -" maxUses integer NOT NULL DEFAULT(0),\n"\ -" expiresAt integer NOT NULL DEFAULT(0),\n"\ -" token varchar(256) NOT NULL\n"\ -");\n"\ -"\n"\ -"CREATE INDEX AuthToken_networkId_token ON AuthToken(networkId,token);\n"\ -"\n"\ -"CREATE TABLE Node (\n"\ -" id char(10) PRIMARY KEY NOT NULL,\n"\ -" identity varchar(4096) NOT NULL\n"\ -");\n"\ -"\n"\ -"CREATE TABLE IpAssignment (\n"\ -" networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,\n"\ -" nodeId char(10) REFERENCES Node(id) ON DELETE CASCADE,\n"\ -" type integer NOT NULL DEFAULT(0),\n"\ -" ip blob(16) NOT NULL,\n"\ -" ipNetmaskBits integer NOT NULL DEFAULT(0),\n"\ -" ipVersion integer NOT NULL DEFAULT(4)\n"\ -");\n"\ -"\n"\ -"CREATE UNIQUE INDEX IpAssignment_networkId_ip ON IpAssignment (networkId, ip);\n"\ -"\n"\ -"CREATE INDEX IpAssignment_networkId_nodeId ON IpAssignment (networkId, nodeId);\n"\ -"\n"\ -"CREATE TABLE IpAssignmentPool (\n"\ -" networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,\n"\ -" ipRangeStart blob(16) NOT NULL,\n"\ -" ipRangeEnd blob(16) NOT NULL,\n"\ -" ipVersion integer NOT NULL DEFAULT(4)\n"\ -");\n"\ -"\n"\ -"CREATE UNIQUE INDEX IpAssignmentPool_networkId_ipRangeStart ON IpAssignmentPool (networkId,ipRangeStart);\n"\ -"\n"\ -"CREATE TABLE Member (\n"\ -" networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,\n"\ -" nodeId char(10) NOT NULL REFERENCES Node(id) ON DELETE CASCADE,\n"\ -" authorized integer NOT NULL DEFAULT(0),\n"\ -" activeBridge integer NOT NULL DEFAULT(0),\n"\ -" memberRevision integer NOT NULL DEFAULT(0),\n"\ -" flags integer NOT NULL DEFAULT(0),\n"\ -" lastRequestTime integer NOT NULL DEFAULT(0),\n"\ -" lastPowDifficulty integer NOT NULL DEFAULT(0),\n"\ -" lastPowTime integer NOT NULL DEFAULT(0),\n"\ -" recentHistory blob,\n"\ -" PRIMARY KEY (networkId, nodeId)\n"\ -");\n"\ -"\n"\ -"CREATE INDEX Member_networkId_nodeId ON Member(networkId,nodeId);\n"\ -"CREATE INDEX Member_networkId_activeBridge ON Member(networkId, activeBridge);\n"\ -"CREATE INDEX Member_networkId_memberRevision ON Member(networkId, memberRevision);\n"\ -"CREATE INDEX Member_networkId_lastRequestTime ON Member(networkId, lastRequestTime);\n"\ -"\n"\ -"CREATE TABLE Route (\n"\ -" networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,\n"\ -" target blob(16) NOT NULL,\n"\ -" via blob(16),\n"\ -" targetNetmaskBits integer NOT NULL,\n"\ -" ipVersion integer NOT NULL,\n"\ -" flags integer NOT NULL,\n"\ -" metric integer NOT NULL\n"\ -");\n"\ -"\n"\ -"CREATE INDEX Route_networkId ON Route (networkId);\n"\ -"\n"\ -"CREATE TABLE Relay (\n"\ -" networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,\n"\ -" address char(10) NOT NULL,\n"\ -" phyAddress varchar(64) NOT NULL\n"\ -");\n"\ -"\n"\ -"CREATE UNIQUE INDEX Relay_networkId_address ON Relay (networkId,address);\n"\ -"\n"\ -"CREATE TABLE Rule (\n"\ -" networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,\n"\ -" ruleNo integer NOT NULL,\n"\ -" nodeId char(10) REFERENCES Node(id),\n"\ -" sourcePort char(10),\n"\ -" destPort char(10),\n"\ -" vlanId integer,\n"\ -" vlanPcp integer,\n"\ -" etherType integer,\n"\ -" macSource char(12),\n"\ -" macDest char(12),\n"\ -" ipSource varchar(64),\n"\ -" ipDest varchar(64),\n"\ -" ipTos integer,\n"\ -" ipProtocol integer,\n"\ -" ipSourcePort integer,\n"\ -" ipDestPort integer,\n"\ -" flags integer,\n"\ -" invFlags integer,\n"\ -" \"action\" varchar(4096) NOT NULL DEFAULT('accept')\n"\ -");\n"\ -"\n"\ -"CREATE UNIQUE INDEX Rule_networkId_ruleNo ON Rule (networkId, ruleNo);\n"\ -"" diff --git a/controller/schema2c.sh b/controller/schema2c.sh deleted file mode 100755 index 4f4f164..0000000 --- a/controller/schema2c.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -# Run this file to package the .sql file into a .c file whenever the SQL changes. - -rm -f schema.sql.c -echo '#define ZT_NETCONF_SCHEMA_SQL \' >schema.sql.c -cat schema.sql | sed 's/"/\\"/g' | sed 's/^/"/' | sed 's/$/\\n"\\/' >>schema.sql.c -echo '""' >>schema.sql.c diff --git a/debian/changelog b/debian/changelog index aa2fb53..3384315 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,21 @@ +zerotier-one (1.2.4) unstable; urgency=medium + + * See https://github.com/zerotier/ZeroTierOne for release notes. + + -- Adam Ierymenko Mon, 24 Mar 2017 01:00:00 -0700 + +zerotier-one (1.2.2) unstable; urgency=medium + + * See https://github.com/zerotier/ZeroTierOne for release notes. + + -- Adam Ierymenko Fri, 17 Mar 2017 01:00:00 -0700 + +zerotier-one (1.2.0) unstable; urgency=medium + + * See https://github.com/zerotier/ZeroTierOne for release notes. + + -- Adam Ierymenko Tue, 14 Mar 2017 09:08:00 -0700 + zerotier-one (1.1.14) unstable; urgency=medium * See https://github.com/zerotier/ZeroTierOne for release notes. diff --git a/debian/control b/debian/control index 46b8307..a9554f1 100644 --- a/debian/control +++ b/debian/control @@ -3,14 +3,14 @@ Maintainer: Adam Ierymenko Section: net Priority: optional Standards-Version: 3.9.6 -Build-Depends: debhelper (>= 9), liblz4-dev, libnatpmp-dev, dh-systemd, ruby-ronn +Build-Depends: debhelper (>= 9), dh-systemd Vcs-Git: git://github.com/zerotier/ZeroTierOne Vcs-Browser: https://github.com/zerotier/ZeroTierOne Homepage: https://www.zerotier.com/ Package: zerotier-one Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends}, liblz4-1, libnatpmp1, iproute2 +Depends: ${shlibs:Depends}, ${misc:Depends}, iproute2, adduser, libstdc++6 Homepage: https://www.zerotier.com/ Description: ZeroTier network virtualization service ZeroTier One lets you join ZeroTier virtual networks and diff --git a/debian/control.wheezy b/debian/control.wheezy index 0cbd151..f14c876 100644 --- a/debian/control.wheezy +++ b/debian/control.wheezy @@ -3,14 +3,14 @@ Maintainer: Adam Ierymenko Section: net Priority: optional Standards-Version: 3.9.4 -Build-Depends: debhelper (>= 9), ruby-ronn +Build-Depends: debhelper (>= 9) Vcs-Git: git://github.com/zerotier/ZeroTierOne Vcs-Browser: https://github.com/zerotier/ZeroTierOne Homepage: https://www.zerotier.com/ Package: zerotier-one Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends}, iproute +Depends: ${shlibs:Depends}, ${misc:Depends}, iproute, libstdc++6 Homepage: https://www.zerotier.com/ Description: ZeroTier network virtualization service ZeroTier One lets you join ZeroTier virtual networks and diff --git a/debian/postinst b/debian/postinst new file mode 100644 index 0000000..ecd148a --- /dev/null +++ b/debian/postinst @@ -0,0 +1,9 @@ +#!/bin/sh -e + +case "$1" in + configure) + adduser --system --group --home /var/lib/zerotier-one --no-create-home zerotier-one + ;; +esac + +#DEBHELPER# diff --git a/debian/rules b/debian/rules index cf0b04f..0ef81e0 100755 --- a/debian/rules +++ b/debian/rules @@ -7,7 +7,7 @@ CXXFLAGS=-O3 -fstack-protector-strong dh $@ --with systemd override_dh_auto_build: - make ZT_USE_MINIUPNPC=1 -j 2 + make -j 2 override_dh_systemd_start: dh_systemd_start --restart-after-upgrade diff --git a/debian/rules.static b/debian/rules.static new file mode 100644 index 0000000..72c5295 --- /dev/null +++ b/debian/rules.static @@ -0,0 +1,16 @@ +#!/usr/bin/make -f + +CFLAGS=-O3 -fstack-protector-strong +CXXFLAGS=-O3 -fstack-protector-strong + +%: + dh $@ --with systemd + +override_dh_auto_build: +# make -j 2 + +override_dh_systemd_start: + dh_systemd_start --restart-after-upgrade + +override_dh_installinit: + dh_installinit --name=zerotier-one -- defaults diff --git a/debian/rules.wheezy b/debian/rules.wheezy index e51d794..55e2647 100755 --- a/debian/rules.wheezy +++ b/debian/rules.wheezy @@ -7,5 +7,5 @@ CXXFLAGS=-O3 -fstack-protector dh $@ override_dh_auto_build: - make ZT_USE_MINIUPNPC=1 -j 2 + make -j 2 diff --git a/debian/rules.wheezy.static b/debian/rules.wheezy.static new file mode 100644 index 0000000..0165be3 --- /dev/null +++ b/debian/rules.wheezy.static @@ -0,0 +1,11 @@ +#!/usr/bin/make -f + +CFLAGS=-O3 -fstack-protector +CXXFLAGS=-O3 -fstack-protector + +%: + dh $@ + +override_dh_auto_build: +# make -j 2 + diff --git a/debian/format b/debian/source/format similarity index 100% rename from debian/format rename to debian/source/format diff --git a/doc/ext/kubernetes/.zerotierCliSettings b/doc/ext/kubernetes/.zerotierCliSettings new file mode 100644 index 0000000..0e7df9b --- /dev/null +++ b/doc/ext/kubernetes/.zerotierCliSettings @@ -0,0 +1,18 @@ +{ + "configVersion": 1, + "defaultCentral": "@my.zerotier.com", + "defaultController": "@my.zerotier.com", + "defaultOne": "@local", + "things": { + "local": { + "auth": "local_service_auth_token_replaced_automatically", + "type": "one", + "url": "http://127.0.0.1:9993/" + }, + "my.zerotier.com": { + "auth": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "type": "central", + "url": "https://my.zerotier.com/" + } + } +} diff --git a/doc/ext/kubernetes/Dockerfile b/doc/ext/kubernetes/Dockerfile new file mode 100644 index 0000000..6437a2b --- /dev/null +++ b/doc/ext/kubernetes/Dockerfile @@ -0,0 +1,19 @@ +FROM node:4.4 +EXPOSE 8080/tcp 9993/udp + +# Install ZT network conf files +RUN mkdir -p /var/lib/zerotier-one/networks.d +ADD *.conf /var/lib/zerotier-one/networks.d/ +ADD *.conf / +ADD zerotier-one / +ADD zerotier-cli / +ADD .zerotierCliSettings / + +# Install App +ADD server.js / + +# script which will start/auth VM on ZT network +ADD entrypoint.sh / +RUN chmod -v +x /entrypoint.sh + +CMD ["./entrypoint.sh"] \ No newline at end of file diff --git a/doc/ext/kubernetes/README.md b/doc/ext/kubernetes/README.md new file mode 100644 index 0000000..482e77e --- /dev/null +++ b/doc/ext/kubernetes/README.md @@ -0,0 +1,150 @@ +Kubernetes + ZeroTier +==== + +A self-authorizing Kubernetes cluster deployment over a private ZeroTier network. + +This is a quick tutorial for setting up a Kubernetes deployment which can self-authorize each new replica onto your private ZeroTier network with no additional configuration needed when you scale. The Kubernetes-specific instructions and content is based on the [hellonode](http://kubernetes.io/docs/hellonode/) tutorial. All of the files discussed below can be found [here](); + + + +## Preliminary tasks + +**Step 1: Go to [my.zerotier.com](https://my.zerotier.com) and generate a network controller API key. This key will be used by ZeroTier to automatically authorize new instances of your VMs to join your secure deployment network during replication.** + +**Step 2: Create a new `private` network. Take note of the network ID, henceforth: `nwid`** + +**Step 3: Follow the instructions from the [hellonode](ttp://kubernetes.io/docs/hellonode/) tutorial to set up your development system.** + +*** +## Construct docker image + +**Step 4: Create necessary files for inclusion into image, your resultant directory should contain:** + + - `ztkube/.conf` + - `ztkube/Dockerfile` + - `ztkube/entrypoint.sh` + - `ztkube/server.js` + - `ztkube/zerotier-cli` + - `ztkube/zerotier-one` + +Start by creating a build directory to copy all required files into `mkdir ztkube`. Then build the following: + - `make one` + - `make cli` + +Add the following files to the `ztkube` directory. These files will be compiled into the Docker image. + + - Create an empty `.conf` file to specify the private deployment network you created in *Step 2*: + + - Create a CLI tool config file `.zerotierCliSettings` which should only contain your network controller API key to authorize new devices on your network (the local service API key will be filled in automatically). In this example the default controller is hosted by us at [my.zerotier.com](https://my.zerotier.com). Alternatively, you can host your own network controller but you'll need to modify the CLI config file accordingly. + +``` +{ + "configVersion": 1, + "defaultCentral": "@my.zerotier.com", + "defaultController": "@my.zerotier.com", + "defaultOne": "@local", + "things": { + "local": { + "auth": "local_service_auth_token_replaced_automatically", + "type": "one", + "url": "http://127.0.0.1:9993/" + }, + "my.zerotier.com": { + "auth": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "type": "central", + "url": "https://my.zerotier.com/" + } + } +} +``` + + + - Create a `Dockerfile` which will copy the ZeroTier service as well as the ZeroTier CLI to the image: + +``` +FROM node:4.4 +EXPOSE 8080/tcp 9993/udp + +# Install ZT network conf files +RUN mkdir -p /var/lib/zerotier-one/networks.d +ADD *.conf /var/lib/zerotier-one/networks.d/ +ADD *.conf / +ADD zerotier-one / +ADD zerotier-cli / +ADD .zerotierCliSettings / + +# Install App +ADD server.js / + +# script which will start/auth VM on ZT network +ADD entrypoint.sh / +RUN chmod -v +x /entrypoint.sh + +CMD ["./entrypoint.sh"] +``` + + - Create the `entrypoint.sh` script which will start the ZeroTier service in the VM, attempt to join your deployment network and automatically authorize the new VM if your network is set to private: + +``` +#!/bin/bash + +echo '*** ZeroTier-Kubernetes self-auth test script' +chown -R daemon /var/lib/zerotier-one +chgrp -R daemon /var/lib/zerotier-one +su daemon -s /bin/bash -c '/zerotier-one -d -U -p9993 >>/tmp/zerotier-one.out 2>&1' +dev="" +nwconf=$(ls *.conf) +nwid="${nwconf%.*}" + +sleep 10 +dev=$(cat /var/lib/zerotier-one/identity.public| cut -d ':' -f 1) + +echo '*** Joining' +./zerotier-cli join "$nwid".conf +# Fill out local service auth token +AUTHTOKEN=$(cat /var/lib/zerotier-one/authtoken.secret) +sed "s|\local_service_auth_token_replaced_automatically|${AUTHTOKEN}|" .zerotierCliSettings > /root/.zerotierCliSettings +echo '*** Authorizing' +./zerotier-cli net-auth @my.zerotier.com "$nwid" "$dev" +echo '*** Cleaning up' # Remove controller auth token +rm -rf .zerotierCliSettings /root/.zerotierCliSettings +node server.js +``` + +**Step 5: Build the image:** + + - `docker build -t gcr.io/$PROJECT_ID/hello-node .` + + + +**Step 6: Push the docker image to your *Container Registry*** + + - `gcloud docker push gcr.io/$PROJECT_ID/hello-node:v1` + +*** +## Deploy! + +**Step 7: Create Kubernetes Cluster** + + - `gcloud config set compute/zone us-central1-a` + + - `gcloud container clusters create hello-world` + + - `gcloud container clusters get-credentials hello-world` + + + +**Step 8: Create your pod** + + - `kubectl run hello-node --image=gcr.io/$PROJECT_ID/hello-node:v1 --port=8080` + + + +**Step 9: Scale** + + - `kubectl scale deployment hello-node --replicas=4` + +*** +## Verify + +Now, after a minute or so you can use `zerotier-cli net-members ` to show all of your VM instances on your ZeroTier deployment network. If you haven't [configured your local CLI](https://github.com/zerotier/ZeroTierOne/tree/dev/cli), you can simply log into [my.zerotier.com](https://my.zerotier.com), go to *Networks -> nwid* to check that your VMs are indeed members of your private network. You should also note that the `entrypoint.sh` script will automatically delete your network controller API key once it has authorized your VM. This is merely a security measure and can be removed if needed. diff --git a/doc/ext/kubernetes/entrypoint.sh b/doc/ext/kubernetes/entrypoint.sh new file mode 100644 index 0000000..80cd278 --- /dev/null +++ b/doc/ext/kubernetes/entrypoint.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +echo '*** ZeroTier-Kubernetes self-auth test script' +chown -R daemon /var/lib/zerotier-one +chgrp -R daemon /var/lib/zerotier-one +su daemon -s /bin/bash -c '/zerotier-one -d -U -p9993 >>/tmp/zerotier-one.out 2>&1' +dev="" +nwconf=$(ls *.conf) +nwid="${nwconf%.*}" + +sleep 10 +dev=$(cat /var/lib/zerotier-one/identity.public| cut -d ':' -f 1) + +echo '*** Joining' +./zerotier-cli join "$nwid".conf +# Fill out local service auth token +AUTHTOKEN=$(cat /var/lib/zerotier-one/authtoken.secret) +sed "s|\local_service_auth_token_replaced_automatically|${AUTHTOKEN}|" .zerotierCliSettings > /root/.zerotierCliSettings +echo '*** Authorizing' +./zerotier-cli net-auth @my.zerotier.com "$nwid" "$dev" +echo '*** Cleaning up' # Remove controller auth token +rm -rf .zerotierCliSettings /root/.zerotierCliSettings +node server.js \ No newline at end of file diff --git a/doc/ext/kubernetes/server.js b/doc/ext/kubernetes/server.js new file mode 100644 index 0000000..a4b08bb --- /dev/null +++ b/doc/ext/kubernetes/server.js @@ -0,0 +1,8 @@ +var http = require('http'); +var handleRequest = function(request, response) { + console.log('Received request for URL: ' + request.url); + response.writeHead(200); + response.end('Hello World!'); +}; +var www = http.createServer(handleRequest); +www.listen(8080); diff --git a/doc/zerotier-cli.1 b/doc/zerotier-cli.1 new file mode 100644 index 0000000..167109e --- /dev/null +++ b/doc/zerotier-cli.1 @@ -0,0 +1,83 @@ +.TH "ZEROTIER\-CLI" "1" "December 2016" "" "" +.SH "NAME" +\fBzerotier-cli\fR \- control local ZeroTier virtual network service +.SH SYNOPSIS +.P +\fBzerotier\-cli\fP [\-switches] [arguments] +.SH DESCRIPTION +.P +\fBzerotier\-cli\fR provides a simple command line interface to the local JSON API of the ZeroTier virtual network endpoint service zerotier\-one(8)\. +.P +By default \fBzerotier\-cli\fR must be run as root or with \fBsudo\fP\|\. If you want to allow an unprivileged user to use \fBzerotier\-cli\fR to control the system ZeroTier service, you can create a local copy of the ZeroTier service authorization token in the user's home directory: +.P +.RS 2 +.nf +sudo cp /var/lib/zerotier\-one/authtoken\.secret /home/user/\.zeroTierOneAuthToken +chown user /home/user/\.zeroTierOneAuthToken +chmod 0600 /home/user/\.zeroTierOneAuthToken +.fi +.RE +.P +(The location of ZeroTier's service home may differ by platform\. See zerotier\-one(8)\.) +.P +Note that this gives the user the power to connect or disconnect the system to or from any virtual network, which is a significant permission\. +.P +\fBzerotier\-cli\fR has several command line arguments that are visible in \fBhelp\fP output\. The two most commonly used are \fB\-j\fP for raw JSON output and \fB\-D\fP to specify an alternative ZeroTier service working directory\. Raw JSON output is easier to parse in scripts and also contains verbose details not present in the tabular output\. The \fB\-D\fP option specifies where the service's zerotier\-one\.port and authtoken\.secret files are located if the service is not running at the default location for your system\. +.SH COMMANDS +.RS 0 +.IP \(bu 2 +\fBhelp\fP: +Displays \fBzerotier\-cli\fR help\. +.IP \(bu 2 +\fBinfo\fP: +Shows information about this device including its 10\-digit ZeroTier address and apparent connection status\. Use \fB\-j\fP for more verbose output\. +.IP \(bu 2 +\fBlistpeers\fP: +This command lists the ZeroTier VL1 (virtual layer 1, the peer to peer network) peers this service knows about and has recently (within the past 30 minutes or so) communicated with\. These are not necessarily all the devices on your virtual network(s), and may also include a few devices not on any virtual network you've joined\. These are typically either root servers or network controllers\. +.IP \(bu 2 +\fBlistnetworks\fP: +This lists the networks your system belongs to and some information about them, such as any ZeroTier\-managed IP addresses you have been assigned\. (IP addresses assigned manually to ZeroTier interfaces will not be listed here\. Use the standard network interface commands to see these\.) +.IP \(bu 2 +\fBjoin\fP: +To join a network just use \fBjoin\fP and its 16\-digit hex network ID\. That's it\. Then use \fBlistnetworks\fP to see the status\. You'll either get a reply from the network controller with a certificate and other info such as IP assignments, or you'll get "access denied\." In this case you'll need the administrator of this network to authorize your device by its 10\-digit device ID (visible with \fBinfo\fP) on the network's controller\. +.IP \(bu 2 +\fBleave\fP: +Leaving a network is as easy as joining it\. This disconnects from the network and deletes its interface from the system\. Note that peers on the network may hang around in \fBlistpeers\fP for up to 30 minutes until they time out due to lack of traffic\. But if they no longer share a network with you, they can't actually communicate with you in any meaningful way\. + +.RE +.SH EXAMPLES +.P +Join "Earth," ZeroTier's big public party line network: +.P +.RS 2 +.nf +$ sudo zerotier\-cli join 8056c2e21c000001 +$ sudo zerotier\-cli listnetworks +( wait until you get an Earth IP ) +$ ping earth\.zerotier\.net +( you should now be able to ping our Earth test IP ) +.fi +.RE +.P +Leave "Earth": +.P +.RS 2 +.nf +$ sudo zerotier\-cli leave 8056c2e21c000001 +.fi +.RE +.P +List VL1 peers: +.P +.RS 2 +.nf +$ sudo zerotier\-cli listpeers +.fi +.RE +.SH COPYRIGHT +.P +(c)2011\-2016 ZeroTier, Inc\. \-\- https://www\.zerotier\.com/ \-\- https://github\.com/zerotier +.SH SEE ALSO +.P +zerotier\-one(8), zerotier\-idtool(1) + diff --git a/doc/zerotier-idtool.1 b/doc/zerotier-idtool.1 new file mode 100644 index 0000000..fbc367a --- /dev/null +++ b/doc/zerotier-idtool.1 @@ -0,0 +1,84 @@ +.TH "ZEROTIER\-IDTOOL" "1" "December 2016" "" "" +.SH "NAME" +\fBzerotier-idtool\fR \- tool for creating and manipulating ZeroTier identities +.SH SYNOPSIS +.P +\fBzerotier\-idtool\fP [args] +.SH DESCRIPTION +.P +\fBzerotier\-idtool\fR is a command line utility for doing things with ZeroTier identities\. A ZeroTier identity consists of a public/private key pair (or just the public if it's only an identity\.public) and a 10\-digit hexadecimal ZeroTier address derived from the public key by way of a proof of work based hash function\. +.SH COMMANDS +.P +When command arguments call for a public or secret (full) identity, the identity can be specified as a path to a file or directly on the command line\. +.RS 0 +.IP \(bu 2 +\fBhelp\fP: +Display help\. (Also running with no command does this\.) +.IP \(bu 2 +\fBgenerate\fP [secret file] [public file] [vanity]: +Generate a new ZeroTier identity\. If a secret file is specified, the full identity including the private key will be written to this file\. If the public file is specified, the public portion will be written there\. If no file paths are specified the full secret identity is output to STDOUT\. The vanity prefix is a series of hexadecimal digits that the generated identity's address should start with\. Typically this isn't used, and if it's specified generation can take a very long time due to the intrinsic cost of generating identities with their proof of work function\. Generating an identity with a known 16\-bit (4 digit) prefix on a 2\.8ghz Core i5 (using one core) takes an average of two hours\. +.IP \(bu 2 +\fBvalidate\fP : +Locally validate an identity's key and proof of work function correspondence\. +.IP \(bu 2 +\fBgetpublic\fP : +Extract the public portion of an identity\.secret and print to STDOUT\. +.IP \(bu 2 +\fBsign\fP : +Sign a file's contents with SHA512+ECC\-256 (ed25519)\. The signature is output in hex to STDOUT\. +.IP \(bu 2 +\fBverify\fP : +Verify a signature created with \fBsign\fP\|\. +.IP \(bu 2 +\fBmkcom\fP [id,value,maxdelta] [\|\.\.\.]: +Create and sign a network membership certificate\. This is not generally useful since network controllers do this automatically and is included mostly for testing purposes\. + +.RE +.SH EXAMPLES +.P +Generate and dump a new identity: +.P +.RS 2 +.nf +$ zerotier\-idtool generate +.fi +.RE +.P +Generate and write a new identity, both secret and public parts: +.P +.RS 2 +.nf +$ zerotier\-idtool generate identity\.secret identity\.public +.fi +.RE +.P +Generate a vanity address that begins with the hex digits "beef" (this will take a while!): +.P +.RS 2 +.nf +$ zerotier\-idtool generate beef\.secret beef\.public beef +.fi +.RE +.P +Sign a file with an identity's secret key: +.P +.RS 2 +.nf +$ zerotier\-idtool sign identity\.secret last_will_and_testament\.txt +.fi +.RE +.P +Verify a file's signature with a public key: +.P +.RS 2 +.nf +$ zerotier\-idtool verify identity\.public last_will_and_testament\.txt +.fi +.RE +.SH COPYRIGHT +.P +(c)2011\-2016 ZeroTier, Inc\. \-\- https://www\.zerotier\.com/ \-\- https://github\.com/zerotier +.SH SEE ALSO +.P +zerotier\-one(8), zerotier\-cli(1) + diff --git a/doc/zerotier-one.8 b/doc/zerotier-one.8 new file mode 100644 index 0000000..4ad7a15 --- /dev/null +++ b/doc/zerotier-one.8 @@ -0,0 +1,104 @@ +.TH "ZEROTIER\-ONE" "8" "December 2016" "" "" +.SH "NAME" +\fBzerotier-one\fR \- ZeroTier virtual network endpoint service +.SH SYNOPSIS +.P +\fBzerotier\-one\fP [\-switches] [working directory] +.SH DESCRIPTION +.P +\fBzerotier\-one\fR is the service/daemon responsible for connecting a Unix (Linux/BSD/OSX) system to one or more ZeroTier virtual networks and presenting those networks to the system as virtual network ports\. You can think of it as a peer to peer VPN client\. +.P +It's typically run by init systems like systemd (Linux) or launchd (Mac) rather than directly by the user, and it must be run as root unless you give it the \fB\-U\fP switch and don't plan on actually joining networks (e\.g\. to run a network controller microservice only)\. +.P +The \fBzerotier\-one\fR service keeps its state and other files in a working directory\. If this directory is not specified at launch it defaults to "/var/lib/zerotier\-one" on Linux, "/Library/Application Support/ZeroTier/One" on Mac, and "/var/db/zerotier\-one" on FreeBSD and other similar BSDs\. The working directory should persist\. It shouldn't be automatically cleaned by system cleanup daemons or stored in a volatile location\. Loss of its identity\.secret file results in loss of this system's unique 10\-digit ZeroTier address and key\. +.P +Multiple instances of \fBzerotier\-one\fR can be run on the same system as long as they are run with different primary ports (see switches) and a different working directory\. But since a single service can join any number of networks, typically there's no point in doing this\. +.P +The \fBzerotier\-one\fR service is controlled via a JSON API available at 127\.0\.0\.1: with the default primary port being 9993\. Access to this API requires an authorization token normally found in the authtoken\.secret file in the service's working directory\. On some platforms access may be guarded by other measures such as socket peer UID/GID lookup if additional security options are enabled (this is not the default)\. +.P +The first time the service is started in a fresh working directory, it generates a ZeroTier identity\. On slow systems this process can take ten seconds or more due to an anti\-DDOS/anti\-counterfeit proof of work function used by ZeroTier in address generation\. This only happens once, and once generated the result is saved in identity\.secret in the working directory\. This file represents and defines/claims your ZeroTier address and associated ECC\-256 key pair\. +.SH SWITCHES +.RS 0 +.IP \(bu 2 +\fB\-h\fP: +Display help\. +.IP \(bu 2 +\fB\-v\fP: +Display ZeroTier One version\. +.IP \(bu 2 +\fB\-U\fP: +Skip privilege check and allow to be run by non\-privileged user\. This is typically used when \fBzerotier\-one\fR is built with the network controller option included\. In this case the ZeroTier service might only be acting as a network controller and might never actually join networks, in which case it does not require elevated system permissions\. +.IP \(bu 2 +\fB\-p\fP: +Specify a different primary port\. If this is not given the default is 9993\. If zero is given a random port is chosen each time\. +.IP \(bu 2 +\fB\-d\fP: +Fork and run as a daemon\. +.IP \(bu 2 +\fB\-i\fP: +Invoke the \fBzerotier\-idtool\fR personality, in which case the binary behaves like zerotier\-idtool(1)\. This happens automatically if the name of the binary (or a symlink to it) is zerotier\-idtool\. +.IP \(bu 2 +\fB\-q\fP: +Invoke the \fBzerotier\-cli\fR personality, in which case the binary behaves like zerotier\-cli(1)\. This happens automatically if the name of the binary (or a symlink to it) is zerotier\-cli\. + +.RE +.SH EXAMPLES +.P +Run as daemon with OS default working directory and default port: +.P +.RS 2 +.nf +$ sudo zerotier\-one \-d +.fi +.RE +.P +Run as daemon with a different working directory and port: +.P +.RS 2 +.nf +$ sudo zerotier\-one \-d \-p12345 /tmp/zerotier\-working\-directory\-test +.fi +.RE +.SH FILES +.P +These are found in the service's working directory\. +.RS 0 +.IP \(bu 2 +\fBidentity\.public\fP: +The public portion of your ZeroTier identity, which is your 10\-digit hex address and the associated public key\. +.IP \(bu 2 +\fBidentity\.secret\fP: +Your full ZeroTier identity including its private key\. This file identifies the system on the network, which means you can move a ZeroTier address around by copying this file and you should back up this file if you want to save your system's static ZeroTier address\. This file must be protected, since theft of its secret key will allow anyone to impersonate your device on any network and decrypt traffic\. For network controllers this file is particularly sensitive since it constitutes the private key for a certificate authority for the controller's networks\. +.IP \(bu 2 +\fBauthtoken\.secret\fP: +The secret token used to authenticate requests to the service's local JSON API\. If it does not exist it is generated from a secure random source on service start\. To use, send it in the "X\-ZT1\-Auth" header with HTTP requests to 127\.0\.0\.1:\|\. +.IP \(bu 2 +\fBdevicemap\fP: +Remembers mappings of zt# interface numbers to ZeroTier networks so they'll persist across restarts\. On some systems that support longer interface names that can encode the network ID (such as FreeBSD) this file may not be present\. +.IP \(bu 2 +\fBzerotier\-one\.pid\fP: +ZeroTier's PID\. This file is deleted on normal shutdown\. +.IP \(bu 2 +\fBzerotier\-one\.port\fP: +ZeroTier's primary port, which is also where its JSON API is found at 127\.0\.0\.1:\|\. This file is created on startup and is read by zerotier\-cli(1) to determine where it should find the control API\. +.IP \(bu 2 +\fBcontroller\.db\fP: +If the ZeroTier One service is built with the network controller enabled, this file contains the controller's SQLite3 database\. +.IP \(bu 2 +\fBcontroller\.db\.backup\fP: +If the ZeroTier One service is built with the network controller enabled, it periodically backs up its controller\.db database in this file (currently every 5 minutes if there have been changes)\. Since this file is not a currently in use SQLite3 database it's safer to back up without corruption\. On new backups the file is rotated out rather than being rewritten in place\. +.IP \(bu 2 +\fBiddb\.d/\fP (directory): +Caches the public identity of every peer ZeroTier has spoken with in the last 60 days\. This directory and its contents can be deleted, but this may result in slower connection initations since it will require that we go out and re\-fetch full identities for peers we're speaking to\. +.IP \(bu 2 +\fBnetworks\.d\fP (directory): +This caches network configurations and certificate information for networks you belong to\. ZeroTier scans this directory for \|\.conf files on startup to recall its networks, so "touch"ing an empty \|\.conf file in this directory is a way of pre\-configuring ZeroTier to join a specific network on startup without using the API\. If the config file is empty ZeroTIer will just fetch it from the network's controller\. + +.RE +.SH COPYRIGHT +.P +(c)2011\-2016 ZeroTier, Inc\. \-\- https://www\.zerotier\.com/ \-\- https://github\.com/zerotier +.SH SEE ALSO +.P +zerotier\-cli(1), zerotier\-idtool(1) + diff --git a/ext/arm32-neon-salsa2012-asm/README.md b/ext/arm32-neon-salsa2012-asm/README.md new file mode 100644 index 0000000..54fc6f5 --- /dev/null +++ b/ext/arm32-neon-salsa2012-asm/README.md @@ -0,0 +1,6 @@ +ARM NEON (32-bit) ASM implementation of Salsa20/12 +====== + +This is from [supercop](http://bench.cr.yp.to/supercop.html) and was originally written by Daniel J. Bernstein. Code is in the public domain like the rest of Salsa20. It's much faster than the naive implementation. + +It's included automatically in 32-bit Linux ARM builds. It likely will not work on 64-bit ARM, so it'll need to be ported at least. That will unfortunately keep it out of mobile versions for now since those are all going 64-bit. diff --git a/ext/arm32-neon-salsa2012-asm/salsa2012.h b/ext/arm32-neon-salsa2012-asm/salsa2012.h new file mode 100644 index 0000000..95b247f --- /dev/null +++ b/ext/arm32-neon-salsa2012-asm/salsa2012.h @@ -0,0 +1,23 @@ +#ifndef ZT_SALSA2012_ARM32NEON_ASM +#define ZT_SALSA2012_ARM32NEON_ASM + +#if defined(__linux__) || defined(linux) || defined(__LINUX__) || defined(__linux) +#include +#include +#define zt_arm_has_neon() ((getauxval(AT_HWCAP) & HWCAP_NEON) != 0) +#else +#define zt_arm_has_neon() (true) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// ciphertext buffer, message/NULL, length, nonce (8 bytes), key (32 bytes) +extern int zt_salsa2012_armneon3_xor(unsigned char *c,const unsigned char *m,unsigned long long len,const unsigned char *n,const unsigned char *k); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext/arm32-neon-salsa2012-asm/salsa2012.s b/ext/arm32-neon-salsa2012-asm/salsa2012.s new file mode 100644 index 0000000..9e5989c --- /dev/null +++ b/ext/arm32-neon-salsa2012-asm/salsa2012.s @@ -0,0 +1,2231 @@ + +# qhasm: int32 input_0 + +# qhasm: int32 input_1 + +# qhasm: int32 input_2 + +# qhasm: int32 input_3 + +# qhasm: stack32 input_4 + +# qhasm: stack32 input_5 + +# qhasm: stack32 input_6 + +# qhasm: stack32 input_7 + +# qhasm: int32 caller_r4 + +# qhasm: int32 caller_r5 + +# qhasm: int32 caller_r6 + +# qhasm: int32 caller_r7 + +# qhasm: int32 caller_r8 + +# qhasm: int32 caller_r9 + +# qhasm: int32 caller_r10 + +# qhasm: int32 caller_r11 + +# qhasm: int32 caller_r14 + +# qhasm: reg128 caller_q4 + +# qhasm: reg128 caller_q5 + +# qhasm: reg128 caller_q6 + +# qhasm: reg128 caller_q7 + +# qhasm: startcode +.fpu neon +.text + +# qhasm: constant sigma: +.align 2 +sigma: + +# qhasm: const32 1634760805 +.word 1634760805 + +# qhasm: const32 857760878 +.word 857760878 + +# qhasm: const32 2036477234 +.word 2036477234 + +# qhasm: const32 1797285236 +.word 1797285236 + +# qhasm: int128 abab + +# qhasm: int128 diag0 + +# qhasm: int128 diag1 + +# qhasm: int128 diag2 + +# qhasm: int128 diag3 + +# qhasm: int128 a0 + +# qhasm: int128 a1 + +# qhasm: int128 a2 + +# qhasm: int128 a3 + +# qhasm: int128 b0 + +# qhasm: int128 b1 + +# qhasm: int128 b2 + +# qhasm: int128 b3 + +# qhasm: int128 next_diag0 + +# qhasm: int128 next_diag1 + +# qhasm: int128 next_diag2 + +# qhasm: int128 next_diag3 + +# qhasm: int128 next_a0 + +# qhasm: int128 next_a1 + +# qhasm: int128 next_a2 + +# qhasm: int128 next_a3 + +# qhasm: int128 next_b0 + +# qhasm: int128 next_b1 + +# qhasm: int128 next_b2 + +# qhasm: int128 next_b3 + +# qhasm: int128 x0x5x10x15 + +# qhasm: int128 x12x1x6x11 + +# qhasm: int128 x8x13x2x7 + +# qhasm: int128 x4x9x14x3 + +# qhasm: int128 x0x1x10x11 + +# qhasm: int128 x12x13x6x7 + +# qhasm: int128 x8x9x2x3 + +# qhasm: int128 x4x5x14x15 + +# qhasm: int128 x0x1x2x3 + +# qhasm: int128 x4x5x6x7 + +# qhasm: int128 x8x9x10x11 + +# qhasm: int128 x12x13x14x15 + +# qhasm: int128 m0m1m2m3 + +# qhasm: int128 m4m5m6m7 + +# qhasm: int128 m8m9m10m11 + +# qhasm: int128 m12m13m14m15 + +# qhasm: int128 start0 + +# qhasm: int128 start1 + +# qhasm: int128 start2 + +# qhasm: int128 start3 + +# qhasm: stack128 stack_start3 + +# qhasm: stack128 next_start2 + +# qhasm: stack128 next_start3 + +# qhasm: int128 k0k1k2k3 + +# qhasm: int128 k4k5k6k7 + +# qhasm: int128 k1n1k7k2 + +# qhasm: int128 n2n3n3n2 + +# qhasm: int128 k2k3k6k7 + +# qhasm: int128 nextblock + +# qhasm: stack128 stack_q4 + +# qhasm: stack128 stack_q5 + +# qhasm: stack128 stack_q6 + +# qhasm: stack128 stack_q7 + +# qhasm: stack32 stack_r4 + +# qhasm: stack128 k2k3k6k7_stack + +# qhasm: stack128 k1n1k7k2_stack + +# qhasm: stack512 tmp + +# qhasm: stack32 savec + +# qhasm: int32 i + +# qhasm: int32 ci + +# qhasm: int32 mi + +# qhasm: enter zt_salsa2012_armneon3_xor +.align 2 +.global _zt_salsa2012_armneon3_xor +.global zt_salsa2012_armneon3_xor +.type _zt_salsa2012_armneon3_xor STT_FUNC +.type zt_salsa2012_armneon3_xor STT_FUNC +_zt_salsa2012_armneon3_xor: +zt_salsa2012_armneon3_xor: +sub sp,sp,#256 + +# qhasm: new stack_q4 + +# qhasm: new stack_q5 + +# qhasm: new stack_q6 + +# qhasm: new stack_q7 + +# qhasm: stack_q4 bot = caller_q4 bot +# asm 1: vstr stack_r4=stack32#2 +# asm 2: str stack_r4=[sp,#68] +str r4,[sp,#68] + +# qhasm: int32 c + +# qhasm: c = input_0 +# asm 1: mov >c=int32#1,c=r0,m=int32#2,m=r1,mlenlow=int32#3,mlenlow=r2,mlenhigh=int32#4,mlenhigh=r3,n=int32#5,n=r4,k=int32#13,k=r12,k0k1k2k3=reg128#1%bot->k0k1k2k3=reg128#1%top},[k0k1k2k3=d0->k0k1k2k3=d1},[k4k5k6k7=reg128#2%bot->k4k5k6k7=reg128#2%top},[k4k5k6k7=d2->k4k5k6k7=d3},[i=int32#13,=sigma +# asm 2: ldr >i=r12,=sigma +ldr r12,=sigma + +# qhasm: start0 = mem128[i] +# asm 1: vld1.8 {>start0=reg128#3%bot->start0=reg128#3%top},[start0=d4->start0=d5},[start1=reg128#4,#0 +# asm 2: vmov.i64 >start1=q3,#0 +vmov.i64 q3,#0 + +# qhasm: start1 bot = mem64[n] +# asm 1: vld1.8 {k2k3k6k7=reg128#6,k2k3k6k7=q5,n2n3n3n2=reg128#1,#0 +# asm 2: vmov.i64 >n2n3n3n2=q0,#0 +vmov.i64 q0,#0 + +# qhasm: unsigneddiag0=reg128#8,diag0=q7,diag1=reg128#9,diag1=q8,start2=reg128#10,start2=q9,nextblock=reg128#11,#0xff +# asm 2: vmov.i64 >nextblock=q10,#0xff +vmov.i64 q10,#0xff + +# qhasm: 4x nextblock unsigned>>= 7 +# asm 1: vshr.u32 >nextblock=reg128#11,nextblock=q10,n2n3n3n2=reg128#1,n2n3n3n2=q0,n2n3n3n2=reg128#1,n2n3n3n2=q0,next_diag0=reg128#2,next_diag0=q1,next_diag1=reg128#5,next_diag1=q4,i=int32#5,=12 +# asm 2: ldr >i=r4,=12 +ldr r4,=12 + +# qhasm: mainloop2: +._mainloop2: + +# qhasm: 4x a0 = diag1 + diag0 +# asm 1: vadd.i32 >a0=reg128#11,a0=q10,next_a0=reg128#14,next_a0=q13,b0=reg128#15,b0=q14,next_b0=reg128#16,next_b0=q15,> 25 +# asm 1: vsri.i32 > 25 +# asm 1: vsri.i32 diag3=reg128#7,diag3=q6,next_diag3=reg128#11,next_diag3=q10,a1=reg128#13,a1=q12,next_a1=reg128#14,next_a1=q13,b1=reg128#15,b1=q14,next_b1=reg128#16,next_b1=q15,> 23 +# asm 1: vsri.i32 > 23 +# asm 1: vsri.i32 diag2=reg128#6,diag2=q5,next_diag2=reg128#12,next_diag2=q11,a2=reg128#13,a2=q12,diag3=reg128#7,diag3=q6,next_a2=reg128#14,next_a2=q13,b2=reg128#15,b2=q14,next_diag3=reg128#11,next_diag3=q10,next_b2=reg128#16,next_b2=q15,> 19 +# asm 1: vsri.i32 > 19 +# asm 1: vsri.i32 diag1=reg128#9,diag1=q8,next_diag1=reg128#5,next_diag1=q4,a3=reg128#13,a3=q12,next_a3=reg128#14,next_a3=q13,b3=reg128#15,b3=q14,next_b3=reg128#16,next_b3=q15,> 14 +# asm 1: vsri.i32 diag1=reg128#9,diag1=q8,> 14 +# asm 1: vsri.i32 diag0=reg128#8,diag0=q7,next_diag1=reg128#5,next_diag1=q4,next_diag0=reg128#2,next_diag0=q1,a0=reg128#13,a0=q12,next_a0=reg128#14,next_a0=q13,b0=reg128#15,b0=q14,next_b0=reg128#16,next_b0=q15,> 25 +# asm 1: vsri.i32 > 25 +# asm 1: vsri.i32 diag1=reg128#9,diag1=q8,next_diag1=reg128#5,next_diag1=q4,a1=reg128#13,a1=q12,next_a1=reg128#14,next_a1=q13,b1=reg128#15,b1=q14,next_b1=reg128#16,next_b1=q15,> 23 +# asm 1: vsri.i32 ? i -= 2 +# asm 1: subs > 23 +# asm 1: vsri.i32 diag2=reg128#6,diag2=q5,next_diag2=reg128#12,next_diag2=q11,a2=reg128#13,a2=q12,diag1=reg128#9,diag1=q8,next_a2=reg128#14,next_a2=q13,b2=reg128#15,b2=q14,next_diag1=reg128#5,next_diag1=q4,next_b2=reg128#16,next_b2=q15,> 19 +# asm 1: vsri.i32 > 19 +# asm 1: vsri.i32 diag3=reg128#7,diag3=q6,next_diag3=reg128#11,next_diag3=q10,a3=reg128#13,a3=q12,next_a3=reg128#14,next_a3=q13,b3=reg128#15,b3=q14,next_b3=reg128#16,next_b3=q15,> 14 +# asm 1: vsri.i32 diag3=reg128#7,diag3=q6,> 14 +# asm 1: vsri.i32 diag0=reg128#8,diag0=q7,next_diag3=reg128#13,next_diag3=q12,next_diag0=reg128#2,next_diag0=q1, +bhi ._mainloop2 + +# qhasm: 2x abab = 0xffffffff +# asm 1: vmov.i64 >abab=reg128#11,#0xffffffff +# asm 2: vmov.i64 >abab=q10,#0xffffffff +vmov.i64 q10,#0xffffffff + +# qhasm: new x4x9x14x3 + +# qhasm: x4x9x14x3 bot = stack_start3 bot +# asm 1: vldr x0x5x10x15=reg128#8,x0x5x10x15=q7,x12x1x6x11=reg128#9,x12x1x6x11=q8,x8x13x2x7=reg128#6,x8x13x2x7=q5,x4x9x14x3=reg128#7,x4x9x14x3=q6,x0x1x10x11=reg128#10,x0x1x10x11=q9,x12x13x6x7=reg128#14,x12x13x6x7=q13,x8x9x2x3=reg128#15,x8x9x2x3=q14,x4x5x14x15=reg128#16,x4x5x14x15=q15,x0x1x2x3=reg128#6,x0x1x2x3=q5,x4x5x6x7=reg128#7,x4x5x6x7=q6,x8x9x10x11=reg128#8,x8x9x10x11=q7,x12x13x14x15=reg128#9,x12x13x14x15=q8,m0m1m2m3=reg128#10%bot->m0m1m2m3=reg128#10%top},[m0m1m2m3=d18->m0m1m2m3=d19},[m4m5m6m7=reg128#14%bot->m4m5m6m7=reg128#14%top},[m4m5m6m7=d26->m4m5m6m7=d27},[m8m9m10m11=reg128#15%bot->m8m9m10m11=reg128#15%top},[m8m9m10m11=d28->m8m9m10m11=d29},[m12m13m14m15=reg128#16%bot->m12m13m14m15=reg128#16%top},[m12m13m14m15=d30->m12m13m14m15=d31},[x0x1x2x3=reg128#6,x0x1x2x3=q5,x4x5x6x7=reg128#7,x4x5x6x7=q6,x8x9x10x11=reg128#8,x8x9x10x11=q7,x12x13x14x15=reg128#9,x12x13x14x15=q8,x0x5x10x15=reg128#2,x0x5x10x15=q1,x12x1x6x11=reg128#5,x12x1x6x11=q4,x8x13x2x7=reg128#6,x8x13x2x7=q5,x4x9x14x3=reg128#7,x4x9x14x3=q6,x0x1x10x11=reg128#8,x0x1x10x11=q7,x12x13x6x7=reg128#9,x12x13x6x7=q8,x8x9x2x3=reg128#10,x8x9x2x3=q9,x4x5x14x15=reg128#12,x4x5x14x15=q11,x0x1x2x3=reg128#2,x0x1x2x3=q1,x4x5x6x7=reg128#5,x4x5x6x7=q4,x8x9x10x11=reg128#6,x8x9x10x11=q5,x12x13x14x15=reg128#7,x12x13x14x15=q6,m0m1m2m3=reg128#8%bot->m0m1m2m3=reg128#8%top},[m0m1m2m3=d14->m0m1m2m3=d15},[m4m5m6m7=reg128#9%bot->m4m5m6m7=reg128#9%top},[m4m5m6m7=d16->m4m5m6m7=d17},[m8m9m10m11=reg128#10%bot->m8m9m10m11=reg128#10%top},[m8m9m10m11=d18->m8m9m10m11=d19},[m12m13m14m15=reg128#11%bot->m12m13m14m15=reg128#11%top},[m12m13m14m15=d20->m12m13m14m15=d21},[x0x1x2x3=reg128#2,x0x1x2x3=q1,x4x5x6x7=reg128#5,x4x5x6x7=q4,x8x9x10x11=reg128#6,x8x9x10x11=q5,x12x13x14x15=reg128#7,x12x13x14x15=q6,? mlenhigh - 0 +# asm 1: cmp +bhi ._mlenatleast128 + +# qhasm: =? mlenlow - 0 +# asm 1: cmp savec=stack32#1 +# asm 2: str savec=[sp,#64] +str r0,[sp,#64] + +# qhasm: c = &tmp +# asm 1: lea >c=int32#1,c=r0,i=int32#4,=0 +# asm 2: ldr >i=r3,=0 +ldr r3,=0 + +# qhasm: mcopy: +._mcopy: + +# qhasm: mi = mem8[m + 0] +# asm 1: ldrb >mi=int32#5,[mi=r4,[mi=int32#2,=0 +# asm 2: ldr >mi=r1,=0 +ldr r1,=0 + +# qhasm: pad: +._pad: + +# qhasm: mem8[c + 0] = mi +# asm 1: strb m=int32#2,m=r1,diag0=reg128#2,diag0=q1,diag1=reg128#5,diag1=q4,diag2=reg128#8,diag2=q7,diag3=reg128#9,diag3=q8,nextblock=reg128#10,#0xff +# asm 2: vmov.i64 >nextblock=q9,#0xff +vmov.i64 q9,#0xff + +# qhasm: 4x nextblock unsigned>>= 7 +# asm 1: vshr.u32 >nextblock=reg128#10,nextblock=q9,n2n3n3n2=reg128#1,n2n3n3n2=q0,i=int32#4,=12 +# asm 2: ldr >i=r3,=12 +ldr r3,=12 + +# qhasm: mainloop1: +._mainloop1: + +# qhasm: 4x a0 = diag1 + diag0 +# asm 1: vadd.i32 >a0=reg128#10,a0=q9,b0=reg128#11,b0=q10,> 25 +# asm 1: vsri.i32 diag3=reg128#9,diag3=q8,a1=reg128#10,a1=q9,b1=reg128#11,b1=q10,> 23 +# asm 1: vsri.i32 diag2=reg128#8,diag2=q7,a2=reg128#10,a2=q9,diag3=reg128#9,diag3=q8,b2=reg128#11,b2=q10,> 19 +# asm 1: vsri.i32 diag1=reg128#5,diag1=q4,a3=reg128#10,a3=q9,b3=reg128#11,b3=q10,> 14 +# asm 1: vsri.i32 diag1=reg128#5,diag1=q4,diag0=reg128#2,diag0=q1,a0=reg128#10,a0=q9,b0=reg128#11,b0=q10,> 25 +# asm 1: vsri.i32 diag1=reg128#5,diag1=q4,a1=reg128#10,a1=q9,b1=reg128#11,b1=q10,> 23 +# asm 1: vsri.i32 ? i -= 2 +# asm 1: subs diag2=reg128#8,diag2=q7,a2=reg128#10,a2=q9,diag1=reg128#5,diag1=q4,b2=reg128#11,b2=q10,> 19 +# asm 1: vsri.i32 diag3=reg128#9,diag3=q8,a3=reg128#10,a3=q9,b3=reg128#11,b3=q10,> 14 +# asm 1: vsri.i32 diag3=reg128#9,diag3=q8,diag0=reg128#2,diag0=q1, +bhi ._mainloop1 + +# qhasm: 2x abab = 0xffffffff +# asm 1: vmov.i64 >abab=reg128#10,#0xffffffff +# asm 2: vmov.i64 >abab=q9,#0xffffffff +vmov.i64 q9,#0xffffffff + +# qhasm: 4x x0x5x10x15 = diag0 + start0 +# asm 1: vadd.i32 >x0x5x10x15=reg128#2,x0x5x10x15=q1,x12x1x6x11=reg128#5,x12x1x6x11=q4,x8x13x2x7=reg128#6,x8x13x2x7=q5,x4x9x14x3=reg128#7,x4x9x14x3=q6,x0x1x10x11=reg128#8,x0x1x10x11=q7,x12x13x6x7=reg128#9,x12x13x6x7=q8,x8x9x2x3=reg128#11,x8x9x2x3=q10,x4x5x14x15=reg128#12,x4x5x14x15=q11,x0x1x2x3=reg128#2,x0x1x2x3=q1,x4x5x6x7=reg128#5,x4x5x6x7=q4,x8x9x10x11=reg128#6,x8x9x10x11=q5,x12x13x14x15=reg128#7,x12x13x14x15=q6,m0m1m2m3=reg128#8%bot->m0m1m2m3=reg128#8%top},[m0m1m2m3=d14->m0m1m2m3=d15},[m4m5m6m7=reg128#9%bot->m4m5m6m7=reg128#9%top},[m4m5m6m7=d16->m4m5m6m7=d17},[m8m9m10m11=reg128#10%bot->m8m9m10m11=reg128#10%top},[m8m9m10m11=d18->m8m9m10m11=d19},[m12m13m14m15=reg128#11%bot->m12m13m14m15=reg128#11%top},[m12m13m14m15=d20->m12m13m14m15=d21},[x0x1x2x3=reg128#2,x0x1x2x3=q1,x4x5x6x7=reg128#5,x4x5x6x7=q4,x8x9x10x11=reg128#6,x8x9x10x11=q5,x12x13x14x15=reg128#7,x12x13x14x15=q6,i=int32#4,=0 +# asm 2: ldr >i=r3,=0 +ldr r3,=0 + +# qhasm: m = c - 64 +# asm 1: sub >m=int32#2,m=r1,c=int32#1,c=r0,ci=int32#5,[ci=r4,[? mlenlow -= 64 +# asm 1: subs +bhi ._mlenatleast1 + +# qhasm: done: +._done: + +# qhasm: new caller_r4 + +# qhasm: caller_r4 = stack_r4 +# asm 1: ldr >caller_r4=int32#5,caller_r4=r4,result=int32#1,=0 +# asm 2: ldr >result=r0,=0 +ldr r0,=0 + +# qhasm: return result +add sp,sp,#256 +bx lr diff --git a/ext/bin/tap-windows-ndis5/x64/WdfCoinstaller01011.dll b/ext/bin/tap-windows-ndis5/x64/WdfCoinstaller01011.dll deleted file mode 100644 index d49d291..0000000 Binary files a/ext/bin/tap-windows-ndis5/x64/WdfCoinstaller01011.dll and /dev/null differ diff --git a/ext/bin/tap-windows-ndis5/x64/zttap200.cat b/ext/bin/tap-windows-ndis5/x64/zttap200.cat deleted file mode 100644 index a3769e4..0000000 Binary files a/ext/bin/tap-windows-ndis5/x64/zttap200.cat and /dev/null differ diff --git a/ext/bin/tap-windows-ndis5/x64/zttap200.inf b/ext/bin/tap-windows-ndis5/x64/zttap200.inf deleted file mode 100644 index dc1a742..0000000 --- a/ext/bin/tap-windows-ndis5/x64/zttap200.inf +++ /dev/null @@ -1,79 +0,0 @@ -[Version] -Signature="$WINDOWS NT$" -Class=Net -ClassGuid={4d36e972-e325-11ce-bfc1-08002be10318} -Provider=%Provider% -CatalogFile=zttap200.cat -DriverVer=01/23/2014,15.19.17.816 - -[Strings] -DeviceDescription = "ZeroTier One Virtual Network Port" -Provider = "ZeroTier Networks LLC" - -; To build for x86, take NTamd64 off this and off the named section manually, build, then put it back! -[Manufacturer] -%Provider%=zttap200,NTamd64 - -[zttap200] -%DeviceDescription%=zttap200.ndi,zttap200 - -[ztTap200.NTamd64] -%DeviceDescription%=zttap200.ndi,zttap200 - -[zttap200.ndi] -CopyFiles = zttap200.driver,zttap200.files -AddReg = zttap200.reg -AddReg = zttap200.params.reg -Characteristics = 0x81 - -[zttap200.ndi.Services] -AddService = zttap200, 2, zttap200.service - -[zttap200.reg] -HKR, Ndi, Service, 0, "zttap200" -HKR, Ndi\Interfaces, UpperRange, 0, "ndis5" -HKR, Ndi\Interfaces, LowerRange, 0, "ethernet" -HKR, , Manufacturer, 0, "%Provider%" -HKR, , ProductName, 0, "%DeviceDescription%" - -[zttap200.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" - -[zttap200.service] -DisplayName = %DeviceDescription% -ServiceType = 1 -StartType = 3 -ErrorControl = 1 -LoadOrderGroup = NDIS -ServiceBinary = %12%\zttap200.sys - -[SourceDisksNames] -1 = %DeviceDescription%, zttap200.sys - -[SourceDisksFiles] -zttap200.sys = 1 - -[DestinationDirs] -zttap200.files = 11 -zttap200.driver = 12 - -[zttap200.files] -; - -[zttap200.driver] -zttap200.sys,,,6 ; COPYFLG_NOSKIP | COPYFLG_NOVERSIONCHECK diff --git a/ext/bin/tap-windows-ndis5/x64/zttap200.sys b/ext/bin/tap-windows-ndis5/x64/zttap200.sys deleted file mode 100644 index 339351f..0000000 Binary files a/ext/bin/tap-windows-ndis5/x64/zttap200.sys and /dev/null differ diff --git a/ext/bin/tap-windows-ndis5/x86/WdfCoinstaller01011.dll b/ext/bin/tap-windows-ndis5/x86/WdfCoinstaller01011.dll deleted file mode 100644 index e943ea4..0000000 Binary files a/ext/bin/tap-windows-ndis5/x86/WdfCoinstaller01011.dll and /dev/null differ diff --git a/ext/bin/tap-windows-ndis5/x86/zttap200.cat b/ext/bin/tap-windows-ndis5/x86/zttap200.cat deleted file mode 100644 index d90ecbb..0000000 Binary files a/ext/bin/tap-windows-ndis5/x86/zttap200.cat and /dev/null differ diff --git a/ext/bin/tap-windows-ndis5/x86/zttap200.inf b/ext/bin/tap-windows-ndis5/x86/zttap200.inf deleted file mode 100644 index 99aac9f..0000000 --- a/ext/bin/tap-windows-ndis5/x86/zttap200.inf +++ /dev/null @@ -1,76 +0,0 @@ -[Version] -Signature="$WINDOWS NT$" -Class=Net -ClassGuid={4d36e972-e325-11ce-bfc1-08002be10318} -Provider=%Provider% -CatalogFile=zttap200.cat -DriverVer=01/24/2014,17.25.51.226 - -[Strings] -DeviceDescription = "ZeroTier One Virtual Network Port" -Provider = "ZeroTier Networks LLC" - -; To build for x86, take NTamd64 off this and off the named section manually, build, then put it back! -[Manufacturer] -%Provider%=zttap200 - -[zttap200] -%DeviceDescription%=zttap200.ndi,zttap200 - -[zttap200.ndi] -CopyFiles = zttap200.driver,zttap200.files -AddReg = zttap200.reg -AddReg = zttap200.params.reg -Characteristics = 0x81 - -[zttap200.ndi.Services] -AddService = zttap200, 2, zttap200.service - -[zttap200.reg] -HKR, Ndi, Service, 0, "zttap200" -HKR, Ndi\Interfaces, UpperRange, 0, "ndis5" -HKR, Ndi\Interfaces, LowerRange, 0, "ethernet" -HKR, , Manufacturer, 0, "%Provider%" -HKR, , ProductName, 0, "%DeviceDescription%" - -[zttap200.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" - -[zttap200.service] -DisplayName = %DeviceDescription% -ServiceType = 1 -StartType = 3 -ErrorControl = 1 -LoadOrderGroup = NDIS -ServiceBinary = %12%\zttap200.sys - -[SourceDisksNames] -1 = %DeviceDescription%, zttap200.sys - -[SourceDisksFiles] -zttap200.sys = 1 - -[DestinationDirs] -zttap200.files = 11 -zttap200.driver = 12 - -[zttap200.files] -; - -[zttap200.driver] -zttap200.sys,,,6 ; COPYFLG_NOSKIP | COPYFLG_NOVERSIONCHECK diff --git a/ext/bin/tap-windows-ndis5/x86/zttap200.sys b/ext/bin/tap-windows-ndis5/x86/zttap200.sys deleted file mode 100644 index b7b11fb..0000000 Binary files a/ext/bin/tap-windows-ndis5/x86/zttap200.sys and /dev/null differ diff --git a/ext/bin/tap-windows-ndis6/x64/ZeroTierOne_NDIS6_x64.msi b/ext/bin/tap-windows-ndis6/x64/ZeroTierOne_NDIS6_x64.msi index 818796f..4cfb7d7 100644 Binary files a/ext/bin/tap-windows-ndis6/x64/ZeroTierOne_NDIS6_x64.msi and b/ext/bin/tap-windows-ndis6/x64/ZeroTierOne_NDIS6_x64.msi differ diff --git a/ext/bin/tap-windows-ndis6/x86/ZeroTierOne_NDIS6_x86.msi b/ext/bin/tap-windows-ndis6/x86/ZeroTierOne_NDIS6_x86.msi index b9e2d7e..1b9aec4 100644 Binary files a/ext/bin/tap-windows-ndis6/x86/ZeroTierOne_NDIS6_x86.msi and b/ext/bin/tap-windows-ndis6/x86/ZeroTierOne_NDIS6_x86.msi differ diff --git a/ext/http-parser/http_parser.c b/ext/http-parser/http_parser.c index 3c896ff..895bf0c 100644 --- a/ext/http-parser/http_parser.c +++ b/ext/http-parser/http_parser.c @@ -1366,12 +1366,7 @@ reexecute: || c != CONTENT_LENGTH[parser->index]) { parser->header_state = h_general; } else if (parser->index == sizeof(CONTENT_LENGTH)-2) { - if (parser->flags & F_CONTENTLENGTH) { - SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH); - goto error; - } parser->header_state = h_content_length; - parser->flags |= F_CONTENTLENGTH; } break; @@ -1474,6 +1469,12 @@ reexecute: 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; diff --git a/ext/http-parser/http_parser.h b/ext/http-parser/http_parser.h index 105ae51..45c72a0 100644 --- a/ext/http-parser/http_parser.h +++ b/ext/http-parser/http_parser.h @@ -27,7 +27,7 @@ extern "C" { /* 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 0 +#define HTTP_PARSER_VERSION_PATCH 1 #include #if defined(_WIN32) && !defined(__MINGW32__) && \ @@ -90,6 +90,76 @@ 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) \ diff --git a/ext/installfiles/linux/zerotier-containerized/Dockerfile b/ext/installfiles/linux/zerotier-containerized/Dockerfile new file mode 100644 index 0000000..678216d --- /dev/null +++ b/ext/installfiles/linux/zerotier-containerized/Dockerfile @@ -0,0 +1,20 @@ +FROM alpine:latest +MAINTAINER Adam Ierymenko + +LABEL version="1.1.14" +LABEL description="Containerized ZeroTier One for use on CoreOS or other Docker-only Linux hosts." + +# Uncomment to build in container +#RUN apk add --update alpine-sdk linux-headers + +RUN apk add --update libgcc libstdc++ + +ADD zerotier-one / +RUN chmod 0755 /zerotier-one +RUN ln -sf /zerotier-one /zerotier-cli +RUN mkdir -p /var/lib/zerotier-one + +ADD main.sh / +RUN chmod 0755 /main.sh + +ENTRYPOINT /main.sh diff --git a/ext/installfiles/linux/zerotier-containerized/main.sh b/ext/installfiles/linux/zerotier-containerized/main.sh new file mode 100755 index 0000000..685a689 --- /dev/null +++ b/ext/installfiles/linux/zerotier-containerized/main.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +export PATH=/bin:/usr/bin:/usr/local/bin:/sbin:/usr/sbin + +if [ ! -e /dev/net/tun ]; then + echo 'FATAL: cannot start ZeroTier One in container: /dev/net/tun not present.' + exit 1 +fi + +exec /zerotier-one diff --git a/ext/installfiles/mac-update/updater.tmpl.sh b/ext/installfiles/mac-update/updater.tmpl.sh new file mode 100644 index 0000000..0b07f6d --- /dev/null +++ b/ext/installfiles/mac-update/updater.tmpl.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +export PATH=/bin:/usr/bin:/sbin:/usr/sbin +shopt -s expand_aliases + +if [ "$UID" -ne 0 ]; then + echo '*** Auto-updater must be run as root.' + exit 1 +fi + +scriptPath="`dirname "$0"`/`basename "$0"`" +if [ ! -s "$scriptPath" ]; then + scriptPath="$0" + if [ ! -s "$scriptPath" ]; then + echo "*** Auto-updater cannot determine its own path; $scriptPath is not readable." + exit 2 + fi +fi + +endMarkerIndex=`grep -a -b -E '^################' "$scriptPath" | head -c 16 | cut -d : -f 1` +if [ "$endMarkerIndex" -le 100 ]; then + echo 'Internal error: unable to find end of script / start of binary data marker.' + exit 2 +fi +blobStart=`expr $endMarkerIndex + 17` +if [ "$blobStart" -le "$endMarkerIndex" ]; then + echo 'Internal error: unable to find end of script / start of binary data marker.' + exit 2 +fi + +rm -f /tmp/ZeroTierOne-update.pkg +tail -c +$blobStart "$scriptPath" >/tmp/ZeroTierOne-update.pkg +chmod 0600 /tmp/ZeroTierOne-update.pkg + +if [ -s /tmp/ZeroTierOne-update.pkg ]; then + rm -f '/Library/Application Support/ZeroTier/One/latest-update.exe' '/Library/Application Support/ZeroTier/One/latest-update.json' /tmp/ZeroTierOne-update.log + installer -verbose -pkg /tmp/ZeroTierOne-update.pkg -target / >/tmp/ZeroTierOne-update.log 2>&1 + rm -f /tmp/ZeroTierOne-update.pkg + exit 0 +else + echo '*** Error self-unpacking update!' + exit 3 +fi + +# Do not remove the last line or add a carriage return to it! The installer +# looks for an unterminated line beginning with 16 #'s in itself to find +# the binary blob data, which is appended after it. + +################ \ No newline at end of file diff --git a/ext/installfiles/mac/ZeroTier One.pkgproj b/ext/installfiles/mac/ZeroTier One.pkgproj index d973052..96b1338 100755 --- a/ext/installfiles/mac/ZeroTier One.pkgproj +++ b/ext/installfiles/mac/ZeroTier One.pkgproj @@ -37,7 +37,7 @@ GID 80 PATH - mac-ui-macgap1-wrapper/bin/ZeroTier One.app + ../../../macui/build/Release/ZeroTier One.app PATH_TYPE 1 PERMISSIONS @@ -121,119 +121,6 @@ UID 0 - - CHILDREN - - - CHILDREN - - GID - 0 - PATH - ui/index.html - PATH_TYPE - 1 - PERMISSIONS - 420 - TYPE - 3 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - ui/main.js - PATH_TYPE - 1 - PERMISSIONS - 420 - TYPE - 3 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - ui/react.min.js - PATH_TYPE - 1 - PERMISSIONS - 420 - TYPE - 3 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - ui/simpleajax.min.js - PATH_TYPE - 1 - PERMISSIONS - 420 - TYPE - 3 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - ui/zerotier.css - PATH_TYPE - 1 - PERMISSIONS - 420 - TYPE - 3 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - ui/ztui.min.js - PATH_TYPE - 1 - PERMISSIONS - 420 - TYPE - 3 - UID - 0 - - - GID - 0 - PATH - ui - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 2 - UID - 0 - CHILDREN @@ -759,7 +646,7 @@ OVERWRITE_PERMISSIONS VERSION - 1.1.14 + 1.2.4 PROJECT_COMMENTS @@ -773,7 +660,7 @@ ZW50LVN0eWxlLVR5cGUiIGNvbnRlbnQ9InRleHQvY3NzIj4KPHRp dGxlPjwvdGl0bGU+CjxtZXRhIG5hbWU9IkdlbmVyYXRvciIgY29u dGVudD0iQ29jb2EgSFRNTCBXcml0ZXIiPgo8bWV0YSBuYW1lPSJD - b2NvYVZlcnNpb24iIGNvbnRlbnQ9IjE0MDQuNDciPgo8c3R5bGUg + b2NvYVZlcnNpb24iIGNvbnRlbnQ9IjE1MDQuNzYiPgo8c3R5bGUg dHlwZT0idGV4dC9jc3MiPgpwLnAxIHttYXJnaW46IDAuMHB4IDAu MHB4IDAuMHB4IDAuMHB4OyBsaW5lLWhlaWdodDogMTQuMHB4OyBm b250OiAxMi4wcHggSGVsdmV0aWNhOyBjb2xvcjogIzAwMDAwMDsg @@ -782,7 +669,7 @@ b2R5Pgo8cCBjbGFzcz0icDEiPjxzcGFuIGNsYXNzPSJzMSI+WmVy b1RpZXIgT25lIC0gTmV0d29yayBWaXJ0dWFsaXphdGlvbiBFdmVy eXdoZXJlPC9zcGFuPjwvcD4KPHAgY2xhc3M9InAxIj48c3BhbiBj - bGFzcz0iczEiPihjKTIwMTEtMjAxNiBaZXJvVGllciwgSW5jLjwv + bGFzcz0iczEiPihjKTIwMTEtMjAxNyBaZXJvVGllciwgSW5jLjwv c3Bhbj48L3A+CjxwIGNsYXNzPSJwMSI+PHNwYW4gY2xhc3M9InMx Ij5jb250YWN0QHplcm90aWVyLmNvbTwvc3Bhbj48L3A+CjxwIGNs YXNzPSJwMSI+PHNwYW4gY2xhc3M9InMxIj48YnI+Cjwvc3Bhbj48 diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/bin/ZeroTier One.app/Contents/Info.plist b/ext/installfiles/mac/mac-ui-macgap1-wrapper/bin/ZeroTier One.app/Contents/Info.plist deleted file mode 100644 index c67923c..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/bin/ZeroTier One.app/Contents/Info.plist +++ /dev/null @@ -1,59 +0,0 @@ - - - - - BuildMachineOSBuild - 15B42 - CFBundleDevelopmentRegion - en - CFBundleExecutable - ZeroTier One - CFBundleIconFile - ZeroTierIcon - CFBundleIdentifier - com.zerotier.ZeroTier-One - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - ZeroTier One - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleSupportedPlatforms - - MacOSX - - CFBundleVersion - 1 - DTCompiler - com.apple.compilers.llvm.clang.1_0 - DTPlatformBuild - 7B1005 - DTPlatformVersion - GM - DTSDKBuild - 15A278 - DTSDKName - macosx10.11 - DTXcode - 0711 - DTXcodeBuild - 7B1005 - LSApplicationCategoryType - public.app-category.utilities - LSMinimumSystemVersion - 10.7 - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - - NSMainNibFile - MainMenu - NSPrincipalClass - NSApplication - - diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/bin/ZeroTier One.app/Contents/MacOS/ZeroTier One b/ext/installfiles/mac/mac-ui-macgap1-wrapper/bin/ZeroTier One.app/Contents/MacOS/ZeroTier One deleted file mode 100755 index 8e38b86..0000000 Binary files a/ext/installfiles/mac/mac-ui-macgap1-wrapper/bin/ZeroTier One.app/Contents/MacOS/ZeroTier One and /dev/null differ diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/bin/ZeroTier One.app/Contents/PkgInfo b/ext/installfiles/mac/mac-ui-macgap1-wrapper/bin/ZeroTier One.app/Contents/PkgInfo deleted file mode 100644 index bd04210..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/bin/ZeroTier One.app/Contents/PkgInfo +++ /dev/null @@ -1 +0,0 @@ -APPL???? \ No newline at end of file diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/bin/ZeroTier One.app/Contents/Resources/en.lproj/Credits.rtf b/ext/installfiles/mac/mac-ui-macgap1-wrapper/bin/ZeroTier One.app/Contents/Resources/en.lproj/Credits.rtf deleted file mode 100644 index 6f388f6..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/bin/ZeroTier One.app/Contents/Resources/en.lproj/Credits.rtf +++ /dev/null @@ -1,13 +0,0 @@ -{\rtf1\ansi\ansicpg1252\cocoartf1347\cocoasubrtf570 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\vieww9600\viewh8400\viewkind0 -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720 - -\f0\b\fs24 \cf0 (c)2011-2015 ZeroTier, Inc.\ -Licensed under the GNU GPLv3\ -\ -UI Wrapper MacGap (c) Twitter, Inc.\ -Licensed under the MIT License\ -http://macgap.com/\ -} \ No newline at end of file diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/bin/ZeroTier One.app/Contents/Resources/en.lproj/InfoPlist.strings b/ext/installfiles/mac/mac-ui-macgap1-wrapper/bin/ZeroTier One.app/Contents/Resources/en.lproj/InfoPlist.strings deleted file mode 100644 index 5e45963..0000000 Binary files a/ext/installfiles/mac/mac-ui-macgap1-wrapper/bin/ZeroTier One.app/Contents/Resources/en.lproj/InfoPlist.strings and /dev/null differ diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/bin/ZeroTier One.app/Contents/Resources/en.lproj/MainMenu.nib b/ext/installfiles/mac/mac-ui-macgap1-wrapper/bin/ZeroTier One.app/Contents/Resources/en.lproj/MainMenu.nib deleted file mode 100644 index bac7faa..0000000 Binary files a/ext/installfiles/mac/mac-ui-macgap1-wrapper/bin/ZeroTier One.app/Contents/Resources/en.lproj/MainMenu.nib and /dev/null differ diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/bin/ZeroTier One.app/Contents/Resources/en.lproj/Window.nib b/ext/installfiles/mac/mac-ui-macgap1-wrapper/bin/ZeroTier One.app/Contents/Resources/en.lproj/Window.nib deleted file mode 100644 index e7b174a..0000000 Binary files a/ext/installfiles/mac/mac-ui-macgap1-wrapper/bin/ZeroTier One.app/Contents/Resources/en.lproj/Window.nib and /dev/null differ diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/bin/ZeroTier One.app/Contents/_CodeSignature/CodeResources b/ext/installfiles/mac/mac-ui-macgap1-wrapper/bin/ZeroTier One.app/Contents/_CodeSignature/CodeResources deleted file mode 100644 index 5e334db..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/bin/ZeroTier One.app/Contents/_CodeSignature/CodeResources +++ /dev/null @@ -1,187 +0,0 @@ - - - - - files - - Resources/ZeroTierIcon.icns - - 430Gd+4+jnim7WxXEEugp6G+Tgk= - - Resources/en.lproj/Credits.rtf - - hash - - ePttkAH2X1GJ6OL0UhDBAktxB3Y= - - optional - - - Resources/en.lproj/InfoPlist.strings - - hash - - MiLKDDnrUKr4EmuvhS5VQwxHGK8= - - optional - - - Resources/en.lproj/MainMenu.nib - - hash - - 8JZXf4/3df3LD+o74Y8WM0dV8io= - - optional - - - Resources/en.lproj/Window.nib - - hash - - aP0mIANPPnnTMmxYlELioz9ZO1I= - - optional - - - - files2 - - Resources/ZeroTierIcon.icns - - 430Gd+4+jnim7WxXEEugp6G+Tgk= - - Resources/en.lproj/Credits.rtf - - hash - - ePttkAH2X1GJ6OL0UhDBAktxB3Y= - - optional - - - Resources/en.lproj/InfoPlist.strings - - hash - - MiLKDDnrUKr4EmuvhS5VQwxHGK8= - - optional - - - Resources/en.lproj/MainMenu.nib - - hash - - 8JZXf4/3df3LD+o74Y8WM0dV8io= - - optional - - - Resources/en.lproj/Window.nib - - hash - - aP0mIANPPnnTMmxYlELioz9ZO1I= - - optional - - - - rules - - ^Resources/ - - ^Resources/.*\.lproj/ - - optional - - weight - 1000 - - ^Resources/.*\.lproj/locversion.plist$ - - omit - - weight - 1100 - - ^version.plist$ - - - rules2 - - .*\.dSYM($|/) - - weight - 11 - - ^(.*/)?\.DS_Store$ - - omit - - weight - 2000 - - ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ - - nested - - weight - 10 - - ^.* - - ^Info\.plist$ - - omit - - weight - 20 - - ^PkgInfo$ - - omit - - weight - 20 - - ^Resources/ - - weight - 20 - - ^Resources/.*\.lproj/ - - optional - - weight - 1000 - - ^Resources/.*\.lproj/locversion.plist$ - - omit - - weight - 1100 - - ^[^/]+$ - - nested - - weight - 10 - - ^embedded\.provisionprofile$ - - weight - 20 - - ^version\.plist$ - - weight - 20 - - - - diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/LICENSE b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/LICENSE deleted file mode 100644 index c7fd4a4..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/LICENSE +++ /dev/null @@ -1,25 +0,0 @@ -MacGap was ported from phonegap-mac, and is under the same license (MIT) - -The MIT License -***************** - -Copyright (c) <2012> - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap.xcodeproj/project.pbxproj b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap.xcodeproj/project.pbxproj deleted file mode 100644 index 775c596..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap.xcodeproj/project.pbxproj +++ /dev/null @@ -1,489 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 1495814F15C15CCC00E1CFE5 /* Notice.m in Sources */ = {isa = PBXBuildFile; fileRef = 1495814E15C15CCC00E1CFE5 /* Notice.m */; }; - 6F169DA718CC332E005EDDF3 /* Command.m in Sources */ = {isa = PBXBuildFile; fileRef = 6F169DA618CC332E005EDDF3 /* Command.m */; }; - 6F169DAA18CC35FD005EDDF3 /* CallbackDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6F169DA918CC35FD005EDDF3 /* CallbackDelegate.m */; }; - 6F169DAC18CD8A4A005EDDF3 /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6F169DAB18CD8A4A005EDDF3 /* JavaScriptCore.framework */; }; - 6F169DB118CD906F005EDDF3 /* MenuItemProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 6F169DAE18CD906F005EDDF3 /* MenuItemProxy.m */; }; - 6F169DB218CD906F005EDDF3 /* MenuProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 6F169DB018CD906F005EDDF3 /* MenuProxy.m */; }; - 6FD672B618FE618E00C0DAAD /* UserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = 6FD672B518FE618E00C0DAAD /* UserDefaults.m */; }; - 6FD6E4ED18C2D48C00DFFBE6 /* fonts.m in Sources */ = {isa = PBXBuildFile; fileRef = 6FD6E4EC18C2D48C00DFFBE6 /* fonts.m */; }; - 88746BEE14CCA435001E160E /* JSEventHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 88746BED14CCA435001E160E /* JSEventHelper.m */; }; - 88C0646014BDE10A00E4BCE2 /* Window.m in Sources */ = {isa = PBXBuildFile; fileRef = 88C0645F14BDE10A00E4BCE2 /* Window.m */; }; - 88C0646614BDEC5800E4BCE2 /* Window.xib in Resources */ = {isa = PBXBuildFile; fileRef = 88C0646414BDEC5800E4BCE2 /* Window.xib */; }; - 88C0646D14BDF6A600E4BCE2 /* WindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 88C0646C14BDF6A600E4BCE2 /* WindowController.m */; }; - C14EFCA71B0986AF00894B5F /* ZeroTierIcon.icns in Resources */ = {isa = PBXBuildFile; fileRef = C14EFCA61B0986AF00894B5F /* ZeroTierIcon.icns */; }; - C1C2B9911AFB0CF10060D7C2 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1C2B9901AFB0CF10060D7C2 /* Security.framework */; }; - F2B80016179E0FC100B069A8 /* Clipboard.m in Sources */ = {isa = PBXBuildFile; fileRef = F2B80015179E0FC100B069A8 /* Clipboard.m */; }; - FA32509D14BA813600BF0781 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA32509C14BA813600BF0781 /* WebKit.framework */; }; - FA3250C314BA85E700BF0781 /* ContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = FA3250BC14BA85E700BF0781 /* ContentView.m */; }; - FA3250C514BA85E700BF0781 /* Utils.m in Sources */ = {isa = PBXBuildFile; fileRef = FA3250BE14BA85E700BF0781 /* Utils.m */; }; - FA3250C714BA85E700BF0781 /* WebViewDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = FA3250C014BA85E700BF0781 /* WebViewDelegate.m */; }; - FA3250D314BA860800BF0781 /* App.m in Sources */ = {isa = PBXBuildFile; fileRef = FA3250C914BA860800BF0781 /* App.m */; }; - FA3250D514BA860800BF0781 /* Dock.m in Sources */ = {isa = PBXBuildFile; fileRef = FA3250CB14BA860800BF0781 /* Dock.m */; }; - FA3250D914BA860800BF0781 /* Path.m in Sources */ = {isa = PBXBuildFile; fileRef = FA3250CF14BA860800BF0781 /* Path.m */; }; - FA3250DB14BA860800BF0781 /* Sound.m in Sources */ = {isa = PBXBuildFile; fileRef = FA3250D114BA860800BF0781 /* Sound.m */; }; - FA3F7742168F70790027B324 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA3F7741168F70780027B324 /* Cocoa.framework */; }; - FAE451C914BA79C600190544 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = FAE451C714BA79C600190544 /* InfoPlist.strings */; }; - FAE451CB14BA79C600190544 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = FAE451CA14BA79C600190544 /* main.m */; }; - FAE451CF14BA79C600190544 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = FAE451CD14BA79C600190544 /* Credits.rtf */; }; - FAE451D214BA79C600190544 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = FAE451D114BA79C600190544 /* AppDelegate.m */; }; - FAE451D514BA79C600190544 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = FAE451D314BA79C600190544 /* MainMenu.xib */; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - FA3250DD14BA876F00BF0781 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 1495814D15C15CCC00E1CFE5 /* Notice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Notice.h; path = Classes/Commands/Notice.h; sourceTree = ""; }; - 1495814E15C15CCC00E1CFE5 /* Notice.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Notice.m; path = Classes/Commands/Notice.m; sourceTree = ""; }; - 6F169DA518CC332E005EDDF3 /* Command.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Command.h; path = Classes/Commands/Command.h; sourceTree = ""; }; - 6F169DA618CC332E005EDDF3 /* Command.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Command.m; path = Classes/Commands/Command.m; sourceTree = ""; }; - 6F169DA818CC35FD005EDDF3 /* CallbackDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CallbackDelegate.h; path = Classes/CallbackDelegate.h; sourceTree = ""; }; - 6F169DA918CC35FD005EDDF3 /* CallbackDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CallbackDelegate.m; path = Classes/CallbackDelegate.m; sourceTree = ""; }; - 6F169DAB18CD8A4A005EDDF3 /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; - 6F169DAD18CD906F005EDDF3 /* MenuItemProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MenuItemProxy.h; path = Classes/Commands/MenuItemProxy.h; sourceTree = ""; }; - 6F169DAE18CD906F005EDDF3 /* MenuItemProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MenuItemProxy.m; path = Classes/Commands/MenuItemProxy.m; sourceTree = ""; }; - 6F169DAF18CD906F005EDDF3 /* MenuProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MenuProxy.h; path = Classes/Commands/MenuProxy.h; sourceTree = ""; }; - 6F169DB018CD906F005EDDF3 /* MenuProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MenuProxy.m; path = Classes/Commands/MenuProxy.m; sourceTree = ""; }; - 6FD672B418FE618E00C0DAAD /* UserDefaults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = UserDefaults.h; path = Classes/Commands/UserDefaults.h; sourceTree = ""; }; - 6FD672B518FE618E00C0DAAD /* UserDefaults.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = UserDefaults.m; path = Classes/Commands/UserDefaults.m; sourceTree = ""; }; - 6FD6E4EB18C2D48200DFFBE6 /* fonts.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = fonts.h; path = Classes/Commands/fonts.h; sourceTree = ""; }; - 6FD6E4EC18C2D48C00DFFBE6 /* fonts.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = fonts.m; path = Classes/Commands/fonts.m; sourceTree = ""; }; - 88746BEC14CCA435001E160E /* JSEventHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JSEventHelper.h; path = Classes/JSEventHelper.h; sourceTree = ""; }; - 88746BED14CCA435001E160E /* JSEventHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = JSEventHelper.m; path = Classes/JSEventHelper.m; sourceTree = ""; }; - 88C0645E14BDE10A00E4BCE2 /* Window.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Window.h; path = Classes/Window.h; sourceTree = ""; }; - 88C0645F14BDE10A00E4BCE2 /* Window.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Window.m; path = Classes/Window.m; sourceTree = ""; }; - 88C0646514BDEC5800E4BCE2 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/Window.xib; sourceTree = ""; }; - 88C0646B14BDF6A600E4BCE2 /* WindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowController.h; sourceTree = ""; }; - 88C0646C14BDF6A600E4BCE2 /* WindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WindowController.m; sourceTree = ""; }; - C14EFCA61B0986AF00894B5F /* ZeroTierIcon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = ZeroTierIcon.icns; path = ../../../../artwork/ZeroTierIcon.icns; sourceTree = ""; }; - C1C2B9901AFB0CF10060D7C2 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; - F2B80014179E0FC100B069A8 /* Clipboard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Clipboard.h; sourceTree = ""; }; - F2B80015179E0FC100B069A8 /* Clipboard.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Clipboard.m; sourceTree = ""; }; - FA32509C14BA813600BF0781 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; - FA3250BA14BA85E700BF0781 /* Constants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Constants.h; path = Classes/Constants.h; sourceTree = ""; }; - FA3250BB14BA85E700BF0781 /* ContentView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ContentView.h; path = Classes/ContentView.h; sourceTree = ""; }; - FA3250BC14BA85E700BF0781 /* ContentView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = ContentView.m; path = Classes/ContentView.m; sourceTree = ""; }; - FA3250BD14BA85E700BF0781 /* Utils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Utils.h; path = Classes/Utils.h; sourceTree = ""; }; - FA3250BE14BA85E700BF0781 /* Utils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = Utils.m; path = Classes/Utils.m; sourceTree = ""; }; - FA3250BF14BA85E700BF0781 /* WebViewDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = WebViewDelegate.h; path = Classes/WebViewDelegate.h; sourceTree = ""; }; - FA3250C014BA85E700BF0781 /* WebViewDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = WebViewDelegate.m; path = Classes/WebViewDelegate.m; sourceTree = ""; }; - FA3250C814BA860800BF0781 /* App.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = App.h; path = Classes/Commands/App.h; sourceTree = ""; }; - FA3250C914BA860800BF0781 /* App.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = App.m; path = Classes/Commands/App.m; sourceTree = ""; }; - FA3250CA14BA860800BF0781 /* Dock.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Dock.h; path = Classes/Commands/Dock.h; sourceTree = ""; }; - FA3250CB14BA860800BF0781 /* Dock.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = Dock.m; path = Classes/Commands/Dock.m; sourceTree = ""; }; - FA3250CE14BA860800BF0781 /* Path.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Path.h; path = Classes/Commands/Path.h; sourceTree = ""; }; - FA3250CF14BA860800BF0781 /* Path.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = Path.m; path = Classes/Commands/Path.m; sourceTree = ""; }; - FA3250D014BA860800BF0781 /* Sound.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Sound.h; path = Classes/Commands/Sound.h; sourceTree = ""; }; - FA3250D114BA860800BF0781 /* Sound.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = Sound.m; path = Classes/Commands/Sound.m; sourceTree = ""; }; - FA3F7741168F70780027B324 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.8.sdk/System/Library/Frameworks/Cocoa.framework; sourceTree = DEVELOPER_DIR; }; - FAE451BA14BA79C600190544 /* ZeroTier One.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "ZeroTier One.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - FAE451BE14BA79C600190544 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; - FAE451C114BA79C600190544 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; - FAE451C214BA79C600190544 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; - FAE451C314BA79C600190544 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; - FAE451C614BA79C600190544 /* MacGap-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "MacGap-Info.plist"; sourceTree = ""; }; - FAE451C814BA79C600190544 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; - FAE451CA14BA79C600190544 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - FAE451CC14BA79C600190544 /* MacGap-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MacGap-Prefix.pch"; sourceTree = ""; }; - FAE451CE14BA79C600190544 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = en; path = en.lproj/Credits.rtf; sourceTree = ""; }; - FAE451D014BA79C600190544 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - FAE451D114BA79C600190544 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - FAE451D414BA79C600190544 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MainMenu.xib; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - FAE451B714BA79C600190544 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - C1C2B9911AFB0CF10060D7C2 /* Security.framework in Frameworks */, - 6F169DAC18CD8A4A005EDDF3 /* JavaScriptCore.framework in Frameworks */, - FA3F7742168F70790027B324 /* Cocoa.framework in Frameworks */, - FA32509D14BA813600BF0781 /* WebKit.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - FA3250E014BA87B800BF0781 /* Classes */ = { - isa = PBXGroup; - children = ( - FA3250E114BA87DD00BF0781 /* Commands */, - FA3250BA14BA85E700BF0781 /* Constants.h */, - 6F169DA818CC35FD005EDDF3 /* CallbackDelegate.h */, - 6F169DA918CC35FD005EDDF3 /* CallbackDelegate.m */, - FA3250BB14BA85E700BF0781 /* ContentView.h */, - FA3250BC14BA85E700BF0781 /* ContentView.m */, - FA3250BF14BA85E700BF0781 /* WebViewDelegate.h */, - FA3250C014BA85E700BF0781 /* WebViewDelegate.m */, - 88C0646B14BDF6A600E4BCE2 /* WindowController.h */, - 88C0646C14BDF6A600E4BCE2 /* WindowController.m */, - ); - name = Classes; - sourceTree = ""; - }; - FA3250E114BA87DD00BF0781 /* Commands */ = { - isa = PBXGroup; - children = ( - 6F169DA518CC332E005EDDF3 /* Command.h */, - 6F169DA618CC332E005EDDF3 /* Command.m */, - 1495814D15C15CCC00E1CFE5 /* Notice.h */, - 1495814E15C15CCC00E1CFE5 /* Notice.m */, - FA3250CA14BA860800BF0781 /* Dock.h */, - FA3250CB14BA860800BF0781 /* Dock.m */, - 6FD6E4EB18C2D48200DFFBE6 /* fonts.h */, - 6FD6E4EC18C2D48C00DFFBE6 /* fonts.m */, - FA3250BD14BA85E700BF0781 /* Utils.h */, - FA3250BE14BA85E700BF0781 /* Utils.m */, - 6FD672B418FE618E00C0DAAD /* UserDefaults.h */, - 6FD672B518FE618E00C0DAAD /* UserDefaults.m */, - FA3250CE14BA860800BF0781 /* Path.h */, - FA3250CF14BA860800BF0781 /* Path.m */, - FA3250D014BA860800BF0781 /* Sound.h */, - FA3250D114BA860800BF0781 /* Sound.m */, - FA3250C814BA860800BF0781 /* App.h */, - FA3250C914BA860800BF0781 /* App.m */, - 6F169DAD18CD906F005EDDF3 /* MenuItemProxy.h */, - 6F169DAE18CD906F005EDDF3 /* MenuItemProxy.m */, - 6F169DAF18CD906F005EDDF3 /* MenuProxy.h */, - 6F169DB018CD906F005EDDF3 /* MenuProxy.m */, - 88C0645E14BDE10A00E4BCE2 /* Window.h */, - 88C0645F14BDE10A00E4BCE2 /* Window.m */, - 88746BEC14CCA435001E160E /* JSEventHelper.h */, - 88746BED14CCA435001E160E /* JSEventHelper.m */, - F2B80014179E0FC100B069A8 /* Clipboard.h */, - F2B80015179E0FC100B069A8 /* Clipboard.m */, - ); - name = Commands; - sourceTree = ""; - }; - FAE451AF14BA79C600190544 = { - isa = PBXGroup; - children = ( - FA3F7741168F70780027B324 /* Cocoa.framework */, - FAE451C414BA79C600190544 /* MacGap */, - FAE451BD14BA79C600190544 /* Frameworks */, - FAE451BB14BA79C600190544 /* Products */, - ); - sourceTree = ""; - }; - FAE451BB14BA79C600190544 /* Products */ = { - isa = PBXGroup; - children = ( - FAE451BA14BA79C600190544 /* ZeroTier One.app */, - ); - name = Products; - sourceTree = ""; - }; - FAE451BD14BA79C600190544 /* Frameworks */ = { - isa = PBXGroup; - children = ( - C1C2B9901AFB0CF10060D7C2 /* Security.framework */, - 6F169DAB18CD8A4A005EDDF3 /* JavaScriptCore.framework */, - FA32509C14BA813600BF0781 /* WebKit.framework */, - FAE451BE14BA79C600190544 /* Cocoa.framework */, - FAE451C014BA79C600190544 /* Other Frameworks */, - ); - name = Frameworks; - sourceTree = ""; - }; - FAE451C014BA79C600190544 /* Other Frameworks */ = { - isa = PBXGroup; - children = ( - FAE451C114BA79C600190544 /* AppKit.framework */, - FAE451C214BA79C600190544 /* CoreData.framework */, - FAE451C314BA79C600190544 /* Foundation.framework */, - ); - name = "Other Frameworks"; - sourceTree = ""; - }; - FAE451C414BA79C600190544 /* MacGap */ = { - isa = PBXGroup; - children = ( - FA3250E014BA87B800BF0781 /* Classes */, - FAE451D014BA79C600190544 /* AppDelegate.h */, - FAE451D114BA79C600190544 /* AppDelegate.m */, - C14EFCA61B0986AF00894B5F /* ZeroTierIcon.icns */, - FAE451D314BA79C600190544 /* MainMenu.xib */, - 88C0646414BDEC5800E4BCE2 /* Window.xib */, - FAE451C514BA79C600190544 /* Supporting Files */, - ); - path = MacGap; - sourceTree = ""; - }; - FAE451C514BA79C600190544 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - FAE451C614BA79C600190544 /* MacGap-Info.plist */, - FAE451C714BA79C600190544 /* InfoPlist.strings */, - FAE451CA14BA79C600190544 /* main.m */, - FAE451CC14BA79C600190544 /* MacGap-Prefix.pch */, - FAE451CD14BA79C600190544 /* Credits.rtf */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - FAE451B914BA79C600190544 /* MacGap */ = { - isa = PBXNativeTarget; - buildConfigurationList = FAE451D814BA79C600190544 /* Build configuration list for PBXNativeTarget "MacGap" */; - buildPhases = ( - FAE451B814BA79C600190544 /* Resources */, - FAE451B614BA79C600190544 /* Sources */, - FAE451B714BA79C600190544 /* Frameworks */, - FA3250DD14BA876F00BF0781 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = MacGap; - productName = MacGap; - productReference = FAE451BA14BA79C600190544 /* ZeroTier One.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - FAE451B114BA79C600190544 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0710; - ORGANIZATIONNAME = Twitter; - }; - buildConfigurationList = FAE451B414BA79C600190544 /* Build configuration list for PBXProject "MacGap" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - ); - mainGroup = FAE451AF14BA79C600190544; - productRefGroup = FAE451BB14BA79C600190544 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - FAE451B914BA79C600190544 /* MacGap */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - FAE451B814BA79C600190544 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - C14EFCA71B0986AF00894B5F /* ZeroTierIcon.icns in Resources */, - FAE451C914BA79C600190544 /* InfoPlist.strings in Resources */, - FAE451CF14BA79C600190544 /* Credits.rtf in Resources */, - FAE451D514BA79C600190544 /* MainMenu.xib in Resources */, - 88C0646614BDEC5800E4BCE2 /* Window.xib in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - FAE451B614BA79C600190544 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 6F169DAA18CC35FD005EDDF3 /* CallbackDelegate.m in Sources */, - FA3250D314BA860800BF0781 /* App.m in Sources */, - FA3250D514BA860800BF0781 /* Dock.m in Sources */, - FA3250D914BA860800BF0781 /* Path.m in Sources */, - FA3250DB14BA860800BF0781 /* Sound.m in Sources */, - FA3250C314BA85E700BF0781 /* ContentView.m in Sources */, - FA3250C514BA85E700BF0781 /* Utils.m in Sources */, - FA3250C714BA85E700BF0781 /* WebViewDelegate.m in Sources */, - FAE451CB14BA79C600190544 /* main.m in Sources */, - 6F169DB118CD906F005EDDF3 /* MenuItemProxy.m in Sources */, - FAE451D214BA79C600190544 /* AppDelegate.m in Sources */, - 6F169DA718CC332E005EDDF3 /* Command.m in Sources */, - 6FD672B618FE618E00C0DAAD /* UserDefaults.m in Sources */, - 88C0646014BDE10A00E4BCE2 /* Window.m in Sources */, - 6F169DB218CD906F005EDDF3 /* MenuProxy.m in Sources */, - 88C0646D14BDF6A600E4BCE2 /* WindowController.m in Sources */, - 6FD6E4ED18C2D48C00DFFBE6 /* fonts.m in Sources */, - 88746BEE14CCA435001E160E /* JSEventHelper.m in Sources */, - 1495814F15C15CCC00E1CFE5 /* Notice.m in Sources */, - F2B80016179E0FC100B069A8 /* Clipboard.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - 88C0646414BDEC5800E4BCE2 /* Window.xib */ = { - isa = PBXVariantGroup; - children = ( - 88C0646514BDEC5800E4BCE2 /* en */, - ); - name = Window.xib; - sourceTree = ""; - }; - FAE451C714BA79C600190544 /* InfoPlist.strings */ = { - isa = PBXVariantGroup; - children = ( - FAE451C814BA79C600190544 /* en */, - ); - name = InfoPlist.strings; - sourceTree = ""; - }; - FAE451CD14BA79C600190544 /* Credits.rtf */ = { - isa = PBXVariantGroup; - children = ( - FAE451CE14BA79C600190544 /* en */, - ); - name = Credits.rtf; - sourceTree = ""; - }; - FAE451D314BA79C600190544 /* MainMenu.xib */ = { - isa = PBXVariantGroup; - children = ( - FAE451D414BA79C600190544 /* en */, - ); - name = MainMenu.xib; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - FAE451D614BA79C600190544 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ENABLE_OBJC_ARC = YES; - COPY_PHASE_STRIP = NO; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_ENABLE_OBJC_EXCEPTIONS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_VERSION = ""; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.7; - ONLY_ACTIVE_ARCH = YES; - PRODUCT_NAME = "ZeroTier One"; - SDKROOT = ""; - }; - name = Debug; - }; - FAE451D714BA79C600190544 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ENABLE_OBJC_ARC = YES; - COPY_PHASE_STRIP = YES; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_ENABLE_OBJC_EXCEPTIONS = YES; - GCC_VERSION = ""; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.7; - PRODUCT_NAME = "ZeroTier One"; - SDKROOT = ""; - }; - name = Release; - }; - FAE451D914BA79C600190544 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_CXX_LANGUAGE_STANDARD = "compiler-default"; - CLANG_CXX_LIBRARY = "compiler-default"; - COMBINE_HIDPI_IMAGES = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "\"$(SRCROOT)/MacGap\"", - ); - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "MacGap/MacGap-Prefix.pch"; - GCC_VERSION = ""; - INFOPLIST_FILE = "MacGap/MacGap-Info.plist"; - MACOSX_DEPLOYMENT_TARGET = 10.7; - PRODUCT_BUNDLE_IDENTIFIER = "com.zerotier.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = "ZeroTier One"; - SDKROOT = macosx; - WRAPPER_EXTENSION = app; - }; - name = Debug; - }; - FAE451DA14BA79C600190544 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_CXX_LANGUAGE_STANDARD = "compiler-default"; - CLANG_CXX_LIBRARY = "compiler-default"; - COMBINE_HIDPI_IMAGES = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "\"$(SRCROOT)/MacGap\"", - ); - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "MacGap/MacGap-Prefix.pch"; - GCC_VERSION = ""; - INFOPLIST_FILE = "MacGap/MacGap-Info.plist"; - MACOSX_DEPLOYMENT_TARGET = 10.7; - PRODUCT_BUNDLE_IDENTIFIER = "com.zerotier.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = "ZeroTier One"; - SDKROOT = macosx; - WRAPPER_EXTENSION = app; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - FAE451B414BA79C600190544 /* Build configuration list for PBXProject "MacGap" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - FAE451D614BA79C600190544 /* Debug */, - FAE451D714BA79C600190544 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - FAE451D814BA79C600190544 /* Build configuration list for PBXNativeTarget "MacGap" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - FAE451D914BA79C600190544 /* Debug */, - FAE451DA14BA79C600190544 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = FAE451B114BA79C600190544 /* Project object */; -} diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap.xcodeproj/project.xcworkspace/xcshareddata/MacGap.xccheckout b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap.xcodeproj/project.xcworkspace/xcshareddata/MacGap.xccheckout deleted file mode 100644 index 7fdde85..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap.xcodeproj/project.xcworkspace/xcshareddata/MacGap.xccheckout +++ /dev/null @@ -1,41 +0,0 @@ - - - - - IDESourceControlProjectFavoriteDictionaryKey - - IDESourceControlProjectIdentifier - 4D486E78-E297-4CC3-AAAE-1A58EDAC87E6 - IDESourceControlProjectName - MacGap - IDESourceControlProjectOriginsDictionary - - ABA3617E9F0148F844A82502F0D808DE6591AA97 - http://adam.ierymenko@git.int.zerotier.com/zerotier/zerotierone - - IDESourceControlProjectPath - ext/mac-ui-macgap1-wrapper/src/MacGap.xcodeproj - IDESourceControlProjectRelativeInstallPathDictionary - - ABA3617E9F0148F844A82502F0D808DE6591AA97 - ../../../../.. - - IDESourceControlProjectURL - http://adam.ierymenko@git.int.zerotier.com/zerotier/zerotierone - IDESourceControlProjectVersion - 111 - IDESourceControlProjectWCCIdentifier - ABA3617E9F0148F844A82502F0D808DE6591AA97 - IDESourceControlProjectWCConfigurations - - - IDESourceControlRepositoryExtensionIdentifierKey - public.vcs.git - IDESourceControlWCCIdentifierKey - ABA3617E9F0148F844A82502F0D808DE6591AA97 - IDESourceControlWCCName - ZeroTierOne - - - - diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap.xcodeproj/project.xcworkspace/xcuserdata/Alex.xcuserdatad/UserInterfaceState.xcuserstate b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap.xcodeproj/project.xcworkspace/xcuserdata/Alex.xcuserdatad/UserInterfaceState.xcuserstate deleted file mode 100644 index 2028181..0000000 Binary files a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap.xcodeproj/project.xcworkspace/xcuserdata/Alex.xcuserdatad/UserInterfaceState.xcuserstate and /dev/null differ diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap.xcodeproj/project.xcworkspace/xcuserdata/api.xcuserdatad/WorkspaceSettings.xcsettings b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap.xcodeproj/project.xcworkspace/xcuserdata/api.xcuserdatad/WorkspaceSettings.xcsettings deleted file mode 100644 index 659c876..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap.xcodeproj/project.xcworkspace/xcuserdata/api.xcuserdatad/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,10 +0,0 @@ - - - - - HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges - - SnapshotAutomaticallyBeforeSignificantChanges - - - diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap.xcodeproj/project.xcworkspace/xcuserdata/liamks.xcuserdatad/UserInterfaceState.xcuserstate b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap.xcodeproj/project.xcworkspace/xcuserdata/liamks.xcuserdatad/UserInterfaceState.xcuserstate deleted file mode 100644 index 822ed3c..0000000 Binary files a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap.xcodeproj/project.xcworkspace/xcuserdata/liamks.xcuserdatad/UserInterfaceState.xcuserstate and /dev/null differ diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap.xcodeproj/project.xcworkspace/xcuserdata/liamks.xcuserdatad/WorkspaceSettings.xcsettings b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap.xcodeproj/project.xcworkspace/xcuserdata/liamks.xcuserdatad/WorkspaceSettings.xcsettings deleted file mode 100644 index 6ff33e6..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap.xcodeproj/project.xcworkspace/xcuserdata/liamks.xcuserdatad/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,10 +0,0 @@ - - - - - IDEWorkspaceUserSettings_HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges - - IDEWorkspaceUserSettings_SnapshotAutomaticallyBeforeSignificantChanges - - - diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/AppDelegate.h b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/AppDelegate.h deleted file mode 100644 index bf7370b..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/AppDelegate.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// AppDelegate.h -// MacGap -// -// Created by Alex MacCaw on 08/01/2012. -// Copyright (c) 2012 Twitter. All rights reserved. -// - -#import -#import "Classes/ContentView.h" - -#import "WindowController.h" - -@interface AppDelegate : NSObject - -@property (retain, nonatomic) WindowController *windowController; - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/AppDelegate.m b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/AppDelegate.m deleted file mode 100644 index 45923bb..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/AppDelegate.m +++ /dev/null @@ -1,159 +0,0 @@ -// -// AppDelegate.m -// MacGap -// -// Created by Alex MacCaw on 08/01/2012. -// Copyright (c) 2012 Twitter. All rights reserved. -// - -#import "AppDelegate.h" -#include -#include - -@implementation AppDelegate - -@synthesize windowController; - -- (void) applicationWillFinishLaunching:(NSNotification *)aNotification -{ -} - --(BOOL)applicationShouldHandleReopen:(NSApplication*)application - hasVisibleWindows:(BOOL)visibleWindows{ - if(!visibleWindows){ - [self.windowController.window makeKeyAndOrderFront: nil]; - } - return YES; -} - -- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication { - return YES; -} - -- (void) applicationDidFinishLaunching:(NSNotification *)aNotification { - char buf[16384],userAuthTokenPath[4096]; - struct stat systemAuthTokenStat,userAuthTokenStat; - - FILE *pf = fopen("/Library/Application Support/ZeroTier/One/zerotier-one.port","r"); - long port = 9993; // default - if (pf) { - long n = fread(buf,1,sizeof(buf)-1,pf); - if (n > 0) { - buf[n] = (char)0; - port = strtol(buf,(char **)0,10); - } - fclose(pf); - } - - char url[16384]; - memset(url,0,sizeof(url)); - - const char *homeDir = getenv("HOME"); - if (homeDir) { - snprintf(userAuthTokenPath,sizeof(userAuthTokenPath),"%s/Library/Application Support/ZeroTier/One/authtoken.secret",homeDir); - - bool userAuthTokenOutOfDate = false; - memset(&systemAuthTokenStat,0,sizeof(systemAuthTokenStat)); - memset(&userAuthTokenStat,0,sizeof(userAuthTokenStat)); - if (stat("/Library/Application Support/ZeroTier/One/authtoken.secret",&systemAuthTokenStat) == 0) { - if (stat(userAuthTokenPath,&userAuthTokenStat) == 0) { - if (userAuthTokenStat.st_mtimespec.tv_sec < systemAuthTokenStat.st_mtimespec.tv_sec) - userAuthTokenOutOfDate = true; - } - } - - if (!userAuthTokenOutOfDate) { - pf = fopen(userAuthTokenPath,"r"); - if (pf) { - long n = fread(buf,1,sizeof(buf)-1,pf); - if (n > 0) { - buf[n] = (char)0; - snprintf(url,sizeof(url),"http://127.0.0.1:%ld/index.html?authToken=%s",port,buf); - } - fclose(pf); - } - } - } - - if (!url[0]) { - // Create authorization reference - OSStatus status; - AuthorizationRef authorizationRef; - - // AuthorizationCreate and pass NULL as the initial - // AuthorizationRights set so that the AuthorizationRef gets created - // successfully, and then later call AuthorizationCopyRights to - // determine or extend the allowable rights. - // http://developer.apple.com/qa/qa2001/qa1172.html - status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authorizationRef); - if (status != errAuthorizationSuccess) - { - NSLog(@"Error Creating Initial Authorization: %d", status); - return; - } - - // kAuthorizationRightExecute == "system.privilege.admin" - AuthorizationItem right = {kAuthorizationRightExecute, 0, NULL, 0}; - AuthorizationRights rights = {1, &right}; - AuthorizationFlags flags = kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed | - kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights; - - // Call AuthorizationCopyRights to determine or extend the allowable rights. - status = AuthorizationCopyRights(authorizationRef, &rights, NULL, flags, NULL); - if (status != errAuthorizationSuccess) - { - NSLog(@"Copy Rights Unsuccessful: %d", status); - return; - } - - // use rm tool with -rf - char *tool = "/bin/cat"; - char *args[] = {"/Library/Application Support/ZeroTier/One/authtoken.secret", NULL}; - FILE *pipe = NULL; - - status = AuthorizationExecuteWithPrivileges(authorizationRef, tool, kAuthorizationFlagDefaults, args, &pipe); - if (status != errAuthorizationSuccess) - { - NSLog(@"Error: %d", status); - } - - if (pipe) { - long n = (long)fread(buf,1,sizeof(buf)-1,pipe); - if (n > 0) { - buf[n] = (char)0; - snprintf(url,sizeof(url),"http://127.0.0.1:%ld/index.html?authToken=%s",port,buf); - - if (homeDir) { - snprintf(userAuthTokenPath,sizeof(userAuthTokenPath),"%s/Library/Application Support/ZeroTier",homeDir); - mkdir(userAuthTokenPath,0755); - snprintf(userAuthTokenPath,sizeof(userAuthTokenPath),"%s/Library/Application Support/ZeroTier/One",homeDir); - mkdir(userAuthTokenPath,0755); - snprintf(userAuthTokenPath,sizeof(userAuthTokenPath),"%s/Library/Application Support/ZeroTier/One/authtoken.secret",homeDir); - pf = fopen(userAuthTokenPath,"w"); - if (pf) { - fwrite(buf,1,strlen(buf),pf); - fclose(pf); - chmod(userAuthTokenPath,0600); - } - } - } - fclose(pipe); - } - - // The only way to guarantee that a credential acquired when you - // request a right is not shared with other authorization instances is - // to destroy the credential. To do so, call the AuthorizationFree - // function with the flag kAuthorizationFlagDestroyRights. - // http://developer.apple.com/documentation/Security/Conceptual/authorization_concepts/02authconcepts/chapter_2_section_7.html - status = AuthorizationFree(authorizationRef, kAuthorizationFlagDestroyRights); - } - - NSString *urlStr = [[NSString alloc] initWithCString:url]; - self.windowController = [[WindowController alloc] initWithURL: urlStr]; - [self.windowController showWindow: [NSApplication sharedApplication].delegate]; - self.windowController.contentView.webView.alphaValue = 1.0; - self.windowController.contentView.alphaValue = 1.0; - [self.windowController showWindow:self]; -} - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/CallbackDelegate.h b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/CallbackDelegate.h deleted file mode 100755 index 0f31ee4..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/CallbackDelegate.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// CallbackDelegate.h -// MacGap -// -// Created by Joe Hildebrand on 1/10/12. -// Copyright (c) 2012 Twitter. All rights reserved. -// - -#import "Command.h" - -@interface CallbackDelegate : Command { -} - -@property JSObjectRef callback; - -- (id) initWithContext:(JSContextRef)aContext forCallback:(WebScriptObject*)aCallback; -- (id) call; -- (id) callWithParams:(id)firstOrNil, ... NS_REQUIRES_NIL_TERMINATION; - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/CallbackDelegate.m b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/CallbackDelegate.m deleted file mode 100755 index 5ce8fbe..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/CallbackDelegate.m +++ /dev/null @@ -1,168 +0,0 @@ -// -// CallbackDelegate.m -// MacGap -// -// Created by Joe Hildebrand on 1/10/12. -// Copyright (c) 2012 Twitter. All rights reserved. -// - -#import "CallbackDelegate.h" -#import - -@implementation CallbackDelegate - -@synthesize callback; - -- (id) initWithContext:(JSContextRef)aContext forCallback:(WebScriptObject*)aCallback -{ - if (!aCallback) - return nil; - if ([aCallback isKindOfClass:[WebUndefined class]]) - return nil; - - self = [super initWithContext:aContext]; - if (!self) - return nil; - - callback = [aCallback JSObject]; - JSValueProtect(context, callback); - return self; -} - -- (void) dealloc -{ - if (callback) - { - JSValueUnprotect(context, callback); - callback = nil; - } -} - -- (id) objectFromValue:(JSValueRef)val -{ - JSStringRef jstr; - NSString *rets; - - switch(JSValueGetType(context, val)) - { - case kJSTypeUndefined: - case kJSTypeNull: - return nil; - case kJSTypeBoolean: - return [NSNumber numberWithBool:JSValueToBoolean(context, val)]; - case kJSTypeNumber: - return [NSNumber numberWithDouble:JSValueToNumber(context, val, NULL)]; - case kJSTypeString: - jstr = JSValueToStringCopy(context, val, NULL); - size_t sz = JSStringGetMaximumUTF8CStringSize(jstr); - char *buf = (char*)malloc(sz); - JSStringGetUTF8CString(jstr, buf, sz); - rets = [NSString stringWithUTF8String:buf]; - free(buf); - return rets; - case kJSTypeObject: - // TODO: dictionary or something - return nil; - default: - NSAssert(false, @"Invalid JavaScript type"); - return nil; - } -} - -- (JSValueRef) valueFromObject:(id)obj -{ - JSValueRef val = nil; - if (!obj) - { - val = JSValueMakeNull(context); - } - else if ([obj isKindOfClass:[NSString class]]) - { - JSStringRef jstr = JSStringCreateWithUTF8CString([obj UTF8String]); - val = JSValueMakeString(context, jstr); - JSStringRelease(jstr); - } - else if ([obj isKindOfClass:[NSNumber class]]) - { - val = JSValueMakeNumber(context, [obj doubleValue]); - } - else if ([obj isKindOfClass:[NSDictionary class]]) - { - JSObjectRef o = JSObjectMake(context, NULL, NULL); - for (NSString *key in obj) - { - JSStringRef kstr = JSStringCreateWithUTF8CString([key UTF8String]); - JSValueRef v = [self valueFromObject:[obj objectForKey:key]]; - - JSObjectSetProperty(context, o, kstr, v, kJSPropertyAttributeNone, NULL); - JSStringRelease(kstr); - } - val = o; - } - else if ([obj isKindOfClass:[NSArray class]]) - { - NSUInteger pcount = [obj count]; - JSValueRef jsArgs[pcount]; - NSUInteger i=0; - for (id v in obj) - { - jsArgs[i++] = [self valueFromObject:v]; - } - val = JSObjectMakeArray(context, pcount, jsArgs, NULL); - } - else if ([obj isKindOfClass:[NSDate class]]) - { - NSTimeInterval secs = [obj timeIntervalSince1970]; - JSValueRef jsArgs[1]; - // call the Date(milliseconds) constructor in JS - jsArgs[0] = JSValueMakeNumber(context, secs * 1000.0); - val = JSObjectMakeDate(context, 1, jsArgs, NULL); - } - else - { - NSLog(@"Warning: unknown object type for: %@", obj); - val = JSValueMakeUndefined(context); - } - return val; -} - -- (id) call -{ - NSAssert(callback, @"Callback required"); - if (!JSObjectIsFunction(context, callback)) - return nil; - - JSValueRef jsArgs[0]; - JSValueRef ret = JSObjectCallAsFunction(context, callback, NULL, 0, jsArgs, NULL); - return [self objectFromValue:ret]; -} - -- (id) callWithParams:(id)firstOrNil, ... -{ - NSAssert(callback, @"Callback required"); - if (!JSObjectIsFunction(context, callback)) - return nil; - NSUInteger pcount = 0; - id p; - va_list args; - va_start(args, firstOrNil); - for (p=firstOrNil; p; p=va_arg(args, id)) - { - pcount++; - } - va_end(args); - - JSValueRef jsArgs[pcount]; - NSUInteger j = 0; - va_start(args, firstOrNil); - for (p=firstOrNil; p; p=va_arg(args, id)) - { - jsArgs[j++] = [self valueFromObject:p]; - } - va_end(args); - - JSValueRef ret = JSObjectCallAsFunction(context, callback, NULL, j, jsArgs, NULL); - return [self objectFromValue:ret]; -} - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/App.h b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/App.h deleted file mode 100644 index f65ba61..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/App.h +++ /dev/null @@ -1,21 +0,0 @@ -#import - -#import "WindowController.h" - -@interface App : NSObject { - -} - -@property (nonatomic, retain) WebView *webView; - -- (id) initWithWebView:(WebView *)view; - -- (void) terminate; -- (void) activate; -- (void) hide; -- (void) unhide; -- (void) beep; -- (void) bounce; -- (void) setCustomUserAgent:(NSString *)userAgentString; -- (NSNumber*) systemIdleTime; -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/App.m b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/App.m deleted file mode 100644 index 6d47a17..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/App.m +++ /dev/null @@ -1,128 +0,0 @@ -#import "App.h" - -#import "JSEventHelper.h" - -@implementation App - -@synthesize webView; - -- (id) initWithWebView:(WebView *) view{ - self = [super init]; - - if (self) { - self.webView = view; - [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver: self - selector: @selector(receiveSleepNotification:) - name: NSWorkspaceWillSleepNotification object: NULL]; - [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver: self - selector: @selector(receiveWakeNotification:) - name: NSWorkspaceDidWakeNotification object: NULL]; - [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver: self - selector: @selector(receiveActivateNotification:) - name: NSWorkspaceDidActivateApplicationNotification object: NULL]; - } - - return self; -} - -- (void) terminate { - [NSApp terminate:nil]; -} - -- (void) activate { - [NSApp activateIgnoringOtherApps:YES]; -} - -- (void) hide { - [NSApp hide:nil]; -} - -- (void) unhide { - [NSApp unhide:nil]; -} - -- (void)beep { - NSBeep(); -} - -- (void) bounce { - [NSApp requestUserAttention:NSInformationalRequest]; -} - -- (void)setCustomUserAgent:(NSString *)userAgentString { - [self.webView setCustomUserAgent: userAgentString]; -} - -- (void) open:(NSString*)url { - [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:url]]; -} - -- (void) launch:(NSString *)name { - [[NSWorkspace sharedWorkspace] launchApplication:name]; -} - -- (void)receiveSleepNotification:(NSNotification*)note{ - [JSEventHelper triggerEvent:@"sleep" forWebView:self.webView]; -} - -- (void) receiveWakeNotification:(NSNotification*)note{ - [JSEventHelper triggerEvent:@"wake" forWebView:self.webView]; -} - -- (void) receiveActivateNotification:(NSNotification*)notification{ - NSDictionary* userInfo = [notification userInfo]; - NSRunningApplication* runningApplication = [userInfo objectForKey:NSWorkspaceApplicationKey]; - if (runningApplication) { - NSMutableDictionary* applicationDidGetFocusDict = [[NSMutableDictionary alloc] initWithCapacity:2]; - [applicationDidGetFocusDict setObject:runningApplication.localizedName - forKey:@"localizedName"]; - [applicationDidGetFocusDict setObject:[runningApplication.bundleURL absoluteString] - forKey:@"bundleURL"]; - - [JSEventHelper triggerEvent:@"appActivated" withArgs:applicationDidGetFocusDict forWebView:self.webView]; - } -} - - - - -/* - To get the elapsed time since the previous input event—keyboard, mouse, or tablet—specify kCGAnyInputEventType. - */ -- (NSNumber*)systemIdleTime { - CFTimeInterval timeSinceLastEvent = CGEventSourceSecondsSinceLastEventType(kCGEventSourceStateHIDSystemState, kCGAnyInputEventType); - - return [NSNumber numberWithDouble:timeSinceLastEvent]; -} - - - - -+ (NSString*) webScriptNameForSelector:(SEL)selector -{ - id result = nil; - - if (selector == @selector(open:)) { - result = @"open"; - } else if (selector == @selector(launch:)) { - result = @"launch"; - } else if (selector == @selector(setCustomUserAgent:)) { - result = @"setCustomUserAgent"; - } else if (selector == @selector(systemIdleTime)) { - result = @"systemIdleTime"; - } - - return result; -} - -+ (BOOL) isSelectorExcludedFromWebScript:(SEL)selector -{ - return NO; -} - -+ (BOOL) isKeyExcludedFromWebScript:(const char*)name -{ - return YES; -} - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/Command.h b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/Command.h deleted file mode 100755 index 65d6b6d..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/Command.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// Command.h -// MacGap -// -// Created by Joe Hildebrand on 1/10/12. -// Copyright (c) 2012 Twitter. All rights reserved. -// - -#import -#import - -@interface Command : NSObject { - JSContextRef context; -} - -- (id) initWithContext:(JSContextRef)aContext; - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/Command.m b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/Command.m deleted file mode 100755 index 39b8563..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/Command.m +++ /dev/null @@ -1,28 +0,0 @@ -// -// Command.m -// MacGap -// -// Created by Joe Hildebrand on 1/10/12. -// Copyright (c) 2012 Twitter. All rights reserved. -// - -#import "Command.h" -#import - -@implementation Command - -- (id) initWithContext:(JSContextRef)aContext { - self = [super init]; - if (!self) - return nil; - context = aContext; - JSGlobalContextRetain((JSGlobalContextRef)context); - return self; -} - -- (void)dealloc -{ - if (context) - JSGlobalContextRelease((JSGlobalContextRef)context); -} -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/Dock.h b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/Dock.h deleted file mode 100644 index b3c533d..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/Dock.h +++ /dev/null @@ -1,11 +0,0 @@ -#import - -@interface Dock : NSObject { - -} -- (void) setBadge:(NSString*)value; -- (NSString *) badge; - -@property (readwrite, copy) NSString *badge; - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/Dock.m b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/Dock.m deleted file mode 100644 index a4494d1..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/Dock.m +++ /dev/null @@ -1,31 +0,0 @@ -#import "Dock.h" - -@implementation Dock - -@synthesize badge; - -- (void) setBadge:(NSString *)value -{ - NSDockTile *tile = [[NSApplication sharedApplication] dockTile]; - [tile setBadgeLabel:value]; -} - -- (NSString *) badge -{ - NSDockTile *tile = [[NSApplication sharedApplication] dockTile]; - return [tile badgeLabel]; -} - -#pragma mark WebScripting Protocol - -+ (BOOL) isSelectorExcludedFromWebScript:(SEL)selector -{ - return NO; -} - -+ (BOOL) isKeyExcludedFromWebScript:(const char*)name -{ - return NO; -} - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/MenuItemProxy.h b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/MenuItemProxy.h deleted file mode 100755 index d765978..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/MenuItemProxy.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// MenuItemProxy.h -// MacGap -// -// Created by Joe Hildebrand on 1/15/12. -// Copyright (c) 2012 Twitter. All rights reserved. -// - -#import "Command.h" -#import "CallbackDelegate.h" - -@class MenuProxy; - -@interface MenuItemProxy : Command { - NSMenuItem *item; - CallbackDelegate *callback; -} - -+ (MenuItemProxy*) proxyWithContext:(JSContextRef)aContext andMenuItem:(NSMenuItem*)anItem; - -- (MenuProxy*)addSubmenu; - -- (void) remove; -- (void) setCallback:(WebScriptObject*)aCallback; -- (void) setKey:(NSString*)keyCommand; -- (void) setTitle:(NSString*)title; -- (void) enable; -- (void) disable; -- (MenuProxy*)submenu; - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/MenuItemProxy.m b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/MenuItemProxy.m deleted file mode 100755 index 7b9702c..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/MenuItemProxy.m +++ /dev/null @@ -1,150 +0,0 @@ -// -// MenuItemProxy.m -// MacGap -// -// Created by Joe Hildebrand on 1/15/12. -// Copyright (c) 2012 Twitter. All rights reserved. -// - -#import "MenuItemProxy.h" -#import "MenuProxy.h" - -@implementation MenuItemProxy - -- (id) initWithContext:(JSContextRef)aContext andMenuItem:(NSMenuItem*)anItem -{ - NSAssert(anItem, @"anItem required"); - self = [super initWithContext:aContext]; - if (!self) - return nil; - item = anItem; - item.representedObject = self; - - return self; -} - -+ (MenuItemProxy*) proxyWithContext:(JSContextRef)aContext andMenuItem:(NSMenuItem*)anItem -{ - MenuItemProxy *proxy = [anItem representedObject]; - if (proxy) - { - NSLog(@"MIP Cache hit"); - NSAssert([proxy class] == [MenuItemProxy class], @"Bad proxy"); - return proxy; - } - return [[MenuItemProxy alloc] initWithContext:aContext andMenuItem:anItem]; -} - -- (NSString*) description -{ - return [item description]; -} - -- (MenuProxy*)addSubmenu -{ - NSMenu *s = [item submenu]; - if (!s) - { - s = [[NSMenu alloc] initWithTitle:@"FFFFFFOOOOO"]; - [item setSubmenu:s]; - } - return [MenuProxy proxyWithContext:context andMenu:s]; -} - -- (void) remove -{ - NSMenu *menu = [item menu]; - [menu removeItem:item]; -} - -- (void)callCallback:(id)sender -{ - [callback callWithParams:[sender title], nil]; -} - -- (void) setCallback:(WebScriptObject*)aCallback -{ - NSAssert(item, @"item required"); - callback = [[CallbackDelegate alloc] initWithContext:context forCallback:aCallback]; - [item setAction:@selector(callCallback:)]; - [item setTarget:self]; -} - -- (void)setKey:(NSString*)keyCommand -{ - NSString *aKey = [MenuProxy getKeyFromString:keyCommand]; - [item setKeyEquivalent:aKey]; - - NSUInteger modifiers = [MenuProxy getModifiersFromString:keyCommand]; - [item setKeyEquivalentModifierMask:modifiers]; -} - -- (void) setTitle:(NSString*)title -{ - [item setTitle:title]; -} - -- (MenuProxy*)submenu; -{ - // TODO: make this work as a property - NSMenu *s = [item submenu]; - if (!s) - return nil; - return [MenuProxy proxyWithContext:context andMenu:s]; -} - -- (void) enable -{ - [item setEnabled:YES]; -} - -- (void) disable -{ - [item setEnabled:NO]; -} - -#pragma mark WebScripting protocol - -+ (BOOL) isSelectorExcludedFromWebScript:(SEL)selector -{ - return [self webScriptNameForSelector:selector] == nil; -} - -+ (BOOL) isKeyExcludedFromWebScript:(const char*)name -{ - return YES; -} - -+ (NSString*) webScriptNameForSelector:(SEL)selector -{ - id result = nil; - - if (selector == @selector(addSubmenu)) { - result = @"addSubmenu"; - } - else if (selector == @selector(remove)) { - result = @"remove"; - } - else if (selector == @selector(setCallback:)) { - result = @"setCallback"; - } - else if (selector == @selector(setKey:)) { - result = @"setKey"; - } - else if (selector == @selector(setTitle:)) { - result = @"setTitle"; - } - else if (selector == @selector(submenu)) { - result = @"submenu"; - } - else if (selector == @selector(enable)) { - result = @"enable"; - } - else if (selector == @selector(disable)) { - result = @"disable"; - } - - return result; -} - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/MenuProxy.h b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/MenuProxy.h deleted file mode 100755 index afd6c6e..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/MenuProxy.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// MenuProxy.h -// MacGap -// -// Created by Joe Hildebrand on 1/14/12. -// Copyright (c) 2012 Twitter. All rights reserved. -// - -#import "Command.h" - -@class MenuItemProxy; - -@interface MenuProxy : Command { - NSMenu *menu; -} - -+ (MenuProxy*)proxyWithContext:(JSContextRef)aContext andMenu:(NSMenu*)aMenu; - -- (MenuItemProxy*)addItemWithTitle:(NSString*)title - keyEquivalent:(NSString*)aKey - callback:(WebScriptObject*)aCallback - atIndex:(NSInteger)index; - -- (MenuItemProxy*)addSeparator; -- (MenuItemProxy*)itemForKey:(id)key; -- (MenuProxy*)removeItem:(id)key; - -+ (NSString*)getKeyFromString:(NSString*)keyCommand; -+ (NSUInteger*)getModifiersFromString:(NSString*)keyCommand; - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/MenuProxy.m b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/MenuProxy.m deleted file mode 100755 index 5bc10a7..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/MenuProxy.m +++ /dev/null @@ -1,233 +0,0 @@ -// -// MenuProxy.m -// MacGap -// -// Created by Joe Hildebrand on 1/14/12. -// Copyright (c) 2012 Twitter. All rights reserved. -// - -#import -#import - -#import "MenuProxy.h" -#import "MenuItemProxy.h" - -static char REPRESENTED_OBJECT; - -@interface NSMenu (represented) -@property (strong) id representedObject; -@end - -@implementation NSMenu (represented) - -- (id) representedObject -{ - return objc_getAssociatedObject(self, &REPRESENTED_OBJECT); -} - -- (void) setRepresentedObject:(id)representedObject -{ - objc_setAssociatedObject(self, - &REPRESENTED_OBJECT, - representedObject, - OBJC_ASSOCIATION_RETAIN); -} - -@end - -@implementation MenuProxy - -- (id) initWithContext:(JSContextRef)aContext andMenu:(NSMenu*)aMenu -{ - self = [super initWithContext:aContext]; - if (!self) - return nil; - menu = aMenu; - menu.representedObject = self; - return self; -} - -+ (MenuProxy*)proxyWithContext:(JSContextRef)aContext andMenu:(NSMenu*)aMenu -{ - // singleton-ish. - MenuProxy *ret = [aMenu representedObject]; - if (ret) - { - NSLog(@"MP cache hit"); - return ret; - } - return [[MenuProxy alloc] initWithContext:aContext andMenu:aMenu]; -} - -- (void) dealloc -{ - menu.representedObject = nil; -} - -- (NSString*) description -{ - return [menu description]; -} - -static BOOL isNullish(id o) -{ - if (!o) - return YES; - if ([o isKindOfClass:[WebUndefined class]]) - return YES; - return NO; -} - -- (MenuItemProxy*)addItemWithTitle:(NSString*)title - keyEquivalent:(NSString*)keyCommand - callback:(WebScriptObject*)aCallback - atIndex:(NSInteger)index -{ - if (isNullish(title)) - title = @""; - - NSString *aKey = [MenuProxy getKeyFromString:keyCommand]; - NSMenuItem *item = nil; - - if(index) { - item = [menu insertItemWithTitle:title action:nil keyEquivalent:aKey atIndex:index ]; - } else { - item = [menu addItemWithTitle:title action:nil keyEquivalent:aKey ]; - - } - - // Set the modifiers. - NSUInteger modifiers = [MenuProxy getModifiersFromString:keyCommand]; - [item setKeyEquivalentModifierMask:modifiers]; - - if(!menu.supermenu) { - NSMenu *s = [[NSMenu alloc] initWithTitle:title]; - [item setSubmenu:s]; - } - - MenuItemProxy *mip = [MenuItemProxy proxyWithContext:context andMenuItem:item]; - if (!isNullish(aCallback)) - [mip setCallback:aCallback]; - - - return mip; -} - -+ (NSString*)getKeyFromString:(NSString*)keyCommand { - if (isNullish(keyCommand)) - keyCommand = @""; - - // Obtain the key (if there are modifiers, it will be the last character). - NSString *aKey = @""; - if ([keyCommand length] > 0) { - aKey = [keyCommand substringFromIndex:[keyCommand length] - 1]; - } - - return aKey; -} - -+ (NSUInteger*)getModifiersFromString:(NSString*)keyCommand { - // aKeys may optionally specify one or more modifiers. - NSUInteger modifiers = 0; - - if ([keyCommand rangeOfString:@"caps"].location != NSNotFound) modifiers += NSAlphaShiftKeyMask; - if ([keyCommand rangeOfString:@"shift"].location != NSNotFound) modifiers += NSShiftKeyMask; - if ([keyCommand rangeOfString:@"cmd"].location != NSNotFound) modifiers += NSCommandKeyMask; - if ([keyCommand rangeOfString:@"ctrl"].location != NSNotFound) modifiers += NSControlKeyMask; - if ([keyCommand rangeOfString:@"opt"].location != NSNotFound) modifiers += NSAlternateKeyMask; - if ([keyCommand rangeOfString:@"alt"].location != NSNotFound) modifiers += NSAlternateKeyMask; - - return modifiers; -} - -- (MenuItemProxy*)addSeparator -{ - NSMenuItem *sep = [NSMenuItem separatorItem]; - [menu addItem:sep]; - return [MenuItemProxy proxyWithContext:context andMenuItem:sep]; -} - -- (MenuItemProxy*)itemForKey:(id)key -{ - if (isNullish(key)) - return nil; - NSMenuItem *item = nil; - if ([key isKindOfClass:[NSNumber class]]) - { - item = [menu itemAtIndex:[key intValue]]; - } - else if ([key isKindOfClass:[NSString class]]) - { - item = [menu itemWithTitle:key]; - if (!item) - { - // Try again, with ... appended. e.g. "Save..." - item = [menu itemWithTitle: - [key stringByAppendingString:@"\u2026"]]; - } - } - if (!item) - return nil; - - return [MenuItemProxy proxyWithContext:context andMenuItem:item]; -} - -- (MenuProxy*)removeItem:(id)key -{ - if (isNullish(key)) - return nil; - - NSMenuItem *item = nil; - if ([key isKindOfClass:[NSNumber class]]) - { - item = [menu itemAtIndex:[key intValue]]; - } - else if ([key isKindOfClass:[NSString class]]) - { - item = [menu itemWithTitle:key]; - if (!item) - { - // Try again, with ... appended. e.g. "Save..." - item = [menu itemWithTitle: - [key stringByAppendingString:@"\u2026"]]; - } - } - if (!item) - return nil; - - [menu removeItem:item]; - return [MenuProxy proxyWithContext:context andMenu:menu]; -} - -+ (BOOL) isSelectorExcludedFromWebScript:(SEL)selector -{ - return [self webScriptNameForSelector:selector] == nil; -} - -+ (BOOL) isKeyExcludedFromWebScript:(const char*)name -{ - return YES; -} - -+ (NSString*) webScriptNameForSelector:(SEL)selector -{ - id result = nil; - - if (selector == @selector(addItemWithTitle:keyEquivalent:callback:atIndex:)) { - result = @"addItem"; - } - else if (selector == @selector(addSeparator)) { - result = @"addSeparator"; - } - else if (selector == @selector(itemForKey:)) { - result = @"getItem"; - } - else if (selector == @selector(removeItem:)) { - result = @"removeMenu"; - } - - return result; -} - - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/Notice.h b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/Notice.h deleted file mode 100644 index 51077a4..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/Notice.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// Notice.h -// MacGap -// -// Created by Christian Sullivan on 7/26/12. -// Copyright (c) 2012 Twitter. All rights reserved. -// - -#import -#import "WindowController.h" - -#define APP_NOTICE_NOTIFICATION @"Notice" - -@interface Notice : NSObject { - -} - -@property (nonatomic, retain) WebView *webView; - -- (id) initWithWebView:(WebView *)view; -- (void) notify:(NSDictionary*)message; -- (void) close:(NSString*)notificationId; -+ (BOOL) available; - -@end - diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/Notice.m b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/Notice.m deleted file mode 100644 index a4095f9..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/Notice.m +++ /dev/null @@ -1,108 +0,0 @@ -// -// Notice.m -// MacGap -// -// Created by Christian Sullivan on 7/26/12. -// Copyright (c) 2012 Twitter. All rights reserved. -// - -#import "Notice.h" - -#import "JSEventHelper.h" - -@implementation Notice - -- (id) initWithWebView:(WebView*)view -{ - if(self = [super init]) { - self.webView = view; - [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self]; - } - return self; -} - -- (void) notify:(NSDictionary *)message { - NSUserNotification *notification = [[NSUserNotification alloc] init]; - [notification setTitle:[message valueForKey:@"title"]]; - [notification setInformativeText:[message valueForKey:@"content"]]; - [notification setDeliveryDate:[NSDate dateWithTimeInterval:0 sinceDate:[NSDate date]]]; - BOOL playSound = true; // optional parameter, false only when {sound: false} - @try { - NSNumber *s = [message valueForKey:@"sound"]; - if ([[s className] isEqual: @"__NSCFBoolean"]) { - playSound = [s boolValue]; - } - } - @catch (NSException *exception) { - } - if (playSound) { - [notification setSoundName:NSUserNotificationDefaultSoundName]; - } - NSString *id = @""; // optional, needed for close - @try { - id = [message valueForKey:@"id"]; - } - @catch (NSException *exception) { - } - [notification setUserInfo:[NSDictionary dictionaryWithObjectsAndKeys:id, @"id", nil]]; - NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter]; - [center scheduleNotification:notification]; -} - -// close all notifications with id == notificationId or close all notifications if notificationId == "*" -- (void) close:(NSString*)notificationId { - NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter]; - for(NSUserNotification * deliveredNote in center.deliveredNotifications) { - if ([notificationId isEqualToString:@"*"] || [deliveredNote.userInfo[@"id"] isEqualToString:notificationId]) { - [center removeDeliveredNotification: deliveredNote]; - } - } -} - -+ (BOOL) available { - if ([NSUserNotificationCenter respondsToSelector:@selector(defaultUserNotificationCenter)]) - return YES; - - return NO; -} - -- (void) userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification -{ - NSString *notificationId = [notification.userInfo valueForKey:@"id"]; - [JSEventHelper triggerEvent:@"macgap.notify.activated" forDetail:notificationId forWebView:self.webView]; -} - -#pragma mark WebScripting Protocol - -+ (BOOL) isSelectorExcludedFromWebScript:(SEL)selector -{ - BOOL result = YES; - if (selector == @selector(notify:)) - result = NO; - if (selector == @selector(close:)) - result = NO; - - return result; -} - -+ (NSString*) webScriptNameForSelector:(SEL)selector -{ - id result = nil; - - if (selector == @selector(notify:)) { - result = @"notify"; - } - if (selector == @selector(close:)) { - result = @"close"; - } - - return result; -} - -// right now exclude all properties (eg keys) -+ (BOOL) isKeyExcludedFromWebScript:(const char*)name -{ - return YES; -} - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/Path.h b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/Path.h deleted file mode 100644 index f931340..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/Path.h +++ /dev/null @@ -1,21 +0,0 @@ -#import - -@interface Path : NSObject { - -} - -- (NSString *) application; -- (NSString *) resource; -- (NSString *) documents; -- (NSString *) library; -- (NSString *) home; -- (NSString *) temp; - -@property (readonly,copy) NSString* application; -@property (readonly,copy) NSString* resource; -@property (readonly,copy) NSString* documents; -@property (readonly,copy) NSString* library; -@property (readonly,copy) NSString* home; -@property (readonly,copy) NSString* temp; - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/Path.m b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/Path.m deleted file mode 100644 index 8c54100..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/Path.m +++ /dev/null @@ -1,53 +0,0 @@ -#import "Path.h" - -@implementation Path - -@synthesize application; -@synthesize resource; -@synthesize documents; -@synthesize library; -@synthesize home; -@synthesize temp; - -- (NSString *)application { - return [[NSBundle mainBundle] bundlePath]; -} - -- (NSString *)resource { - return [[NSBundle mainBundle] resourcePath]; -} - -- (NSString *)documents { - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); - return [paths objectAtIndex:0]; -} - -- (NSString *)library { - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); - NSLog( @"%@", paths ); - return [paths objectAtIndex:0]; -} - -- (NSString *)home { - return NSHomeDirectory(); -} - -- (NSString *)temp { - return NSTemporaryDirectory(); -} - -#pragma mark WebScripting Protocol - -/* checks whether a selector is acceptable to be called from JavaScript */ -+ (BOOL) isSelectorExcludedFromWebScript:(SEL)selector -{ - return NO; -} - -// right now exclude all properties (eg keys) -+ (BOOL) isKeyExcludedFromWebScript:(const char*)name -{ - return NO; -} - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/Sound.h b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/Sound.h deleted file mode 100644 index 0670764..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/Sound.h +++ /dev/null @@ -1,17 +0,0 @@ -#import -#import "Command.h" -#import "CallbackDelegate.h" - - -@interface Sound : Command { - -} - -// pending callbacks for sounds being played, to keep -// ARC from freeing them too early -@property (nonatomic, strong) NSMutableSet *pending; - -- (void) play:(NSString*)file onComplete:(WebScriptObject*)callback; -- (void) playSystem:(NSString*)name onComplete:(WebScriptObject*)callback; - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/Sound.m b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/Sound.m deleted file mode 100644 index 9f4a44d..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/Sound.m +++ /dev/null @@ -1,97 +0,0 @@ -#import "Sound.h" - - -@interface PlayDelegate : CallbackDelegate { -} - -@property (nonatomic, weak) Sound *sound; - -- (id) initWithContext:(JSContextRef)aContext - forCallback:(WebScriptObject*)aCallback - withSound:(Sound*)aSound; -@end - -@implementation PlayDelegate - -@synthesize sound; - -- (id) initWithContext:(JSContextRef)aContext - forCallback:(WebScriptObject*)aCallback - withSound:(Sound*)aSound -{ - self = [super initWithContext:aContext forCallback:aCallback]; - if (!self) - return nil; - sound = aSound; - return self; -} - -- (void)sound:(NSSound *)aSound didFinishPlaying:(BOOL)finishedPlaying { - [self callWithParams:[aSound name], nil]; - [sound.pending removeObject:self]; -} - -@end - -@implementation Sound - -@synthesize pending; - -- (id) initWithContext:(JSContextRef)aContext { - self = [super initWithContext:aContext]; - if (!self) { - return nil; - } - - pending = [NSMutableSet new]; - return self; -} - -- (void) playSound:(NSSound*)sound onComplete:(WebScriptObject*)callback { - if (callback != (id)[WebUndefined undefined]) { - PlayDelegate *d = [[PlayDelegate alloc] initWithContext:context - forCallback:callback - withSound:self]; - [pending addObject:d]; - [sound setDelegate:d]; - } - [sound play]; -} - -- (void) play:(NSString*)file onComplete:(WebScriptObject*)callback { - NSURL* fileUrl = [NSURL fileURLWithPath:[[Utils sharedInstance] pathForResource:file]]; - DebugNSLog(@"Sound file:%@", [fileUrl description]); - - NSSound* sound = [[NSSound alloc] initWithContentsOfURL:fileUrl byReference:YES]; - [self playSound:sound onComplete:callback]; -} - -- (void) playSystem:(NSString*)name onComplete:(WebScriptObject*)callback { - NSSound *systemSound = [NSSound soundNamed:name]; - [self playSound:systemSound onComplete:callback]; -} - -#pragma mark WebScripting Protocol - -+ (BOOL) isSelectorExcludedFromWebScript:(SEL)selector { - return [self webScriptNameForSelector:selector] == nil; -} - -+ (BOOL) isKeyExcludedFromWebScript:(const char*)name { - return YES; -} - -+ (NSString*) webScriptNameForSelector:(SEL)selector { - id result = nil; - - if (selector == @selector(play:onComplete:)) { - result = @"play"; - } - else if (selector == @selector(playSystem:onComplete:)) { - result = @"playSystem"; - } - - return result; -} - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/UserDefaults.h b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/UserDefaults.h deleted file mode 100644 index 269191b..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/UserDefaults.h +++ /dev/null @@ -1,43 +0,0 @@ -// -// UserDefaults.h -// MacGap -// -// Created by Jeff Hanbury on 16/04/2014. -// Copyright (c) 2014 Twitter. All rights reserved. -// - -#import - -#import "WindowController.h" - -@interface UserDefaults : NSObject - -@property (nonatomic, retain) WebView *webView; - -- (id) initWithWebView:(WebView *)view; -- (NSString*) getMyDefaults; -- (NSDictionary*) myDefaultsDictionary; -- (void) removeObjectForKey:(NSString*)key; -- (NSArray*) getUserDefaultsKeys; - -- (NSString*) addPrefix:(NSString*)key; - -- (void) setString:(NSString*)key withValue:(NSString*)value; -- (NSString*) getString:(NSString*)key; - -- (void) setInteger:(NSString*)key withValue:(NSString*)value; -- (NSNumber*) getInteger:(NSString*)key; - -- (void) setBool:(NSString*)key withValue:(NSString*)value; -- (NSNumber*) getBool:(NSString*)key; - -- (void) setFloat:(NSString*)key withValue:(NSString*)value; -- (NSNumber*) getFloat:(NSString*)key; - -// Could also be implemented: -//– setObject:forKey: -//– setDouble:forKey: -//– setURL:forKey: - -@end - diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/UserDefaults.m b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/UserDefaults.m deleted file mode 100644 index 4856871..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/UserDefaults.m +++ /dev/null @@ -1,211 +0,0 @@ -// -// UserDefaults.m -// MacGap -// -// Created by Jeff Hanbury on 16/04/2014. -// Copyright (c) 2014 Twitter. All rights reserved. -// - -#import "UserDefaults.h" -#import "JSEventHelper.h" - -@interface UserDefaults() { - -} - --(void) setupNotificationCenter; - -@end - - -@implementation UserDefaults - -- (id) initWithWebView:(WebView *) view{ - self = [super init]; - - if (self) { - self.webView = view; - [self setupNotificationCenter]; - } - - return self; -} - - --(void) setupNotificationCenter{ - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(defaultsChanged:) - name:NSUserDefaultsDidChangeNotification - object:nil]; -} - -- (void)defaultsChanged:(NSNotification *)notification { - NSDictionary* returnDict = [self myDefaultsDictionary]; - [JSEventHelper triggerEvent:@"userDefaultsChanged" withArgs:returnDict forWebView:self.webView]; -} - -- (NSString*) getMyDefaults { - NSDictionary* myDefaults = [self myDefaultsDictionary]; - - return [[Utils sharedInstance] convertDictionaryToJSON:myDefaults]; -} - -- (NSDictionary*) myDefaultsDictionary { - NSString* prefix = [kWebScriptNamespace stringByAppendingString:@"_"]; - NSMutableDictionary* returnDict = [[NSMutableDictionary alloc] init]; - - // Get the user defaults. - NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; - - // Build up a dictionary containing just the items beginning with our - // prefix. - for (NSString* key in [self getUserDefaultsKeys]) { - if ([key hasPrefix:prefix]) { - id val = [defaults valueForKey:key]; - [returnDict setObject:val forKey:key]; - } - } - - return returnDict; -} - -- (NSArray*) getUserDefaultsKeys { - NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; - return [[prefs dictionaryRepresentation] allKeys]; -} - -- (void) removeObjectForKey:(NSString*)key { - NSString* prefixedKey; - prefixedKey = [self addPrefix:key]; - - [[NSUserDefaults standardUserDefaults] removeObjectForKey:prefixedKey]; - [[NSUserDefaults standardUserDefaults] synchronize]; -} - -// Check we have a standard prefix for JS-modified keys, for security purposes. -// If not, add it. This stops JavaScript from ever being able to modify keys -// it did not create. -- (NSString*) addPrefix:(NSString*)key { - NSString* prefix; - prefix = [kWebScriptNamespace stringByAppendingString:@"_"]; - - if (![key hasPrefix:prefix]) { - key = [prefix stringByAppendingString:key]; - } - return key; -} - -// String - -- (void) setString:(NSString*)key withValue:(NSString*)value { - NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; - NSString* prefixedKey; - prefixedKey = [self addPrefix:key]; - [prefs setObject:value forKey:prefixedKey]; -} - -- (NSString*) getString:(NSString *)key { - NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; - return [prefs stringForKey:key]; -} - -// All the following must convert their type to NSNumber for JavaScript. - -// Integer - -- (void) setInteger:(NSString*)key withValue:(NSString*)value { - NSString* prefixedKey; - prefixedKey = [self addPrefix:key]; - - NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; - NSInteger myInt = [value intValue]; - [prefs setInteger:myInt forKey:prefixedKey]; -} - -- (NSNumber*) getInteger:(NSString *)key { - NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; - return [NSNumber numberWithInteger:[prefs integerForKey:key]]; -} - -// Boolean - -- (void) setBool:(NSString*)key withValue:(NSString*)value { - NSString* prefixedKey; - prefixedKey = [self addPrefix:key]; - - NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; - BOOL myBool = [value boolValue]; - [prefs setBool:myBool forKey:prefixedKey]; -} - -- (NSNumber*) getBool:(NSString *)key { - NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; - return [NSNumber numberWithBool:[prefs boolForKey:key]]; -} - -// Float - -- (void) setFloat:(NSString*)key withValue:(NSString*)value { - NSString* prefixedKey; - prefixedKey = [self addPrefix:key]; - - NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; - float myFloat = [value floatValue]; - [prefs setFloat:myFloat forKey:prefixedKey]; -} - -- (NSNumber*) getFloat:(NSString *)key { - NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; - return [NSNumber numberWithFloat:[prefs floatForKey:key]]; -} - - -#pragma mark WebScripting Protocol - -+ (BOOL) isSelectorExcludedFromWebScript:(SEL)selector { - return NO; -} - -+ (NSString*) webScriptNameForSelector:(SEL)selector { - id result = nil; - - if (selector == @selector(getMyDefaults)) { - result = @"getMyDefaults"; - } - - if (selector == @selector(removeObjectForKey:)) { - result = @"removeObjectForKey"; - } - - else if (selector == @selector(setString:withValue:)) { - result = @"setString"; - } else if (selector == @selector(getString:)) { - result = @"getString"; - } - - else if (selector == @selector(setInteger:withValue:)) { - result = @"setInteger"; - } else if (selector == @selector(getInteger:)) { - result = @"getInteger"; - } - - else if (selector == @selector(setBool:withValue:)) { - result = @"setBool"; - } else if (selector == @selector(getBool:)) { - result = @"getBool"; - } - - else if (selector == @selector(setFloat:withValue:)) { - result = @"setFloat"; - } else if (selector == @selector(getFloat:)) { - result = @"getFloat"; - } - - return result; -} - -+ (BOOL) isKeyExcludedFromWebScript:(const char*)name { - return NO; -} - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/fonts.h b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/fonts.h deleted file mode 100644 index 62c7b7e..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/fonts.h +++ /dev/null @@ -1,9 +0,0 @@ -@interface Fonts : NSObject { -} - -- (NSArray*) availableFonts; -- (NSArray*) availableFontFamilies; -- (NSArray*) availableMembersOfFontFamily:(NSString*)fontFamily; -- (CGFloat) defaultLineHeightForFont:(NSString *)theFontName ofSize:(CGFloat)theFontSize; - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/fonts.m b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/fonts.m deleted file mode 100644 index b17818a..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Commands/fonts.m +++ /dev/null @@ -1,48 +0,0 @@ -#import "fonts.h" - -@implementation Fonts - - -- (NSArray*) availableFonts { - return [[NSFontManager sharedFontManager] availableFonts]; -} - -- (NSArray*) availableFontFamilies { - return [[NSFontManager sharedFontManager] availableFontFamilies]; -} - -- (NSArray*) availableMembersOfFontFamily:(NSString *)fontFamily { - return [[NSFontManager sharedFontManager] availableMembersOfFontFamily:fontFamily]; -} - -- (CGFloat) defaultLineHeightForFont:(NSString*)theFontName ofSize:(CGFloat)theFontSize { - NSFont *theFont = [NSFont fontWithName:theFontName size:theFontSize]; - NSLayoutManager *lm = [[NSLayoutManager alloc] init]; - - return [lm defaultLineHeightForFont:theFont]; -} - - -#pragma mark WebScripting Protocol - -+ (BOOL) isSelectorExcludedFromWebScript:(SEL)selector { - return NO; -} - -+ (NSString*) webScriptNameForSelector:(SEL)selector { - id result = nil; - - if (selector == @selector(availableMembersOfFontFamily:)) { - result = @"availableMembersOfFontFamily"; - } else if (selector == @selector(defaultLineHeightForFont:ofSize:)) { - result = @"defaultLineHeightForFont"; - } - - return result; -} - -+ (BOOL) isKeyExcludedFromWebScript:(const char*)name { - return NO; -} - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Constants.h b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Constants.h deleted file mode 100644 index 1fe59d6..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Constants.h +++ /dev/null @@ -1,7 +0,0 @@ -// Application constants - -#define kStartPage @"http://127.0.0.1:9993/" - -#define kStartFolder @"." - -#define kWebScriptNamespace @"macgap" \ No newline at end of file diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/ContentView.h b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/ContentView.h deleted file mode 100644 index 65890a5..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/ContentView.h +++ /dev/null @@ -1,15 +0,0 @@ -#import -#import - -@class WebViewDelegate; - -@interface ContentView : NSView { - IBOutlet WebView* webView; - WebViewDelegate* delegate; -} - -@property (retain) WebView* webView; -@property (retain) WebViewDelegate* delegate; -@property (strong) IBOutlet NSMenu *mainMenu; - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/ContentView.m b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/ContentView.m deleted file mode 100644 index 6558a19..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/ContentView.m +++ /dev/null @@ -1,68 +0,0 @@ -#import "ContentView.h" -#import "WebViewDelegate.h" -#import "AppDelegate.h" -#import "JSEventHelper.h" - -@interface WebPreferences (WebPreferencesPrivate) - - (void)_setLocalStorageDatabasePath:(NSString *)path; - - (void) setLocalStorageEnabled: (BOOL) localStorageEnabled; - - (void) setDatabasesEnabled:(BOOL)databasesEnabled; - - (void) setDeveloperExtrasEnabled:(BOOL)developerExtrasEnabled; - - (void) setWebGLEnabled:(BOOL)webGLEnabled; - - (void) setOfflineWebApplicationCacheEnabled:(BOOL)offlineWebApplicationCacheEnabled; -@end - -@implementation ContentView - -@synthesize webView, delegate, mainMenu; - -- (void) awakeFromNib -{ - WebPreferences *webPrefs = [WebPreferences standardPreferences]; - - NSString *cappBundleName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; - NSString *applicationSupportFile = [@"~/Library/Application Support/" stringByExpandingTildeInPath]; - NSString *savePath = [NSString pathWithComponents:[NSArray arrayWithObjects:applicationSupportFile, cappBundleName, @"LocalStorage", nil]]; - [webPrefs _setLocalStorageDatabasePath:savePath]; - [webPrefs setLocalStorageEnabled:YES]; - [webPrefs setDatabasesEnabled:YES]; - [webPrefs setDeveloperExtrasEnabled:[[NSUserDefaults standardUserDefaults] boolForKey: @"developer"]]; - [webPrefs setOfflineWebApplicationCacheEnabled:YES]; - [webPrefs setWebGLEnabled:YES]; - - [self.webView setPreferences:webPrefs]; - - NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage - sharedHTTPCookieStorage]; - [cookieStorage setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways]; - - [self.webView setApplicationNameForUserAgent: @"MacGap"]; - - self.delegate = [[WebViewDelegate alloc] initWithMenu:[NSApp mainMenu]]; -// [self.webView setFrameLoadDelegate:self.delegate]; -// [self.webView setUIDelegate:self.delegate]; -// [self.webView setResourceLoadDelegate:self.delegate]; -// [self.webView setDownloadDelegate:self.delegate]; -// [self.webView setPolicyDelegate:self.delegate]; - [self.webView setDrawsBackground:NO]; - [self.webView setShouldCloseWithWindow:NO]; - - [self.webView setGroupName:@"MacGap"]; - -} - -- (void) windowResized:(NSNotification*)notification; -{ - NSWindow* window = (NSWindow*)notification.object; - NSSize size = [window frame].size; - - DebugNSLog(@"window width = %f, window height = %f", size.width, size.height); - - bool isFullScreen = (window.styleMask & NSFullScreenWindowMask) == NSFullScreenWindowMask; - int titleBarHeight = isFullScreen ? 0 : [[Utils sharedInstance] titleBarHeight:window]; - - [self.webView setFrame:NSMakeRect(0, 0, size.width, size.height - titleBarHeight)]; - [JSEventHelper triggerEvent:@"orientationchange" forWebView:self.webView]; -} - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/JSEventHelper.h b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/JSEventHelper.h deleted file mode 100644 index 401f3e3..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/JSEventHelper.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// Helper.h -// MacGap -// -// Created by Liam Kaufman Simpkins on 12-01-22. -// Copyright (c) 2012 Twitter. All rights reserved. -// - -#import -#import "WindowController.h" - -@interface JSEventHelper : NSObject - -+ (void) triggerEvent:(NSString *)event forWebView:(WebView *)webView; -+ (void) triggerEvent:(NSString *)event withArgs:(NSDictionary *)args forWebView:(WebView *)webView; -+ (void) triggerEvent:(NSString *)event withArgs:(NSDictionary *)args forObject:(NSString *)objName forWebView:(WebView *)webView; -+ (void) triggerEvent:(NSString *)event forDetail:(NSString *)detail forWebView:(WebView *)webView; -+ (void) triggerEvent:(NSString *)event forDetail:(NSString *)detail forObject:(NSString *)objName forWebView:(WebView *)webView; - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/JSEventHelper.m b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/JSEventHelper.m deleted file mode 100644 index 65406b3..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/JSEventHelper.m +++ /dev/null @@ -1,41 +0,0 @@ -// -// Helper.m -// MacGap -// -// Created by Liam Kaufman Simpkins on 12-01-22. -// Copyright (c) 2012 Twitter. All rights reserved. -// - -#import "JSEventHelper.h" - -@implementation JSEventHelper - -+ (void) triggerEvent:(NSString *)event forWebView:(WebView *)webView { - [self triggerEvent:event withArgs:[NSMutableDictionary dictionary] forObject:@"document" forWebView:webView]; -} - -+ (void) triggerEvent:(NSString *)event withArgs:(NSDictionary *)args forWebView:(WebView *)webView { - [self triggerEvent:event withArgs:args forObject:@"document" forWebView:webView]; -} - -+ (void) triggerEvent:(NSString *)event withArgs:(NSDictionary *)args forObject:(NSString *)objName forWebView:(WebView *)webView { - - // Convert args Dictionary to JSON. - NSString* jsonString = [[Utils sharedInstance] convertDictionaryToJSON:args]; - - // Create the event JavaScript and run it. - NSString * str = [NSString stringWithFormat:@"var e = document.createEvent('Events'); e.initEvent('%@', true, false); e.data=%@; %@.dispatchEvent(e); ", event, jsonString, objName]; - [webView stringByEvaluatingJavaScriptFromString:str]; -} - -+ (void) triggerEvent:(NSString *)event forDetail:(NSString *)detail forWebView:(WebView *)webView { - [self triggerEvent:event forDetail:detail forObject:@"document" forWebView:webView]; -} - -+ (void) triggerEvent:(NSString *)event forDetail:(NSString *)detail forObject:(NSString *)objName forWebView:(WebView *)webView { - NSString *detailEscaped = [detail stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]; - NSString *str = [NSString stringWithFormat:@"var e = new CustomEvent('%@', { 'detail': decodeURIComponent(\"%@\") }); %@.dispatchEvent(e); ", event, detailEscaped, objName]; - [webView stringByEvaluatingJavaScriptFromString:str]; -} - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Utils.h b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Utils.h deleted file mode 100644 index f573d88..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Utils.h +++ /dev/null @@ -1,20 +0,0 @@ -#import -#import - -#define DEG_EPS 0.001 -#define fequal(a,b) (fabs((a) - (b)) < DEG_EPS) -#define fequalzero(a) (fabs(a) < DEG_EPS) - -@class LoadingView; - -@interface Utils : NSObject { -} - -- (float) titleBarHeight:(NSWindow*)aWindow; -- (NSString*) pathForResource:(NSString*)resourcepath; -- (NSString*) convertDictionaryToJSON:(NSDictionary*)dict; -- (NSArray*) convertJSarrayToNSArray:(WebScriptObject*)jsArray; - -+ (Utils*) sharedInstance; - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Utils.m b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Utils.m deleted file mode 100644 index 8d85c29..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Utils.m +++ /dev/null @@ -1,93 +0,0 @@ -#import "Utils.h" -#import - -static Utils* sharedInstance = nil; - -@implementation Utils - -- (float) titleBarHeight:(NSWindow*)aWindow -{ - NSRect frame = [aWindow frame]; - NSRect contentRect = [NSWindow contentRectForFrameRect: frame - styleMask: NSTitledWindowMask]; - - return (frame.size.height - contentRect.size.height); -} - -- (NSString*) pathForResource:(NSString*)resourcepath -{ - NSBundle * mainBundle = [NSBundle mainBundle]; - NSMutableArray *directoryParts = [NSMutableArray arrayWithArray:[resourcepath componentsSeparatedByString:@"/"]]; - NSString *filename = [directoryParts lastObject]; - [directoryParts removeLastObject]; - - NSString *directoryStr = [NSString stringWithFormat:@"%@/%@", kStartFolder, [directoryParts componentsJoinedByString:@"/"]]; - return [mainBundle pathForResource:filename - ofType:@"" - inDirectory:directoryStr]; -} - -- (NSString*) convertDictionaryToJSON:(NSDictionary*)dict { - // Convert defaults Dictionary to JSON. - NSError *error; - NSData *jsonData = [NSJSONSerialization - dataWithJSONObject:dict - options:NSJSONWritingPrettyPrinted // Pass 0 if you don't care about the readability of the generated string - error:&error]; - - NSString *jsonString; - if (! jsonData) { - NSLog(@"Got an error converting to JSON: %@", error); - } - else { - jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; - } - - return jsonString; -} - -// Convert JavaScript array (arrives as a WebScriptObject) into an NSArray of strings. -- (NSArray*) convertJSarrayToNSArray:(WebScriptObject*)jsArray { - NSInteger count = [[jsArray valueForKey:@"length"] integerValue]; - - NSMutableArray *args = [NSMutableArray array]; - for (int i = 0; i < count; i++) { - NSString *item = [jsArray webScriptValueAtIndex:i]; - if ([item isKindOfClass:[NSString class]]) { - [args addObject:item]; - } - } - - return args; -} - -#pragma mark - -#pragma mark Singleton methods - -+ (Utils*) sharedInstance -{ - @synchronized(self) - { - if (sharedInstance == nil){ - sharedInstance = [[Utils alloc] init]; - } - } - return sharedInstance; -} - -+ (id) allocWithZone:(NSZone *)zone { - @synchronized(self) { - if (sharedInstance == nil) { - sharedInstance = [super allocWithZone:zone]; - return sharedInstance; // assignment and return on first allocation - } - } - return nil; // on subsequent allocation attempts return nil -} - -- (id) copyWithZone:(NSZone *)zone -{ - return self; -} - -@end \ No newline at end of file diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/WebViewDelegate.h b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/WebViewDelegate.h deleted file mode 100644 index 49c6da6..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/WebViewDelegate.h +++ /dev/null @@ -1,49 +0,0 @@ -#import -#import - -@class Sound; -@class Dock; -@class Growl; -@class Notice; -@class Path; -@class App; -@class Window; -@class Clipboard; -@class Fonts; -@class MenuProxy; -@class UserDefaults; - -@class WindowController; - -@interface WebViewDelegate : NSObject { - Sound* sound; - Dock* dock; - Growl* growl; - Notice* notice; - Path* path; - App* app; - Window* window; - Clipboard* clipboard; - Fonts* fonts; - NSMenu *mainMenu; - UserDefaults* userDefaults; -} - - - -@property (nonatomic, retain) Sound* sound; -@property (nonatomic, retain) Dock* dock; -@property (nonatomic, retain) Growl* growl; -@property (nonatomic, retain) Notice* notice; -@property (nonatomic, retain) Path* path; -@property (nonatomic, retain) App* app; -@property (nonatomic, retain) Window* window; -@property (nonatomic, retain) Clipboard* clipboard; -@property (nonatomic, retain) Fonts* fonts; -@property (nonatomic, retain) MenuProxy* menu; -@property (nonatomic, retain) UserDefaults* userDefaults; - -@property (nonatomic, retain) WindowController *requestedWindow; - -- (id) initWithMenu:(NSMenu*)menu; -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/WebViewDelegate.m b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/WebViewDelegate.m deleted file mode 100644 index 5057801..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/WebViewDelegate.m +++ /dev/null @@ -1,206 +0,0 @@ -#import "WebViewDelegate.h" -#import "Sound.h" -#import "Dock.h" -#import "Notice.h" -#import "Path.h" -#import "App.h" -#import "Window.h" -#import "WindowController.h" -#import "Clipboard.h" -#import "Fonts.h" -#import "MenuProxy.h" -#import "UserDefaults.h" - -@implementation WebViewDelegate - -@synthesize sound; -@synthesize dock; -@synthesize growl; -@synthesize notice; -@synthesize path; -@synthesize app; -@synthesize window; -@synthesize requestedWindow; -@synthesize clipboard; -@synthesize fonts; -@synthesize menu; -@synthesize userDefaults; - -- (id) initWithMenu:(NSMenu*)aMenu -{ - self = [super init]; - if (!self) - return nil; - - mainMenu = aMenu; - return self; -} - -- (void) webView:(WebView*)webView didClearWindowObject:(WebScriptObject*)windowScriptObject forFrame:(WebFrame *)frame -{ - JSContextRef context = [frame globalContext]; - if (self.sound == nil) { self.sound = [[Sound alloc] initWithContext:context]; } - if (self.dock == nil) { self.dock = [Dock new]; } - if (self.path == nil) { self.path = [Path new]; } - if (self.clipboard == nil) { self.clipboard = [Clipboard new]; } - if (self.fonts == nil) { self.fonts = [Fonts new]; } - - if (self.notice == nil && [Notice available] == YES) { - self.notice = [[Notice alloc] initWithWebView:webView]; - } - - if (self.app == nil) { - self.app = [[App alloc] initWithWebView:webView]; - } - - if (self.window == nil) { - self.window = [[Window alloc] initWithWebView:webView]; - } - - if (self.menu == nil) { - self.menu = [MenuProxy proxyWithContext:context andMenu:mainMenu]; - } - - if (self.userDefaults == nil) { - self.userDefaults = [[UserDefaults alloc] initWithWebView:webView]; - } - - [windowScriptObject setValue:self forKey:kWebScriptNamespace]; -} - - -- (void)webView:(WebView *)sender runOpenPanelForFileButtonWithResultListener:(id < WebOpenPanelResultListener >)resultListener allowMultipleFiles:(BOOL)allowMultipleFiles{ - - NSOpenPanel * openDlg = [NSOpenPanel openPanel]; - - [openDlg setCanChooseFiles:YES]; - [openDlg setCanChooseDirectories:NO]; - - [openDlg beginWithCompletionHandler:^(NSInteger result){ - if (result == NSFileHandlingPanelOKButton) { - NSArray * files = [[openDlg URLs] valueForKey: @"relativePath"]; - [resultListener chooseFilenames: files]; - } else { - [resultListener cancel]; - } - }]; -} - -- (void) webView:(WebView*)webView addMessageToConsole:(NSDictionary*)message -{ - if (![message isKindOfClass:[NSDictionary class]]) { - return; - } - - NSLog(@"JavaScript console: %@:%@: %@", - [[message objectForKey:@"sourceURL"] lastPathComponent], // could be nil - [message objectForKey:@"lineNumber"], - [message objectForKey:@"message"]); -} - -- (void)webView:(WebView *)sender runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WebFrame *)frame -{ - NSAlert *alert = [[NSAlert alloc] init]; - [alert addButtonWithTitle:@"OK"]; - [alert setMessageText:message]; - [alert setAlertStyle:NSWarningAlertStyle]; - [alert runModal]; -} - -- (BOOL)webView:(WebView *)sender runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WebFrame *)frame -{ - NSAlert *alert = [[NSAlert alloc] init]; - [alert addButtonWithTitle:@"Yes"]; - [alert addButtonWithTitle:@"No"]; - [alert setMessageText:message]; - [alert setAlertStyle:NSWarningAlertStyle]; - - if ([alert runModal] == NSAlertFirstButtonReturn) - return YES; - else - return NO; -} - -/* - By default the size of a database is set to 0 [1]. When a database is being created - it calls this delegate method to get an increase in quota size - or call an error. - PS this method is defined in WebUIDelegatePrivate and may make it difficult, but - not impossible [2], to get an app accepted into the mac app store. - - Further reading: - [1] http://stackoverflow.com/questions/353808/implementing-a-webview-database-quota-delegate - [2] http://stackoverflow.com/questions/4527905/how-do-i-enable-local-storage-in-my-webkit-based-application/4608549#4608549 - */ -- (void)webView:(WebView *)sender frame:(WebFrame *)frame exceededDatabaseQuotaForSecurityOrigin:(id) origin database:(NSString *)databaseIdentifier -{ - static const unsigned long long defaultQuota = 5 * 1024 * 1024; - if ([origin respondsToSelector: @selector(setQuota:)]) { - [origin performSelector:@selector(setQuota:) withObject:[NSNumber numberWithLongLong: defaultQuota]]; - } else { - NSLog(@"could not increase quota for %lld", defaultQuota); - } -} - -- (NSArray *)webView:(WebView *)sender contextMenuItemsForElement:(NSDictionary *)element defaultMenuItems:(NSArray *)defaultMenuItems -{ - NSMutableArray *webViewMenuItems = [defaultMenuItems mutableCopy]; - - if (webViewMenuItems) - { - NSEnumerator *itemEnumerator = [defaultMenuItems objectEnumerator]; - NSMenuItem *menuItem = nil; - while ((menuItem = [itemEnumerator nextObject])) - { - NSInteger tag = [menuItem tag]; - - switch (tag) - { - case WebMenuItemTagOpenLinkInNewWindow: - case WebMenuItemTagDownloadLinkToDisk: - case WebMenuItemTagCopyLinkToClipboard: - case WebMenuItemTagOpenImageInNewWindow: - case WebMenuItemTagDownloadImageToDisk: - case WebMenuItemTagCopyImageToClipboard: - case WebMenuItemTagOpenFrameInNewWindow: - case WebMenuItemTagGoBack: - case WebMenuItemTagGoForward: - case WebMenuItemTagStop: - case WebMenuItemTagOpenWithDefaultApplication: - case WebMenuItemTagReload: - [webViewMenuItems removeObjectIdenticalTo: menuItem]; - } - } - } - - return webViewMenuItems; -} - -- (WebView *)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)request{ - requestedWindow = [[WindowController alloc] initWithRequest:request]; - return requestedWindow.contentView.webView; -} - -- (void)webViewShow:(WebView *)sender{ - [requestedWindow showWindow:sender]; -} - -- (void)webView:(WebView *)webView decidePolicyForNewWindowAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request newFrameName:(NSString *)frameName decisionListener:(id < WebPolicyDecisionListener >)listener -{ - [[NSWorkspace sharedWorkspace] openURL:[request URL]]; - [listener ignore]; -} - -#pragma mark WebScripting protocol - -+ (BOOL) isSelectorExcludedFromWebScript:(SEL)selector -{ - return YES; -} - -+ (BOOL) isKeyExcludedFromWebScript:(const char*)name -{ - return NO; -} - - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Window.h b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Window.h deleted file mode 100644 index f721376..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Window.h +++ /dev/null @@ -1,23 +0,0 @@ -#import - -#import "WindowController.h" - -@interface Window : NSObject{ - CGRect _oldRestoreFrame; -} - -@property (retain, nonatomic) WindowController *windowController; -@property (nonatomic, retain) WebView *webView; - -- (id) initWithWebView:(WebView *)view; -- (void) open:(NSDictionary *)properties; -- (void) move:(NSDictionary *)properties; -- (void) resize:(NSDictionary *) properties; -- (Boolean) isMaximized; -- (CGFloat) getX; -- (CGFloat) getY; -- (void) maximize; -- (void) restore; -- (void) toggleFullscreen; - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Window.m b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Window.m deleted file mode 100644 index 2444f62..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Classes/Window.m +++ /dev/null @@ -1,94 +0,0 @@ -#import "Window.h" - -@implementation Window - -@synthesize windowController, webView; - -- (id) initWithWebView:(WebView*)view -{ - if(self = [super init]) { - self.webView = view; - } - return self; -} - -- (void) open:(NSDictionary *)properties -{ - self.windowController = [[WindowController alloc] initWithURL:[properties valueForKey:@"url"]]; - [self.windowController showWindow: [NSApplication sharedApplication].delegate]; - [self.windowController.window makeKeyWindow]; -} - -- (void) minimize { - [[NSApp mainWindow] miniaturize:[NSApp mainWindow]]; -} - -- (void) toggleFullscreen { - [[NSApp mainWindow] toggleFullScreen:[NSApp mainWindow]]; -} - -- (void) maximize { - CGRect a = [NSApp mainWindow].frame; - _oldRestoreFrame = CGRectMake(a.origin.x, a.origin.y, a.size.width, a.size.height); - [[NSApp mainWindow] setFrame:[[NSScreen mainScreen] visibleFrame] display:YES]; -} - -- (Boolean) isMaximized { - NSRect a = [NSApp mainWindow].frame; - NSRect b = [[NSScreen mainScreen] visibleFrame]; - return a.origin.x == b.origin.x && a.origin.y == b.origin.y && a.size.width == b.size.width && a.size.height == b.size.height; -} - -- (CGFloat) getX { - NSRect frame = [self.webView window].frame; - return frame.origin.x; -} - -- (CGFloat) getY { - NSRect frame = [self.webView window].frame; - return frame.origin.y; -} - -- (void) move:(NSDictionary *)properties -{ - NSRect frame = [self.webView window].frame; - frame.origin.x = [[properties valueForKey:@"x"] doubleValue]; - frame.origin.y = [[properties valueForKey:@"y"] doubleValue]; - [[self.webView window] setFrame:frame display:YES]; - -} - -- (void) resize:(NSDictionary *) properties -{ - NSRect frame = [self.webView window].frame; - frame.size.width = [[properties valueForKey:@"width"] doubleValue]; - frame.size.height = [[properties valueForKey:@"height"] doubleValue]; - [[self.webView window] setFrame:frame display:YES]; -} - - -+ (BOOL) isSelectorExcludedFromWebScript:(SEL)selector -{ - return NO; -} - -+ (NSString*) webScriptNameForSelector:(SEL)selector{ - id result = nil; - - if (selector == @selector(open:)) { - result = @"open"; - }else if (selector == @selector(move:)){ - result = @"move"; - }else if (selector == @selector(resize:)){ - result = @"resize"; - } - - return result; -} - -+ (BOOL) isKeyExcludedFromWebScript:(const char*)name -{ - return YES; -} - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Clipboard.h b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Clipboard.h deleted file mode 100644 index 6c1a2f5..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Clipboard.h +++ /dev/null @@ -1,10 +0,0 @@ -#import - -@interface Clipboard : NSObject { - -} - -- (void) copy:(NSString*)text; -- (NSString *) paste; - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Clipboard.m b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Clipboard.m deleted file mode 100644 index 1c18dea..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/Clipboard.m +++ /dev/null @@ -1,51 +0,0 @@ -// -// clipboard.m -// MacGap -// -// Created by David Zorychta on 2013-07-22. -// Copyright (c) 2013 Twitter. All rights reserved. -// - -#import "Clipboard.h" - -@implementation Clipboard - -- (void) copy:(NSString*)text { - [[NSPasteboard generalPasteboard] clearContents]; - [[NSPasteboard generalPasteboard] setString:text forType:NSStringPboardType]; -} - -- (NSString *) paste { - NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; - NSArray *classArray = [NSArray arrayWithObject:[NSString class]]; - NSDictionary *options = [NSDictionary dictionary]; - BOOL ok = [pasteboard canReadObjectForClasses:classArray options:options]; - if (ok) { - NSArray *objectsToPaste = [pasteboard readObjectsForClasses:classArray options:options]; - return (NSString *) [objectsToPaste objectAtIndex:0]; - } - return @""; -} - -+ (NSString*) webScriptNameForSelector:(SEL)selector -{ - id result = nil; - - if (selector == @selector(copy:)) { - result = @"copy"; - } - - return result; -} - -+ (BOOL) isSelectorExcludedFromWebScript:(SEL)selector -{ - return NO; -} - -+ (BOOL) isKeyExcludedFromWebScript:(const char*)name -{ - return YES; -} - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/MacGap-Prefix.pch b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/MacGap-Prefix.pch deleted file mode 100644 index ad05e84..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/MacGap-Prefix.pch +++ /dev/null @@ -1,15 +0,0 @@ -// -// Prefix header for all source files of the 'MacGap' target in the 'MacGap' project -// - -#ifdef __OBJC__ - #ifdef _DEBUG - #define DebugNSLog(format, ...) NSLog(format, ## __VA_ARGS__) - #else - #define DebugNSLog(format, ...) - #endif - - #import - #import "Constants.h" - #import "Utils.h" -#endif diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/WindowController.h b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/WindowController.h deleted file mode 100644 index 72927ef..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/WindowController.h +++ /dev/null @@ -1,13 +0,0 @@ -#import -#import "ContentView.h" - -@interface WindowController : NSWindowController { - -} - -- (id) initWithURL:(NSString *) url; -- (id) initWithRequest: (NSURLRequest *)request; -@property (retain) NSURL * url; -@property (retain) IBOutlet ContentView *contentView; - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/WindowController.m b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/WindowController.m deleted file mode 100644 index 2765a2e..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/WindowController.m +++ /dev/null @@ -1,54 +0,0 @@ -#import "WindowController.h" - - -@interface WindowController() { - -} - --(void) notificationCenter; - -@end - -@implementation WindowController - -@synthesize contentView, url; - -- (id) initWithURL:(NSString *) relativeURL{ - self = [super initWithWindowNibName:@"Window"]; - self.url = [NSURL URLWithString:relativeURL relativeToURL:[[NSBundle mainBundle] resourceURL]]; - - [self.window setFrameAutosaveName:@"MacGapWindow"]; - [self notificationCenter]; - - return self; -} - --(id) initWithRequest: (NSURLRequest *)request{ - self = [super initWithWindowNibName:@"Window"]; - [self notificationCenter]; - [[self.contentView.webView mainFrame] loadRequest:request]; - - return self; -} - --(void) notificationCenter{ - [[NSNotificationCenter defaultCenter] addObserver:self.contentView - selector:@selector(windowResized:) - name:NSWindowDidResizeNotification - object:[self window]]; -} - -- (void)windowDidLoad -{ - [super windowDidLoad]; - - if (self.url != nil) { - [self.contentView.webView setMainFrameURL:[self.url absoluteString]]; - } - - - // Implement this method to handle any initialization after your - // window controller's window has been loaded from its nib file. -} - -@end diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/en.lproj/Credits.rtf b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/en.lproj/Credits.rtf deleted file mode 100644 index 6f388f6..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/en.lproj/Credits.rtf +++ /dev/null @@ -1,13 +0,0 @@ -{\rtf1\ansi\ansicpg1252\cocoartf1347\cocoasubrtf570 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\vieww9600\viewh8400\viewkind0 -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720 - -\f0\b\fs24 \cf0 (c)2011-2015 ZeroTier, Inc.\ -Licensed under the GNU GPLv3\ -\ -UI Wrapper MacGap (c) Twitter, Inc.\ -Licensed under the MIT License\ -http://macgap.com/\ -} \ No newline at end of file diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/en.lproj/InfoPlist.strings b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/en.lproj/InfoPlist.strings deleted file mode 100644 index 477b28f..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/en.lproj/InfoPlist.strings +++ /dev/null @@ -1,2 +0,0 @@ -/* Localized versions of Info.plist keys */ - diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/en.lproj/MainMenu.xib b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/en.lproj/MainMenu.xib deleted file mode 100644 index dd67a86..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/en.lproj/MainMenu.xib +++ /dev/null @@ -1,3404 +0,0 @@ - - - - 1070 - 14D136 - 7702 - 1347.57 - 758.70 - - com.apple.InterfaceBuilder.CocoaPlugin - 7702 - - - NSCustomObject - NSMenu - NSMenuItem - - - com.apple.InterfaceBuilder.CocoaPlugin - - - PluginDependencyRecalculationVersion - - - - - NSApplication - - - FirstResponder - - - NSApplication - - - AppDelegate - - - AMainMenu - - - - ZeroTier One - - 1048576 - 2147483647 - - NSImage - NSMenuCheckmark - - - NSImage - NSMenuMixedState - - submenuAction: - - - ZeroTier One - - - - About ZeroTier One - - 2147483647 - - - - - - YES - YES - - - 1048576 - 2147483647 - - - - - - Preferences… - , - 1048576 - 2147483647 - - - - - - YES - YES - - - 1048576 - 2147483647 - - - - - - Services - - 1048576 - 2147483647 - - - submenuAction: - - - Services - - _NSServicesMenu - - - - - YES - YES - - - 1048576 - 2147483647 - - - - - - Hide ZeroTier One - h - 1048576 - 2147483647 - - - - - - Hide Others - h - 1572864 - 2147483647 - - - - - - Show All - - 1048576 - 2147483647 - - - - - - YES - YES - - - 1048576 - 2147483647 - - - - - - Quit ZeroTier One - q - 1048576 - 2147483647 - - - - - _NSAppleMenu - - - - - File - - 1048576 - 2147483647 - - - submenuAction: - - - File - - - - New - n - 1048576 - 2147483647 - - - - - - Open… - o - 1048576 - 2147483647 - - - - - - Open Recent - - 1048576 - 2147483647 - - - submenuAction: - - - Open Recent - - - - Clear Menu - - 1048576 - 2147483647 - - - - - _NSRecentDocumentsMenu - - - - - YES - YES - - - 1048576 - 2147483647 - - - - - - Close - w - 1048576 - 2147483647 - - - - - - Save… - s - 1048576 - 2147483647 - - - - - - Revert to Saved - - 2147483647 - - - - - - YES - YES - - - 1048576 - 2147483647 - - - - - - Page Setup... - P - 1179648 - 2147483647 - - - - - - - Print… - p - 1048576 - 2147483647 - - - - - - - - - Edit - - 1048576 - 2147483647 - - - submenuAction: - - - Edit - - - - Undo - z - 1048576 - 2147483647 - - - - - - Redo - Z - 1179648 - 2147483647 - - - - - - YES - YES - - - 1048576 - 2147483647 - - - - - - Cut - x - 1048576 - 2147483647 - - - - - - Copy - c - 1048576 - 2147483647 - - - - - - Paste - v - 1048576 - 2147483647 - - - - - - Paste and Match Style - V - 1572864 - 2147483647 - - - - - - Delete - - 1048576 - 2147483647 - - - - - - Select All - a - 1048576 - 2147483647 - - - - - - YES - YES - - - 1048576 - 2147483647 - - - - - - Find - - 1048576 - 2147483647 - - - submenuAction: - - - Find - - - - Find… - f - 1048576 - 2147483647 - - - 1 - - - - Find and Replace… - f - 1572864 - 2147483647 - - - 12 - - - - Find Next - g - 1048576 - 2147483647 - - - 2 - - - - Find Previous - G - 1179648 - 2147483647 - - - 3 - - - - Use Selection for Find - e - 1048576 - 2147483647 - - - 7 - - - - Jump to Selection - j - 1048576 - 2147483647 - - - - - - - - - Spelling and Grammar - - 1048576 - 2147483647 - - - submenuAction: - - - Spelling and Grammar - - - - Show Spelling and Grammar - : - 1048576 - 2147483647 - - - - - - Check Document Now - ; - 1048576 - 2147483647 - - - - - - YES - YES - - - 2147483647 - - - - - - Check Spelling While Typing - - 1048576 - 2147483647 - - - - - - Check Grammar With Spelling - - 1048576 - 2147483647 - - - - - - Correct Spelling Automatically - - 2147483647 - - - - - - - - - Substitutions - - 1048576 - 2147483647 - - - submenuAction: - - - Substitutions - - - - Show Substitutions - - 2147483647 - - - - - - YES - YES - - - 2147483647 - - - - - - Smart Copy/Paste - f - 1048576 - 2147483647 - - - 1 - - - - Smart Quotes - g - 1048576 - 2147483647 - - - 2 - - - - Smart Dashes - - 2147483647 - - - - - - Smart Links - G - 1179648 - 2147483647 - - - 3 - - - - Text Replacement - - 2147483647 - - - - - - - - - Transformations - - 2147483647 - - - submenuAction: - - - Transformations - - - - Make Upper Case - - 2147483647 - - - - - - Make Lower Case - - 2147483647 - - - - - - Capitalize - - 2147483647 - - - - - - - - - Speech - - 1048576 - 2147483647 - - - submenuAction: - - - Speech - - - - Start Speaking - - 1048576 - 2147483647 - - - - - - Stop Speaking - - 1048576 - 2147483647 - - - - - - - - - - - - Format - - 2147483647 - - - submenuAction: - - - Format - - - - Font - - 2147483647 - - - submenuAction: - - - Font - - - - Show Fonts - t - 1048576 - 2147483647 - - - - - - Bold - b - 1048576 - 2147483647 - - - 2 - - - - Italic - i - 1048576 - 2147483647 - - - 1 - - - - Underline - u - 1048576 - 2147483647 - - - - - - YES - YES - - - 2147483647 - - - - - - Bigger - + - 1048576 - 2147483647 - - - 3 - - - - Smaller - - - 1048576 - 2147483647 - - - 4 - - - - YES - YES - - - 2147483647 - - - - - - Kern - - 2147483647 - - - submenuAction: - - - Kern - - - - Use Default - - 2147483647 - - - - - - Use None - - 2147483647 - - - - - - Tighten - - 2147483647 - - - - - - Loosen - - 2147483647 - - - - - - - - - Ligature - - 2147483647 - - - submenuAction: - - - Ligature - - - - Use Default - - 2147483647 - - - - - - Use None - - 2147483647 - - - - - - Use All - - 2147483647 - - - - - - - - - Baseline - - 2147483647 - - - submenuAction: - - - Baseline - - - - Use Default - - 2147483647 - - - - - - Superscript - - 2147483647 - - - - - - Subscript - - 2147483647 - - - - - - Raise - - 2147483647 - - - - - - Lower - - 2147483647 - - - - - - - - - YES - YES - - - 2147483647 - - - - - - Show Colors - C - 1048576 - 2147483647 - - - - - - YES - YES - - - 2147483647 - - - - - - Copy Style - c - 1572864 - 2147483647 - - - - - - Paste Style - v - 1572864 - 2147483647 - - - - - _NSFontMenu - - - - - Text - - 2147483647 - - - submenuAction: - - - Text - - - - Align Left - { - 1048576 - 2147483647 - - - - - - Center - | - 1048576 - 2147483647 - - - - - - Justify - - 2147483647 - - - - - - Align Right - } - 1048576 - 2147483647 - - - - - - YES - YES - - - 2147483647 - - - - - - Writing Direction - - 2147483647 - - - submenuAction: - - - Writing Direction - - - - YES - Paragraph - - 2147483647 - - - - - - CURlZmF1bHQ - - 2147483647 - - - - - - CUxlZnQgdG8gUmlnaHQ - - 2147483647 - - - - - - CVJpZ2h0IHRvIExlZnQ - - 2147483647 - - - - - - YES - YES - - - 2147483647 - - - - - - YES - Selection - - 2147483647 - - - - - - CURlZmF1bHQ - - 2147483647 - - - - - - CUxlZnQgdG8gUmlnaHQ - - 2147483647 - - - - - - CVJpZ2h0IHRvIExlZnQ - - 2147483647 - - - - - - - - - YES - YES - - - 2147483647 - - - - - - Show Ruler - - 2147483647 - - - - - - Copy Ruler - c - 1310720 - 2147483647 - - - - - - Paste Ruler - v - 1310720 - 2147483647 - - - - - - - - - - - - View - - 1048576 - 2147483647 - - - submenuAction: - - - View - - - - Show Toolbar - t - 1572864 - 2147483647 - - - - - - Customize Toolbar… - - 1048576 - 2147483647 - - - - - - - - - Window - - 1048576 - 2147483647 - - - submenuAction: - - - Window - - - - Minimize - m - 1048576 - 2147483647 - - - - - - Zoom - - 1048576 - 2147483647 - - - - - - YES - YES - - - 1048576 - 2147483647 - - - - - - Bring All to Front - - 1048576 - 2147483647 - - - - - _NSWindowsMenu - - - - - Help - - 2147483647 - - - submenuAction: - - - Help - - - - ZeroTier One Help - ? - 1048576 - 2147483647 - - - - - _NSHelpMenu - - - - _NSMainMenu - - - - - - - terminate: - - - - 449 - - - - orderFrontStandardAboutPanel: - - - - 142 - - - - delegate - - - - 547 - - - - performMiniaturize: - - - - 37 - - - - arrangeInFront: - - - - 39 - - - - print: - - - - 86 - - - - runPageLayout: - - - - 87 - - - - clearRecentDocuments: - - - - 127 - - - - performClose: - - - - 193 - - - - toggleContinuousSpellChecking: - - - - 222 - - - - undo: - - - - 223 - - - - copy: - - - - 224 - - - - checkSpelling: - - - - 225 - - - - paste: - - - - 226 - - - - stopSpeaking: - - - - 227 - - - - cut: - - - - 228 - - - - showGuessPanel: - - - - 230 - - - - redo: - - - - 231 - - - - selectAll: - - - - 232 - - - - startSpeaking: - - - - 233 - - - - delete: - - - - 235 - - - - performZoom: - - - - 240 - - - - performFindPanelAction: - - - - 241 - - - - centerSelectionInVisibleArea: - - - - 245 - - - - toggleGrammarChecking: - - - - 347 - - - - toggleSmartInsertDelete: - - - - 355 - - - - toggleAutomaticQuoteSubstitution: - - - - 356 - - - - toggleAutomaticLinkDetection: - - - - 357 - - - - saveDocument: - - - - 362 - - - - revertDocumentToSaved: - - - - 364 - - - - runToolbarCustomizationPalette: - - - - 365 - - - - toggleToolbarShown: - - - - 366 - - - - hide: - - - - 367 - - - - hideOtherApplications: - - - - 368 - - - - unhideAllApplications: - - - - 370 - - - - newDocument: - - - - 373 - - - - openDocument: - - - - 374 - - - - raiseBaseline: - - - - 426 - - - - lowerBaseline: - - - - 427 - - - - copyFont: - - - - 428 - - - - subscript: - - - - 429 - - - - superscript: - - - - 430 - - - - tightenKerning: - - - - 431 - - - - underline: - - - - 432 - - - - orderFrontColorPanel: - - - - 433 - - - - useAllLigatures: - - - - 434 - - - - loosenKerning: - - - - 435 - - - - pasteFont: - - - - 436 - - - - unscript: - - - - 437 - - - - useStandardKerning: - - - - 438 - - - - useStandardLigatures: - - - - 439 - - - - turnOffLigatures: - - - - 440 - - - - turnOffKerning: - - - - 441 - - - - toggleAutomaticSpellingCorrection: - - - - 456 - - - - orderFrontSubstitutionsPanel: - - - - 458 - - - - toggleAutomaticDashSubstitution: - - - - 461 - - - - toggleAutomaticTextReplacement: - - - - 463 - - - - uppercaseWord: - - - - 464 - - - - capitalizeWord: - - - - 467 - - - - lowercaseWord: - - - - 468 - - - - pasteAsPlainText: - - - - 486 - - - - performFindPanelAction: - - - - 487 - - - - performFindPanelAction: - - - - 488 - - - - performFindPanelAction: - - - - 489 - - - - showHelp: - - - - 493 - - - - alignCenter: - - - - 518 - - - - pasteRuler: - - - - 519 - - - - toggleRuler: - - - - 520 - - - - alignRight: - - - - 521 - - - - copyRuler: - - - - 522 - - - - alignJustified: - - - - 523 - - - - alignLeft: - - - - 524 - - - - makeBaseWritingDirectionNatural: - - - - 525 - - - - makeBaseWritingDirectionLeftToRight: - - - - 526 - - - - makeBaseWritingDirectionRightToLeft: - - - - 527 - - - - makeTextWritingDirectionNatural: - - - - 528 - - - - makeTextWritingDirectionLeftToRight: - - - - 529 - - - - makeTextWritingDirectionRightToLeft: - - - - 530 - - - - performFindPanelAction: - - - - 535 - - - - delegate - - - - 545 - - - - - - 0 - - - - - - -2 - - - File's Owner - - - -1 - - - First Responder - - - -3 - - - Application - - - 29 - - - - - - - - - - - - - - 19 - - - - - - - - 56 - - - - - - - - 217 - - - - - - - - 83 - - - - - - - - 81 - - - - - - - - - - - - - - - - - 75 - - - - - 78 - - - - - 72 - - - - - 82 - - - - - 124 - - - - - - - - 77 - - - - - 73 - - - - - 79 - - - - - 112 - - - - - 74 - - - - - 125 - - - - - - - - 126 - - - - - 205 - - - - - - - - - - - - - - - - - - - - - - 202 - - - - - 198 - - - - - 207 - - - - - 214 - - - - - 199 - - - - - 203 - - - - - 197 - - - - - 206 - - - - - 215 - - - - - 218 - - - - - - - - 216 - - - - - - - - 200 - - - - - - - - - - - - - 219 - - - - - 201 - - - - - 204 - - - - - 220 - - - - - - - - - - - - - 213 - - - - - 210 - - - - - 221 - - - - - 208 - - - - - 209 - - - - - 57 - - - - - - - - - - - - - - - - - - 58 - - - - - 134 - - - - - 150 - - - - - 136 - - - - - 144 - - - - - 129 - - - - - 143 - - - - - 236 - - - - - 131 - - - - - - - - 149 - - - - - 145 - - - - - 130 - - - - - 24 - - - - - - - - - - - 92 - - - - - 5 - - - - - 239 - - - - - 23 - - - - - 295 - - - - - - - - 296 - - - - - - - - - 297 - - - - - 298 - - - - - 211 - - - - - - - - 212 - - - - - - - - - 195 - - - - - 196 - - - - - 346 - - - - - 348 - - - - - - - - 349 - - - - - - - - - - - - - - 350 - - - - - 351 - - - - - 354 - - - - - 375 - - - - - - - - 376 - - - - - - - - - 377 - - - - - - - - 388 - - - - - - - - - - - - - - - - - - - - - - - 389 - - - - - 390 - - - - - 391 - - - - - 392 - - - - - 393 - - - - - 394 - - - - - 395 - - - - - 396 - - - - - 397 - - - - - - - - 398 - - - - - - - - 399 - - - - - - - - 400 - - - - - 401 - - - - - 402 - - - - - 403 - - - - - 404 - - - - - 405 - - - - - - - - - - - - 406 - - - - - 407 - - - - - 408 - - - - - 409 - - - - - 410 - - - - - 411 - - - - - - - - - - 412 - - - - - 413 - - - - - 414 - - - - - 415 - - - - - - - - - - - 416 - - - - - 417 - - - - - 418 - - - - - 419 - - - - - 450 - - - - - - - - 451 - - - - - - - - - - 452 - - - - - 453 - - - - - 454 - - - - - 457 - - - - - 459 - - - - - 460 - - - - - 462 - - - - - 465 - - - - - 466 - - - - - 485 - - - - - 490 - - - - - - - - 491 - - - - - - - - 492 - - - - - 496 - - - - - - - - 497 - - - - - - - - - - - - - - - - - 498 - - - - - 499 - - - - - 500 - - - - - 501 - - - - - 502 - - - - - 503 - - - - - - - - 504 - - - - - 505 - - - - - 506 - - - - - 507 - - - - - 508 - - - - - - - - - - - - - - - - 509 - - - - - 510 - - - - - 511 - - - - - 512 - - - - - 513 - - - - - 514 - - - - - 515 - - - - - 516 - - - - - 517 - - - - - 534 - - - - - 546 - - - - - - - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - - - - - - 547 - - - - - AppDelegate - NSObject - - IBProjectSource - ../MacGap/AppDelegate.h - - - - - - NSApplication - NSResponder - - IBFrameworkSource - AppKit.framework/Headers/NSApplication.h - - - - NSBrowser - NSControl - - IBFrameworkSource - AppKit.framework/Headers/NSBrowser.h - - - - NSControl - NSView - - IBFrameworkSource - AppKit.framework/Headers/NSControl.h - - - - NSDocument - NSObject - - id - id - id - id - id - id - - - - printDocument: - id - - - revertDocumentToSaved: - id - - - runPageLayout: - id - - - saveDocument: - id - - - saveDocumentAs: - id - - - saveDocumentTo: - id - - - - IBFrameworkSource - AppKit.framework/Headers/NSDocument.h - - - - NSDocumentController - NSObject - - id - id - id - id - - - - clearRecentDocuments: - id - - - newDocument: - id - - - openDocument: - id - - - saveAllDocuments: - id - - - - IBFrameworkSource - AppKit.framework/Headers/NSDocumentController.h - - - - NSFormatter - NSObject - - IBFrameworkSource - Foundation.framework/Headers/NSFormatter.h - - - - NSMatrix - NSControl - - IBFrameworkSource - AppKit.framework/Headers/NSMatrix.h - - - - NSMenu - NSObject - - IBFrameworkSource - AppKit.framework/Headers/NSMenu.h - - - - NSMenuItem - NSObject - - IBFrameworkSource - AppKit.framework/Headers/NSMenuItem.h - - - - NSMovieView - NSView - - IBFrameworkSource - AppKit.framework/Headers/NSMovieView.h - - - - NSPopover - NSResponder - - IBFrameworkSource - AppKit.framework/Headers/NSPopover.h - - - - NSResponder - NSObject - - IBFrameworkSource - AppKit.framework/Headers/NSResponder.h - - - - NSTableView - NSControl - - IBFrameworkSource - AppKit.framework/Headers/NSTableView.h - - - - NSText - NSView - - IBFrameworkSource - AppKit.framework/Headers/NSText.h - - - - NSTextView - NSText - - IBFrameworkSource - AppKit.framework/Headers/NSTextView.h - - - - NSView - NSResponder - - IBFrameworkSource - AppKit.framework/Headers/NSView.h - - - - NSViewController - NSResponder - - view - NSView - - - view - - view - NSView - - - - IBFrameworkSource - AppKit.framework/Headers/NSViewController.h - - - - NSWindow - NSResponder - - IBFrameworkSource - AppKit.framework/Headers/NSWindow.h - - - - WebView - NSView - - id - id - id - id - id - id - id - id - id - id - id - - - - goBack: - id - - - goForward: - id - - - makeTextLarger: - id - - - makeTextSmaller: - id - - - makeTextStandardSize: - id - - - reload: - id - - - reloadFromOrigin: - id - - - stopLoading: - id - - - takeStringURLFrom: - id - - - toggleContinuousSpellChecking: - id - - - toggleSmartInsertDelete: - id - - - - IBFrameworkSource - WebKit.framework/Headers/WebView.h - - - - - 0 - IBCocoaFramework - NO - - com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 - - - YES - 3 - - {12, 12} - {10, 2} - - - diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/en.lproj/Window.xib b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/en.lproj/Window.xib deleted file mode 100644 index fa70aca..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/en.lproj/Window.xib +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/main.m b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/main.m deleted file mode 100644 index 4ad50ad..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/main.m +++ /dev/null @@ -1,14 +0,0 @@ -// -// main.m -// MacGap -// -// Created by Alex MacCaw on 08/01/2012. -// Copyright (c) 2012 Twitter. All rights reserved. -// - -#import - -int main(int argc, char *argv[]) -{ - return NSApplicationMain(argc, (const char **)argv); -} diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/README.md b/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/README.md deleted file mode 100644 index daf3eae..0000000 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/README.md +++ /dev/null @@ -1,6 +0,0 @@ -Mac Web UI Wrapper -====== - -This is a modified version of MacGap1 which launches a WebKit view and accesses the local ZeroTier service at its web URL. It builds the URL from the authtoken.secret file in the system home (or the user home) and the zerotier-one.port file that ZeroTier creates to advertise its control port. - -It's based on the original MacGap1 source by Twitter, Inc. which is licensed under the MIT license. diff --git a/ext/installfiles/mac/postinst.sh b/ext/installfiles/mac/postinst.sh index da15f9c..2e4f591 100755 --- a/ext/installfiles/mac/postinst.sh +++ b/ext/installfiles/mac/postinst.sh @@ -22,7 +22,7 @@ if [ "$OSX_RELEASE" = "10.7" ]; then rm -f tap.kext.10_7.tar.gz fi -rm -rf node.log node.log.old root-topology shutdownIfUnreadable autoupdate.log updates.d +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 diff --git a/ext/installfiles/mac/ui/Makefile b/ext/installfiles/mac/ui/Makefile deleted file mode 100644 index 4be0322..0000000 --- a/ext/installfiles/mac/ui/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -all: - mkdir -p build - jsx --target es3 -x jsx . ./build - rm -f ztui.min.js - minify build/*.js >>ztui.min.js - rm -rf build diff --git a/ext/installfiles/mac/ui/README.md b/ext/installfiles/mac/ui/README.md deleted file mode 100644 index bd5eddb..0000000 --- a/ext/installfiles/mac/ui/README.md +++ /dev/null @@ -1,10 +0,0 @@ -ZeroTier HTML5 UI -====== - -This is the new (as of 1.0.3) ZeroTier One UI. It's implemented in HTML5 and React. - -If you make changes to the .jsx files, type 'make'. You will need NodeJS, react-tools, and minify installed and available in your path. - -For this to work, these files must be installed in the 'ui' subfolder of the ZeroTier home path. For development it's nice to symlink this to the 'ui' folder in your working directory. If the 'ui' subfolder is not present, the UI static files will not be served by the embedded web server. - -Packaging for Mac and Windows is accomplished by way of the wrappers in ext/. For Mac this is done with a modified version of MacGap. Windows uses a custom project that embeds a web view. diff --git a/ext/installfiles/mac/ui/ZeroTierNetwork.jsx b/ext/installfiles/mac/ui/ZeroTierNetwork.jsx deleted file mode 100644 index f842d75..0000000 --- a/ext/installfiles/mac/ui/ZeroTierNetwork.jsx +++ /dev/null @@ -1,74 +0,0 @@ -var ZeroTierNetwork = React.createClass({ - getInitialState: function() { - return {}; - }, - - leaveNetwork: function(event) { - Ajax.call({ - url: 'network/'+this.props.nwid+'?auth='+this.props.authToken, - cache: false, - type: 'DELETE', - success: function(data) { - if (this.props.onNetworkDeleted) - this.props.onNetworkDeleted(this.props.nwid); - }.bind(this), - error: function(error) { - }.bind(this) - }); - event.preventDefault(); - }, - - render: function() { - return ( -
-
- {this.props.nwid}  - {this.props.name} -
-
-
-
Status
-
{this.props['status']}
-
-
-
Type
-
{this.props['type']}
-
-
-
MAC
-
{this.props['mac']}
-
-
-
MTU
-
{this.props['mtu']}
-
-
-
Broadcast
-
{(this.props['broadcastEnabled']) ? 'ENABLED' : 'DISABLED'}
-
-
-
Bridging
-
{(this.props['bridge']) ? 'ACTIVE' : 'DISABLED'}
-
-
-
Device
-
{(this.props['portDeviceName']) ? this.props['portDeviceName'] : '(none)'}
-
-
-
Managed IPs
-
- { - this.props['assignedAddresses'].map(function(ipAssignment) { - return ( -
{ipAssignment}
- ); - }) - } -
-
-
- -
- ); - } -}); diff --git a/ext/installfiles/mac/ui/ZeroTierNode.jsx b/ext/installfiles/mac/ui/ZeroTierNode.jsx deleted file mode 100644 index b4c2922..0000000 --- a/ext/installfiles/mac/ui/ZeroTierNode.jsx +++ /dev/null @@ -1,158 +0,0 @@ -var ZeroTierNode = React.createClass({ - getInitialState: function() { - return { - address: '----------', - online: false, - version: '_._._', - _networks: [], - _peers: [] - }; - }, - - ago: function(ms) { - if (ms > 0) { - var tmp = Math.round((Date.now() - ms) / 1000); - return ((tmp > 0) ? tmp : 0); - } else return 0; - }, - - updatePeers: function() { - Ajax.call({ - url: 'peer?auth='+this.props.authToken, - cache: false, - type: 'GET', - success: function(data) { - if (data) { - var pl = JSON.parse(data); - if (Array.isArray(pl)) { - this.setState({_peers: pl}); - } - } - }.bind(this), - error: function() { - }.bind(this) - }); - }, - updateNetworks: function() { - Ajax.call({ - url: 'network?auth='+this.props.authToken, - cache: false, - type: 'GET', - success: function(data) { - if (data) { - var nwl = JSON.parse(data); - if (Array.isArray(nwl)) { - this.setState({_networks: nwl}); - } - } - }.bind(this), - error: function() { - }.bind(this) - }); - }, - updateAll: function() { - Ajax.call({ - url: 'status?auth='+this.props.authToken, - cache: false, - type: 'GET', - success: function(data) { - this.alertedToFailure = false; - if (data) { - var status = JSON.parse(data); - this.setState(status); - document.title = 'ZeroTier One [' + status.address + ']'; - } - this.updateNetworks(); - this.updatePeers(); - }.bind(this), - error: function() { - this.setState(this.getInitialState()); - if (!this.alertedToFailure) { - this.alertedToFailure = true; - alert('Authorization token invalid or ZeroTier One service not running.'); - } - }.bind(this) - }); - }, - joinNetwork: function(event) { - event.preventDefault(); - if ((this.networkToJoin)&&(this.networkToJoin.length === 16)) { - Ajax.call({ - url: 'network/'+this.networkToJoin+'?auth='+this.props.authToken, - cache: false, - type: 'POST', - success: function(data) { - this.networkToJoin = ''; - if (this.networkInputElement) - this.networkInputElement.value = ''; - this.updateNetworks(); - }.bind(this), - error: function() { - }.bind(this) - }); - } else { - alert('To join a network, enter its 16-digit network ID.'); - } - }, - handleNetworkIdEntry: function(event) { - this.networkInputElement = event.target; - var nid = this.networkInputElement.value; - if (nid) { - nid = nid.toLowerCase(); - var nnid = ''; - for(var i=0;((i= 0) - nnid += nid.charAt(i); - } - this.networkToJoin = nnid; - this.networkInputElement.value = nnid; - } else { - this.networkToJoin = ''; - this.networkInputElement.value = ''; - } - }, - - handleNetworkDelete: function(nwid) { - var networks = []; - for(var i=0;i -
-
-
- { - this.state._networks.map(function(network) { - network['authToken'] = this.props.authToken; - network['onNetworkDeleted'] = this.handleNetworkDelete; - return React.createElement('div',{className: 'network',key: network.nwid},React.createElement(ZeroTierNetwork,network)); - }.bind(this)) - } -
-
-
-
-
- {this.state.address}  {this.state.online ? (this.state.tcpFallbackActive ? 'TUNNELED' : 'ONLINE') : 'OFFLINE'}  {this.state.version} -
-
-
-
-
- - ); - } -}); diff --git a/ext/installfiles/mac/ui/index.html b/ext/installfiles/mac/ui/index.html deleted file mode 100644 index 44edb39..0000000 --- a/ext/installfiles/mac/ui/index.html +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - ZeroTier One - - - - - - -
- - - diff --git a/ext/installfiles/mac/ui/main.js b/ext/installfiles/mac/ui/main.js deleted file mode 100644 index a164712..0000000 --- a/ext/installfiles/mac/ui/main.js +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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/ - */ - -function getUrlParameter(parameter) -{ - var currLocation = window.location.search; - if (currLocation.indexOf('?') < 0) - return ''; - var parArr = currLocation.split("?")[1].split("&"); - for(var i = 0; i < parArr.length; i++){ - parr = parArr[i].split("="); - if (parr[0] == parameter) { - return decodeURIComponent(parr[1]); - } - } - return ''; -} - -var ztAuthToken = getUrlParameter('authToken'); -if ((!ztAuthToken)||(ztAuthToken.length <= 0)) { - ztAuthToken = prompt('No authToken specified in URL. Enter token from\nauthtoken.secret to authorize.'); -} - -React.render( - React.createElement(ZeroTierNode, {authToken: ztAuthToken}), - document.getElementById('main') -); diff --git a/ext/installfiles/mac/ui/react.min.js b/ext/installfiles/mac/ui/react.min.js deleted file mode 100644 index 9040c97..0000000 --- a/ext/installfiles/mac/ui/react.min.js +++ /dev/null @@ -1,15 +0,0 @@ -/** - * React v0.13.2 - * - * Copyright 2013-2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ -!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var t;t="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,t.React=e()}}(function(){return function e(t,n,r){function o(a,u){if(!n[a]){if(!t[a]){var s="function"==typeof require&&require;if(!u&&s)return s(a,!0);if(i)return i(a,!0);var l=new Error("Cannot find module '"+a+"'");throw l.code="MODULE_NOT_FOUND",l}var c=n[a]={exports:{}};t[a][0].call(c.exports,function(e){var n=t[a][1][e];return o(n?n:e)},c,c.exports,e,t,n,r)}return n[a].exports}for(var i="function"==typeof require&&require,a=0;a8&&11>=x),N=32,I=String.fromCharCode(N),T=f.topLevelTypes,R={beforeInput:{phasedRegistrationNames:{bubbled:C({onBeforeInput:null}),captured:C({onBeforeInputCapture:null})},dependencies:[T.topCompositionEnd,T.topKeyPress,T.topTextInput,T.topPaste]},compositionEnd:{phasedRegistrationNames:{bubbled:C({onCompositionEnd:null}),captured:C({onCompositionEndCapture:null})},dependencies:[T.topBlur,T.topCompositionEnd,T.topKeyDown,T.topKeyPress,T.topKeyUp,T.topMouseDown]},compositionStart:{phasedRegistrationNames:{bubbled:C({onCompositionStart:null}),captured:C({onCompositionStartCapture:null})},dependencies:[T.topBlur,T.topCompositionStart,T.topKeyDown,T.topKeyPress,T.topKeyUp,T.topMouseDown]},compositionUpdate:{phasedRegistrationNames:{bubbled:C({onCompositionUpdate:null}),captured:C({onCompositionUpdateCapture:null})},dependencies:[T.topBlur,T.topCompositionUpdate,T.topKeyDown,T.topKeyPress,T.topKeyUp,T.topMouseDown]}},P=!1,w=null,O={eventTypes:R,extractEvents:function(e,t,n,r){return[l(e,t,n,r),d(e,t,n,r)]}};t.exports=O},{139:139,15:15,20:20,21:21,22:22,91:91,95:95}],4:[function(e,t,n){"use strict";function r(e,t){return e+t.charAt(0).toUpperCase()+t.substring(1)}var o={boxFlex:!0,boxFlexGroup:!0,columnCount:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,strokeDashoffset:!0,strokeOpacity:!0,strokeWidth:!0},i=["Webkit","ms","Moz","O"];Object.keys(o).forEach(function(e){i.forEach(function(t){o[r(t,e)]=o[e]})});var a={background:{backgroundImage:!0,backgroundPosition:!0,backgroundRepeat:!0,backgroundColor:!0},border:{borderWidth:!0,borderStyle:!0,borderColor:!0},borderBottom:{borderBottomWidth:!0,borderBottomStyle:!0,borderBottomColor:!0},borderLeft:{borderLeftWidth:!0,borderLeftStyle:!0,borderLeftColor:!0},borderRight:{borderRightWidth:!0,borderRightStyle:!0,borderRightColor:!0},borderTop:{borderTopWidth:!0,borderTopStyle:!0,borderTopColor:!0},font:{fontStyle:!0,fontVariant:!0,fontWeight:!0,fontSize:!0,lineHeight:!0,fontFamily:!0}},u={isUnitlessNumber:o,shorthandPropertyExpansions:a};t.exports=u},{}],5:[function(e,t,n){"use strict";var r=e(4),o=e(21),i=(e(106),e(111)),a=e(131),u=e(141),s=(e(150),u(function(e){return a(e)})),l="cssFloat";o.canUseDOM&&void 0===document.documentElement.style.cssFloat&&(l="styleFloat");var c={createMarkupForStyles:function(e){var t="";for(var n in e)if(e.hasOwnProperty(n)){var r=e[n];null!=r&&(t+=s(n)+":",t+=i(n,r)+";")}return t||null},setValueForStyles:function(e,t){var n=e.style;for(var o in t)if(t.hasOwnProperty(o)){var a=i(o,t[o]);if("float"===o&&(o=l),a)n[o]=a;else{var u=r.shorthandPropertyExpansions[o];if(u)for(var s in u)n[s]="";else n[o]=""}}}};t.exports=c},{106:106,111:111,131:131,141:141,150:150,21:21,4:4}],6:[function(e,t,n){"use strict";function r(){this._callbacks=null,this._contexts=null}var o=e(28),i=e(27),a=e(133);i(r.prototype,{enqueue:function(e,t){this._callbacks=this._callbacks||[],this._contexts=this._contexts||[],this._callbacks.push(e),this._contexts.push(t)},notifyAll:function(){var e=this._callbacks,t=this._contexts;if(e){a(e.length===t.length),this._callbacks=null,this._contexts=null;for(var n=0,r=e.length;r>n;n++)e[n].call(t[n]);e.length=0,t.length=0}},reset:function(){this._callbacks=null,this._contexts=null},destructor:function(){this.reset()}}),o.addPoolingTo(r),t.exports=r},{133:133,27:27,28:28}],7:[function(e,t,n){"use strict";function r(e){return"SELECT"===e.nodeName||"INPUT"===e.nodeName&&"file"===e.type}function o(e){var t=x.getPooled(T.change,P,e);E.accumulateTwoPhaseDispatches(t),_.batchedUpdates(i,t)}function i(e){C.enqueueEvents(e),C.processEventQueue()}function a(e,t){R=e,P=t,R.attachEvent("onchange",o)}function u(){R&&(R.detachEvent("onchange",o),R=null,P=null)}function s(e,t,n){return e===I.topChange?n:void 0}function l(e,t,n){e===I.topFocus?(u(),a(t,n)):e===I.topBlur&&u()}function c(e,t){R=e,P=t,w=e.value,O=Object.getOwnPropertyDescriptor(e.constructor.prototype,"value"),Object.defineProperty(R,"value",k),R.attachEvent("onpropertychange",d)}function p(){R&&(delete R.value,R.detachEvent("onpropertychange",d),R=null,P=null,w=null,O=null)}function d(e){if("value"===e.propertyName){var t=e.srcElement.value;t!==w&&(w=t,o(e))}}function f(e,t,n){return e===I.topInput?n:void 0}function h(e,t,n){e===I.topFocus?(p(),c(t,n)):e===I.topBlur&&p()}function m(e,t,n){return e!==I.topSelectionChange&&e!==I.topKeyUp&&e!==I.topKeyDown||!R||R.value===w?void 0:(w=R.value,P)}function v(e){return"INPUT"===e.nodeName&&("checkbox"===e.type||"radio"===e.type)}function g(e,t,n){return e===I.topClick?n:void 0}var y=e(15),C=e(17),E=e(20),b=e(21),_=e(85),x=e(93),D=e(134),M=e(136),N=e(139),I=y.topLevelTypes,T={change:{phasedRegistrationNames:{bubbled:N({onChange:null}),captured:N({onChangeCapture:null})},dependencies:[I.topBlur,I.topChange,I.topClick,I.topFocus,I.topInput,I.topKeyDown,I.topKeyUp,I.topSelectionChange]}},R=null,P=null,w=null,O=null,S=!1;b.canUseDOM&&(S=D("change")&&(!("documentMode"in document)||document.documentMode>8));var A=!1;b.canUseDOM&&(A=D("input")&&(!("documentMode"in document)||document.documentMode>9));var k={get:function(){return O.get.call(this)},set:function(e){w=""+e,O.set.call(this,e)}},L={eventTypes:T,extractEvents:function(e,t,n,o){var i,a;if(r(t)?S?i=s:a=l:M(t)?A?i=f:(i=m,a=h):v(t)&&(i=g),i){var u=i(e,t,n);if(u){var c=x.getPooled(T.change,u,o);return E.accumulateTwoPhaseDispatches(c),c}}a&&a(e,t,n)}};t.exports=L},{134:134,136:136,139:139,15:15,17:17,20:20,21:21,85:85,93:93}],8:[function(e,t,n){"use strict";var r=0,o={createReactRootIndex:function(){return r++}};t.exports=o},{}],9:[function(e,t,n){"use strict";function r(e,t,n){e.insertBefore(t,e.childNodes[n]||null)}var o=e(12),i=e(70),a=e(145),u=e(133),s={dangerouslyReplaceNodeWithMarkup:o.dangerouslyReplaceNodeWithMarkup,updateTextContent:a,processUpdates:function(e,t){for(var n,s=null,l=null,c=0;ct||o.hasOverloadedBooleanValue[e]&&t===!1}var o=e(10),i=e(143),a=(e(150),{createMarkupForID:function(e){return o.ID_ATTRIBUTE_NAME+"="+i(e)},createMarkupForProperty:function(e,t){if(o.isStandardName.hasOwnProperty(e)&&o.isStandardName[e]){if(r(e,t))return"";var n=o.getAttributeName[e];return o.hasBooleanValue[e]||o.hasOverloadedBooleanValue[e]&&t===!0?n:n+"="+i(t)}return o.isCustomAttribute(e)?null==t?"":e+"="+i(t):null},setValueForProperty:function(e,t,n){if(o.isStandardName.hasOwnProperty(t)&&o.isStandardName[t]){var i=o.getMutationMethod[t];if(i)i(e,n);else if(r(t,n))this.deleteValueForProperty(e,t);else if(o.mustUseAttribute[t])e.setAttribute(o.getAttributeName[t],""+n);else{var a=o.getPropertyName[t];o.hasSideEffects[t]&&""+e[a]==""+n||(e[a]=n)}}else o.isCustomAttribute(t)&&(null==n?e.removeAttribute(t):e.setAttribute(t,""+n))},deleteValueForProperty:function(e,t){if(o.isStandardName.hasOwnProperty(t)&&o.isStandardName[t]){var n=o.getMutationMethod[t];if(n)n(e,void 0);else if(o.mustUseAttribute[t])e.removeAttribute(o.getAttributeName[t]);else{var r=o.getPropertyName[t],i=o.getDefaultValueForProperty(e.nodeName,r);o.hasSideEffects[t]&&""+e[r]===i||(e[r]=i)}}else o.isCustomAttribute(t)&&e.removeAttribute(t)}});t.exports=a},{10:10,143:143,150:150}],12:[function(e,t,n){"use strict";function r(e){return e.substring(1,e.indexOf(" "))}var o=e(21),i=e(110),a=e(112),u=e(125),s=e(133),l=/^(<[^ \/>]+)/,c="data-danger-index",p={dangerouslyRenderMarkup:function(e){s(o.canUseDOM);for(var t,n={},p=0;ps;s++){var c=u[s];if(c){var p=c.extractEvents(e,t,n,o);p&&(a=i(a,p))}}return a},enqueueEvents:function(e){e&&(l=i(l,e))},processEventQueue:function(){var e=l;l=null,a(e,c),u(!l)},__purge:function(){s={}},__getListenerBank:function(){return s}};t.exports=d},{103:103,118:118,133:133,18:18,19:19}],18:[function(e,t,n){"use strict";function r(){if(u)for(var e in s){var t=s[e],n=u.indexOf(e);if(a(n>-1),!l.plugins[n]){a(t.extractEvents),l.plugins[n]=t;var r=t.eventTypes;for(var i in r)a(o(r[i],t,i))}}}function o(e,t,n){a(!l.eventNameDispatchConfigs.hasOwnProperty(n)),l.eventNameDispatchConfigs[n]=e;var r=e.phasedRegistrationNames;if(r){for(var o in r)if(r.hasOwnProperty(o)){var u=r[o];i(u,t,n)}return!0}return e.registrationName?(i(e.registrationName,t,n),!0):!1}function i(e,t,n){a(!l.registrationNameModules[e]),l.registrationNameModules[e]=t,l.registrationNameDependencies[e]=t.eventTypes[n].dependencies}var a=e(133),u=null,s={},l={plugins:[],eventNameDispatchConfigs:{},registrationNameModules:{},registrationNameDependencies:{},injectEventPluginOrder:function(e){a(!u),u=Array.prototype.slice.call(e),r()},injectEventPluginsByName:function(e){var t=!1;for(var n in e)if(e.hasOwnProperty(n)){var o=e[n];s.hasOwnProperty(n)&&s[n]===o||(a(!s[n]),s[n]=o,t=!0)}t&&r()},getPluginModuleForEvent:function(e){var t=e.dispatchConfig;if(t.registrationName)return l.registrationNameModules[t.registrationName]||null;for(var n in t.phasedRegistrationNames)if(t.phasedRegistrationNames.hasOwnProperty(n)){var r=l.registrationNameModules[t.phasedRegistrationNames[n]];if(r)return r}return null},_resetEventPlugins:function(){u=null;for(var e in s)s.hasOwnProperty(e)&&delete s[e];l.plugins.length=0;var t=l.eventNameDispatchConfigs;for(var n in t)t.hasOwnProperty(n)&&delete t[n];var r=l.registrationNameModules;for(var o in r)r.hasOwnProperty(o)&&delete r[o]}};t.exports=l},{133:133}],19:[function(e,t,n){"use strict";function r(e){return e===v.topMouseUp||e===v.topTouchEnd||e===v.topTouchCancel}function o(e){return e===v.topMouseMove||e===v.topTouchMove}function i(e){return e===v.topMouseDown||e===v.topTouchStart}function a(e,t){var n=e._dispatchListeners,r=e._dispatchIDs;if(Array.isArray(n))for(var o=0;oe&&n[e]===o[e];e++);var a=r-e;for(t=1;a>=t&&n[r-t]===o[i-t];t++);var u=t>1?1-t:void 0;return this._fallbackText=o.slice(e,u),this._fallbackText}}),o.addPoolingTo(r),t.exports=r},{128:128,27:27,28:28}],23:[function(e,t,n){"use strict";var r,o=e(10),i=e(21),a=o.injection.MUST_USE_ATTRIBUTE,u=o.injection.MUST_USE_PROPERTY,s=o.injection.HAS_BOOLEAN_VALUE,l=o.injection.HAS_SIDE_EFFECTS,c=o.injection.HAS_NUMERIC_VALUE,p=o.injection.HAS_POSITIVE_NUMERIC_VALUE,d=o.injection.HAS_OVERLOADED_BOOLEAN_VALUE;if(i.canUseDOM){var f=document.implementation;r=f&&f.hasFeature&&f.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1")}var h={isCustomAttribute:RegExp.prototype.test.bind(/^(data|aria)-[a-z_][a-z\d_.\-]*$/),Properties:{accept:null,acceptCharset:null,accessKey:null,action:null,allowFullScreen:a|s,allowTransparency:a,alt:null,async:s,autoComplete:null,autoPlay:s,cellPadding:null,cellSpacing:null,charSet:a,checked:u|s,classID:a,className:r?a:u,cols:a|p,colSpan:null,content:null,contentEditable:null,contextMenu:a,controls:u|s,coords:null,crossOrigin:null,data:null,dateTime:a,defer:s,dir:null,disabled:a|s,download:d,draggable:null,encType:null,form:a,formAction:a,formEncType:a,formMethod:a,formNoValidate:s,formTarget:a,frameBorder:a,headers:null,height:a,hidden:a|s,high:null,href:null,hrefLang:null,htmlFor:null,httpEquiv:null,icon:null,id:u,label:null,lang:null,list:a,loop:u|s,low:null,manifest:a,marginHeight:null,marginWidth:null,max:null,maxLength:a,media:a,mediaGroup:null,method:null,min:null,multiple:u|s,muted:u|s,name:null,noValidate:s,open:s,optimum:null,pattern:null,placeholder:null,poster:null,preload:null,radioGroup:null,readOnly:u|s,rel:null,required:s,role:a,rows:a|p,rowSpan:null,sandbox:null,scope:null,scoped:s,scrolling:null,seamless:a|s,selected:u|s,shape:null,size:a|p,sizes:a,span:p,spellCheck:null,src:null,srcDoc:u,srcSet:a,start:c,step:null,style:null,tabIndex:null,target:null,title:null,type:null,useMap:null,value:u|l,width:a,wmode:a,autoCapitalize:null,autoCorrect:null,itemProp:a,itemScope:a|s,itemType:a,itemID:a,itemRef:a,property:null,unselectable:a},DOMAttributeNames:{acceptCharset:"accept-charset",className:"class",htmlFor:"for",httpEquiv:"http-equiv"},DOMPropertyNames:{autoCapitalize:"autocapitalize",autoComplete:"autocomplete",autoCorrect:"autocorrect",autoFocus:"autofocus",autoPlay:"autoplay",encType:"encoding",hrefLang:"hreflang",radioGroup:"radiogroup",spellCheck:"spellcheck",srcDoc:"srcdoc",srcSet:"srcset"}};t.exports=h},{10:10,21:21}],24:[function(e,t,n){"use strict";function r(e){l(null==e.props.checkedLink||null==e.props.valueLink)}function o(e){r(e),l(null==e.props.value&&null==e.props.onChange)}function i(e){r(e),l(null==e.props.checked&&null==e.props.onChange)}function a(e){this.props.valueLink.requestChange(e.target.value)}function u(e){this.props.checkedLink.requestChange(e.target.checked)}var s=e(76),l=e(133),c={button:!0,checkbox:!0,image:!0,hidden:!0,radio:!0,reset:!0,submit:!0},p={Mixin:{propTypes:{value:function(e,t,n){return!e[t]||c[e.type]||e.onChange||e.readOnly||e.disabled?null:new Error("You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`.")},checked:function(e,t,n){return!e[t]||e.onChange||e.readOnly||e.disabled?null:new Error("You provided a `checked` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultChecked`. Otherwise, set either `onChange` or `readOnly`.")},onChange:s.func}},getValue:function(e){return e.props.valueLink?(o(e),e.props.valueLink.value):e.props.value},getChecked:function(e){return e.props.checkedLink?(i(e),e.props.checkedLink.value):e.props.checked},getOnChange:function(e){return e.props.valueLink?(o(e),a):e.props.checkedLink?(i(e),u):e.props.onChange}};t.exports=p},{133:133,76:76}],25:[function(e,t,n){"use strict";function r(e){e.remove()}var o=e(30),i=e(103),a=e(118),u=e(133),s={trapBubbledEvent:function(e,t){u(this.isMounted());var n=this.getDOMNode();u(n);var r=o.trapBubbledEvent(e,t,n);this._localEventListeners=i(this._localEventListeners,r)},componentWillUnmount:function(){this._localEventListeners&&a(this._localEventListeners,r)}};t.exports=s},{103:103,118:118,133:133,30:30}],26:[function(e,t,n){"use strict";var r=e(15),o=e(112),i=r.topLevelTypes,a={eventTypes:null,extractEvents:function(e,t,n,r){if(e===i.topTouchStart){var a=r.target;a&&!a.onclick&&(a.onclick=o)}}};t.exports=a},{112:112,15:15}],27:[function(e,t,n){"use strict";function r(e,t){if(null==e)throw new TypeError("Object.assign target cannot be null or undefined");for(var n=Object(e),r=Object.prototype.hasOwnProperty,o=1;ol;l++){var d=u[l];i.hasOwnProperty(d)&&i[d]||(d===s.topWheel?c("wheel")?v.ReactEventListener.trapBubbledEvent(s.topWheel,"wheel",n):c("mousewheel")?v.ReactEventListener.trapBubbledEvent(s.topWheel,"mousewheel",n):v.ReactEventListener.trapBubbledEvent(s.topWheel,"DOMMouseScroll",n):d===s.topScroll?c("scroll",!0)?v.ReactEventListener.trapCapturedEvent(s.topScroll,"scroll",n):v.ReactEventListener.trapBubbledEvent(s.topScroll,"scroll",v.ReactEventListener.WINDOW_HANDLE):d===s.topFocus||d===s.topBlur?(c("focus",!0)?(v.ReactEventListener.trapCapturedEvent(s.topFocus,"focus",n),v.ReactEventListener.trapCapturedEvent(s.topBlur,"blur",n)):c("focusin")&&(v.ReactEventListener.trapBubbledEvent(s.topFocus,"focusin",n),v.ReactEventListener.trapBubbledEvent(s.topBlur,"focusout",n)),i[s.topBlur]=!0,i[s.topFocus]=!0):h.hasOwnProperty(d)&&v.ReactEventListener.trapBubbledEvent(d,h[d],n),i[d]=!0)}},trapBubbledEvent:function(e,t,n){ -return v.ReactEventListener.trapBubbledEvent(e,t,n)},trapCapturedEvent:function(e,t,n){return v.ReactEventListener.trapCapturedEvent(e,t,n)},ensureScrollValueMonitoring:function(){if(!d){var e=s.refreshScrollValues;v.ReactEventListener.monitorScrollValue(e),d=!0}},eventNameDispatchConfigs:i.eventNameDispatchConfigs,registrationNameModules:i.registrationNameModules,putListener:i.putListener,getListener:i.getListener,deleteListener:i.deleteListener,deleteAllListeners:i.deleteAllListeners});t.exports=v},{102:102,134:134,15:15,17:17,18:18,27:27,59:59}],31:[function(e,t,n){"use strict";var r=e(79),o=e(116),i=e(132),a=e(147),u={instantiateChildren:function(e,t,n){var r=o(e);for(var a in r)if(r.hasOwnProperty(a)){var u=r[a],s=i(u,null);r[a]=s}return r},updateChildren:function(e,t,n,u){var s=o(t);if(!s&&!e)return null;var l;for(l in s)if(s.hasOwnProperty(l)){var c=e&&e[l],p=c&&c._currentElement,d=s[l];if(a(p,d))r.receiveComponent(c,d,n,u),s[l]=c;else{c&&r.unmountComponent(c,l);var f=i(d,null);s[l]=f}}for(l in e)!e.hasOwnProperty(l)||s&&s.hasOwnProperty(l)||r.unmountComponent(e[l]);return s},unmountChildren:function(e){for(var t in e){var n=e[t];r.unmountComponent(n)}}};t.exports=u},{116:116,132:132,147:147,79:79}],32:[function(e,t,n){"use strict";function r(e,t){this.forEachFunction=e,this.forEachContext=t}function o(e,t,n,r){var o=e;o.forEachFunction.call(o.forEachContext,t,r)}function i(e,t,n){if(null==e)return e;var i=r.getPooled(t,n);f(e,o,i),r.release(i)}function a(e,t,n){this.mapResult=e,this.mapFunction=t,this.mapContext=n}function u(e,t,n,r){var o=e,i=o.mapResult,a=!i.hasOwnProperty(n);if(a){var u=o.mapFunction.call(o.mapContext,t,r);i[n]=u}}function s(e,t,n){if(null==e)return e;var r={},o=a.getPooled(r,t,n);return f(e,u,o),a.release(o),d.create(r)}function l(e,t,n,r){return null}function c(e,t){return f(e,l,null)}var p=e(28),d=e(61),f=e(149),h=(e(150),p.twoArgumentPooler),m=p.threeArgumentPooler;p.addPoolingTo(r,h),p.addPoolingTo(a,m);var v={forEach:i,map:s,count:c};t.exports=v},{149:149,150:150,28:28,61:61}],33:[function(e,t,n){"use strict";function r(e,t){var n=D.hasOwnProperty(t)?D[t]:null;N.hasOwnProperty(t)&&y(n===_.OVERRIDE_BASE),e.hasOwnProperty(t)&&y(n===_.DEFINE_MANY||n===_.DEFINE_MANY_MERGED)}function o(e,t){if(t){y("function"!=typeof t),y(!d.isValidElement(t));var n=e.prototype;t.hasOwnProperty(b)&&M.mixins(e,t.mixins);for(var o in t)if(t.hasOwnProperty(o)&&o!==b){var i=t[o];if(r(n,o),M.hasOwnProperty(o))M[o](e,i);else{var a=D.hasOwnProperty(o),l=n.hasOwnProperty(o),c=i&&i.__reactDontBind,p="function"==typeof i,f=p&&!a&&!l&&!c;if(f)n.__reactAutoBindMap||(n.__reactAutoBindMap={}),n.__reactAutoBindMap[o]=i,n[o]=i;else if(l){var h=D[o];y(a&&(h===_.DEFINE_MANY_MERGED||h===_.DEFINE_MANY)),h===_.DEFINE_MANY_MERGED?n[o]=u(n[o],i):h===_.DEFINE_MANY&&(n[o]=s(n[o],i))}else n[o]=i}}}}function i(e,t){if(t)for(var n in t){var r=t[n];if(t.hasOwnProperty(n)){var o=n in M;y(!o);var i=n in e;y(!i),e[n]=r}}}function a(e,t){y(e&&t&&"object"==typeof e&&"object"==typeof t);for(var n in t)t.hasOwnProperty(n)&&(y(void 0===e[n]),e[n]=t[n]);return e}function u(e,t){return function(){var n=e.apply(this,arguments),r=t.apply(this,arguments);if(null==n)return r;if(null==r)return n;var o={};return a(o,n),a(o,r),o}}function s(e,t){return function(){e.apply(this,arguments),t.apply(this,arguments)}}function l(e,t){var n=t.bind(e);return n}function c(e){for(var t in e.__reactAutoBindMap)if(e.__reactAutoBindMap.hasOwnProperty(t)){var n=e.__reactAutoBindMap[t];e[t]=l(e,f.guard(n,e.constructor.displayName+"."+t))}}var p=e(34),d=(e(39),e(55)),f=e(58),h=e(65),m=e(66),v=(e(75),e(74),e(84)),g=e(27),y=e(133),C=e(138),E=e(139),b=(e(150),E({mixins:null})),_=C({DEFINE_ONCE:null,DEFINE_MANY:null,OVERRIDE_BASE:null,DEFINE_MANY_MERGED:null}),x=[],D={mixins:_.DEFINE_MANY,statics:_.DEFINE_MANY,propTypes:_.DEFINE_MANY,contextTypes:_.DEFINE_MANY,childContextTypes:_.DEFINE_MANY,getDefaultProps:_.DEFINE_MANY_MERGED,getInitialState:_.DEFINE_MANY_MERGED,getChildContext:_.DEFINE_MANY_MERGED,render:_.DEFINE_ONCE,componentWillMount:_.DEFINE_MANY,componentDidMount:_.DEFINE_MANY,componentWillReceiveProps:_.DEFINE_MANY,shouldComponentUpdate:_.DEFINE_ONCE,componentWillUpdate:_.DEFINE_MANY,componentDidUpdate:_.DEFINE_MANY,componentWillUnmount:_.DEFINE_MANY,updateComponent:_.OVERRIDE_BASE},M={displayName:function(e,t){e.displayName=t},mixins:function(e,t){if(t)for(var n=0;n";return this._createOpenTagMarkupAndPutListeners(t)+this._createContentMarkup(t,n)+o},_createOpenTagMarkupAndPutListeners:function(e){var t=this._currentElement.props,n="<"+this._tag;for(var r in t)if(t.hasOwnProperty(r)){var i=t[r];if(null!=i)if(b.hasOwnProperty(r))o(this._rootNodeID,r,i,e);else{r===x&&(i&&(i=this._previousStyleCopy=m({},t.style)),i=u.createMarkupForStyles(i));var a=l.createMarkupForProperty(r,i);a&&(n+=" "+a)}}if(e.renderToStaticMarkup)return n+">";var s=l.createMarkupForID(this._rootNodeID);return n+" "+s+">"},_createContentMarkup:function(e,t){var n="";("listing"===this._tag||"pre"===this._tag||"textarea"===this._tag)&&(n="\n");var r=this._currentElement.props,o=r.dangerouslySetInnerHTML;if(null!=o){if(null!=o.__html)return n+o.__html}else{var i=_[typeof r.children]?r.children:null,a=null!=i?null:r.children;if(null!=i)return n+v(i);if(null!=a){var u=this.mountChildren(a,e,t);return n+u.join("")}}return n},receiveComponent:function(e,t,n){var r=this._currentElement;this._currentElement=e,this.updateComponent(t,r,e,n)},updateComponent:function(e,t,n,o){r(this._currentElement.props),this._updateDOMProperties(t.props,e),this._updateDOMChildren(t.props,e,o)},_updateDOMProperties:function(e,t){var n,r,i,a=this._currentElement.props;for(n in e)if(!a.hasOwnProperty(n)&&e.hasOwnProperty(n))if(n===x){var u=this._previousStyleCopy;for(r in u)u.hasOwnProperty(r)&&(i=i||{},i[r]="");this._previousStyleCopy=null}else b.hasOwnProperty(n)?C(this._rootNodeID,n):(s.isStandardName[n]||s.isCustomAttribute(n))&&M.deletePropertyByID(this._rootNodeID,n);for(n in a){var l=a[n],c=n===x?this._previousStyleCopy:e[n];if(a.hasOwnProperty(n)&&l!==c)if(n===x)if(l?l=this._previousStyleCopy=m({},l):this._previousStyleCopy=null,c){for(r in c)!c.hasOwnProperty(r)||l&&l.hasOwnProperty(r)||(i=i||{},i[r]="");for(r in l)l.hasOwnProperty(r)&&c[r]!==l[r]&&(i=i||{},i[r]=l[r])}else i=l;else b.hasOwnProperty(n)?o(this._rootNodeID,n,l,t):(s.isStandardName[n]||s.isCustomAttribute(n))&&M.updatePropertyByID(this._rootNodeID,n,l)}i&&M.updateStylesByID(this._rootNodeID,i)},_updateDOMChildren:function(e,t,n){var r=this._currentElement.props,o=_[typeof e.children]?e.children:null,i=_[typeof r.children]?r.children:null,a=e.dangerouslySetInnerHTML&&e.dangerouslySetInnerHTML.__html,u=r.dangerouslySetInnerHTML&&r.dangerouslySetInnerHTML.__html,s=null!=o?null:e.children,l=null!=i?null:r.children,c=null!=o||null!=a,p=null!=i||null!=u;null!=s&&null==l?this.updateChildren(null,t,n):c&&!p&&this.updateTextContent(""),null!=i?o!==i&&this.updateTextContent(""+i):null!=u?a!==u&&M.updateInnerHTMLByID(this._rootNodeID,u):null!=l&&this.updateChildren(l,t,n)},unmountComponent:function(){this.unmountChildren(),c.deleteAllListeners(this._rootNodeID),p.unmountIDFromEnvironment(this._rootNodeID),this._rootNodeID=null}},h.measureMethods(a,"ReactDOMComponent",{mountComponent:"mountComponent",updateComponent:"updateComponent"}),m(a.prototype,a.Mixin,f.Mixin),a.injection={injectIDOperations:function(e){a.BackendIDOperations=M=e}},t.exports=a},{10:10,11:11,114:114,133:133,134:134,139:139,150:150,27:27,30:30,35:35,5:5,68:68,69:69,73:73}],43:[function(e,t,n){"use strict";var r=e(15),o=e(25),i=e(29),a=e(33),u=e(55),s=u.createFactory("form"),l=a.createClass({displayName:"ReactDOMForm",tagName:"FORM",mixins:[i,o],render:function(){return s(this.props)},componentDidMount:function(){this.trapBubbledEvent(r.topLevelTypes.topReset,"reset"),this.trapBubbledEvent(r.topLevelTypes.topSubmit,"submit")}});t.exports=l},{15:15,25:25,29:29,33:33,55:55}],44:[function(e,t,n){"use strict";var r=e(5),o=e(9),i=e(11),a=e(68),u=e(73),s=e(133),l=e(144),c={dangerouslySetInnerHTML:"`dangerouslySetInnerHTML` must be set using `updateInnerHTMLByID()`.",style:"`style` must be set using `updateStylesByID()`."},p={updatePropertyByID:function(e,t,n){var r=a.getNode(e);s(!c.hasOwnProperty(t)),null!=n?i.setValueForProperty(r,t,n):i.deleteValueForProperty(r,t)},deletePropertyByID:function(e,t,n){var r=a.getNode(e);s(!c.hasOwnProperty(t)),i.deleteValueForProperty(r,t,n)},updateStylesByID:function(e,t){var n=a.getNode(e);r.setValueForStyles(n,t)},updateInnerHTMLByID:function(e,t){var n=a.getNode(e);l(n,t)},updateTextContentByID:function(e,t){var n=a.getNode(e);o.updateTextContent(n,t)},dangerouslyReplaceNodeWithMarkupByID:function(e,t){var n=a.getNode(e);o.dangerouslyReplaceNodeWithMarkup(n,t)},dangerouslyProcessChildrenUpdates:function(e,t){for(var n=0;nl;l++){var h=s[l];if(h!==i&&h.form===i.form){var v=c.getID(h);f(v);var g=m[v];f(g),p.asap(r,g)}}}return t}});t.exports=v},{11:11,133:133,2:2,24:24,27:27,29:29,33:33,55:55,68:68,85:85}],48:[function(e,t,n){"use strict";var r=e(29),o=e(33),i=e(55),a=(e(150),i.createFactory("option")),u=o.createClass({displayName:"ReactDOMOption",tagName:"OPTION",mixins:[r],componentWillMount:function(){},render:function(){return a(this.props,this.props.children)}});t.exports=u},{150:150,29:29,33:33,55:55}],49:[function(e,t,n){"use strict";function r(){if(this._pendingUpdate){this._pendingUpdate=!1;var e=u.getValue(this);null!=e&&this.isMounted()&&i(this,e)}}function o(e,t,n){if(null==e[t])return null;if(e.multiple){if(!Array.isArray(e[t]))return new Error("The `"+t+"` prop supplied to must be a scalar value if `multiple` is false.")}function i(e,t){var n,r,o,i=e.getDOMNode().options;if(e.props.multiple){for(n={},r=0,o=t.length;o>r;r++)n[""+t[r]]=!0;for(r=0,o=i.length;o>r;r++){var a=n.hasOwnProperty(i[r].value);i[r].selected!==a&&(i[r].selected=a)}}else{for(n=""+t,r=0,o=i.length;o>r;r++)if(i[r].value===n)return void(i[r].selected=!0);i.length&&(i[0].selected=!0)}}var a=e(2),u=e(24),s=e(29),l=e(33),c=e(55),p=e(85),d=e(27),f=c.createFactory("select"),h=l.createClass({displayName:"ReactDOMSelect",tagName:"SELECT",mixins:[a,u.Mixin,s],propTypes:{defaultValue:o,value:o},render:function(){var e=d({},this.props);return e.onChange=this._handleChange,e.value=null,f(e,this.props.children)},componentWillMount:function(){this._pendingUpdate=!1},componentDidMount:function(){var e=u.getValue(this);null!=e?i(this,e):null!=this.props.defaultValue&&i(this,this.props.defaultValue)},componentDidUpdate:function(e){var t=u.getValue(this);null!=t?(this._pendingUpdate=!1,i(this,t)):!e.multiple!=!this.props.multiple&&(null!=this.props.defaultValue?i(this,this.props.defaultValue):i(this,this.props.multiple?[]:""))},_handleChange:function(e){var t,n=u.getOnChange(this);return n&&(t=n.call(this,e)),this._pendingUpdate=!0,p.asap(r,this),t}});t.exports=h},{2:2,24:24,27:27,29:29,33:33,55:55,85:85}],50:[function(e,t,n){"use strict";function r(e,t,n,r){return e===n&&t===r}function o(e){var t=document.selection,n=t.createRange(),r=n.text.length,o=n.duplicate();o.moveToElementText(e),o.setEndPoint("EndToStart",n);var i=o.text.length,a=i+r;return{start:i,end:a}}function i(e){var t=window.getSelection&&window.getSelection();if(!t||0===t.rangeCount)return null;var n=t.anchorNode,o=t.anchorOffset,i=t.focusNode,a=t.focusOffset,u=t.getRangeAt(0),s=r(t.anchorNode,t.anchorOffset,t.focusNode,t.focusOffset),l=s?0:u.toString().length,c=u.cloneRange();c.selectNodeContents(e),c.setEnd(u.startContainer,u.startOffset);var p=r(c.startContainer,c.startOffset,c.endContainer,c.endOffset),d=p?0:c.toString().length,f=d+l,h=document.createRange();h.setStart(n,o),h.setEnd(i,a);var m=h.collapsed;return{start:m?f:d,end:m?d:f}}function a(e,t){var n,r,o=document.selection.createRange().duplicate();"undefined"==typeof t.end?(n=t.start,r=n):t.start>t.end?(n=t.end,r=t.start):(n=t.start,r=t.end),o.moveToElementText(e),o.moveStart("character",n),o.setEndPoint("EndToStart",o),o.moveEnd("character",r-n),o.select()}function u(e,t){if(window.getSelection){var n=window.getSelection(),r=e[c()].length,o=Math.min(t.start,r),i="undefined"==typeof t.end?o:Math.min(t.end,r);if(!n.extend&&o>i){var a=i;i=o,o=a}var u=l(e,o),s=l(e,i);if(u&&s){var p=document.createRange();p.setStart(u.node,u.offset),n.removeAllRanges(),o>i?(n.addRange(p),n.extend(s.node,s.offset)):(p.setEnd(s.node,s.offset),n.addRange(p))}}}var s=e(21),l=e(126),c=e(128),p=s.canUseDOM&&"selection"in document&&!("getSelection"in window),d={getOffsets:p?o:i,setOffsets:p?a:u};t.exports=d},{126:126,128:128,21:21}],51:[function(e,t,n){"use strict";var r=e(11),o=e(35),i=e(42),a=e(27),u=e(114),s=function(e){};a(s.prototype,{construct:function(e){this._currentElement=e,this._stringText=""+e,this._rootNodeID=null,this._mountIndex=0},mountComponent:function(e,t,n){this._rootNodeID=e;var o=u(this._stringText);return t.renderToStaticMarkup?o:""+o+""},receiveComponent:function(e,t){if(e!==this._currentElement){this._currentElement=e;var n=""+e;n!==this._stringText&&(this._stringText=n,i.BackendIDOperations.updateTextContentByID(this._rootNodeID,n))}},unmountComponent:function(){o.unmountIDFromEnvironment(this._rootNodeID)}}),t.exports=s},{11:11,114:114,27:27,35:35,42:42}],52:[function(e,t,n){"use strict";function r(){this.isMounted()&&this.forceUpdate()}var o=e(2),i=e(11),a=e(24),u=e(29),s=e(33),l=e(55),c=e(85),p=e(27),d=e(133),f=(e(150),l.createFactory("textarea")),h=s.createClass({displayName:"ReactDOMTextarea",tagName:"TEXTAREA",mixins:[o,a.Mixin,u],getInitialState:function(){var e=this.props.defaultValue,t=this.props.children;null!=t&&(d(null==e),Array.isArray(t)&&(d(t.length<=1),t=t[0]),e=""+t),null==e&&(e="");var n=a.getValue(this);return{initialValue:""+(null!=n?n:e)}},render:function(){var e=p({},this.props);return d(null==e.dangerouslySetInnerHTML),e.defaultValue=null,e.value=null,e.onChange=this._handleChange,f(e,this.state.initialValue)},componentDidUpdate:function(e,t,n){var r=a.getValue(this);if(null!=r){var o=this.getDOMNode();i.setValueForProperty(o,"value",""+r)}},_handleChange:function(e){var t,n=a.getOnChange(this);return n&&(t=n.call(this,e)),c.asap(r,this),t}});t.exports=h},{11:11,133:133,150:150,2:2,24:24,27:27,29:29,33:33,55:55,85:85}],53:[function(e,t,n){"use strict";function r(){this.reinitializeTransaction()}var o=e(85),i=e(101),a=e(27),u=e(112),s={initialize:u,close:function(){d.isBatchingUpdates=!1}},l={initialize:u,close:o.flushBatchedUpdates.bind(o)},c=[l,s];a(r.prototype,i.Mixin,{getTransactionWrappers:function(){return c}});var p=new r,d={isBatchingUpdates:!1,batchedUpdates:function(e,t,n,r,o){var i=d.isBatchingUpdates;d.isBatchingUpdates=!0,i?e(t,n,r,o):p.perform(e,null,t,n,r,o)}};t.exports=d},{101:101,112:112,27:27,85:85}],54:[function(e,t,n){"use strict";function r(e){return h.createClass({tagName:e.toUpperCase(),render:function(){return new T(e,null,null,null,null,this.props)}})}function o(){P.EventEmitter.injectReactEventListener(R),P.EventPluginHub.injectEventPluginOrder(s),P.EventPluginHub.injectInstanceHandle(w),P.EventPluginHub.injectMount(O),P.EventPluginHub.injectEventPluginsByName({SimpleEventPlugin:L,EnterLeaveEventPlugin:l,ChangeEventPlugin:a,MobileSafariClickEventPlugin:d,SelectEventPlugin:A,BeforeInputEventPlugin:i}),P.NativeComponent.injectGenericComponentClass(g),P.NativeComponent.injectTextComponentClass(I),P.NativeComponent.injectAutoWrapper(r),P.Class.injectMixin(f),P.NativeComponent.injectComponentClasses({button:y,form:C,iframe:_,img:E,input:x,option:D,select:M,textarea:N,html:F("html"),head:F("head"),body:F("body")}),P.DOMProperty.injectDOMPropertyConfig(p),P.DOMProperty.injectDOMPropertyConfig(U),P.EmptyComponent.injectEmptyComponent("noscript"),P.Updates.injectReconcileTransaction(S),P.Updates.injectBatchingStrategy(v),P.RootIndex.injectCreateReactRootIndex(c.canUseDOM?u.createReactRootIndex:k.createReactRootIndex),P.Component.injectEnvironment(m),P.DOMComponent.injectIDOperations(b)}var i=e(3),a=e(7),u=e(8),s=e(13),l=e(14),c=e(21),p=e(23),d=e(26),f=e(29),h=e(33),m=e(35),v=e(53),g=e(42),y=e(41),C=e(43),E=e(46),b=e(44),_=e(45),x=e(47),D=e(48),M=e(49),N=e(52),I=e(51),T=e(55),R=e(60),P=e(62),w=e(64),O=e(68),S=e(78),A=e(87),k=e(88),L=e(89),U=e(86),F=e(109);t.exports={inject:o}},{109:109,13:13,14:14,21:21,23:23,26:26,29:29,3:3,33:33,35:35,41:41,42:42,43:43,44:44,45:45,46:46,47:47,48:48,49:49,51:51,52:52,53:53,55:55,60:60,62:62,64:64,68:68,7:7,78:78,8:8,86:86,87:87,88:88, -89:89}],55:[function(e,t,n){"use strict";var r=e(38),o=e(39),i=e(27),a=(e(150),{key:!0,ref:!0}),u=function(e,t,n,r,o,i){this.type=e,this.key=t,this.ref=n,this._owner=r,this._context=o,this.props=i};u.prototype={_isReactElement:!0},u.createElement=function(e,t,n){var i,s={},l=null,c=null;if(null!=t){c=void 0===t.ref?null:t.ref,l=void 0===t.key?null:""+t.key;for(i in t)t.hasOwnProperty(i)&&!a.hasOwnProperty(i)&&(s[i]=t[i])}var p=arguments.length-2;if(1===p)s.children=n;else if(p>1){for(var d=Array(p),f=0;p>f;f++)d[f]=arguments[f+2];s.children=d}if(e&&e.defaultProps){var h=e.defaultProps;for(i in h)"undefined"==typeof s[i]&&(s[i]=h[i])}return new u(e,l,c,o.current,r.current,s)},u.createFactory=function(e){var t=u.createElement.bind(null,e);return t.type=e,t},u.cloneAndReplaceProps=function(e,t){var n=new u(e.type,e.key,e.ref,e._owner,e._context,t);return n},u.cloneElement=function(e,t,n){var r,s=i({},e.props),l=e.key,c=e.ref,p=e._owner;if(null!=t){void 0!==t.ref&&(c=t.ref,p=o.current),void 0!==t.key&&(l=""+t.key);for(r in t)t.hasOwnProperty(r)&&!a.hasOwnProperty(r)&&(s[r]=t[r])}var d=arguments.length-2;if(1===d)s.children=n;else if(d>1){for(var f=Array(d),h=0;d>h;h++)f[h]=arguments[h+2];s.children=f}return new u(e.type,l,c,p,e._context,s)},u.isValidElement=function(e){var t=!(!e||!e._isReactElement);return t},t.exports=u},{150:150,27:27,38:38,39:39}],56:[function(e,t,n){"use strict";function r(){if(y.current){var e=y.current.getName();if(e)return" Check the render method of `"+e+"`."}return""}function o(e){var t=e&&e.getPublicInstance();if(!t)return void 0;var n=t.constructor;return n?n.displayName||n.name||void 0:void 0}function i(){var e=y.current;return e&&o(e)||void 0}function a(e,t){e._store.validated||null!=e.key||(e._store.validated=!0,s('Each child in an array or iterator should have a unique "key" prop.',e,t))}function u(e,t,n){D.test(e)&&s("Child objects should have non-numeric keys so ordering is preserved.",t,n)}function s(e,t,n){var r=i(),a="string"==typeof n?n:n.displayName||n.name,u=r||a,s=_[e]||(_[e]={});if(!s.hasOwnProperty(u)){s[u]=!0;var l="";if(t&&t._owner&&t._owner!==y.current){var c=o(t._owner);l=" It was passed a child from "+c+"."}}}function l(e,t){if(Array.isArray(e))for(var n=0;n");var u="";o&&(u=" The element was created by "+o+".")}}function d(e,t){return e!==e?t!==t:0===e&&0===t?1/e===1/t:e===t}function f(e){if(e._store){var t=e._store.originalProps,n=e.props;for(var r in n)n.hasOwnProperty(r)&&(t.hasOwnProperty(r)&&d(t[r],n[r])||(p(r,e),t[r]=n[r]))}}function h(e){if(null!=e.type){var t=C.getComponentClassForElement(e),n=t.displayName||t.name;t.propTypes&&c(n,t.propTypes,e.props,g.prop),"function"==typeof t.getDefaultProps}}var m=e(55),v=e(61),g=e(75),y=(e(74),e(39)),C=e(71),E=e(124),b=e(133),_=(e(150),{}),x={},D=/^\d+$/,M={},N={checkAndWarnForMutatedProps:f,createElement:function(e,t,n){var r=m.createElement.apply(this,arguments);if(null==r)return r;for(var o=2;oo;o++){t=e.ancestors[o];var a=p.getID(t)||"";v._handleTopLevel(e.topLevelType,t,a,e.nativeEvent)}}function a(e){var t=m(window);e(t)}var u=e(16),s=e(21),l=e(28),c=e(64),p=e(68),d=e(85),f=e(27),h=e(123),m=e(129);f(o.prototype,{destructor:function(){this.topLevelType=null,this.nativeEvent=null,this.ancestors.length=0}}),l.addPoolingTo(o,l.twoArgumentPooler);var v={_enabled:!0,_handleTopLevel:null,WINDOW_HANDLE:s.canUseDOM?window:null,setHandleTopLevel:function(e){v._handleTopLevel=e},setEnabled:function(e){v._enabled=!!e},isEnabled:function(){return v._enabled},trapBubbledEvent:function(e,t,n){var r=n;return r?u.listen(r,t,v.dispatchEvent.bind(null,e)):null},trapCapturedEvent:function(e,t,n){var r=n;return r?u.capture(r,t,v.dispatchEvent.bind(null,e)):null},monitorScrollValue:function(e){var t=a.bind(null,e);u.listen(window,"scroll",t)},dispatchEvent:function(e,t){if(v._enabled){var n=o.getPooled(e,t);try{d.batchedUpdates(i,n)}finally{o.release(n)}}}};t.exports=v},{123:123,129:129,16:16,21:21,27:27,28:28,64:64,68:68,85:85}],61:[function(e,t,n){"use strict";var r=(e(55),e(150),{create:function(e){return e},extract:function(e){return e},extractIfFragment:function(e){return e}});t.exports=r},{150:150,55:55}],62:[function(e,t,n){"use strict";var r=e(10),o=e(17),i=e(36),a=e(33),u=e(57),s=e(30),l=e(71),c=e(42),p=e(73),d=e(81),f=e(85),h={Component:i.injection,Class:a.injection,DOMComponent:c.injection,DOMProperty:r.injection,EmptyComponent:u.injection,EventPluginHub:o.injection,EventEmitter:s.injection,NativeComponent:l.injection,Perf:p.injection,RootIndex:d.injection,Updates:f.injection};t.exports=h},{10:10,17:17,30:30,33:33,36:36,42:42,57:57,71:71,73:73,81:81,85:85}],63:[function(e,t,n){"use strict";function r(e){return i(document.documentElement,e)}var o=e(50),i=e(107),a=e(117),u=e(119),s={hasSelectionCapabilities:function(e){return e&&("INPUT"===e.nodeName&&"text"===e.type||"TEXTAREA"===e.nodeName||"true"===e.contentEditable)},getSelectionInformation:function(){var e=u();return{focusedElem:e,selectionRange:s.hasSelectionCapabilities(e)?s.getSelection(e):null}},restoreSelection:function(e){var t=u(),n=e.focusedElem,o=e.selectionRange;t!==n&&r(n)&&(s.hasSelectionCapabilities(n)&&s.setSelection(n,o),a(n))},getSelection:function(e){var t;if("selectionStart"in e)t={start:e.selectionStart,end:e.selectionEnd};else if(document.selection&&"INPUT"===e.nodeName){var n=document.selection.createRange();n.parentElement()===e&&(t={start:-n.moveStart("character",-e.value.length),end:-n.moveEnd("character",-e.value.length)})}else t=o.getOffsets(e);return t||{start:0,end:0}},setSelection:function(e,t){var n=t.start,r=t.end;if("undefined"==typeof r&&(r=n),"selectionStart"in e)e.selectionStart=n,e.selectionEnd=Math.min(r,e.value.length);else if(document.selection&&"INPUT"===e.nodeName){var i=e.createTextRange();i.collapse(!0),i.moveStart("character",n),i.moveEnd("character",r-n),i.select()}else o.setOffsets(e,t)}};t.exports=s},{107:107,117:117,119:119,50:50}],64:[function(e,t,n){"use strict";function r(e){return f+e.toString(36)}function o(e,t){return e.charAt(t)===f||t===e.length}function i(e){return""===e||e.charAt(0)===f&&e.charAt(e.length-1)!==f}function a(e,t){return 0===t.indexOf(e)&&o(t,e.length)}function u(e){return e?e.substr(0,e.lastIndexOf(f)):""}function s(e,t){if(d(i(e)&&i(t)),d(a(e,t)),e===t)return e;var n,r=e.length+h;for(n=r;n=a;a++)if(o(e,a)&&o(t,a))r=a;else if(e.charAt(a)!==t.charAt(a))break;var u=e.substr(0,r);return d(i(u)),u}function c(e,t,n,r,o,i){e=e||"",t=t||"",d(e!==t);var l=a(t,e);d(l||a(e,t));for(var c=0,p=l?u:s,f=e;;f=p(f,t)){var h;if(o&&f===e||i&&f===t||(h=n(f,l,r)),h===!1||f===t)break;d(c++1){var t=e.indexOf(f,1);return t>-1?e.substr(0,t):e}return null},traverseEnterLeave:function(e,t,n,r,o){var i=l(e,t);i!==e&&c(e,i,n,r,!1,!0),i!==t&&c(i,t,n,o,!0,!1)},traverseTwoPhase:function(e,t,n){e&&(c("",e,t,n,!0,!1),c(e,"",t,n,!1,!0))},traverseAncestors:function(e,t,n){c("",e,t,n,!0,!1)},_getFirstCommonAncestorID:l,_getNextDescendantID:s,isAncestorIDOf:a,SEPARATOR:f};t.exports=v},{133:133,81:81}],65:[function(e,t,n){"use strict";var r={remove:function(e){e._reactInternalInstance=void 0},get:function(e){return e._reactInternalInstance},has:function(e){return void 0!==e._reactInternalInstance},set:function(e,t){e._reactInternalInstance=t}};t.exports=r},{}],66:[function(e,t,n){"use strict";var r={currentlyMountingInstance:null,currentlyUnmountingInstance:null};t.exports=r},{}],67:[function(e,t,n){"use strict";var r=e(104),o={CHECKSUM_ATTR_NAME:"data-react-checksum",addChecksumToMarkup:function(e){var t=r(e);return e.replace(">"," "+o.CHECKSUM_ATTR_NAME+'="'+t+'">')},canReuseMarkup:function(e,t){var n=t.getAttribute(o.CHECKSUM_ATTR_NAME);n=n&&parseInt(n,10);var i=r(e);return i===n}};t.exports=o},{104:104}],68:[function(e,t,n){"use strict";function r(e,t){for(var n=Math.min(e.length,t.length),r=0;n>r;r++)if(e.charAt(r)!==t.charAt(r))return r;return e.length===t.length?-1:n}function o(e){var t=R(e);return t&&K.getID(t)}function i(e){var t=a(e);if(t)if(L.hasOwnProperty(t)){var n=L[t];n!==e&&(w(!c(n,t)),L[t]=e)}else L[t]=e;return t}function a(e){return e&&e.getAttribute&&e.getAttribute(k)||""}function u(e,t){var n=a(e);n!==t&&delete L[n],e.setAttribute(k,t),L[t]=e}function s(e){return L.hasOwnProperty(e)&&c(L[e],e)||(L[e]=K.findReactNodeByID(e)),L[e]}function l(e){var t=b.get(e)._rootNodeID;return C.isNullComponentID(t)?null:(L.hasOwnProperty(t)&&c(L[t],t)||(L[t]=K.findReactNodeByID(t)),L[t])}function c(e,t){if(e){w(a(e)===t);var n=K.findReactContainerForID(t);if(n&&T(n,e))return!0}return!1}function p(e){delete L[e]}function d(e){var t=L[e];return t&&c(t,e)?void(W=t):!1}function f(e){W=null,E.traverseAncestors(e,d);var t=W;return W=null,t}function h(e,t,n,r,o){var i=D.mountComponent(e,t,r,I);e._isTopLevel=!0,K._mountImageIntoNode(i,n,o)}function m(e,t,n,r){var o=N.ReactReconcileTransaction.getPooled();o.perform(h,null,e,t,n,o,r),N.ReactReconcileTransaction.release(o)}var v=e(10),g=e(30),y=(e(39),e(55)),C=(e(56),e(57)),E=e(64),b=e(65),_=e(67),x=e(73),D=e(79),M=e(84),N=e(85),I=e(113),T=e(107),R=e(127),P=e(132),w=e(133),O=e(144),S=e(147),A=(e(150),E.SEPARATOR),k=v.ID_ATTRIBUTE_NAME,L={},U=1,F=9,B={},V={},j=[],W=null,K={_instancesByReactRootID:B,scrollMonitor:function(e,t){t()},_updateRootComponent:function(e,t,n,r){return K.scrollMonitor(n,function(){M.enqueueElementInternal(e,t),r&&M.enqueueCallbackInternal(e,r)}),e},_registerComponent:function(e,t){w(t&&(t.nodeType===U||t.nodeType===F)),g.ensureScrollValueMonitoring();var n=K.registerContainer(t);return B[n]=e,n},_renderNewRootComponent:function(e,t,n){var r=P(e,null),o=K._registerComponent(r,t);return N.batchedUpdates(m,r,o,t,n),r},render:function(e,t,n){w(y.isValidElement(e));var r=B[o(t)];if(r){var i=r._currentElement;if(S(i,e))return K._updateRootComponent(r,e,t,n).getPublicInstance();K.unmountComponentAtNode(t)}var a=R(t),u=a&&K.isRenderedByReact(a),s=u&&!r,l=K._renderNewRootComponent(e,t,s).getPublicInstance();return n&&n.call(l),l},constructAndRenderComponent:function(e,t,n){var r=y.createElement(e,t);return K.render(r,n)},constructAndRenderComponentByID:function(e,t,n){var r=document.getElementById(n);return w(r),K.constructAndRenderComponent(e,t,r)},registerContainer:function(e){var t=o(e);return t&&(t=E.getReactRootIDFromNodeID(t)),t||(t=E.createReactRootID()),V[t]=e,t},unmountComponentAtNode:function(e){w(e&&(e.nodeType===U||e.nodeType===F));var t=o(e),n=B[t];return n?(K.unmountComponentFromNode(n,e),delete B[t],delete V[t],!0):!1},unmountComponentFromNode:function(e,t){for(D.unmountComponent(e),t.nodeType===F&&(t=t.documentElement);t.lastChild;)t.removeChild(t.lastChild)},findReactContainerForID:function(e){var t=E.getReactRootIDFromNodeID(e),n=V[t];return n},findReactNodeByID:function(e){var t=K.findReactContainerForID(e);return K.findComponentRoot(t,e)},isRenderedByReact:function(e){if(1!==e.nodeType)return!1;var t=K.getID(e);return t?t.charAt(0)===A:!1},getFirstReactDOM:function(e){for(var t=e;t&&t.parentNode!==t;){if(K.isRenderedByReact(t))return t;t=t.parentNode}return null},findComponentRoot:function(e,t){var n=j,r=0,o=f(t)||e;for(n[0]=o.firstChild,n.length=1;r>",_=u(),x=d(),D={array:o("array"),bool:o("boolean"),func:o("function"),number:o("number"),object:o("object"),string:o("string"),any:i(),arrayOf:a,element:_,instanceOf:s,node:x,objectOf:c,oneOf:l,oneOfType:p,shape:f};t.exports=D},{112:112,55:55,61:61,74:74}],77:[function(e,t,n){"use strict";function r(){this.listenersToPut=[]}var o=e(28),i=e(30),a=e(27);a(r.prototype,{enqueuePutListener:function(e,t,n){this.listenersToPut.push({rootNodeID:e,propKey:t,propValue:n})},putListeners:function(){for(var e=0;en;n++){var r=g[n],o=r._pendingCallbacks;if(r._pendingCallbacks=null,f.performUpdateIfNecessary(r,e.reconcileTransaction),o)for(var i=0;i":">","<":"<",'"':""","'":"'"},a=/[&><"']/g;t.exports=o},{}],115:[function(e,t,n){"use strict";function r(e){return null==e?null:u(e)?e:o.has(e)?i.getNodeFromInstance(e):(a(null==e.render||"function"!=typeof e.render),void a(!1))}{var o=(e(39),e(65)),i=e(68),a=e(133),u=e(135);e(150)}t.exports=r},{133:133,135:135,150:150,39:39,65:65,68:68}],116:[function(e,t,n){"use strict";function r(e,t,n){var r=e,o=!r.hasOwnProperty(n);o&&null!=t&&(r[n]=t)}function o(e){if(null==e)return e;var t={};return i(e,r,t),t}{var i=e(149);e(150)}t.exports=o},{149:149,150:150}],117:[function(e,t,n){"use strict";function r(e){try{e.focus()}catch(t){}}t.exports=r},{}],118:[function(e,t,n){"use strict";var r=function(e,t,n){Array.isArray(e)?e.forEach(t,n):e&&t.call(n,e)};t.exports=r},{}],119:[function(e,t,n){function r(){try{return document.activeElement||document.body}catch(e){return document.body}}t.exports=r},{}],120:[function(e,t,n){"use strict";function r(e){var t,n=e.keyCode;return"charCode"in e?(t=e.charCode,0===t&&13===n&&(t=13)):t=n,t>=32||13===t?t:0}t.exports=r},{}],121:[function(e,t,n){"use strict";function r(e){if(e.key){var t=i[e.key]||e.key;if("Unidentified"!==t)return t}if("keypress"===e.type){var n=o(e);return 13===n?"Enter":String.fromCharCode(n)}return"keydown"===e.type||"keyup"===e.type?a[e.keyCode]||"Unidentified":""}var o=e(120),i={Esc:"Escape",Spacebar:" ",Left:"ArrowLeft",Up:"ArrowUp",Right:"ArrowRight",Down:"ArrowDown",Del:"Delete",Win:"OS",Menu:"ContextMenu",Apps:"ContextMenu",Scroll:"ScrollLock",MozPrintableKey:"Unidentified"},a={8:"Backspace",9:"Tab",12:"Clear",13:"Enter",16:"Shift",17:"Control",18:"Alt",19:"Pause",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",45:"Insert",46:"Delete",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"NumLock",145:"ScrollLock",224:"Meta"};t.exports=r},{120:120}],122:[function(e,t,n){"use strict";function r(e){var t=this,n=t.nativeEvent;if(n.getModifierState)return n.getModifierState(e);var r=i[e];return r?!!n[r]:!1}function o(e){return r}var i={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"};t.exports=o},{}],123:[function(e,t,n){"use strict";function r(e){var t=e.target||e.srcElement||window;return 3===t.nodeType?t.parentNode:t}t.exports=r},{}],124:[function(e,t,n){"use strict";function r(e){var t=e&&(o&&e[o]||e[i]);return"function"==typeof t?t:void 0}var o="function"==typeof Symbol&&Symbol.iterator,i="@@iterator";t.exports=r},{}],125:[function(e,t,n){function r(e){return i(!!a),d.hasOwnProperty(e)||(e="*"),u.hasOwnProperty(e)||("*"===e?a.innerHTML="":a.innerHTML="<"+e+">",u[e]=!a.firstChild),u[e]?d[e]:null}var o=e(21),i=e(133),a=o.canUseDOM?document.createElement("div"):null,u={circle:!0,defs:!0,ellipse:!0,g:!0,line:!0,linearGradient:!0,path:!0,polygon:!0,polyline:!0,radialGradient:!0,rect:!0,stop:!0,text:!0},s=[1,'"],l=[1,"","
"],c=[3,"","
"],p=[1,"",""],d={"*":[1,"?
","
"],area:[1,"",""],col:[2,"","
"],legend:[1,"
","
"],param:[1,"",""],tr:[2,"","
"],optgroup:s,option:s,caption:l,colgroup:l,tbody:l,tfoot:l,thead:l,td:c,th:c,circle:p,defs:p,ellipse:p,g:p,line:p,linearGradient:p,path:p,polygon:p,polyline:p,radialGradient:p,rect:p,stop:p,text:p};t.exports=r},{133:133,21:21}],126:[function(e,t,n){"use strict";function r(e){for(;e&&e.firstChild;)e=e.firstChild;return e}function o(e){for(;e;){if(e.nextSibling)return e.nextSibling;e=e.parentNode}}function i(e,t){for(var n=r(e),i=0,a=0;n;){if(3===n.nodeType){if(a=i+n.textContent.length,t>=i&&a>=t)return{node:n,offset:t-i};i=a}n=r(o(n))}}t.exports=i},{}],127:[function(e,t,n){"use strict";function r(e){return e?e.nodeType===o?e.documentElement:e.firstChild:null}var o=9;t.exports=r},{}],128:[function(e,t,n){"use strict";function r(){return!i&&o.canUseDOM&&(i="textContent"in document.documentElement?"textContent":"innerText"),i}var o=e(21),i=null;t.exports=r},{21:21}],129:[function(e,t,n){"use strict";function r(e){return e===window?{x:window.pageXOffset||document.documentElement.scrollLeft,y:window.pageYOffset||document.documentElement.scrollTop}:{x:e.scrollLeft,y:e.scrollTop}}t.exports=r},{}],130:[function(e,t,n){function r(e){return e.replace(o,"-$1").toLowerCase()}var o=/([A-Z])/g;t.exports=r},{}],131:[function(e,t,n){"use strict";function r(e){return o(e).replace(i,"-ms-")}var o=e(130),i=/^ms-/;t.exports=r},{130:130}],132:[function(e,t,n){"use strict";function r(e){return"function"==typeof e&&"undefined"!=typeof e.prototype&&"function"==typeof e.prototype.mountComponent&&"function"==typeof e.prototype.receiveComponent}function o(e,t){var n;if((null===e||e===!1)&&(e=a.emptyElement),"object"==typeof e){var o=e;n=t===o.type&&"string"==typeof o.type?u.createInternalComponent(o):r(o.type)?new o.type(o):new c}else"string"==typeof e||"number"==typeof e?n=u.createInstanceForText(e):l(!1);return n.construct(e),n._mountIndex=0,n._mountImage=null,n}var i=e(37),a=e(57),u=e(71),s=e(27),l=e(133),c=(e(150),function(){});s(c.prototype,i.Mixin,{_instantiateReactComponent:o}),t.exports=o},{133:133,150:150,27:27,37:37,57:57,71:71}],133:[function(e,t,n){"use strict";var r=function(e,t,n,r,o,i,a,u){if(!e){var s;if(void 0===t)s=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var l=[n,r,o,i,a,u],c=0;s=new Error("Invariant Violation: "+t.replace(/%s/g,function(){return l[c++]}))}throw s.framesToPop=1,s}};t.exports=r},{}],134:[function(e,t,n){"use strict";function r(e,t){if(!i.canUseDOM||t&&!("addEventListener"in document))return!1;var n="on"+e,r=n in document;if(!r){var a=document.createElement("div");a.setAttribute(n,"return;"),r="function"==typeof a[n]}return!r&&o&&"wheel"===e&&(r=document.implementation.hasFeature("Events.wheel","3.0")),r}var o,i=e(21);i.canUseDOM&&(o=document.implementation&&document.implementation.hasFeature&&document.implementation.hasFeature("","")!==!0),t.exports=r},{21:21}],135:[function(e,t,n){function r(e){return!(!e||!("function"==typeof Node?e instanceof Node:"object"==typeof e&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName))}t.exports=r},{}],136:[function(e,t,n){"use strict";function r(e){return e&&("INPUT"===e.nodeName&&o[e.type]||"TEXTAREA"===e.nodeName)}var o={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0};t.exports=r},{}],137:[function(e,t,n){function r(e){return o(e)&&3==e.nodeType}var o=e(135);t.exports=r},{135:135}],138:[function(e,t,n){"use strict";var r=e(133),o=function(e){var t,n={};r(e instanceof Object&&!Array.isArray(e));for(t in e)e.hasOwnProperty(t)&&(n[t]=t);return n};t.exports=o},{133:133}],139:[function(e,t,n){var r=function(e){var t;for(t in e)if(e.hasOwnProperty(t))return t;return null};t.exports=r},{}],140:[function(e,t,n){"use strict";function r(e,t,n){if(!e)return null;var r={};for(var i in e)o.call(e,i)&&(r[i]=t.call(n,e[i],i,e));return r}var o=Object.prototype.hasOwnProperty;t.exports=r},{}],141:[function(e,t,n){"use strict";function r(e){var t={};return function(n){return t.hasOwnProperty(n)||(t[n]=e.call(this,n)),t[n]}}t.exports=r},{}],142:[function(e,t,n){"use strict";function r(e){return i(o.isValidElement(e)),e}var o=e(55),i=e(133);t.exports=r},{133:133,55:55}],143:[function(e,t,n){"use strict";function r(e){return'"'+o(e)+'"'}var o=e(114);t.exports=r},{114:114}],144:[function(e,t,n){"use strict";var r=e(21),o=/^[ \r\n\t\f]/,i=/<(!--|link|noscript|meta|script|style)[ \r\n\t\f\/>]/,a=function(e,t){e.innerHTML=t};if("undefined"!=typeof MSApp&&MSApp.execUnsafeLocalFunction&&(a=function(e,t){MSApp.execUnsafeLocalFunction(function(){e.innerHTML=t})}),r.canUseDOM){var u=document.createElement("div");u.innerHTML=" ",""===u.innerHTML&&(a=function(e,t){if(e.parentNode&&e.parentNode.replaceChild(e,e),o.test(t)||"<"===t[0]&&i.test(t)){e.innerHTML="\ufeff"+t;var n=e.firstChild;1===n.data.length?e.removeChild(n):n.deleteData(0,1)}else e.innerHTML=t})}t.exports=a},{21:21}],145:[function(e,t,n){"use strict";var r=e(21),o=e(114),i=e(144),a=function(e,t){e.textContent=t};r.canUseDOM&&("textContent"in document.documentElement||(a=function(e,t){i(e,o(t))})),t.exports=a},{114:114,144:144,21:21}],146:[function(e,t,n){"use strict";function r(e,t){if(e===t)return!0;var n;for(n in e)if(e.hasOwnProperty(n)&&(!t.hasOwnProperty(n)||e[n]!==t[n]))return!1;for(n in t)if(t.hasOwnProperty(n)&&!e.hasOwnProperty(n))return!1;return!0}t.exports=r},{}],147:[function(e,t,n){"use strict";function r(e,t){if(null!=e&&null!=t){var n=typeof e,r=typeof t;if("string"===n||"number"===n)return"string"===r||"number"===r;if("object"===r&&e.type===t.type&&e.key===t.key){var o=e._owner===t._owner;return o}}return!1}e(150);t.exports=r},{150:150}],148:[function(e,t,n){function r(e){var t=e.length;if(o(!Array.isArray(e)&&("object"==typeof e||"function"==typeof e)),o("number"==typeof t),o(0===t||t-1 in e),e.hasOwnProperty)try{return Array.prototype.slice.call(e)}catch(n){}for(var r=Array(t),i=0;t>i;i++)r[i]=e[i];return r}var o=e(133);t.exports=r},{133:133}],149:[function(e,t,n){"use strict";function r(e){return v[e]}function o(e,t){return e&&null!=e.key?a(e.key):t.toString(36)}function i(e){return(""+e).replace(g,r)}function a(e){return"$"+i(e)}function u(e,t,n,r,i){var s=typeof e;if(("undefined"===s||"boolean"===s)&&(e=null),null===e||"string"===s||"number"===s||l.isValidElement(e))return r(i,e,""===t?h+o(e,0):t,n),1;var p,v,g,y=0;if(Array.isArray(e))for(var C=0;C=200&&c.status<300||c.status===304){var a=d.dataType=="xml"?c.responseXML:c.responseText;if(d.dataType=="json")a=b.parseJSON(a);if(b.isFunction(d.success))d.success.call(d,a,c.status,c)}else{if(b.isFunction(d.error))d.error.call(d,c,c.status)}if(b.isFunction(d.complete))d.complete.call(d,c,c.status)}};this.xhr=c;if(!d.cache)d.url+=(d.url.indexOf("?")>-1?"&":"?")+"_nocache="+(new Date).getTime();if(d.data){if(d.type=="GET"){d.url+=(d.url.indexOf("?")>-1?"&":"?")+this.param(d.data);d.data=null}else{d.data=this.param(d.data)}}c.open(d.type,d.url,d.async);c.setRequestHeader("Content-type",d.contentType);if(d.dataType&&d.accepts[d.dataType])c.setRequestHeader("Accept",d.accepts[d.dataType]);if(d.async){c.onreadystatechange=e;c.send(d.data)}else{c.send(d.data);e()}return this},get:function(a,b,c){if(this.isFunction(b)){c=b;b=null}return this.call({url:a,type:"GET",data:b,success:c})},post:function(a,b,c){if(this.isFunction(b)){c=b;b=null}return this.call({url:a,type:"POST",data:b,success:c})},load:function(a,b,c,d){if(typeof a=="string")a=document.getElementById(a);return this.call({url:b,type:c?"POST":"GET",data:c||null,complete:d||null,success:function(b){try{a.innerHTML=b}catch(c){var d=document.createElement("div");d.innerHTML=b;while(a.firstChild)a.removeChild(a.firstChild);for(var e=0,f=d.childNodes.length;e .middle { - width: 100%; - height: 100%; - padding: 0; - margin: 0; - overflow: hidden; - display: table-row; -} -.zeroTierNode > .middle > .middleCell { - width: 100%; - height: 100%; - display: table-cell; - border-bottom: 1px solid #cfcfcf; -} -.zeroTierNode > .middle > .middleCell > .middleScroll { - display: block; - width: 100%; - height: 100%; - padding: 0; - margin: 0; - overflow: scroll; - overflow-x: hidden; - overflow-y: scroll; - background: #dddddd; -} -.zeroTierNode > .middle > .middleCell > .middleScroll > .networks { - display: block; - width: 100%; - padding: 0 0 0.25rem 0; - margin: 0; - border: 0; - text-align: left; - border-collapse: collapse; -} -.zeroTierNode > .middle > .middleCell > .middleScroll > .networks > .network { - display: block; - border-top: 0.12rem solid #dddddd; - border-bottom: 0.12rem solid #dddddd; - padding: 0.25rem; - background: #ffffff; -} - -.zeroTierNode > .bottom { - font-size: 12pt; - width: 100%; - overflow: hidden; - display: table-row; - color: #000000; - background: #dfdfdf; -} -.zeroTierNode > .bottom > .left { - text-align: left; - white-space: nowrap; - float: left; - padding: 0 0 0 0.5rem; - font-size: 12pt; - height: 100%; -} -.zeroTierNode > .bottom > .left > .statusLine { - font-family: monospace; - white-space: nowrap; - font-size: 11pt; - height: 100%; -} -.zeroTierNode > .bottom > .right { - text-align: right; - height: 100%; - white-space: nowrap; - float: right; - font-size: 12pt; - background: #ffffff; -} -.zeroTierNode > .bottom > .right form { - height: 100%; -} -.zeroTierNode > .bottom > .right input { - font-family: monospace; - font-size: 12pt; - background: #ffffff; - color: #000000; - outline: none; - outline-style: none; - box-shadow: 0; - border: 0; - margin: 0; - padding: 0 0.25rem 0 0.25rem; - display: inline; - height: 100%; -} -.zeroTierNode > .bottom > .right button { - display: inline-block; - font-size: 12pt; - background: #ffb354; - border: 1px solid #ffb354; - color: #000000; - margin: 0; - padding: 0.05rem 0.75rem 0.05rem 0.75rem; - outline: none; - outline-style: none; - height: 100%; -} -.zeroTierNode > .bottom > .right button:hover { - cursor: pointer; - outline: none; - outline-style: none; - border: 1px solid #000000; -} - -.zeroTierNetwork { - padding: 0; - margin: 0; - display: inline-block; - text-align: right; - width: 100%; - position: relative; -} -.zeroTierNetwork .networkInfo { - padding: 0 0 0.25rem 0; - text-align: left; - font-size: 12pt; -} -.zeroTierNetwork .networkInfo .networkId { - font-size: 11pt; - font-family: monospace; - color: #000000; -} -.zeroTierNetwork .networkInfo .networkName { - padding: 0 0 0 1rem; - float: right; - font-size: 12pt; -} -.zeroTierNetwork .networkProps { - width: 100%; - display: table; - padding: 0; - margin: 0 auto 0 auto; - border-top: 1px solid #999999; - border-bottom: 1px solid #999999; -} -.zeroTierNetwork .networkProps > .row { - display: table-row; -} -.zeroTierNetwork .networkProps > .row > .name { - display: table-cell; - font-size: 10pt; - padding: 0.1rem 0.5rem 0.1rem 0.5rem; -} -.zeroTierNetwork .networkProps > .row > .value { - font-size: 10pt; - display: table-cell; - padding: 0.1rem 0.5rem 0.1rem 0.5rem; - background: #eeeeee; -} -.zeroTierNetwork .ipList { -} -.zeroTierNetwork .ipAddress { - font-family: monospace; - font-size: 10pt; -} -.zeroTierNetwork .leaveNetworkButton { - padding: 0.25rem 0.5rem 0.25rem 0.5rem; - margin: 0.25rem 0 0 0; - font-size: 9pt; - background: #ffffff; - outline: none; - background: #ffb354; - border: 1px solid #ffb354; - cursor: pointer; -} -.zeroTierNetwork .leaveNetworkButton:hover { - border: 1px solid #000000; -} diff --git a/ext/installfiles/mac/ui/ztui.min.js b/ext/installfiles/mac/ui/ztui.min.js deleted file mode 100644 index 1798283..0000000 --- a/ext/installfiles/mac/ui/ztui.min.js +++ /dev/null @@ -1 +0,0 @@ -var ZeroTierNetwork=React.createClass({displayName:"ZeroTierNetwork",getInitialState:function(){return{}},leaveNetwork:function(e){Ajax.call({url:"network/"+this.props.nwid+"?auth="+this.props.authToken,cache:!1,type:"DELETE",success:function(e){this.props.onNetworkDeleted&&this.props.onNetworkDeleted(this.props.nwid)}.bind(this),error:function(e){}.bind(this)}),e.preventDefault()},render:function(){return React.createElement("div",{className:"zeroTierNetwork"},React.createElement("div",{className:"networkInfo"},React.createElement("span",{className:"networkId"},this.props.nwid)," ",React.createElement("span",{className:"networkName"},this.props.name)),React.createElement("div",{className:"networkProps"},React.createElement("div",{className:"row"},React.createElement("div",{className:"name"},"Status"),React.createElement("div",{className:"value"},this.props.status)),React.createElement("div",{className:"row"},React.createElement("div",{className:"name"},"Type"),React.createElement("div",{className:"value"},this.props.type)),React.createElement("div",{className:"row"},React.createElement("div",{className:"name"},"MAC"),React.createElement("div",{className:"value zeroTierAddress"},this.props.mac)),React.createElement("div",{className:"row"},React.createElement("div",{className:"name"},"MTU"),React.createElement("div",{className:"value"},this.props.mtu)),React.createElement("div",{className:"row"},React.createElement("div",{className:"name"},"Broadcast"),React.createElement("div",{className:"value"},this.props.broadcastEnabled?"ENABLED":"DISABLED")),React.createElement("div",{className:"row"},React.createElement("div",{className:"name"},"Bridging"),React.createElement("div",{className:"value"},this.props.bridge?"ACTIVE":"DISABLED")),React.createElement("div",{className:"row"},React.createElement("div",{className:"name"},"Device"),React.createElement("div",{className:"value"},this.props.portDeviceName?this.props.portDeviceName:"(none)")),React.createElement("div",{className:"row"},React.createElement("div",{className:"name"},"Managed IPs"),React.createElement("div",{className:"value ipList"},this.props.assignedAddresses.map(function(e){return React.createElement("div",{key:e,className:"ipAddress"},e)})))),React.createElement("button",{type:"button",className:"leaveNetworkButton",onClick:this.leaveNetwork},"Leave Network"))}}); var ZeroTierNode=React.createClass({displayName:"ZeroTierNode",getInitialState:function(){return{address:"----------",online:!1,version:"_._._",_networks:[],_peers:[]}},ago:function(e){if(e>0){var t=Math.round((Date.now()-e)/1e3);return t>0?t:0}return 0},updatePeers:function(){Ajax.call({url:"peer?auth="+this.props.authToken,cache:!1,type:"GET",success:function(e){if(e){var t=JSON.parse(e);Array.isArray(t)&&this.setState({_peers:t})}}.bind(this),error:function(){}.bind(this)})},updateNetworks:function(){Ajax.call({url:"network?auth="+this.props.authToken,cache:!1,type:"GET",success:function(e){if(e){var t=JSON.parse(e);Array.isArray(t)&&this.setState({_networks:t})}}.bind(this),error:function(){}.bind(this)})},updateAll:function(){Ajax.call({url:"status?auth="+this.props.authToken,cache:!1,type:"GET",success:function(e){if(this.alertedToFailure=!1,e){var t=JSON.parse(e);this.setState(t),document.title="ZeroTier One ["+t.address+"]"}this.updateNetworks(),this.updatePeers()}.bind(this),error:function(){this.setState(this.getInitialState()),this.alertedToFailure||(this.alertedToFailure=!0,alert("Authorization token invalid or ZeroTier One service not running."))}.bind(this)})},joinNetwork:function(e){e.preventDefault(),this.networkToJoin&&16===this.networkToJoin.length?Ajax.call({url:"network/"+this.networkToJoin+"?auth="+this.props.authToken,cache:!1,type:"POST",success:function(e){this.networkToJoin="",this.networkInputElement&&(this.networkInputElement.value=""),this.updateNetworks()}.bind(this),error:function(){}.bind(this)}):alert("To join a network, enter its 16-digit network ID.")},handleNetworkIdEntry:function(e){this.networkInputElement=e.target;var t=this.networkInputElement.value;if(t){t=t.toLowerCase();for(var n="",a=0;aa;++a)"0123456789abcdef".indexOf(t.charAt(a))>=0&&(n+=t.charAt(a));this.networkToJoin=n,this.networkInputElement.value=n}else this.networkToJoin="",this.networkInputElement.value=""},handleNetworkDelete:function(e){for(var t=[],n=0;n - + @@ -15,7 +15,7 @@ - + @@ -58,7 +58,7 @@ - + diff --git a/ext/installfiles/windows/ZeroTier One Virtual Network Port (NDIS6_x86).aip b/ext/installfiles/windows/ZeroTier One Virtual Network Port (NDIS6_x86).aip index 831516d..b83b382 100644 --- a/ext/installfiles/windows/ZeroTier One Virtual Network Port (NDIS6_x86).aip +++ b/ext/installfiles/windows/ZeroTier One Virtual Network Port (NDIS6_x86).aip @@ -1,5 +1,5 @@ - + @@ -16,7 +16,7 @@ - + @@ -59,7 +59,7 @@ - + diff --git a/ext/installfiles/windows/ZeroTier One.aip b/ext/installfiles/windows/ZeroTier One.aip index fdbbeea..a63fa2b 100644 --- a/ext/installfiles/windows/ZeroTier One.aip +++ b/ext/installfiles/windows/ZeroTier One.aip @@ -7,6 +7,7 @@ + @@ -26,19 +27,19 @@ - + - + - + - - + + - - + + @@ -46,6 +47,7 @@ + @@ -58,25 +60,35 @@ - + + + + + - + - + + + + + + + @@ -86,8 +98,18 @@ + + + + + + + + + + @@ -106,9 +128,10 @@ - + + @@ -125,6 +148,14 @@ + + + + + + + + @@ -141,14 +172,20 @@ + + + + - + + + @@ -183,23 +220,40 @@ + + - + + + + + + + + + + + + + + + + @@ -209,10 +263,7 @@ - - - @@ -224,8 +275,6 @@ - - @@ -237,6 +286,7 @@ + @@ -244,34 +294,54 @@ + + + + + + - + - - + - - + + - - - + + + + + + + + + + + + + + + + + + @@ -285,12 +355,18 @@ - + - + + + + + + + @@ -329,7 +405,7 @@ - + @@ -356,10 +432,10 @@ - + - + diff --git a/ext/installfiles/windows/chocolatey/zerotier-one/tools/chocolateyinstall.ps1 b/ext/installfiles/windows/chocolatey/zerotier-one/tools/chocolateyinstall.ps1 index b29fd99..f8a7457 100644 --- a/ext/installfiles/windows/chocolatey/zerotier-one/tools/chocolateyinstall.ps1 +++ b/ext/installfiles/windows/chocolatey/zerotier-one/tools/chocolateyinstall.ps1 @@ -1,7 +1,7 @@ $packageName = 'zerotier-one' $installerType = 'msi' -$url = 'https://download.zerotier.com/RELEASES/1.1.12/dist/ZeroTier%20One.msi' -$url64 = 'https://download.zerotier.com/RELEASES/1.1.12/dist/ZeroTier%20One.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) diff --git a/ext/installfiles/windows/chocolatey/zerotier-one/zerotier-one.nuspec b/ext/installfiles/windows/chocolatey/zerotier-one/zerotier-one.nuspec index 473007c..32fa5a9 100644 --- a/ext/installfiles/windows/chocolatey/zerotier-one/zerotier-one.nuspec +++ b/ext/installfiles/windows/chocolatey/zerotier-one/zerotier-one.nuspec @@ -26,7 +26,7 @@ This is a nuspec. It mostly adheres to https://docs.nuget.org/create/Nuspec-Refe - 1.1.12 + 1.2.4 diff --git a/ext/json-parser/AUTHORS b/ext/json-parser/AUTHORS deleted file mode 100644 index 6a5c799..0000000 --- a/ext/json-parser/AUTHORS +++ /dev/null @@ -1,20 +0,0 @@ -All contributors arranged by first commit: - -James McLaughlin -Alex Gartrell -Peter Scott -Mathias Kaerlev -Emiel Mols -Czarek Tomczak -Nicholas Braden -Ivan Kozub -Árpád Goretity -Igor Gnatenko -Haïkel Guémar -Tobias Waldekranz -Patrick Donnelly -Wilmer van der Gaast -Jin Wei -François Cartegnie -Matthijs Boelstra - diff --git a/ext/json-parser/LICENSE b/ext/json-parser/LICENSE deleted file mode 100644 index 1aee375..0000000 --- a/ext/json-parser/LICENSE +++ /dev/null @@ -1,26 +0,0 @@ - - Copyright (C) 2012, 2013 James McLaughlin et al. All rights reserved. - - 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. - - THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - SUCH DAMAGE. - diff --git a/ext/json-parser/README.md b/ext/json-parser/README.md deleted file mode 100644 index e0b70b6..0000000 --- a/ext/json-parser/README.md +++ /dev/null @@ -1,97 +0,0 @@ -Very low footprint JSON parser written in portable ANSI C. - -* BSD licensed with no dependencies (i.e. just drop the C file into your project) -* Never recurses or allocates more memory than it needs -* Very simple API with operator sugar for C++ - -[![Build Status](https://secure.travis-ci.org/udp/json-parser.png)](http://travis-ci.org/udp/json-parser) - -_Want to serialize? Check out [json-builder](https://github.com/udp/json-builder)!_ - -Installing ----------- - -There is now a makefile which will produce a libjsonparser static and dynamic library. However, this -is _not_ required to build json-parser, and the source files (`json.c` and `json.h`) should be happy -in any build system you already have in place. - - -API ---- - - json_value * json_parse (const json_char * json, - size_t length); - - json_value * json_parse_ex (json_settings * settings, - const json_char * json, - size_t length, - char * error); - - void json_value_free (json_value *); - -The `type` field of `json_value` is one of: - -* `json_object` (see `u.object.length`, `u.object.values[x].name`, `u.object.values[x].value`) -* `json_array` (see `u.array.length`, `u.array.values`) -* `json_integer` (see `u.integer`) -* `json_double` (see `u.dbl`) -* `json_string` (see `u.string.ptr`, `u.string.length`) -* `json_boolean` (see `u.boolean`) -* `json_null` - - -Compile-Time Options --------------------- - - -DJSON_TRACK_SOURCE - -Stores the source location (line and column number) inside each `json_value`. - -This is useful for application-level error reporting. - - -Runtime Options ---------------- - - settings |= json_enable_comments; - -Enables C-style `// line` and `/* block */` comments. - - size_t value_extra - -The amount of space (if any) to allocate at the end of each `json_value`, in -order to give the application space to add metadata. - - void * (* mem_alloc) (size_t, int zero, void * user_data); - void (* mem_free) (void *, void * user_data); - -Custom allocator routines. If NULL, the default `malloc` and `free` will be used. - -The `user_data` pointer will be forwarded from `json_settings` to allow application -context to be passed. - - -Changes in version 1.1.0 ------------------------- - -* UTF-8 byte order marks are now skipped if present - -* Allows cross-compilation by honoring --host if given (@wkz) - -* Maximum size for error buffer is now exposed in header (@LB--) - -* GCC warning for `static` after `const` fixed (@batrick) - -* Optional support for C-style line and block comments added (@Jin-W-FS) - -* `name_length` field added to object values - -* It is now possible to retrieve the source line/column number of a parsed `json_value` when `JSON_TRACK_SOURCE` is enabled - -* The application may now extend `json_value` using the `value_extra` setting - -* Un-ambiguate pow call in the case of C++ overloaded pow (@fcartegnie) - -* Fix null pointer de-reference when a non-existing array is closed and no root value is present - - diff --git a/ext/json-parser/json.c b/ext/json-parser/json.c deleted file mode 100644 index 166cdcb..0000000 --- a/ext/json-parser/json.c +++ /dev/null @@ -1,1012 +0,0 @@ -/* vim: set et ts=3 sw=3 sts=3 ft=c: - * - * Copyright (C) 2012, 2013, 2014 James McLaughlin et al. All rights reserved. - * https://github.com/udp/json-parser - * - * 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. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 "json.h" - -#ifdef _MSC_VER - #ifndef _CRT_SECURE_NO_WARNINGS - #define _CRT_SECURE_NO_WARNINGS - #endif - #pragma warning(disable:4996) -#endif - -const struct _json_value json_value_none; - -#include -#include -#include -#include - -typedef unsigned int json_uchar; - -static unsigned char hex_value (json_char c) -{ - if (isdigit(c)) - return c - '0'; - - switch (c) { - case 'a': case 'A': return 0x0A; - case 'b': case 'B': return 0x0B; - case 'c': case 'C': return 0x0C; - case 'd': case 'D': return 0x0D; - case 'e': case 'E': return 0x0E; - case 'f': case 'F': return 0x0F; - default: return 0xFF; - } -} - -typedef struct -{ - unsigned long used_memory; - - unsigned int uint_max; - unsigned long ulong_max; - - json_settings settings; - int first_pass; - - const json_char * ptr; - unsigned int cur_line, cur_col; - -} json_state; - -static void * default_alloc (size_t size, int zero, void * user_data) -{ - return zero ? calloc (1, size) : malloc (size); -} - -static void default_free (void * ptr, void * user_data) -{ - free (ptr); -} - -static void * json_alloc (json_state * state, unsigned long size, int zero) -{ - if ((state->ulong_max - state->used_memory) < size) - return 0; - - if (state->settings.max_memory - && (state->used_memory += size) > state->settings.max_memory) - { - return 0; - } - - return state->settings.mem_alloc (size, zero, state->settings.user_data); -} - -static int new_value (json_state * state, - json_value ** top, json_value ** root, json_value ** alloc, - json_type type) -{ - json_value * value; - int values_size; - - if (!state->first_pass) - { - value = *top = *alloc; - *alloc = (*alloc)->_reserved.next_alloc; - - if (!*root) - *root = value; - - switch (value->type) - { - case json_array: - - if (value->u.array.length == 0) - break; - - if (! (value->u.array.values = (json_value **) json_alloc - (state, value->u.array.length * sizeof (json_value *), 0)) ) - { - return 0; - } - - value->u.array.length = 0; - break; - - case json_object: - - if (value->u.object.length == 0) - break; - - values_size = sizeof (*value->u.object.values) * value->u.object.length; - - if (! (value->u.object.values = (json_object_entry *) json_alloc - (state, values_size + ((unsigned long) value->u.object.values), 0)) ) - { - return 0; - } - - value->_reserved.object_mem = (*(char **) &value->u.object.values) + values_size; - - value->u.object.length = 0; - break; - - case json_string: - - if (! (value->u.string.ptr = (json_char *) json_alloc - (state, (value->u.string.length + 1) * sizeof (json_char), 0)) ) - { - return 0; - } - - value->u.string.length = 0; - break; - - default: - break; - }; - - return 1; - } - - if (! (value = (json_value *) json_alloc - (state, sizeof (json_value) + state->settings.value_extra, 1))) - { - return 0; - } - - if (!*root) - *root = value; - - value->type = type; - value->parent = *top; - - #ifdef JSON_TRACK_SOURCE - value->line = state->cur_line; - value->col = state->cur_col; - #endif - - if (*alloc) - (*alloc)->_reserved.next_alloc = value; - - *alloc = *top = value; - - return 1; -} - -#define whitespace \ - case '\n': ++ state.cur_line; state.cur_col = 0; \ - case ' ': case '\t': case '\r' - -#define string_add(b) \ - do { if (!state.first_pass) string [string_length] = b; ++ string_length; } while (0); - -#define line_and_col \ - state.cur_line, state.cur_col - -static const long - flag_next = 1 << 0, - flag_reproc = 1 << 1, - flag_need_comma = 1 << 2, - flag_seek_value = 1 << 3, - flag_escaped = 1 << 4, - flag_string = 1 << 5, - flag_need_colon = 1 << 6, - flag_done = 1 << 7, - flag_num_negative = 1 << 8, - flag_num_zero = 1 << 9, - flag_num_e = 1 << 10, - flag_num_e_got_sign = 1 << 11, - flag_num_e_negative = 1 << 12, - flag_line_comment = 1 << 13, - flag_block_comment = 1 << 14; - -json_value * json_parse_ex (json_settings * settings, - const json_char * json, - size_t length, - char * error_buf) -{ - json_char error [json_error_max]; - const json_char * end; - json_value * top, * root, * alloc = 0; - json_state state = { 0 }; - long flags; - long num_digits = 0, num_e = 0; - json_int_t num_fraction = 0; - - /* Skip UTF-8 BOM - */ - if (length >= 3 && ((unsigned char) json [0]) == 0xEF - && ((unsigned char) json [1]) == 0xBB - && ((unsigned char) json [2]) == 0xBF) - { - json += 3; - length -= 3; - } - - error[0] = '\0'; - end = (json + length); - - memcpy (&state.settings, settings, sizeof (json_settings)); - - if (!state.settings.mem_alloc) - state.settings.mem_alloc = default_alloc; - - if (!state.settings.mem_free) - state.settings.mem_free = default_free; - - memset (&state.uint_max, 0xFF, sizeof (state.uint_max)); - memset (&state.ulong_max, 0xFF, sizeof (state.ulong_max)); - - state.uint_max -= 8; /* limit of how much can be added before next check */ - state.ulong_max -= 8; - - for (state.first_pass = 1; state.first_pass >= 0; -- state.first_pass) - { - json_uchar uchar; - unsigned char uc_b1, uc_b2, uc_b3, uc_b4; - json_char * string = 0; - unsigned int string_length = 0; - - top = root = 0; - flags = flag_seek_value; - - state.cur_line = 1; - - for (state.ptr = json ;; ++ state.ptr) - { - json_char b = (state.ptr == end ? 0 : *state.ptr); - - if (flags & flag_string) - { - if (!b) - { sprintf (error, "Unexpected EOF in string (at %d:%d)", line_and_col); - goto e_failed; - } - - if (string_length > state.uint_max) - goto e_overflow; - - if (flags & flag_escaped) - { - flags &= ~ flag_escaped; - - switch (b) - { - case 'b': string_add ('\b'); break; - case 'f': string_add ('\f'); break; - case 'n': string_add ('\n'); break; - case 'r': string_add ('\r'); break; - case 't': string_add ('\t'); break; - case 'u': - - if (end - state.ptr < 4 || - (uc_b1 = hex_value (*++ state.ptr)) == 0xFF || - (uc_b2 = hex_value (*++ state.ptr)) == 0xFF || - (uc_b3 = hex_value (*++ state.ptr)) == 0xFF || - (uc_b4 = hex_value (*++ state.ptr)) == 0xFF) - { - sprintf (error, "Invalid character value `%c` (at %d:%d)", b, line_and_col); - goto e_failed; - } - - uc_b1 = (uc_b1 << 4) | uc_b2; - uc_b2 = (uc_b3 << 4) | uc_b4; - uchar = (uc_b1 << 8) | uc_b2; - - if ((uchar & 0xF800) == 0xD800) { - json_uchar uchar2; - - if (end - state.ptr < 6 || (*++ state.ptr) != '\\' || (*++ state.ptr) != 'u' || - (uc_b1 = hex_value (*++ state.ptr)) == 0xFF || - (uc_b2 = hex_value (*++ state.ptr)) == 0xFF || - (uc_b3 = hex_value (*++ state.ptr)) == 0xFF || - (uc_b4 = hex_value (*++ state.ptr)) == 0xFF) - { - sprintf (error, "Invalid character value `%c` (at %d:%d)", b, line_and_col); - goto e_failed; - } - - uc_b1 = (uc_b1 << 4) | uc_b2; - uc_b2 = (uc_b3 << 4) | uc_b4; - uchar2 = (uc_b1 << 8) | uc_b2; - - uchar = 0x010000 | ((uchar & 0x3FF) << 10) | (uchar2 & 0x3FF); - } - - if (sizeof (json_char) >= sizeof (json_uchar) || (uchar <= 0x7F)) - { - string_add ((json_char) uchar); - break; - } - - if (uchar <= 0x7FF) - { - if (state.first_pass) - string_length += 2; - else - { string [string_length ++] = 0xC0 | (uchar >> 6); - string [string_length ++] = 0x80 | (uchar & 0x3F); - } - - break; - } - - if (uchar <= 0xFFFF) { - if (state.first_pass) - string_length += 3; - else - { string [string_length ++] = 0xE0 | (uchar >> 12); - string [string_length ++] = 0x80 | ((uchar >> 6) & 0x3F); - string [string_length ++] = 0x80 | (uchar & 0x3F); - } - - break; - } - - if (state.first_pass) - string_length += 4; - else - { string [string_length ++] = 0xF0 | (uchar >> 18); - string [string_length ++] = 0x80 | ((uchar >> 12) & 0x3F); - string [string_length ++] = 0x80 | ((uchar >> 6) & 0x3F); - string [string_length ++] = 0x80 | (uchar & 0x3F); - } - - break; - - default: - string_add (b); - }; - - continue; - } - - if (b == '\\') - { - flags |= flag_escaped; - continue; - } - - if (b == '"') - { - if (!state.first_pass) - string [string_length] = 0; - - flags &= ~ flag_string; - string = 0; - - switch (top->type) - { - case json_string: - - top->u.string.length = string_length; - flags |= flag_next; - - break; - - case json_object: - - if (state.first_pass) - (*(json_char **) &top->u.object.values) += string_length + 1; - else - { - top->u.object.values [top->u.object.length].name - = (json_char *) top->_reserved.object_mem; - - top->u.object.values [top->u.object.length].name_length - = string_length; - - (*(json_char **) &top->_reserved.object_mem) += string_length + 1; - } - - flags |= flag_seek_value | flag_need_colon; - continue; - - default: - break; - }; - } - else - { - string_add (b); - continue; - } - } - - if (state.settings.settings & json_enable_comments) - { - if (flags & (flag_line_comment | flag_block_comment)) - { - if (flags & flag_line_comment) - { - if (b == '\r' || b == '\n' || !b) - { - flags &= ~ flag_line_comment; - -- state.ptr; /* so null can be reproc'd */ - } - - continue; - } - - if (flags & flag_block_comment) - { - if (!b) - { sprintf (error, "%d:%d: Unexpected EOF in block comment", line_and_col); - goto e_failed; - } - - if (b == '*' && state.ptr < (end - 1) && state.ptr [1] == '/') - { - flags &= ~ flag_block_comment; - ++ state.ptr; /* skip closing sequence */ - } - - continue; - } - } - else if (b == '/') - { - if (! (flags & (flag_seek_value | flag_done)) && top->type != json_object) - { sprintf (error, "%d:%d: Comment not allowed here", line_and_col); - goto e_failed; - } - - if (++ state.ptr == end) - { sprintf (error, "%d:%d: EOF unexpected", line_and_col); - goto e_failed; - } - - switch (b = *state.ptr) - { - case '/': - flags |= flag_line_comment; - continue; - - case '*': - flags |= flag_block_comment; - continue; - - default: - sprintf (error, "%d:%d: Unexpected `%c` in comment opening sequence", line_and_col, b); - goto e_failed; - }; - } - } - - if (flags & flag_done) - { - if (!b) - break; - - switch (b) - { - whitespace: - continue; - - default: - - sprintf (error, "%d:%d: Trailing garbage: `%c`", - state.cur_line, state.cur_col, b); - - goto e_failed; - }; - } - - if (flags & flag_seek_value) - { - switch (b) - { - whitespace: - continue; - - case ']': - - if (top && top->type == json_array) - flags = (flags & ~ (flag_need_comma | flag_seek_value)) | flag_next; - else - { sprintf (error, "%d:%d: Unexpected ]", line_and_col); - goto e_failed; - } - - break; - - default: - - if (flags & flag_need_comma) - { - if (b == ',') - { flags &= ~ flag_need_comma; - continue; - } - else - { - sprintf (error, "%d:%d: Expected , before %c", - state.cur_line, state.cur_col, b); - - goto e_failed; - } - } - - if (flags & flag_need_colon) - { - if (b == ':') - { flags &= ~ flag_need_colon; - continue; - } - else - { - sprintf (error, "%d:%d: Expected : before %c", - state.cur_line, state.cur_col, b); - - goto e_failed; - } - } - - flags &= ~ flag_seek_value; - - switch (b) - { - case '{': - - if (!new_value (&state, &top, &root, &alloc, json_object)) - goto e_alloc_failure; - - continue; - - case '[': - - if (!new_value (&state, &top, &root, &alloc, json_array)) - goto e_alloc_failure; - - flags |= flag_seek_value; - continue; - - case '"': - - if (!new_value (&state, &top, &root, &alloc, json_string)) - goto e_alloc_failure; - - flags |= flag_string; - - string = top->u.string.ptr; - string_length = 0; - - continue; - - case 't': - - if ((end - state.ptr) < 3 || *(++ state.ptr) != 'r' || - *(++ state.ptr) != 'u' || *(++ state.ptr) != 'e') - { - goto e_unknown_value; - } - - if (!new_value (&state, &top, &root, &alloc, json_boolean)) - goto e_alloc_failure; - - top->u.boolean = 1; - - flags |= flag_next; - break; - - case 'f': - - if ((end - state.ptr) < 4 || *(++ state.ptr) != 'a' || - *(++ state.ptr) != 'l' || *(++ state.ptr) != 's' || - *(++ state.ptr) != 'e') - { - goto e_unknown_value; - } - - if (!new_value (&state, &top, &root, &alloc, json_boolean)) - goto e_alloc_failure; - - flags |= flag_next; - break; - - case 'n': - - if ((end - state.ptr) < 3 || *(++ state.ptr) != 'u' || - *(++ state.ptr) != 'l' || *(++ state.ptr) != 'l') - { - goto e_unknown_value; - } - - if (!new_value (&state, &top, &root, &alloc, json_null)) - goto e_alloc_failure; - - flags |= flag_next; - break; - - default: - - if (isdigit (b) || b == '-') - { - if (!new_value (&state, &top, &root, &alloc, json_integer)) - goto e_alloc_failure; - - if (!state.first_pass) - { - while (isdigit (b) || b == '+' || b == '-' - || b == 'e' || b == 'E' || b == '.') - { - if ( (++ state.ptr) == end) - { - b = 0; - break; - } - - b = *state.ptr; - } - - flags |= flag_next | flag_reproc; - break; - } - - flags &= ~ (flag_num_negative | flag_num_e | - flag_num_e_got_sign | flag_num_e_negative | - flag_num_zero); - - num_digits = 0; - num_fraction = 0; - num_e = 0; - - if (b != '-') - { - flags |= flag_reproc; - break; - } - - flags |= flag_num_negative; - continue; - } - else - { sprintf (error, "%d:%d: Unexpected %c when seeking value", line_and_col, b); - goto e_failed; - } - }; - }; - } - else - { - switch (top->type) - { - case json_object: - - switch (b) - { - whitespace: - continue; - - case '"': - - if (flags & flag_need_comma) - { sprintf (error, "%d:%d: Expected , before \"", line_and_col); - goto e_failed; - } - - flags |= flag_string; - - string = (json_char *) top->_reserved.object_mem; - string_length = 0; - - break; - - case '}': - - flags = (flags & ~ flag_need_comma) | flag_next; - break; - - case ',': - - if (flags & flag_need_comma) - { - flags &= ~ flag_need_comma; - break; - } - - default: - sprintf (error, "%d:%d: Unexpected `%c` in object", line_and_col, b); - goto e_failed; - }; - - break; - - case json_integer: - case json_double: - - if (isdigit (b)) - { - ++ num_digits; - - if (top->type == json_integer || flags & flag_num_e) - { - if (! (flags & flag_num_e)) - { - if (flags & flag_num_zero) - { sprintf (error, "%d:%d: Unexpected `0` before `%c`", line_and_col, b); - goto e_failed; - } - - if (num_digits == 1 && b == '0') - flags |= flag_num_zero; - } - else - { - flags |= flag_num_e_got_sign; - num_e = (num_e * 10) + (b - '0'); - continue; - } - - top->u.integer = (top->u.integer * 10) + (b - '0'); - continue; - } - - num_fraction = (num_fraction * 10) + (b - '0'); - continue; - } - - if (b == '+' || b == '-') - { - if ( (flags & flag_num_e) && !(flags & flag_num_e_got_sign)) - { - flags |= flag_num_e_got_sign; - - if (b == '-') - flags |= flag_num_e_negative; - - continue; - } - } - else if (b == '.' && top->type == json_integer) - { - if (!num_digits) - { sprintf (error, "%d:%d: Expected digit before `.`", line_and_col); - goto e_failed; - } - - top->type = json_double; - top->u.dbl = (double) top->u.integer; - - num_digits = 0; - continue; - } - - if (! (flags & flag_num_e)) - { - if (top->type == json_double) - { - if (!num_digits) - { sprintf (error, "%d:%d: Expected digit after `.`", line_and_col); - goto e_failed; - } - - top->u.dbl += ((double) num_fraction) / (pow (10.0, (double) num_digits)); - } - - if (b == 'e' || b == 'E') - { - flags |= flag_num_e; - - if (top->type == json_integer) - { - top->type = json_double; - top->u.dbl = (double) top->u.integer; - } - - num_digits = 0; - flags &= ~ flag_num_zero; - - continue; - } - } - else - { - if (!num_digits) - { sprintf (error, "%d:%d: Expected digit after `e`", line_and_col); - goto e_failed; - } - - top->u.dbl *= pow (10.0, (double) - (flags & flag_num_e_negative ? - num_e : num_e)); - } - - if (flags & flag_num_negative) - { - if (top->type == json_integer) - top->u.integer = - top->u.integer; - else - top->u.dbl = - top->u.dbl; - } - - flags |= flag_next | flag_reproc; - break; - - default: - break; - }; - } - - if (flags & flag_reproc) - { - flags &= ~ flag_reproc; - -- state.ptr; - } - - if (flags & flag_next) - { - flags = (flags & ~ flag_next) | flag_need_comma; - - if (!top->parent) - { - /* root value done */ - - flags |= flag_done; - continue; - } - - if (top->parent->type == json_array) - flags |= flag_seek_value; - - if (!state.first_pass) - { - json_value * parent = top->parent; - - switch (parent->type) - { - case json_object: - - parent->u.object.values - [parent->u.object.length].value = top; - - break; - - case json_array: - - parent->u.array.values - [parent->u.array.length] = top; - - break; - - default: - break; - }; - } - - if ( (++ top->parent->u.array.length) > state.uint_max) - goto e_overflow; - - top = top->parent; - - continue; - } - } - - alloc = root; - } - - return root; - -e_unknown_value: - - sprintf (error, "%d:%d: Unknown value", line_and_col); - goto e_failed; - -e_alloc_failure: - - strcpy (error, "Memory allocation failure"); - goto e_failed; - -e_overflow: - - sprintf (error, "%d:%d: Too long (caught overflow)", line_and_col); - goto e_failed; - -e_failed: - - if (error_buf) - { - if (*error) - strcpy (error_buf, error); - else - strcpy (error_buf, "Unknown error"); - } - - if (state.first_pass) - alloc = root; - - while (alloc) - { - top = alloc->_reserved.next_alloc; - state.settings.mem_free (alloc, state.settings.user_data); - alloc = top; - } - - if (!state.first_pass) - json_value_free_ex (&state.settings, root); - - return 0; -} - -json_value * json_parse (const json_char * json, size_t length) -{ - json_settings settings = { 0 }; - return json_parse_ex (&settings, json, length, 0); -} - -void json_value_free_ex (json_settings * settings, json_value * value) -{ - json_value * cur_value; - - if (!value) - return; - - value->parent = 0; - - while (value) - { - switch (value->type) - { - case json_array: - - if (!value->u.array.length) - { - settings->mem_free (value->u.array.values, settings->user_data); - break; - } - - value = value->u.array.values [-- value->u.array.length]; - continue; - - case json_object: - - if (!value->u.object.length) - { - settings->mem_free (value->u.object.values, settings->user_data); - break; - } - - value = value->u.object.values [-- value->u.object.length].value; - continue; - - case json_string: - - settings->mem_free (value->u.string.ptr, settings->user_data); - break; - - default: - break; - }; - - cur_value = value; - value = value->parent; - settings->mem_free (cur_value, settings->user_data); - } -} - -void json_value_free (json_value * value) -{ - json_settings settings = { 0 }; - settings.mem_free = default_free; - json_value_free_ex (&settings, value); -} - diff --git a/ext/json-parser/json.h b/ext/json-parser/json.h deleted file mode 100644 index f6549ec..0000000 --- a/ext/json-parser/json.h +++ /dev/null @@ -1,283 +0,0 @@ - -/* vim: set et ts=3 sw=3 sts=3 ft=c: - * - * Copyright (C) 2012, 2013, 2014 James McLaughlin et al. All rights reserved. - * https://github.com/udp/json-parser - * - * 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. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 _JSON_H -#define _JSON_H - -#ifndef json_char - #define json_char char -#endif - -#ifndef json_int_t - #ifndef _MSC_VER - #include - #define json_int_t int64_t - #else - #define json_int_t __int64 - #endif -#endif - -#include - -#ifdef __cplusplus - - #include - - extern "C" - { - -#endif - -typedef struct -{ - unsigned long max_memory; - int settings; - - /* Custom allocator support (leave null to use malloc/free) - */ - - void * (* mem_alloc) (size_t, int zero, void * user_data); - void (* mem_free) (void *, void * user_data); - - void * user_data; /* will be passed to mem_alloc and mem_free */ - - size_t value_extra; /* how much extra space to allocate for values? */ - -} json_settings; - -#define json_enable_comments 0x01 - -typedef enum -{ - json_none, - json_object, - json_array, - json_integer, - json_double, - json_string, - json_boolean, - json_null - -} json_type; - -extern const struct _json_value json_value_none; - -typedef struct _json_object_entry -{ - json_char * name; - unsigned int name_length; - - struct _json_value * value; - -} json_object_entry; - -typedef struct _json_value -{ - struct _json_value * parent; - - json_type type; - - union - { - int boolean; - json_int_t integer; - double dbl; - - struct - { - unsigned int length; - json_char * ptr; /* null terminated */ - - } string; - - struct - { - unsigned int length; - - json_object_entry * values; - - #if defined(__cplusplus) && __cplusplus >= 201103L - decltype(values) begin () const - { return values; - } - decltype(values) end () const - { return values + length; - } - #endif - - } object; - - struct - { - unsigned int length; - struct _json_value ** values; - - #if defined(__cplusplus) && __cplusplus >= 201103L - decltype(values) begin () const - { return values; - } - decltype(values) end () const - { return values + length; - } - #endif - - } array; - - } u; - - union - { - struct _json_value * next_alloc; - void * object_mem; - - } _reserved; - - #ifdef JSON_TRACK_SOURCE - - /* Location of the value in the source JSON - */ - unsigned int line, col; - - #endif - - - /* Some C++ operator sugar */ - - #ifdef __cplusplus - - public: - - inline _json_value () - { memset (this, 0, sizeof (_json_value)); - } - - inline const struct _json_value &operator [] (int index) const - { - if (type != json_array || index < 0 - || ((unsigned int) index) >= u.array.length) - { - return json_value_none; - } - - return *u.array.values [index]; - } - - inline const struct _json_value &operator [] (const char * index) const - { - if (type != json_object) - return json_value_none; - - for (unsigned int i = 0; i < u.object.length; ++ i) - if (!strcmp (u.object.values [i].name, index)) - return *u.object.values [i].value; - - return json_value_none; - } - - inline operator const char * () const - { - switch (type) - { - case json_string: - return u.string.ptr; - - default: - return ""; - }; - } - - inline operator json_int_t () const - { - switch (type) - { - case json_integer: - return u.integer; - - case json_double: - return (json_int_t) u.dbl; - - default: - return 0; - }; - } - - inline operator bool () const - { - if (type != json_boolean) - return false; - - return u.boolean != 0; - } - - inline operator double () const - { - switch (type) - { - case json_integer: - return (double) u.integer; - - case json_double: - return u.dbl; - - default: - return 0; - }; - } - - #endif - -} json_value; - -json_value * json_parse (const json_char * json, - size_t length); - -#define json_error_max 128 -json_value * json_parse_ex (json_settings * settings, - const json_char * json, - size_t length, - char * error); - -void json_value_free (json_value *); - - -/* Not usually necessary, unless you used a custom mem_alloc and now want to - * use a custom mem_free. - */ -void json_value_free_ex (json_settings * settings, - json_value *); - - -#ifdef __cplusplus - } /* extern "C" */ -#endif - -#endif - - diff --git a/ext/json/README.md b/ext/json/README.md index cb05d74..4bcbe97 100644 --- a/ext/json/README.md +++ b/ext/json/README.md @@ -1,30 +1,30 @@ -![JSON for Modern C++](https://raw.githubusercontent.com/nlohmann/json/master/doc/json.gif) +[![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?svg=true)](https://ci.appveyor.com/project/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) -[![Coverity Scan Build Status](https://scan.coverity.com/projects/5550/badge.svg)](https://scan.coverity.com/projects/nlohmann-json) -[![Try online](https://img.shields.io/badge/try-online-blue.svg)](http://melpon.org/wandbox/permlink/wuiuqYiYqRTdI3rG) +[![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 know, what I mean. +- **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. +- **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) with a decent regular expression processor should be even faster (but would consist of more files which makes the integration harder). +- **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. @@ -283,15 +283,15 @@ 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); // only one entry for "one" is used -// maybe ["one", "two", "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 are 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. +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} }; @@ -323,7 +323,7 @@ json j_original = R"({ })"_json; // access members with a JSON pointer (RFC 6901) -j_original["/baz/2"_json_pointer]; +j_original["/baz/1"_json_pointer]; // "two" // a JSON patch (RFC 6902) @@ -344,8 +344,8 @@ json j_result = j_original.patch(j_patch); json::diff(j_result, j_original); // [ // { "op":" replace", "path": "/baz", "value": ["one", "two", "three"] }, -// { "op":"remove","path":"/hello" }, -// { "op":"add","path":"/foo","value":"bar" } +// { "op": "remove","path": "/hello" }, +// { "op": "add", "path": "/foo", "value": "bar" } // ] ``` @@ -390,7 +390,7 @@ Though it's 2016 already, the support for C++11 is still a bit sparse. Currently - GCC 4.9 - 6.0 (and possibly later) - Clang 3.4 - 3.9 (and possibly later) -- Microsoft Visual C++ 2015 / 14.0 (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. @@ -416,7 +416,13 @@ The following compilers are currently used in continuous integration at [Travis] | 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) | @@ -424,7 +430,7 @@ The following compilers are currently used in continuous integration at [Travis] | 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.5.0 (OSX 10.11.5) | Apple LLVM version 8.0.0 (clang-800.0.24.1) | +| 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 | @@ -483,14 +489,29 @@ I deeply appreciate the help of the following people. - [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). +- 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 @@ -498,11 +519,20 @@ Thanks a lot for helping out! To compile and run the tests, you need to execute ```sh -$ make -$ ./json_unit "*" +$ make check =============================================================================== -All tests passed (5568721 assertions in 32 test cases) +All tests passed (8905491 assertions in 36 test cases) +``` + +Alternatively, you can use [CMake](https://cmake.org) and run + +```sh +$ mkdir build +$ cd build +$ cmake .. +$ make +$ ctest ``` For more information, have a look at the file [.travis.yml](https://github.com/nlohmann/json/blob/master/.travis.yml). diff --git a/ext/json/json.hpp b/ext/json/json.hpp index 9d6687d..9d48e7a 100644 --- a/ext/json/json.hpp +++ b/ext/json/json.hpp @@ -1,11 +1,11 @@ /* __ _____ _____ _____ __| | __| | | | JSON for Modern C++ -| | |__ | | | | | | version 2.0.0 +| | |__ | | | | | | version 2.0.10 |_____|_____|_____|_|___| https://github.com/nlohmann/json Licensed under the MIT License . -Copyright (c) 2013-2016 Niels Lohmann . +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 @@ -29,29 +29,45 @@ SOFTWARE. #ifndef NLOHMANN_JSON_HPP #define NLOHMANN_JSON_HPP -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#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__) @@ -59,6 +75,21 @@ SOFTWARE. #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 @@ -76,29 +107,25 @@ 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 char test(typename C::mapped_type*); - template static char (&test(...))[2]; - public: - static constexpr bool value = sizeof(test(0)) == 1; -}; + template + static int detect(U&&); -/*! -@brief helper class to create locales with decimal point -@sa https://github.com/nlohmann/json/issues/51#issuecomment-86869315 -*/ -class DecimalSeparator : public std::numpunct -{ - protected: - char do_decimal_point() const - { - return '.'; - } + static void detect(...); + public: + static constexpr bool value = + std::is_integral()))>::value; }; } @@ -163,6 +190,13 @@ default) 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 @@ -188,17 +222,13 @@ 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; @@ -207,6 +237,8 @@ class basic_json ///////////////////// /// @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 @@ -231,9 +263,9 @@ class basic_json using const_pointer = typename std::allocator_traits::const_pointer; /// an iterator for a basic_json container - class iterator; + using iterator = iter_impl; /// a const iterator for a basic_json container - class const_iterator; + 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 @@ -256,6 +288,8 @@ class basic_json /////////////////////////// /// @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. /// @{ /*! @@ -358,7 +392,7 @@ class basic_json @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`) + @tparam AllocatorType allocator to use for arrays (e.g., `std::allocator`) #### Default type @@ -595,15 +629,14 @@ class basic_json > 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. + 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 @@ -691,7 +724,19 @@ class basic_json 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(), and @ref is_discarded() rely on it. + 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 */ @@ -702,7 +747,7 @@ class basic_json array, ///< array (ordered collection of values) string, ///< string value boolean, ///< boolean value - number_integer, ///< number value (integer) + 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 @@ -711,73 +756,6 @@ class basic_json private: - /*! - @brief a type to hold JSON type information - - This bitfield type holds information about JSON types. It is internally - used to hold the basic JSON type enumeration, as well as additional - information in the case of values that have been parsed from a string - including whether of not it was created directly or parsed, and in the - case of floating point numbers the number of significant figures in the - original representaiton and if it was in exponential form, if a '+' was - included in the exponent and the capitilization of the exponent marker. - The sole purpose of this information is to permit accurate round trips. - - @since version 2.0.0 - */ - union type_data_t - { - struct - { - /// the type of the value (@ref value_t) - uint16_t type : 4; - /// whether the number was parsed from a string - uint16_t parsed : 1; - /// whether parsed number contained an exponent ('e'/'E') - uint16_t has_exp : 1; - /// whether parsed number contained a plus in the exponent - uint16_t exp_plus : 1; - /// whether parsed number's exponent was capitalized ('E') - uint16_t exp_cap : 1; - /// the number of figures for a parsed number - uint16_t precision : 8; - } bits; - uint16_t data; - - /// return the type as value_t - operator value_t() const - { - return static_cast(bits.type); - } - - /// test type for equality (ignore other fields) - bool operator==(const value_t& rhs) const - { - return static_cast(bits.type) == rhs; - } - - /// assignment - type_data_t& operator=(value_t rhs) - { - bits.type = static_cast(rhs) & 15; // avoid overflow - return *this; - } - - /// construct from value_t - type_data_t(value_t t) noexcept - { - *reinterpret_cast(this) = 0; - bits.type = static_cast(t) & 15; // avoid overflow - } - - /// default constructor - type_data_t() noexcept - { - data = 0; - bits.type = reinterpret_cast(value_t::null); - } - }; - /// helper for exception-safe object creation template static T* create(Args&& ... args) @@ -789,6 +767,7 @@ class basic_json }; std::unique_ptr object(alloc.allocate(1), deleter); alloc.construct(object.get(), std::forward(args)...); + assert(object.get() != nullptr); return object.release(); } @@ -799,7 +778,24 @@ class basic_json /*! @brief a JSON value - The actual storage for a JSON value of the @ref basic_json class. + 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 */ @@ -877,8 +873,17 @@ class basic_json break; } + case value_t::null: + { + break; + } + default: { + if (t == value_t::null) + { + throw std::domain_error("961c151d2e87f2686a955a9be24d316f1362bf21 2.0.10"); // LCOV_EXCL_LINE + } break; } } @@ -903,6 +908,21 @@ class basic_json } }; + /*! + @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: ////////////////////////// @@ -915,6 +935,8 @@ class basic_json 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 @@ -937,12 +959,13 @@ class basic_json @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&, parser_callback_t) or - @ref parse(const string_t&, 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. + 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 @@ -957,6 +980,8 @@ class basic_json 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: @@ -978,11 +1003,13 @@ class basic_json skipped completely or replaced by an empty discarded object. @sa @ref parse(std::istream&, parser_callback_t) or - @ref parse(const string_t&, parser_callback_t) for examples + @ref parse(const CharT, const parser_callback_t) for examples @since version 1.0.0 */ - using parser_callback_t = std::function; + using parser_callback_t = std::function; ////////////////// @@ -990,6 +1017,8 @@ class basic_json ////////////////// /// @name constructors and destructors + /// Constructors of class @ref basic_json, copy/move constructor, copy + /// assignment, static functions creating objects, and the destructor. /// @{ /*! @@ -1033,40 +1062,15 @@ class basic_json */ basic_json(const value_t value_type) : m_type(value_type), m_value(value_type) - {} + { + assert_invariant(); + } /*! - @brief create a null object (implicitly) + @brief create a null object - Create a `null` JSON value. This is the implicit version of the `null` - value constructor as it takes no parameters. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this constructor never throws - exceptions. - - @requirement This function helps `basic_json` satisfying the - [Container](http://en.cppreference.com/w/cpp/concept/Container) - requirements: - - The complexity is constant. - - As postcondition, it holds: `basic_json().empty() == true`. - - @liveexample{The following code shows the constructor for a `null` JSON - value.,basic_json} - - @sa @ref basic_json(std::nullptr_t) -- create a `null` value - - @since version 1.0.0 - */ - basic_json() = default; - - /*! - @brief create a null object (explicitly) - - Create a `null` JSON value. This is the explicitly version of the `null` - value constructor as it takes a null pointer as parameter. It allows to - create `null` values by explicitly assigning a `nullptr` to a JSON value. + 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. @@ -1075,17 +1079,16 @@ class basic_json @exceptionsafety No-throw guarantee: this constructor never throws exceptions. - @liveexample{The following code shows the constructor with null pointer - parameter.,basic_json__nullptr_t} - - @sa @ref basic_json() -- default constructor (implicitly creating a `null` - value) + @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) noexcept + basic_json(std::nullptr_t = nullptr) noexcept : basic_json(value_t::null) - {} + { + assert_invariant(); + } /*! @brief create an object (explicit) @@ -1108,7 +1111,9 @@ class basic_json */ basic_json(const object_t& val) : m_type(value_t::object), m_value(val) - {} + { + assert_invariant(); + } /*! @brief create an object (implicit) @@ -1136,17 +1141,16 @@ class basic_json @since version 1.0.0 */ - template ::value and - std::is_constructible::value, int>::type - = 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(); } /*! @@ -1170,7 +1174,9 @@ class basic_json */ basic_json(const array_t& val) : m_type(value_t::array), m_value(val) - {} + { + assert_invariant(); + } /*! @brief create an array (implicit) @@ -1198,22 +1204,21 @@ class basic_json @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> + 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(); } /*! @@ -1239,7 +1244,9 @@ class basic_json */ basic_json(const string_t& val) : m_type(value_t::string), m_value(val) - {} + { + assert_invariant(); + } /*! @brief create a string (explicit) @@ -1263,7 +1270,9 @@ class basic_json */ basic_json(const typename string_t::value_type* val) : basic_json(string_t(val)) - {} + { + assert_invariant(); + } /*! @brief create a string (implicit) @@ -1288,13 +1297,13 @@ class basic_json @since version 1.0.0 */ - template ::value, int>::type - = 0> + template::value, int>::type = 0> basic_json(const CompatibleStringType& val) : basic_json(string_t(val)) - {} + { + assert_invariant(); + } /*! @brief create a boolean (explicit) @@ -1312,7 +1321,9 @@ class basic_json */ basic_json(boolean_t val) noexcept : m_type(value_t::boolean), m_value(val) - {} + { + assert_invariant(); + } /*! @brief create an integer number (explicit) @@ -1337,15 +1348,14 @@ class basic_json @since version 1.0.0 */ - template::value) - and std::is_same::value - , int>::type - = 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) @@ -1375,7 +1385,9 @@ class basic_json 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) @@ -1402,25 +1414,25 @@ class basic_json @since version 1.0.0 */ - template::value and std::numeric_limits::is_integer and std::numeric_limits::is_signed, - CompatibleNumberIntegerType>::type - = 0> + 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. + @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 @@ -1431,15 +1443,14 @@ class basic_json @since version 2.0.0 */ - template::value) - and std::is_same::value - , int>::type - = 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) @@ -1461,17 +1472,17 @@ class basic_json @since version 2.0.0 */ - template ::value and - std::numeric_limits::is_integer and - not std::numeric_limits::is_signed, - CompatibleNumberUnsignedType>::type - = 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) @@ -1484,8 +1495,8 @@ class basic_json 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. + In case the parameter @a val is not a number, a JSON null value is created + instead. @complexity Constant. @@ -1506,6 +1517,8 @@ class basic_json m_type = value_t::null; m_value = json_value(); } + + assert_invariant(); } /*! @@ -1539,14 +1552,14 @@ class basic_json @since version 1.0.0 */ - template::value and - std::is_floating_point::value>::type - > + 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 @@ -1558,21 +1571,21 @@ class basic_json 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. + 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. + 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. + 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. + 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: @@ -1621,22 +1634,13 @@ class basic_json bool type_deduction = true, value_t manual_type = value_t::array) { - // the initializer list could describe an object - bool is_an_object = true; - // check if each element is an array with two elements whose first // element is a string - for (const auto& element : init) + bool is_an_object = std::all_of(init.begin(), init.end(), + [](const basic_json & element) { - if (not element.is_array() or element.size() != 2 - or not element[0].is_string()) - { - // we found an element that makes it impossible to use the - // initializer list as object - is_an_object = false; - break; - } - } + 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) @@ -1660,12 +1664,10 @@ class basic_json m_type = value_t::object; m_value = value_t::object; - assert(m_value.object != nullptr); - - for (auto& element : init) + std::for_each(init.begin(), init.end(), [this](const basic_json & element) { m_value.object->emplace(*(element[0].m_value.string), element[1]); - } + }); } else { @@ -1673,6 +1675,8 @@ class basic_json m_type = value_t::array; m_value.array = create(init); } + + assert_invariant(); } /*! @@ -1777,6 +1781,7 @@ class basic_json : m_type(value_t::array) { m_value.array = create(cnt, val); + assert_invariant(); } /*! @@ -1797,6 +1802,9 @@ class basic_json @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, @@ -1813,20 +1821,23 @@ class basic_json @since version 1.0.0 */ - template ::value or - std::is_same::value - , int>::type - = 0> - basic_json(InputIT first, InputIT last) : m_type(first.m_object->m_type) + 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) { @@ -1853,35 +1864,30 @@ class basic_json { case value_t::number_integer: { - assert(first.m_object != nullptr); m_value.number_integer = first.m_object->m_value.number_integer; break; } case value_t::number_unsigned: { - assert(first.m_object != nullptr); m_value.number_unsigned = first.m_object->m_value.number_unsigned; break; } case value_t::number_float: { - assert(first.m_object != nullptr); m_value.number_float = first.m_object->m_value.number_float; break; } case value_t::boolean: { - assert(first.m_object != nullptr); m_value.boolean = first.m_object->m_value.boolean; break; } case value_t::string: { - assert(first.m_object != nullptr); m_value = *first.m_object->m_value.string; break; } @@ -1900,10 +1906,11 @@ class basic_json default: { - assert(first.m_object != nullptr); throw std::domain_error("cannot use construct with iterators from " + first.m_object->type_name()); } } + + assert_invariant(); } /*! @@ -1920,15 +1927,25 @@ class basic_json @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 + @since version 2.0.0, deprecated in version 2.0.3, to be removed in + version 3.0.0 */ - explicit basic_json(std::istream& i, parser_callback_t cb = nullptr) + JSON_DEPRECATED + explicit basic_json(std::istream& i, const parser_callback_t cb = nullptr) { *this = parser(i, cb).parse(); + assert_invariant(); } /////////////////////////////////////// @@ -1960,25 +1977,25 @@ class basic_json 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: { - assert(other.m_value.object != nullptr); m_value = *other.m_value.object; break; } case value_t::array: { - assert(other.m_value.array != nullptr); m_value = *other.m_value.array; break; } case value_t::string: { - assert(other.m_value.string != nullptr); m_value = *other.m_value.string; break; } @@ -2012,6 +2029,8 @@ class basic_json break; } } + + assert_invariant(); } /*! @@ -2036,9 +2055,14 @@ class basic_json : 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(); } /*! @@ -2071,9 +2095,14 @@ class basic_json 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; } @@ -2094,6 +2123,8 @@ class basic_json */ ~basic_json() { + assert_invariant(); + switch (m_type) { case value_t::object: @@ -2136,19 +2167,20 @@ class basic_json /////////////////////// /// @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 @p json.dumps() function, and currently supports its @p indent + Python's `json.dumps()` function, and currently supports its @a indent parameter. - @param[in] indent if indent is nonnegative, then array elements and object + @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 + `0` will only insert newlines. `-1` (the default) selects the most compact + representation. @return string containing the serialization of the JSON value @@ -2164,6 +2196,14 @@ class basic_json 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) { @@ -2540,16 +2580,13 @@ class basic_json ////////////////// /// get an object (explicit) - template ::value and - std::is_convertible::value - , int>::type = 0> + template::value and + std::is_convertible::value, int>::type = 0> T get_impl(T*) const { if (is_object()) { - assert(m_value.object != nullptr); return T(m_value.object->begin(), m_value.object->end()); } else @@ -2563,7 +2600,6 @@ class basic_json { if (is_object()) { - assert(m_value.object != nullptr); return *(m_value.object); } else @@ -2573,20 +2609,17 @@ class basic_json } /// 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> + 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; - assert(m_value.array != nullptr); std::transform(m_value.array->begin(), m_value.array->end(), std::inserter(to_vector, to_vector.end()), [](basic_json i) { @@ -2601,17 +2634,14 @@ class basic_json } /// get an array (explicit) - template ::value and - not std::is_same::value - , int>::type = 0> + 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; - assert(m_value.array != nullptr); 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) @@ -2627,16 +2657,13 @@ class basic_json } /// get an array (explicit) - template ::value and - not has_mapped_type::value - , int>::type = 0> + template::value and + not has_mapped_type::value, int>::type = 0> T get_impl(T*) const { if (is_array()) { - assert(m_value.array != nullptr); return T(m_value.array->begin(), m_value.array->end()); } else @@ -2650,7 +2677,6 @@ class basic_json { if (is_array()) { - assert(m_value.array != nullptr); return *(m_value.array); } else @@ -2660,15 +2686,12 @@ class basic_json } /// get a string (explicit) - template ::value - , int>::type = 0> + template::value, int>::type = 0> T get_impl(T*) const { if (is_string()) { - assert(m_value.string != nullptr); return *m_value.string; } else @@ -2678,10 +2701,8 @@ class basic_json } /// get a number (explicit) - template::value - , int>::type = 0> + template::value, int>::type = 0> T get_impl(T*) const { switch (m_type) @@ -2814,8 +2835,10 @@ class basic_json template static ReferenceType get_ref_impl(ThisType& obj) { - // delegate the call to get_ptr<>() + // helper type using PointerType = typename std::add_pointer::type; + + // delegate the call to get_ptr<>() auto ptr = obj.template get_ptr(); if (ptr != nullptr) @@ -2832,6 +2855,7 @@ class basic_json public: /// @name value access + /// Direct access to the stored value of a JSON value. /// @{ /*! @@ -2867,10 +2891,8 @@ class basic_json @since version 1.0.0 */ - template::value - , int>::type = 0> + template::value, int>::type = 0> ValueType get() const { return get_impl(static_cast(nullptr)); @@ -2882,7 +2904,8 @@ class basic_json Explicit pointer access to the internally stored JSON value. No copies are made. - @warning The pointer becomes invalid if the underlying JSON object changes. + @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, @@ -2902,10 +2925,8 @@ class basic_json @since version 1.0.0 */ - template::value - , int>::type = 0> + template::value, int>::type = 0> PointerType get() noexcept { // delegate the call to get_ptr @@ -2916,10 +2937,8 @@ class basic_json @brief get a pointer value (explicit) @copydoc get() */ - template::value - , int>::type = 0> + template::value, int>::type = 0> constexpr const PointerType get() const noexcept { // delegate the call to get_ptr @@ -2937,7 +2956,8 @@ class basic_json @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. + @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 @@ -2951,12 +2971,25 @@ class basic_json @since version 1.0.0 */ - template::value - , int>::type = 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)); } @@ -2965,13 +2998,26 @@ class basic_json @brief get a pointer value (implicit) @copydoc get_ptr() */ - template::value - and std::is_const::type>::value - , int>::type = 0> + 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)); } @@ -2987,7 +3033,7 @@ class basic_json @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. + @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 @@ -3002,10 +3048,8 @@ class basic_json @since version 1.1.0 */ - template::value - , int>::type = 0> + template::value, int>::type = 0> ReferenceType get_ref() { // delegate call to get_ref_impl @@ -3016,11 +3060,9 @@ class basic_json @brief get a reference value (implicit) @copydoc get_ref() */ - template::value - and std::is_const::type>::value - , int>::type = 0> + template::value and + std::is_const::type>::value, int>::type = 0> ReferenceType get_ref() const { // delegate call to get_ref_impl @@ -3055,10 +3097,9 @@ class basic_json @since version 1.0.0 */ - template < typename ValueType, typename - std::enable_if < - not std::is_pointer::value - and not std::is_same::value + 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 @@ -3077,6 +3118,7 @@ class basic_json //////////////////// /// @name element access + /// Access to the JSON value. /// @{ /*! @@ -3108,7 +3150,6 @@ class basic_json { try { - assert(m_value.array != nullptr); return m_value.array->at(idx); } catch (std::out_of_range&) @@ -3152,7 +3193,6 @@ class basic_json { try { - assert(m_value.array != nullptr); return m_value.array->at(idx); } catch (std::out_of_range&) @@ -3200,7 +3240,6 @@ class basic_json { try { - assert(m_value.object != nullptr); return m_value.object->at(key); } catch (std::out_of_range&) @@ -3248,7 +3287,6 @@ class basic_json { try { - assert(m_value.object != nullptr); return m_value.object->at(key); } catch (std::out_of_range&) @@ -3295,16 +3333,18 @@ class basic_json { 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 until given idx is reached - assert(m_value.array != nullptr); - for (size_t i = m_value.array->size(); i <= idx; ++i) + // fill up array with null values if given idx is outside range + if (idx >= m_value.array->size()) { - m_value.array->push_back(basic_json()); + m_value.array->insert(m_value.array->end(), + idx - m_value.array->size() + 1, + basic_json()); } return m_value.array->operator[](idx); @@ -3339,7 +3379,6 @@ class basic_json // const operator[] only works for arrays if (is_array()) { - assert(m_value.array != nullptr); return m_value.array->operator[](idx); } else @@ -3382,12 +3421,12 @@ class basic_json { m_type = value_t::object; m_value.object = create(); + assert_invariant(); } // operator[] only works for objects if (is_object()) { - assert(m_value.object != nullptr); return m_value.object->operator[](key); } else @@ -3409,6 +3448,9 @@ class basic_json @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"` @@ -3428,7 +3470,6 @@ class basic_json // const operator[] only works for objects if (is_object()) { - assert(m_value.object != nullptr); assert(m_value.object->find(key) != m_value.object->end()); return m_value.object->find(key)->second; } @@ -3541,12 +3582,12 @@ class basic_json { m_type = value_t::object; m_value = value_t::object; + assert_invariant(); } // at only works for objects if (is_object()) { - assert(m_value.object != nullptr); return m_value.object->operator[](key); } else @@ -3568,6 +3609,9 @@ class basic_json @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"` @@ -3588,7 +3632,6 @@ class basic_json // at only works for objects if (is_object()) { - assert(m_value.object != nullptr); assert(m_value.object->find(key) != m_value.object->end()); return m_value.object->find(key)->second; } @@ -3601,8 +3644,8 @@ class basic_json /*! @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. + 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} @@ -3646,10 +3689,8 @@ class basic_json @since version 1.0.0 */ - template ::value - , int>::type = 0> + template::value, int>::type = 0> ValueType value(const typename object_t::key_type& key, ValueType default_value) const { // at only works for objects @@ -3674,13 +3715,86 @@ class basic_json /*! @brief overload for a default value of type const char* - @copydoc basic_json::value() + @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 @@ -3688,13 +3802,14 @@ class basic_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 cast of number, string, or boolean values, a + 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). + 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 @@ -3730,13 +3845,14 @@ class basic_json @endcode @return In case of a structured type (array or object), a reference to the - last element is returned. In cast of number, string, or boolean values, a + 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). + 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. @@ -3778,7 +3894,7 @@ class basic_json @return Iterator following the last removed element. If the iterator @a pos refers to the last element, the `end()` iterator is returned. - @tparam InteratorType an @ref iterator or @ref const_iterator + @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. @@ -3800,7 +3916,7 @@ class basic_json @liveexample{The example shows the result of `erase()` for different JSON types.,erase__IteratorType} - @sa @ref erase(InteratorType, InteratorType) -- removes the elements in + @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 @@ -3809,13 +3925,11 @@ class basic_json @since version 1.0.0 */ - template ::value or - std::is_same::value - , int>::type - = 0> - InteratorType erase(InteratorType pos) + 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) @@ -3823,7 +3937,7 @@ class basic_json throw std::domain_error("iterator does not fit current value"); } - InteratorType result = end(); + IteratorType result = end(); switch (m_type) { @@ -3840,24 +3954,25 @@ class basic_json if (is_string()) { - delete m_value.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: { - assert(m_value.object != nullptr); result.m_it.object_iterator = m_value.object->erase(pos.m_it.object_iterator); break; } case value_t::array: { - assert(m_value.array != nullptr); result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator); break; } @@ -3886,7 +4001,7 @@ class basic_json @return Iterator following the last removed element. If the iterator @a second refers to the last element, the `end()` iterator is returned. - @tparam InteratorType an @ref iterator or @ref const_iterator + @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. @@ -3909,7 +4024,7 @@ class basic_json @liveexample{The example shows the result of `erase()` for different JSON types.,erase__IteratorType_IteratorType} - @sa @ref erase(InteratorType) -- removes the element at a given position + @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 @@ -3917,13 +4032,11 @@ class basic_json @since version 1.0.0 */ - template ::value or - std::is_same::value - , int>::type - = 0> - InteratorType erase(InteratorType first, InteratorType last) + 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) @@ -3931,7 +4044,7 @@ class basic_json throw std::domain_error("iterators do not fit current value"); } - InteratorType result = end(); + IteratorType result = end(); switch (m_type) { @@ -3948,17 +4061,19 @@ class basic_json if (is_string()) { - delete m_value.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: { - assert(m_value.object != nullptr); result.m_it.object_iterator = m_value.object->erase(first.m_it.object_iterator, last.m_it.object_iterator); break; @@ -3966,7 +4081,6 @@ class basic_json case value_t::array: { - assert(m_value.array != nullptr); result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator, last.m_it.array_iterator); break; @@ -4002,8 +4116,8 @@ class basic_json @liveexample{The example shows the effect of `erase()`.,erase__key_type} - @sa @ref erase(InteratorType) -- removes the element at a given position - @sa @ref erase(InteratorType, InteratorType) -- removes the elements in + @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 @@ -4015,7 +4129,6 @@ class basic_json // this erase only works for objects if (is_object()) { - assert(m_value.object != nullptr); return m_value.object->erase(key); } else @@ -4040,8 +4153,8 @@ class basic_json @liveexample{The example shows the effect of `erase()`.,erase__size_type} - @sa @ref erase(InteratorType) -- removes the element at a given position - @sa @ref erase(InteratorType, InteratorType) -- removes the elements in + @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 @@ -4058,7 +4171,6 @@ class basic_json throw std::out_of_range("array index " + std::to_string(idx) + " is out of range"); } - assert(m_value.array != nullptr); m_value.array->erase(m_value.array->begin() + static_cast(idx)); } else @@ -4084,10 +4196,14 @@ class basic_json 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, past-the-end (see end()) iterator is returned. + 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. @@ -4101,7 +4217,6 @@ class basic_json if (is_object()) { - assert(m_value.object != nullptr); result.m_it.object_iterator = m_value.object->find(key); } @@ -4118,7 +4233,6 @@ class basic_json if (is_object()) { - assert(m_value.object != nullptr); result.m_it.object_iterator = m_value.object->find(key); } @@ -4132,6 +4246,9 @@ class basic_json 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 @@ -4146,7 +4263,6 @@ class basic_json size_type count(typename object_t::key_type key) const { // return 0 for all nonobject types - assert(not is_object() or m_value.object != nullptr); return is_object() ? m_value.object->count(key) : 0; } @@ -4488,6 +4604,10 @@ class basic_json 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. @@ -4517,13 +4637,13 @@ class basic_json case value_t::array: { - assert(m_value.array != nullptr); + // delegate call to array_t::empty() return m_value.array->empty(); } case value_t::object: { - assert(m_value.object != nullptr); + // delegate call to object_t::empty() return m_value.object->empty(); } @@ -4551,6 +4671,10 @@ class basic_json 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. @@ -4581,13 +4705,13 @@ class basic_json case value_t::array: { - assert(m_value.array != nullptr); + // delegate call to array_t::size() return m_value.array->size(); } case value_t::object: { - assert(m_value.object != nullptr); + // delegate call to object_t::size() return m_value.object->size(); } @@ -4641,13 +4765,13 @@ class basic_json { case value_t::array: { - assert(m_value.array != nullptr); + // delegate call to array_t::max_size() return m_value.array->max_size(); } case value_t::object: { - assert(m_value.object != nullptr); + // delegate call to object_t::max_size() return m_value.object->max_size(); } @@ -4684,9 +4808,6 @@ class basic_json object | `{}` array | `[]` - @note Floating-point numbers are set to `0.0` which will be serialized to - `0`. The vale type remains @ref number_float_t. - @complexity Linear in the size of the JSON value. @liveexample{The example below shows the effect of `clear()` to different @@ -4724,21 +4845,18 @@ class basic_json case value_t::string: { - assert(m_value.string != nullptr); m_value.string->clear(); break; } case value_t::array: { - assert(m_value.array != nullptr); m_value.array->clear(); break; } case value_t::object: { - assert(m_value.object != nullptr); m_value.object->clear(); break; } @@ -4783,10 +4901,10 @@ class basic_json { m_type = value_t::array; m_value = value_t::array; + assert_invariant(); } // add element to array (move semantics) - assert(m_value.array != nullptr); m_value.array->push_back(std::move(val)); // invalidate object val.m_type = value_t::null; @@ -4819,10 +4937,10 @@ class basic_json { m_type = value_t::array; m_value = value_t::array; + assert_invariant(); } // add element to array - assert(m_value.array != nullptr); m_value.array->push_back(val); } @@ -4869,10 +4987,10 @@ class basic_json { m_type = value_t::object; m_value = value_t::object; + assert_invariant(); } // add element to array - assert(m_value.object != nullptr); m_value.object->insert(val); } @@ -4934,6 +5052,102 @@ class basic_json 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 @@ -4969,7 +5183,6 @@ class basic_json // insert to array and return iterator iterator result(this); - assert(m_value.array != nullptr); result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, val); return result; } @@ -5025,7 +5238,6 @@ class basic_json // insert to array and return iterator iterator result(this); - assert(m_value.array != nullptr); result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val); return result; } @@ -5092,7 +5304,6 @@ class basic_json // insert to array and return iterator iterator result(this); - assert(m_value.array != nullptr); result.m_it.array_iterator = m_value.array->insert( pos.m_it.array_iterator, first.m_it.array_iterator, @@ -5140,7 +5351,6 @@ class basic_json // insert to array and return iterator iterator result(this); - assert(m_value.array != nullptr); result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, ilist); return result; } @@ -5171,6 +5381,7 @@ class basic_json { std::swap(m_type, other.m_type); std::swap(m_value, other.m_value); + assert_invariant(); } /*! @@ -5198,7 +5409,6 @@ class basic_json // swap only works for arrays if (is_array()) { - assert(m_value.array != nullptr); std::swap(*(m_value.array), other); } else @@ -5232,7 +5442,6 @@ class basic_json // swap only works for objects if (is_object()) { - assert(m_value.object != nullptr); std::swap(*(m_value.object), other); } else @@ -5266,7 +5475,6 @@ class basic_json // swap only works for strings if (is_string()) { - assert(m_value.string != nullptr); std::swap(*(m_value.string), other); } else @@ -5353,14 +5561,10 @@ class basic_json { case value_t::array: { - assert(lhs.m_value.array != nullptr); - assert(rhs.m_value.array != nullptr); return *lhs.m_value.array == *rhs.m_value.array; } case value_t::object: { - assert(lhs.m_value.object != nullptr); - assert(rhs.m_value.object != nullptr); return *lhs.m_value.object == *rhs.m_value.object; } case value_t::null: @@ -5369,8 +5573,6 @@ class basic_json } case value_t::string: { - assert(lhs.m_value.string != nullptr); - assert(rhs.m_value.string != nullptr); return *lhs.m_value.string == *rhs.m_value.string; } case value_t::boolean: @@ -5543,14 +5745,10 @@ class basic_json { case value_t::array: { - assert(lhs.m_value.array != nullptr); - assert(rhs.m_value.array != nullptr); return *lhs.m_value.array < *rhs.m_value.array; } case value_t::object: { - assert(lhs.m_value.object != nullptr); - assert(rhs.m_value.object != nullptr); return *lhs.m_value.object < *rhs.m_value.object; } case value_t::null: @@ -5559,8 +5757,6 @@ class basic_json } case value_t::string: { - assert(lhs.m_value.string != nullptr); - assert(rhs.m_value.string != nullptr); return *lhs.m_value.string < *rhs.m_value.string; } case value_t::boolean: @@ -5702,6 +5898,10 @@ class basic_json `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 @@ -5723,8 +5923,22 @@ class basic_json // 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; } @@ -5748,10 +5962,16 @@ class basic_json /// @{ /*! - @brief deserialize from string + @brief deserialize from an array - @param[in] s string to read a serialized JSON value from - @param[in] cb a parser callback function of type @ref parser_callback_t + 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) @@ -5763,17 +5983,54 @@ class basic_json @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&, parser_callback_t) for a version that reads - from an input stream + @sa @ref parse(std::istream&, const parser_callback_t) for a version that + reads from an input stream - @since version 1.0.0 + @since version 1.0.0 (originally for @ref string_t) */ - static basic_json parse(const string_t& s, parser_callback_t cb = nullptr) + 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(s, cb).parse(); + return parser(reinterpret_cast(s), cb).parse(); } /*! @@ -5795,24 +6052,150 @@ class basic_json @liveexample{The example below demonstrates the `parse()` function with and without callback function.,parse__istream__parser_callback_t} - @sa @ref parse(const string_t&, parser_callback_t) for a version that - reads from a string + @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, parser_callback_t cb = nullptr) + static basic_json parse(std::istream& i, + const parser_callback_t cb = nullptr) { return parser(i, cb).parse(); } /*! - @copydoc parse(std::istream&, parser_callback_t) + @copydoc parse(std::istream&, const parser_callback_t) */ - static basic_json parse(std::istream&& i, parser_callback_t cb = nullptr) + 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 @@ -5831,8 +6214,8 @@ class basic_json @liveexample{The example below shows how a JSON value is constructed by reading a serialization from a stream.,operator_deserialize} - @sa parse(std::istream&, parser_callback_t) for a variant with a parser - callback function to filter values while parsing + @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 */ @@ -5854,14 +6237,1515 @@ class basic_json /// @} + ////////////////////////////////////////// + // 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 // /////////////////////////// - /// return the type as string - string_t type_name() const noexcept + /*! + @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) { @@ -5892,9 +7776,8 @@ class basic_json */ static std::size_t extra_space(const string_t& s) noexcept { - std::size_t result = 0; - - for (const auto& c : s) + return std::accumulate(s.begin(), s.end(), size_t{}, + [](size_t res, typename string_t::value_type c) { switch (c) { @@ -5907,8 +7790,7 @@ class basic_json case '\t': { // from c (1 byte) to \x (2 bytes) - result += 1; - break; + return res + 1; } default: @@ -5916,14 +7798,15 @@ class basic_json if (c >= 0x00 and c <= 0x1f) { // from c (1 byte) to \uxxxx (6 bytes) - result += 5; + return res + 5; + } + else + { + return res; } - break; } } - } - - return result; + }); } /*! @@ -6017,16 +7900,15 @@ class basic_json { // convert a number 0..15 to its hex representation // (0..f) - const auto hexify = [](const int v) -> char + static const char hexify[16] = { - return (v < 10) - ? ('0' + static_cast(v)) - : ('a' + static_cast((v - 10) & 0x1f)); + '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) + { 'u', '0', '0', hexify[c >> 4], hexify[c & 0x0f] }) { result[++pos] = m; @@ -6076,8 +7958,6 @@ class basic_json { case value_t::object: { - assert(m_value.object != nullptr); - if (m_value.object->empty()) { o << "{}"; @@ -6118,8 +7998,6 @@ class basic_json case value_t::array: { - assert(m_value.array != nullptr); - if (m_value.array->empty()) { o << "[]"; @@ -6158,7 +8036,6 @@ class basic_json case value_t::string: { - assert(m_value.string != nullptr); o << string_t("\"") << escape_string(*m_value.string) << "\""; return; } @@ -6183,79 +8060,14 @@ class basic_json case value_t::number_float: { - // check if number was parsed from a string - if (m_type.bits.parsed) + if (m_value.number_float == 0) { - // check if parsed number had an exponent given - if (m_type.bits.has_exp) - { - // buffer size: precision (2^8-1 = 255) + other ('-.e-xxx' = 7) + null (1) - char buf[263]; - int len; - - // handle capitalization of the exponent - if (m_type.bits.exp_cap) - { - len = snprintf(buf, sizeof(buf), "%.*E", - m_type.bits.precision, m_value.number_float) + 1; - } - else - { - len = snprintf(buf, sizeof(buf), "%.*e", - m_type.bits.precision, m_value.number_float) + 1; - } - - // remove '+' sign from the exponent if necessary - if (not m_type.bits.exp_plus) - { - if (len > static_cast(sizeof(buf))) - { - len = sizeof(buf); - } - for (int i = 0; i < len; i++) - { - if (buf[i] == '+') - { - for (; i + 1 < len; i++) - { - buf[i] = buf[i + 1]; - } - } - } - } - - o << buf; - } - else - { - // no exponent - output as a decimal - std::stringstream ss; - ss.imbue(std::locale(std::locale(), new DecimalSeparator)); // fix locale problems - ss << std::setprecision(m_type.bits.precision) - << std::fixed << m_value.number_float; - o << ss.str(); - } + // special case for zero to get "0.0"/"-0.0" + o << (std::signbit(m_value.number_float) ? "-0.0" : "0.0"); } else { - 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 - { - // Otherwise 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 - std::stringstream ss; - ss.imbue(std::locale(std::locale(), new DecimalSeparator)); // fix locale problems - ss << std::setprecision(std::numeric_limits::digits10) - << m_value.number_float; - o << ss.str(); - } + o << m_value.number_float; } return; } @@ -6280,7 +8092,7 @@ class basic_json ////////////////////// /// the type of the current element - type_data_t m_type = value_t::null; + value_t m_type = value_t::null; /// the value of the current element json_value m_value = {}; @@ -6467,40 +8279,61 @@ class basic_json public: /*! - @brief a const random access iterator for the @ref basic_json class + @brief a template for a random access iterator for the @ref basic_json class - This class implements a const iterator for the @ref basic_json class. From - this class, the @ref iterator class is derived. + 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 + @since version 1.0.0, simplified in version 2.0.9 */ - class const_iterator : public std::iterator + 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 basic_json::const_pointer; + 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 basic_json::const_reference; + 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 - const_iterator() = default; + iter_impl() = default; - /// constructor for a given JSON instance - explicit const_iterator(pointer object) noexcept + /*! + @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); @@ -6527,41 +8360,42 @@ class basic_json } } - /// copy constructor given a nonconst iterator - explicit const_iterator(const iterator& other) noexcept - : m_object(other.m_object) + /* + 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 { - assert(m_object != nullptr); + const_iterator ret; - switch (m_object->m_type) + if (m_object) { - case basic_json::value_t::object: - { - m_it.object_iterator = other.m_it.object_iterator; - break; - } - - case basic_json::value_t::array: - { - m_it.array_iterator = other.m_it.array_iterator; - break; - } - - default: - { - m_it.primitive_iterator = other.m_it.primitive_iterator; - break; - } + ret.m_object = m_object; + ret.m_it = m_it; } + + return ret; } - /// copy constructor - const_iterator(const const_iterator& other) noexcept + /*! + @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) {} - /// copy assignment - const_iterator& operator=(const_iterator other) noexcept( + /*! + @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 @@ -6574,7 +8408,10 @@ class basic_json } private: - /// set the iterator to the first value + /*! + @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); @@ -6583,14 +8420,12 @@ class basic_json { case basic_json::value_t::object: { - assert(m_object->m_value.object != nullptr); m_it.object_iterator = m_object->m_value.object->begin(); break; } case basic_json::value_t::array: { - assert(m_object->m_value.array != nullptr); m_it.array_iterator = m_object->m_value.array->begin(); break; } @@ -6610,7 +8445,10 @@ class basic_json } } - /// set the iterator past the last value + /*! + @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); @@ -6619,14 +8457,12 @@ class basic_json { case basic_json::value_t::object: { - assert(m_object->m_value.object != nullptr); m_it.object_iterator = m_object->m_value.object->end(); break; } case basic_json::value_t::array: { - assert(m_object->m_value.array != nullptr); m_it.array_iterator = m_object->m_value.array->end(); break; } @@ -6640,7 +8476,10 @@ class basic_json } public: - /// return a reference to the value pointed to by the iterator + /*! + @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); @@ -6649,14 +8488,12 @@ class basic_json { case basic_json::value_t::object: { - assert(m_object->m_value.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_object->m_value.array); assert(m_it.array_iterator != m_object->m_value.array->end()); return *m_it.array_iterator; } @@ -6680,7 +8517,10 @@ class basic_json } } - /// dereference the iterator + /*! + @brief dereference the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ pointer operator->() const { assert(m_object != nullptr); @@ -6689,14 +8529,12 @@ class basic_json { case basic_json::value_t::object: { - assert(m_object->m_value.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_object->m_value.array); assert(m_it.array_iterator != m_object->m_value.array->end()); return &*m_it.array_iterator; } @@ -6715,16 +8553,22 @@ class basic_json } } - /// post-increment (it++) - const_iterator operator++(int) + /*! + @brief post-increment (it++) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator++(int) { auto result = *this; ++(*this); return result; } - /// pre-increment (++it) - const_iterator& operator++() + /*! + @brief pre-increment (++it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator++() { assert(m_object != nullptr); @@ -6732,13 +8576,13 @@ class basic_json { case basic_json::value_t::object: { - ++m_it.object_iterator; + std::advance(m_it.object_iterator, 1); break; } case basic_json::value_t::array: { - ++m_it.array_iterator; + std::advance(m_it.array_iterator, 1); break; } @@ -6752,16 +8596,22 @@ class basic_json return *this; } - /// post-decrement (it--) - const_iterator operator--(int) + /*! + @brief post-decrement (it--) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator--(int) { auto result = *this; --(*this); return result; } - /// pre-decrement (--it) - const_iterator& operator--() + /*! + @brief pre-decrement (--it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator--() { assert(m_object != nullptr); @@ -6769,13 +8619,13 @@ class basic_json { case basic_json::value_t::object: { - --m_it.object_iterator; + std::advance(m_it.object_iterator, -1); break; } case basic_json::value_t::array: { - --m_it.array_iterator; + std::advance(m_it.array_iterator, -1); break; } @@ -6789,8 +8639,11 @@ class basic_json return *this; } - /// comparison: equal - bool operator==(const const_iterator& other) const + /*! + @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) @@ -6819,14 +8672,20 @@ class basic_json } } - /// comparison: not equal - bool operator!=(const const_iterator& other) const + /*! + @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); } - /// comparison: smaller - bool operator<(const const_iterator& other) const + /*! + @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) @@ -6855,26 +8714,38 @@ class basic_json } } - /// comparison: less than or equal - bool operator<=(const const_iterator& other) const + /*! + @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); } - /// comparison: greater than - bool operator>(const const_iterator& other) const + /*! + @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); } - /// comparison: greater than or equal - bool operator>=(const const_iterator& other) const + /*! + @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); } - /// add to iterator - const_iterator& operator+=(difference_type i) + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator+=(difference_type i) { assert(m_object != nullptr); @@ -6887,7 +8758,7 @@ class basic_json case basic_json::value_t::array: { - m_it.array_iterator += i; + std::advance(m_it.array_iterator, i); break; } @@ -6901,30 +8772,42 @@ class basic_json return *this; } - /// subtract from iterator - const_iterator& operator-=(difference_type i) + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator-=(difference_type i) { return operator+=(-i); } - /// add to iterator - const_iterator operator+(difference_type 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; } - /// subtract from iterator - const_iterator operator-(difference_type i) + /*! + @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; } - /// return difference - difference_type operator-(const const_iterator& other) const + /*! + @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); @@ -6947,7 +8830,10 @@ class basic_json } } - /// access to successor + /*! + @brief access to successor + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ reference operator[](difference_type n) const { assert(m_object != nullptr); @@ -6961,7 +8847,7 @@ class basic_json case basic_json::value_t::array: { - return *(m_it.array_iterator + n); + return *std::next(m_it.array_iterator, n); } case basic_json::value_t::null: @@ -6983,7 +8869,10 @@ class basic_json } } - /// return the key of an object iterator + /*! + @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); @@ -6998,7 +8887,10 @@ class basic_json } } - /// return the value of an iterator + /*! + @brief return the value of an iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ reference value() const { return operator*(); @@ -7011,141 +8903,6 @@ class basic_json internal_iterator m_it = internal_iterator(); }; - /*! - @brief a mutable random access iterator for the @ref basic_json class - - @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. - - @since version 1.0.0 - */ - class iterator : public const_iterator - { - public: - using base_iterator = const_iterator; - using pointer = typename basic_json::pointer; - using reference = typename basic_json::reference; - - /// default constructor - iterator() = default; - - /// constructor for a given JSON instance - explicit iterator(pointer object) noexcept - : base_iterator(object) - {} - - /// copy constructor - iterator(const iterator& other) noexcept - : base_iterator(other) - {} - - /// copy assignment - iterator& operator=(iterator 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 - ) - { - base_iterator::operator=(other); - return *this; - } - - /// return a reference to the value pointed to by the iterator - reference operator*() const - { - return const_cast(base_iterator::operator*()); - } - - /// dereference the iterator - pointer operator->() const - { - return const_cast(base_iterator::operator->()); - } - - /// post-increment (it++) - iterator operator++(int) - { - iterator result = *this; - base_iterator::operator++(); - return result; - } - - /// pre-increment (++it) - iterator& operator++() - { - base_iterator::operator++(); - return *this; - } - - /// post-decrement (it--) - iterator operator--(int) - { - iterator result = *this; - base_iterator::operator--(); - return result; - } - - /// pre-decrement (--it) - iterator& operator--() - { - base_iterator::operator--(); - return *this; - } - - /// add to iterator - iterator& operator+=(difference_type i) - { - base_iterator::operator+=(i); - return *this; - } - - /// subtract from iterator - iterator& operator-=(difference_type i) - { - base_iterator::operator-=(i); - return *this; - } - - /// add to iterator - iterator operator+(difference_type i) - { - auto result = *this; - result += i; - return result; - } - - /// subtract from iterator - iterator operator-(difference_type i) - { - auto result = *this; - result -= i; - return result; - } - - /// return difference - difference_type operator-(const iterator& other) const - { - return base_iterator::operator-(other); - } - - /// access to successor - reference operator[](difference_type n) const - { - return const_cast(base_iterator::operator[](n)); - } - - /// return the value of an iterator - reference value() const - { - return const_cast(base_iterator::value()); - } - }; - /*! @brief a template for a reverse iterator class @@ -7296,54 +9053,69 @@ class basic_json /// the char type to use in the lexer using lexer_char_t = unsigned char; - /// constructor with a given buffer - explicit lexer(const string_t& s) noexcept - : m_stream(nullptr), m_buffer(s) + /// a lexer from a buffer with given length + lexer(const lexer_char_t* buff, const size_t len) noexcept + : m_content(buff) { - m_content = reinterpret_cast(s.c_str()); assert(m_content != nullptr); m_start = m_cursor = m_content; - m_limit = m_content + s.size(); + m_limit = m_content + len; } - /// constructor with a given stream - explicit lexer(std::istream* s) noexcept - : m_stream(s), m_buffer() + /// a lexer from an input stream + explicit lexer(std::istream& s) + : m_stream(&s), m_line_buffer() { - assert(m_stream != nullptr); - getline(*m_stream, m_buffer); - m_content = reinterpret_cast(m_buffer.c_str()); - assert(m_content != nullptr); - m_start = m_cursor = m_content; - m_limit = m_content + m_buffer.size(); + // 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] = ' '; + } } - /// default constructor - lexer() = default; - - // switch off unwanted functions + // switch off unwanted functions (due to pointer members) + lexer() = delete; lexer(const lexer&) = delete; lexer operator=(const lexer&) = delete; /*! - @brief create a string from a Unicode code point + @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 + @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 codepoint from the given code points + // calculate the code point from the given code points std::size_t codepoint = codepoint1; // check if codepoint1 is a high surrogate @@ -7405,7 +9177,7 @@ class basic_json } /// return name of values of type token_type (only used for errors) - static std::string token_type_name(token_type t) + static std::string token_type_name(const token_type t) { switch (t) { @@ -7454,799 +9226,1035 @@ class basic_json 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() noexcept + token_type scan() { - // pointer for backtracking information - m_marker = nullptr; - - // remember the begin of the token - m_start = m_cursor; - assert(m_start != nullptr); - - + while (true) { - lexer_char_t yych; - unsigned int yyaccept = 0; - static const unsigned char yybm[] = + // pointer for backtracking information + m_marker = nullptr; + + // remember the begin of the token + m_start = m_cursor; + assert(m_start != nullptr); + + { - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 32, 32, 0, 0, 32, 0, 0, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 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, - 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, 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, 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, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - }; - if ((m_limit - m_cursor) < 5) - { - yyfill(); // LCOV_EXCL_LINE; - } - yych = *m_cursor; - if (yybm[0 + yych] & 32) - { - goto basic_json_parser_6; - } - if (yych <= '\\') - { - if (yych <= '-') + lexer_char_t yych; + unsigned int yyaccept = 0; + static const unsigned char yybm[] = { - 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; - } + 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 } - else + yych = *m_cursor; + if (yybm[0 + yych] & 32) { - if (yych <= '9') + goto basic_json_parser_6; + } + if (yych <= '[') + { + if (yych <= '-') { - if (yych <= '/') + if (yych <= '"') { - goto basic_json_parser_4; + if (yych <= 0x00) + { + goto basic_json_parser_2; + } + if (yych <= '!') + { + goto basic_json_parser_4; + } + goto basic_json_parser_9; } - if (yych <= '0') + else { - goto basic_json_parser_13; + if (yych <= '+') + { + goto basic_json_parser_4; + } + if (yych <= ',') + { + goto basic_json_parser_10; + } + goto basic_json_parser_12; } - goto basic_json_parser_15; } else { - if (yych <= ':') + if (yych <= '9') { - goto basic_json_parser_17; + if (yych <= '/') + { + goto basic_json_parser_4; + } + if (yych <= '0') + { + goto basic_json_parser_13; + } + goto basic_json_parser_15; } - if (yych == '[') + else { + if (yych <= ':') + { + goto basic_json_parser_17; + } + if (yych <= 'Z') + { + goto basic_json_parser_4; + } goto basic_json_parser_19; } - goto basic_json_parser_4; } } - } - else - { - if (yych <= 't') + else { - if (yych <= 'f') + if (yych <= 'n') { - if (yych <= ']') - { - goto basic_json_parser_21; - } if (yych <= 'e') { + if (yych == ']') + { + goto basic_json_parser_21; + } goto basic_json_parser_4; } - goto basic_json_parser_23; - } - else - { - if (yych == 'n') + else { + if (yych <= 'f') + { + goto basic_json_parser_23; + } + if (yych <= 'm') + { + goto basic_json_parser_4; + } goto basic_json_parser_24; } - if (yych <= 's') + } + 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; } - goto basic_json_parser_25; } } - else - { - if (yych <= '|') - { - if (yych == '{') - { - goto basic_json_parser_26; - } - goto basic_json_parser_4; - } - else - { - if (yych <= '}') - { - goto basic_json_parser_28; - } - if (yych == 0xEF) - { - goto basic_json_parser_30; - } - goto basic_json_parser_4; - } - } - } basic_json_parser_2: - ++m_cursor; - { - return token_type::end_of_input; - } + ++m_cursor; + { + last_token_type = token_type::end_of_input; + break; + } basic_json_parser_4: - ++m_cursor; + ++m_cursor; basic_json_parser_5: - { - return token_type::parse_error; - } + { + last_token_type = token_type::parse_error; + break; + } basic_json_parser_6: - ++m_cursor; - if (m_limit <= m_cursor) - { - yyfill(); // LCOV_EXCL_LINE; - } - yych = *m_cursor; - if (yybm[0 + yych] & 32) - { - goto basic_json_parser_6; - } - { - return scan(); - } + ++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 <= 0x0F) - { - goto basic_json_parser_5; - } - goto basic_json_parser_32; -basic_json_parser_10: - ++m_cursor; - { - return token_type::value_separator; - } -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 == '.') + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych <= 0x1F) { - goto basic_json_parser_37; + goto basic_json_parser_5; } - } - else - { - if (yych <= 'E') + if (yych <= 0x7F) { - goto basic_json_parser_38; - } - if (yych == 'e') - { - goto basic_json_parser_38; - } - } -basic_json_parser_14: - { - return token_type::value_number; - } -basic_json_parser_15: - yyaccept = 1; - m_marker = ++m_cursor; - if ((m_limit - m_cursor) < 3) - { - yyfill(); // 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_37; - } - goto basic_json_parser_14; - } - else - { - if (yych <= 'E') - { - goto basic_json_parser_38; - } - if (yych == 'e') - { - goto basic_json_parser_38; - } - goto basic_json_parser_14; - } -basic_json_parser_17: - ++m_cursor; - { - return token_type::name_separator; - } -basic_json_parser_19: - ++m_cursor; - { - return token_type::begin_array; - } -basic_json_parser_21: - ++m_cursor; - { - return token_type::end_array; - } -basic_json_parser_23: - yyaccept = 0; - yych = *(m_marker = ++m_cursor); - if (yych == 'a') - { - goto basic_json_parser_39; - } - goto basic_json_parser_5; -basic_json_parser_24: - yyaccept = 0; - yych = *(m_marker = ++m_cursor); - if (yych == 'u') - { - goto basic_json_parser_40; - } - goto basic_json_parser_5; -basic_json_parser_25: - yyaccept = 0; - yych = *(m_marker = ++m_cursor); - if (yych == 'r') - { - goto basic_json_parser_41; - } - goto basic_json_parser_5; -basic_json_parser_26: - ++m_cursor; - { - return token_type::begin_object; - } -basic_json_parser_28: - ++m_cursor; - { - return token_type::end_object; - } -basic_json_parser_30: - yyaccept = 0; - yych = *(m_marker = ++m_cursor); - if (yych == 0xBB) - { - goto basic_json_parser_42; - } - goto basic_json_parser_5; -basic_json_parser_31: - ++m_cursor; - if (m_limit <= m_cursor) - { - yyfill(); // LCOV_EXCL_LINE; - } - yych = *m_cursor; -basic_json_parser_32: - if (yybm[0 + yych] & 128) - { - goto basic_json_parser_31; - } - if (yych <= 0x0F) - { - goto basic_json_parser_33; - } - if (yych <= '"') - { - goto basic_json_parser_34; - } - goto basic_json_parser_36; -basic_json_parser_33: - m_cursor = m_marker; - if (yyaccept == 0) - { - goto basic_json_parser_5; - } - else - { - goto basic_json_parser_14; - } -basic_json_parser_34: - ++m_cursor; - { - return token_type::value_string; - } -basic_json_parser_36: - ++m_cursor; - if (m_limit <= m_cursor) - { - yyfill(); // LCOV_EXCL_LINE; - } - yych = *m_cursor; - if (yych <= 'e') - { - if (yych <= '/') - { - if (yych == '"') - { - goto basic_json_parser_31; - } - if (yych <= '.') - { - goto basic_json_parser_33; - } goto basic_json_parser_31; } - else + if (yych <= 0xC1) { - if (yych <= '\\') - { - if (yych <= '[') - { - goto basic_json_parser_33; - } - goto basic_json_parser_31; - } - else - { - if (yych == 'b') - { - goto basic_json_parser_31; - } - goto basic_json_parser_33; - } + goto basic_json_parser_5; } - } - else - { - if (yych <= 'q') + if (yych <= 0xF4) { - if (yych <= 'f') - { - goto basic_json_parser_31; - } - if (yych == 'n') - { - goto basic_json_parser_31; - } - goto basic_json_parser_33; + goto basic_json_parser_31; } - else + goto basic_json_parser_5; +basic_json_parser_10: + ++m_cursor; { - if (yych <= 's') - { - if (yych <= 'r') - { - goto basic_json_parser_31; - } - goto basic_json_parser_33; - } - else - { - if (yych <= 't') - { - goto basic_json_parser_31; - } - if (yych <= 'u') - { - goto basic_json_parser_43; - } - goto basic_json_parser_33; - } - } - } -basic_json_parser_37: - yych = *++m_cursor; - if (yych <= '/') - { - goto basic_json_parser_33; - } - if (yych <= '9') - { - goto basic_json_parser_44; - } - goto basic_json_parser_33; -basic_json_parser_38: - yych = *++m_cursor; - if (yych <= ',') - { - if (yych == '+') - { - goto basic_json_parser_46; - } - goto basic_json_parser_33; - } - else - { - if (yych <= '-') - { - goto basic_json_parser_46; + last_token_type = token_type::value_separator; + break; } +basic_json_parser_12: + yych = *++m_cursor; if (yych <= '/') { - goto basic_json_parser_33; + 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_33; - } + 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: - yych = *++m_cursor; - if (yych == 'l') - { - goto basic_json_parser_49; - } - goto basic_json_parser_33; + ++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: - yych = *++m_cursor; - if (yych == 'l') - { - goto basic_json_parser_50; - } - goto basic_json_parser_33; + ++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: - yych = *++m_cursor; - if (yych == 'u') - { - goto basic_json_parser_51; - } - goto basic_json_parser_33; + ++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: - yych = *++m_cursor; - if (yych == 0xBF) - { - goto basic_json_parser_52; - } - goto basic_json_parser_33; + ++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: - ++m_cursor; - if (m_limit <= m_cursor) - { - yyfill(); // LCOV_EXCL_LINE; - } - yych = *m_cursor; - if (yych <= '@') - { + yych = *++m_cursor; if (yych <= '/') { - goto basic_json_parser_33; + goto basic_json_parser_32; } if (yych <= '9') { - goto basic_json_parser_54; + goto basic_json_parser_49; } - goto basic_json_parser_33; - } - else - { - if (yych <= 'F') - { - goto basic_json_parser_54; - } - if (yych <= '`') - { - goto basic_json_parser_33; - } - if (yych <= 'f') - { - goto basic_json_parser_54; - } - goto basic_json_parser_33; - } + goto basic_json_parser_32; basic_json_parser_44: - yyaccept = 1; - m_marker = ++m_cursor; - if ((m_limit - m_cursor) < 3) - { - yyfill(); // LCOV_EXCL_LINE; - } - yych = *m_cursor; - if (yych <= 'D') - { + 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_44; + goto basic_json_parser_52; } goto basic_json_parser_14; - } - else - { - if (yych <= 'E') +basic_json_parser_54: + yych = *++m_cursor; + if (yych == 's') { - goto basic_json_parser_38; + 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_38; + goto basic_json_parser_61; } - goto basic_json_parser_14; - } -basic_json_parser_46: - yych = *++m_cursor; - if (yych <= '/') - { - goto basic_json_parser_33; - } - if (yych >= ':') - { - goto basic_json_parser_33; - } -basic_json_parser_47: - ++m_cursor; - if (m_limit <= m_cursor) - { - yyfill(); // LCOV_EXCL_LINE; - } - yych = *m_cursor; - if (yych <= '/') - { - goto basic_json_parser_14; - } - if (yych <= '9') - { - goto basic_json_parser_47; - } - goto basic_json_parser_14; -basic_json_parser_49: - yych = *++m_cursor; - if (yych == 's') - { - goto basic_json_parser_55; - } - goto basic_json_parser_33; -basic_json_parser_50: - yych = *++m_cursor; - if (yych == 'l') - { - goto basic_json_parser_56; - } - goto basic_json_parser_33; -basic_json_parser_51: - yych = *++m_cursor; - if (yych == 'e') - { - goto basic_json_parser_58; - } - goto basic_json_parser_33; -basic_json_parser_52: - ++m_cursor; - { - return scan(); - } -basic_json_parser_54: - ++m_cursor; - if (m_limit <= m_cursor) - { - yyfill(); // LCOV_EXCL_LINE; - } - yych = *m_cursor; - if (yych <= '@') - { - if (yych <= '/') + goto basic_json_parser_32; +basic_json_parser_57: + ++m_cursor; + if (m_limit <= m_cursor) { - goto basic_json_parser_33; + fill_line_buffer(1); // LCOV_EXCL_LINE } - if (yych <= '9') + yych = *m_cursor; + if (yych <= '@') { - goto basic_json_parser_60; + if (yych <= '/') + { + goto basic_json_parser_32; + } + if (yych <= '9') + { + goto basic_json_parser_63; + } + goto basic_json_parser_32; } - goto basic_json_parser_33; - } - else - { - if (yych <= 'F') + else { - goto basic_json_parser_60; + 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; } - if (yych <= '`') - { - goto basic_json_parser_33; - } - if (yych <= 'f') - { - goto basic_json_parser_60; - } - goto basic_json_parser_33; - } -basic_json_parser_55: - yych = *++m_cursor; - if (yych == 'e') - { - goto basic_json_parser_61; - } - goto basic_json_parser_33; -basic_json_parser_56: - ++m_cursor; - { - return token_type::literal_null; - } basic_json_parser_58: - ++m_cursor; - { - return token_type::literal_true; - } -basic_json_parser_60: - ++m_cursor; - if (m_limit <= m_cursor) - { - yyfill(); // LCOV_EXCL_LINE; - } - yych = *m_cursor; - if (yych <= '@') - { - if (yych <= '/') + yych = *++m_cursor; + if (yych == 'e') { - goto basic_json_parser_33; + goto basic_json_parser_64; } - if (yych <= '9') + goto basic_json_parser_32; +basic_json_parser_59: + ++m_cursor; { - goto basic_json_parser_63; + last_token_type = token_type::literal_null; + break; } - goto basic_json_parser_33; - } - else - { - if (yych <= 'F') - { - goto basic_json_parser_63; - } - if (yych <= '`') - { - goto basic_json_parser_33; - } - if (yych <= 'f') - { - goto basic_json_parser_63; - } - goto basic_json_parser_33; - } basic_json_parser_61: - ++m_cursor; - { - return token_type::literal_false; - } + ++m_cursor; + { + last_token_type = token_type::literal_true; + break; + } basic_json_parser_63: - ++m_cursor; - if (m_limit <= m_cursor) - { - yyfill(); // LCOV_EXCL_LINE; - } - yych = *m_cursor; - if (yych <= '@') - { - if (yych <= '/') - { - goto basic_json_parser_33; - } - if (yych <= '9') - { - goto basic_json_parser_31; - } - goto basic_json_parser_33; - } - else - { - if (yych <= 'F') - { - goto basic_json_parser_31; - } - if (yych <= '`') - { - goto basic_json_parser_33; - } - if (yych <= 'f') - { - goto basic_json_parser_31; - } - goto basic_json_parser_33; + ++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; } - /// append data from the stream to the internal buffer - void yyfill() noexcept - { - if (m_stream == nullptr or not * m_stream) - { - return; - } + /*! + @brief append data from the stream to the line buffer - const auto offset_start = m_start - m_content; - const auto offset_marker = m_marker - m_start; + 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; - m_buffer.erase(0, static_cast(offset_start)); - std::string line; - assert(m_stream != nullptr); - std::getline(*m_stream, line); - m_buffer += "\n" + line; // add line with newline symbol + // 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); - m_content = reinterpret_cast(m_buffer.c_str()); + // 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_buffer.size() - 1; + m_limit = m_start + m_line_buffer.size(); } /// return string representation of last read token - string_t get_token() const + string_t get_token_string() const { assert(m_start != nullptr); return string_t(reinterpret_cast(m_start), @@ -8271,21 +10279,69 @@ basic_json_parser_63: 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) { - // process escaped characters - if (*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; @@ -8356,6 +10412,11 @@ basic_json_parser_63: // 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) @@ -8367,12 +10428,6 @@ basic_json_parser_63: } } } - else - { - // all other characters are just copied to the end of the - // string - result.append(1, static_cast(*i)); - } } return result; @@ -8386,17 +10441,10 @@ basic_json_parser_63: supplied via the first parameter. Set this to @a static_cast(nullptr). - @param[in] type the @ref number_float_t in use - @param[in,out] endptr recieves a pointer to the first character after the number @return the floating point number - - @bug This function uses `std::strtof`, `std::strtod`, or `std::strtold` - which use the current C locale to determine which character is used as - decimal point character. This may yield to parse errors if the locale - does not used `.`. */ long double str_to_float_t(long double* /* type */, char** endptr) const { @@ -8411,8 +10459,6 @@ basic_json_parser_63: supplied via the first parameter. Set this to @a static_cast(nullptr). - @param[in] type the @ref number_float_t in use - @param[in,out] endptr recieves a pointer to the first character after the number @@ -8431,8 +10477,6 @@ basic_json_parser_63: supplied via the first parameter. Set this to @a static_cast(nullptr). - @param[in] type the @ref number_float_t in use - @param[in,out] endptr recieves a pointer to the first character after the number @@ -8457,18 +10501,12 @@ basic_json_parser_63: number_integer_t or @ref number_unsigned_t then it sets the result parameter accordingly. - The 'floating point representation' includes the number of significant - figures after the radix point, whether the number is in exponential or - decimal form, the capitalization of the exponent marker, and if the - optional '+' is present in the exponent. This information is necessary - to perform accurate round trips of floating point numbers. - 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. + 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 { @@ -8476,15 +10514,6 @@ basic_json_parser_63: const lexer::lexer_char_t* curptr = m_start; - // remember this number was parsed (for later serialization) - result.m_type.bits.parsed = true; - - // 'found_radix_point' will be set to 0xFF upon finding a radix - // point and later used to mask in/out the precision depending - // whether a radix is found i.e. 'precision &= found_radix_point' - uint8_t found_radix_point = 0; - uint8_t precision = 0; - // accumulate the integer conversion result (unsigned for now) number_unsigned_t value = 0; @@ -8517,50 +10546,34 @@ basic_json_parser_63: { // don't count '.' but change to float type = value_t::number_float; - - // reset precision count - precision = 0; - found_radix_point = 0xFF; continue; } // assume exponent (if not then will fail parse): change to // float, stop counting and record exponent details type = value_t::number_float; - result.m_type.bits.has_exp = true; - - // exponent capitalization - result.m_type.bits.exp_cap = (*curptr == 'E'); - - // exponent '+' sign - result.m_type.bits.exp_plus = (*(++curptr) == '+'); break; } // skip if definitely not an integer if (type != value_t::number_float) { - // multiply last value by ten and add the new digit - auto temp = value * 10 + *curptr - 0x30; + auto digit = static_cast(*curptr - '0'); - // test for overflow - if (temp < value || temp > max) + // 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 - save it - value = temp; + // no overflow + value = value * 10 + digit; } } - ++precision; } - // If no radix point was found then precision would now be set to - // the number of digits, which is wrong - clear it. - result.m_type.bits.precision = precision & found_radix_point; - // save the value (if not a float) if (type == value_t::number_unsigned) { @@ -8568,12 +10581,34 @@ basic_json_parser_63: } else if (type == value_t::number_integer) { - result.m_value.number_integer = -static_cast(value); + // 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 @@ -8583,8 +10618,10 @@ basic_json_parser_63: private: /// optional input stream std::istream* m_stream = nullptr; - /// the buffer - string_t m_buffer; + /// 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 @@ -8595,6 +10632,8 @@ basic_json_parser_63: 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; }; /*! @@ -8605,32 +10644,42 @@ basic_json_parser_63: class parser { public: - /// constructor for strings - parser(const string_t& s, parser_callback_t cb = nullptr) noexcept - : callback(cb), m_lexer(s) - { - // read first token - get_token(); - } + /// 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, parser_callback_t cb = nullptr) noexcept - : callback(cb), m_lexer(&_is) - { - // read first token - get_token(); - } + 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() : result; + return result.is_discarded() ? basic_json() : std::move(result); } private: @@ -8643,11 +10692,12 @@ basic_json_parser_63: { case lexer::token_type::begin_object: { - if (keep and (not callback or (keep = callback(depth++, parse_event_t::object_start, result)))) + 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 = json_value(value_t::object); + result.m_value = value_t::object; } // read next token @@ -8721,11 +10771,12 @@ basic_json_parser_63: case lexer::token_type::begin_array: { - if (keep and (not callback or (keep = callback(depth++, parse_event_t::array_start, result)))) + 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 = json_value(value_t::array); + result.m_value = value_t::array; } // read next token @@ -8827,7 +10878,7 @@ basic_json_parser_63: } /// get next token from lexer - typename lexer::token_type get_token() noexcept + typename lexer::token_type get_token() { last_token = m_lexer.scan(); return last_token; @@ -8838,7 +10889,8 @@ basic_json_parser_63: if (t != last_token) { std::string error_msg = "parse error - unexpected "; - error_msg += (last_token == lexer::token_type::parse_error ? ("'" + m_lexer.get_token() + "'") : + 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); @@ -8850,7 +10902,8 @@ basic_json_parser_63: if (t == last_token) { std::string error_msg = "parse error - unexpected "; - error_msg += (last_token == lexer::token_type::parse_error ? ("'" + m_lexer.get_token() + "'") : + 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); } @@ -8860,7 +10913,7 @@ basic_json_parser_63: /// current level of recursion int depth = 0; /// callback function - parser_callback_t callback; + 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 @@ -8928,14 +10981,12 @@ basic_json_parser_63: */ std::string to_string() const noexcept { - std::string result; - - for (const auto& reference_token : reference_tokens) + return std::accumulate(reference_tokens.begin(), + reference_tokens.end(), std::string{}, + [](const std::string & a, const std::string & b) { - result += "/" + escape(reference_token); - } - - return result; + return a + "/" + escape(b); + }); } /// @copydoc to_string() @@ -8978,6 +11029,8 @@ basic_json_parser_63: /*! @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 { @@ -9038,6 +11091,12 @@ basic_json_parser_63: /*! @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 @@ -9052,6 +11111,29 @@ basic_json_parser_63: { 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: @@ -9233,7 +11315,7 @@ basic_json_parser_63: } /// split the string input to reference tokens - static std::vector split(std::string reference_string) + static std::vector split(const std::string& reference_string) { std::vector result; @@ -9297,12 +11379,10 @@ basic_json_parser_63: /*! @brief replace all occurrences of a substring by another string - @param[in,out] s the string to manipulate + @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[out] t the string to replace @a f - - @return The string @a s where all occurrences of @a f are replaced - with @a t. + @param[in] t the string to replace @a f @pre The search string @a f must not be empty. @@ -9718,7 +11798,7 @@ basic_json_parser_63: json_pointer top_pointer = ptr.top(); if (top_pointer != ptr) { - basic_json& x = result.at(top_pointer); + result.at(top_pointer); } // get reference to parent of JSON pointer ptr @@ -9961,7 +12041,7 @@ basic_json_parser_63: */ static basic_json diff(const basic_json& source, const basic_json& target, - std::string path = "") + const std::string& path = "") { // the patch basic_json result(value_t::array); @@ -10002,9 +12082,12 @@ basic_json_parser_63: // in a second pass, traverse the remaining elements // remove my remaining elements + const auto end_index = static_cast(result.size()); while (i < source.size()) { - result.push_back(object( + // add operations in reverse order to avoid invalid + // indices + result.insert(result.begin() + end_index, object( { {"op", "remove"}, {"path", path + "/" + std::to_string(i)} @@ -10120,7 +12203,7 @@ namespace std @since version 1.0.0 */ -template <> +template<> inline void swap(nlohmann::json& j1, nlohmann::json& j2) noexcept( is_nothrow_move_constructible::value and @@ -10131,7 +12214,7 @@ inline void swap(nlohmann::json& j1, } /// hash value for JSON objects -template <> +template<> struct hash { /*! @@ -10152,27 +12235,36 @@ struct hash @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 \p "_json" to a string literal and returns a JSON object +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) +inline nlohmann::json operator "" _json(const char* s, std::size_t n) { - return nlohmann::json::parse(reinterpret_cast(s)); + 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) +inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t n) { - return nlohmann::json::json_pointer(s); + return nlohmann::json::json_pointer(std::string(s, n)); } // restore GCC/clang diagnostic settings diff --git a/ext/lz4/lz4.c b/ext/lz4/lz4.c deleted file mode 100644 index 08cf6b5..0000000 --- a/ext/lz4/lz4.c +++ /dev/null @@ -1,1516 +0,0 @@ -/* - LZ4 - Fast LZ compression algorithm - Copyright (C) 2011-2015, 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 source repository : https://github.com/Cyan4973/lz4 - - LZ4 public forum : https://groups.google.com/forum/#!forum/lz4c -*/ - - -/************************************** -* Tuning parameters -**************************************/ -/* - * HEAPMODE : - * Select how default compression functions will allocate memory for their hash table, - * in memory stack (0:default, fastest), or in memory heap (1:requires malloc()). - */ -#define HEAPMODE 0 - -/* - * ACCELERATION_DEFAULT : - * Select "acceleration" for LZ4_compress_fast() when parameter value <= 0 - */ -#define ACCELERATION_DEFAULT 1 - - -/************************************** -* CPU Feature Detection -**************************************/ -/* - * 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 - - -/************************************** -* Includes -**************************************/ -#include "lz4.h" - - -/************************************** -* Compiler Options -**************************************/ -#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(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */ -# if defined(__GNUC__) || defined(__clang__) -# define FORCE_INLINE static inline __attribute__((always_inline)) -# else -# define FORCE_INLINE static inline -# endif -# else -# define FORCE_INLINE static -# endif /* __STDC_VERSION__ */ -#endif /* _MSC_VER */ - -/* LZ4_GCC_VERSION is defined into lz4.h */ -#if (LZ4_GCC_VERSION >= 302) || (__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 -**************************************/ -#if defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */ -# include - typedef uint8_t BYTE; - typedef uint16_t U16; - typedef uint32_t U32; - typedef int32_t S32; - typedef uint64_t U64; -#else - typedef unsigned char BYTE; - typedef unsigned short U16; - typedef unsigned int U32; - typedef signed int S32; - typedef unsigned long long U64; -#endif - - -/************************************** -* Reading and writing into memory -**************************************/ -#define STEPSIZE sizeof(size_t) - -static unsigned LZ4_64bits(void) { return sizeof(void*)==8; } - -static unsigned LZ4_isLittleEndian(void) -{ - const union { U32 i; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */ - return one.c[0]; -} - - -static U16 LZ4_read16(const void* memPtr) -{ - U16 val16; - memcpy(&val16, memPtr, 2); - return val16; -} - -static 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 void LZ4_writeLE16(void* memPtr, U16 value) -{ - if (LZ4_isLittleEndian()) - { - memcpy(memPtr, &value, 2); - } - else - { - BYTE* p = (BYTE*)memPtr; - p[0] = (BYTE) value; - p[1] = (BYTE)(value>>8); - } -} - -static U32 LZ4_read32(const void* memPtr) -{ - U32 val32; - memcpy(&val32, memPtr, 4); - return val32; -} - -static U64 LZ4_read64(const void* memPtr) -{ - U64 val64; - memcpy(&val64, memPtr, 8); - return val64; -} - -static size_t LZ4_read_ARCH(const void* p) -{ - if (LZ4_64bits()) - return (size_t)LZ4_read64(p); - else - return (size_t)LZ4_read32(p); -} - - -static void LZ4_copy4(void* dstPtr, const void* srcPtr) { memcpy(dstPtr, srcPtr, 4); } - -static void LZ4_copy8(void* dstPtr, const void* srcPtr) { memcpy(dstPtr, srcPtr, 8); } - -/* customized version of memcpy, which may overwrite up to 7 bytes beyond dstEnd */ -static void LZ4_wildCopy(void* dstPtr, const void* srcPtr, void* dstEnd) -{ - BYTE* d = (BYTE*)dstPtr; - const BYTE* s = (const BYTE*)srcPtr; - BYTE* e = (BYTE*)dstEnd; - do { LZ4_copy8(d,s); d+=8; s+=8; } while (d>3); -# elif (defined(__clang__) || (LZ4_GCC_VERSION >= 304)) && !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__) || (LZ4_GCC_VERSION >= 304)) && !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 (LZ4_64bits()) - { -# 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__) || (LZ4_GCC_VERSION >= 304)) && !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__) || (LZ4_GCC_VERSION >= 304)) && !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 - } - } -} - -static 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 struct { - U32 hashTable[HASH_SIZE_U32]; - U32 currentOffset; - U32 initCheck; - const BYTE* dictionary; - BYTE* bufferStart; /* obsolete, used for slideInputBuffer */ - U32 dictSize; -} LZ4_stream_t_internal; - -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; } -int LZ4_compressBound(int isize) { return LZ4_COMPRESSBOUND(isize); } -int LZ4_sizeofState() { return LZ4_STREAMSIZE; } - - - -/******************************** -* Compression functions -********************************/ - -static U32 LZ4_hashSequence(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 const U64 prime5bytes = 889523592379ULL; -static U32 LZ4_hashSequence64(size_t sequence, tableType_t const tableType) -{ - const U32 hashLog = (tableType == byU16) ? LZ4_HASHLOG+1 : LZ4_HASHLOG; - const U32 hashMask = (1<> (40 - hashLog)) & hashMask; -} - -static U32 LZ4_hashSequenceT(size_t sequence, tableType_t const tableType) -{ - if (LZ4_64bits()) - return LZ4_hashSequence64(sequence, tableType); - return LZ4_hashSequence((U32)sequence, tableType); -} - -static U32 LZ4_hashPosition(const void* p, tableType_t tableType) { return LZ4_hashSequenceT(LZ4_read_ARCH(p), tableType); } - -static 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; } - } -} - -static void LZ4_putPosition(const BYTE* p, void* tableBase, tableType_t tableType, const BYTE* srcBase) -{ - U32 h = LZ4_hashPosition(p, tableType); - LZ4_putPositionOnHash(p, h, tableBase, tableType, srcBase); -} - -static 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) { U32* hashTable = (U32*) tableBase; return hashTable[h] + srcBase; } - { U16* hashTable = (U16*) tableBase; return hashTable[h] + srcBase; } /* default, to ensure a return */ -} - -static const BYTE* LZ4_getPosition(const BYTE* p, void* tableBase, tableType_t tableType, const BYTE* srcBase) -{ - U32 h = LZ4_hashPosition(p, tableType); - return LZ4_getPositionOnHash(h, tableBase, tableType, srcBase); -} - -FORCE_INLINE int LZ4_compress_generic( - void* const ctx, - 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) -{ - LZ4_stream_t_internal* const dictPtr = (LZ4_stream_t_internal*)ctx; - - const BYTE* ip = (const BYTE*) source; - const BYTE* base; - const BYTE* lowLimit; - const BYTE* const lowRefLimit = ip - dictPtr->dictSize; - const BYTE* const dictionary = dictPtr->dictionary; - const BYTE* const dictEnd = dictionary + dictPtr->dictSize; - const size_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; - size_t refDelta=0; - - /* Init conditions */ - if ((U32)inputSize > (U32)LZ4_MAX_INPUT_SIZE) return 0; /* Unsupported input size, too large (or negative) */ - switch(dict) - { - case noDict: - default: - base = (const BYTE*)source; - lowLimit = (const BYTE*)source; - break; - case withPrefix64k: - base = (const BYTE*)source - dictPtr->currentOffset; - lowLimit = (const BYTE*)source - dictPtr->dictSize; - break; - case usingExtDict: - base = (const BYTE*)source - dictPtr->currentOffset; - lowLimit = (const BYTE*)source; - break; - } - if ((tableType == byU16) && (inputSize>=LZ4_64Klimit)) return 0; /* Size too large (not within 64K limit) */ - if (inputSize> LZ4_skipTrigger); - - if (unlikely(forwardIp > mflimit)) goto _last_literals; - - match = LZ4_getPositionOnHash(h, ctx, 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, ctx, 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 Literal length */ - unsigned litLength = (unsigned)(ip - anchor); - token = op++; - if ((outputLimited) && (unlikely(op + litLength + (2 + 1 + LASTLITERALS) + (litLength/255) > olimit))) - return 0; /* Check output limit */ - 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; - matchLength = LZ4_count(ip+MINMATCH, match+MINMATCH, limit); - ip += MINMATCH + matchLength; - if (ip==limit) - { - unsigned more = LZ4_count(ip, (const BYTE*)source, matchlimit); - matchLength += more; - ip += more; - } - } - else - { - matchLength = LZ4_count(ip+MINMATCH, match+MINMATCH, matchlimit); - ip += MINMATCH + matchLength; - } - - if ((outputLimited) && (unlikely(op + (1 + LASTLITERALS) + (matchLength>>8) > olimit))) - return 0; /* Check output limit */ - if (matchLength>=ML_MASK) - { - *token += ML_MASK; - matchLength -= ML_MASK; - for (; matchLength >= 510 ; matchLength-=510) { *op++ = 255; *op++ = 255; } - if (matchLength >= 255) { matchLength-=255; *op++ = 255; } - *op++ = (BYTE)matchLength; - } - else *token += (BYTE)(matchLength); - } - - anchor = ip; - - /* Test end of chunk */ - if (ip > mflimit) break; - - /* Fill table */ - LZ4_putPosition(ip-2, ctx, tableType, base); - - /* Test next position */ - match = LZ4_getPosition(ip, ctx, tableType, base); - if (dict==usingExtDict) - { - if (match<(const BYTE*)source) - { - refDelta = dictDelta; - lowLimit = dictionary; - } - else - { - refDelta = 0; - lowLimit = (const BYTE*)source; - } - } - LZ4_putPosition(ip, ctx, 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 */ - { - const size_t lastRun = (size_t)(iend - anchor); - if ((outputLimited) && ((op - (BYTE*)dest) + lastRun + 1 + ((lastRun+255-RUN_MASK)/255) > (U32)maxOutputSize)) - return 0; /* Check output limit */ - 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<= LZ4_compressBound(inputSize)) - { - if (inputSize < LZ4_64Klimit) - return LZ4_compress_generic(state, source, dest, inputSize, 0, notLimited, byU16, noDict, noDictIssue, acceleration); - else - return LZ4_compress_generic(state, source, dest, inputSize, 0, notLimited, LZ4_64bits() ? byU32 : byPtr, noDict, noDictIssue, acceleration); - } - else - { - if (inputSize < LZ4_64Klimit) - return LZ4_compress_generic(state, source, dest, inputSize, maxOutputSize, limitedOutput, byU16, noDict, noDictIssue, acceleration); - else - return LZ4_compress_generic(state, source, dest, inputSize, maxOutputSize, limitedOutput, LZ4_64bits() ? byU32 : byPtr, noDict, noDictIssue, acceleration); - } -} - - -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* ctxPtr = &ctx; -#endif - - int result = LZ4_compress_fast_extState(ctxPtr, source, dest, inputSize, maxOutputSize, acceleration); - -#if (HEAPMODE) - FREEMEM(ctxPtr); -#endif - return result; -} - - -int LZ4_compress_default(const char* source, char* dest, int inputSize, int maxOutputSize) -{ - return LZ4_compress_fast(source, dest, inputSize, maxOutputSize, 1); -} - - -/* hidden debug function */ -/* strangely enough, gcc generates faster code when this function is uncommented, even if unused */ -int LZ4_compress_fast_force(const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration) -{ - LZ4_stream_t ctx; - - LZ4_resetStream(&ctx); - - 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, LZ4_64bits() ? byU32 : byPtr, noDict, noDictIssue, acceleration); -} - - -/******************************** -* destSize variant -********************************/ - -static int LZ4_compress_destSize_generic( - void* const ctx, - const char* const src, - char* const dst, - int* const srcSizePtr, - const int targetDstSize, - const tableType_t tableType) -{ - const BYTE* ip = (const BYTE*) src; - const BYTE* base = (const BYTE*) src; - const BYTE* lowLimit = (const BYTE*) src; - const BYTE* anchor = ip; - const BYTE* const iend = ip + *srcSizePtr; - const BYTE* const mflimit = iend - MFLIMIT; - const BYTE* const matchlimit = iend - LASTLITERALS; - - BYTE* op = (BYTE*) dst; - BYTE* const oend = op + targetDstSize; - BYTE* const oMaxLit = op + targetDstSize - 2 /* offset */ - 8 /* because 8+MINMATCH==MFLIMIT */ - 1 /* token */; - BYTE* const oMaxMatch = op + targetDstSize - (LASTLITERALS + 1 /* token */); - BYTE* const oMaxSeq = oMaxLit - 1 /* token */; - - U32 forwardH; - - - /* Init conditions */ - if (targetDstSize < 1) return 0; /* Impossible to store anything */ - if ((U32)*srcSizePtr > (U32)LZ4_MAX_INPUT_SIZE) return 0; /* Unsupported input size, too large (or negative) */ - if ((tableType == byU16) && (*srcSizePtr>=LZ4_64Klimit)) return 0; /* Size too large (not within 64K limit) */ - if (*srcSizePtr> LZ4_skipTrigger); - - if (unlikely(forwardIp > mflimit)) - goto _last_literals; - - match = LZ4_getPositionOnHash(h, ctx, tableType, base); - forwardH = LZ4_hashPosition(forwardIp, tableType); - LZ4_putPositionOnHash(ip, h, ctx, tableType, base); - - } while ( ((tableType==byU16) ? 0 : (match + MAX_DISTANCE < ip)) - || (LZ4_read32(match) != LZ4_read32(ip)) ); - } - - /* Catch up */ - while ((ip>anchor) && (match > lowLimit) && (unlikely(ip[-1]==match[-1]))) { ip--; match--; } - - { - /* Encode Literal length */ - unsigned litLength = (unsigned)(ip - anchor); - token = op++; - if (op + ((litLength+240)/255) + litLength > oMaxLit) - { - /* Not enough space for a last match */ - op--; - goto _last_literals; - } - if (litLength>=RUN_MASK) - { - unsigned len = litLength - RUN_MASK; - *token=(RUN_MASK<= 255 ; len-=255) *op++ = 255; - *op++ = (BYTE)len; - } - else *token = (BYTE)(litLength< oMaxMatch) - { - /* Match description too long : reduce it */ - matchLength = (15-1) + (oMaxMatch-op) * 255; - } - //printf("offset %5i, matchLength%5i \n", (int)(ip-match), matchLength + MINMATCH); - ip += MINMATCH + matchLength; - - if (matchLength>=ML_MASK) - { - *token += ML_MASK; - matchLength -= ML_MASK; - while (matchLength >= 255) { matchLength-=255; *op++ = 255; } - *op++ = (BYTE)matchLength; - } - else *token += (BYTE)(matchLength); - } - - anchor = ip; - - /* Test end of block */ - if (ip > mflimit) break; - if (op > oMaxSeq) break; - - /* Fill table */ - LZ4_putPosition(ip-2, ctx, tableType, base); - - /* Test next position */ - match = LZ4_getPosition(ip, ctx, tableType, base); - LZ4_putPosition(ip, ctx, tableType, base); - if ( (match+MAX_DISTANCE>=ip) - && (LZ4_read32(match)==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 lastRunSize = (size_t)(iend - anchor); - if (op + 1 /* token */ + ((lastRunSize+240)/255) /* litLength */ + lastRunSize /* literals */ > oend) - { - /* adapt lastRunSize to fill 'dst' */ - lastRunSize = (oend-op) - 1; - lastRunSize -= (lastRunSize+240)/255; - } - ip = anchor + lastRunSize; - - if (lastRunSize >= RUN_MASK) - { - size_t accumulator = lastRunSize - RUN_MASK; - *op++ = RUN_MASK << ML_BITS; - for(; accumulator >= 255 ; accumulator-=255) *op++ = 255; - *op++ = (BYTE) accumulator; - } - else - { - *op++ = (BYTE)(lastRunSize<= LZ4_compressBound(*srcSizePtr)) /* compression success is guaranteed */ - { - return LZ4_compress_fast_extState(state, src, dst, *srcSizePtr, targetDstSize, 1); - } - else - { - if (*srcSizePtr < LZ4_64Klimit) - return LZ4_compress_destSize_generic(state, src, dst, srcSizePtr, targetDstSize, byU16); - else - return LZ4_compress_destSize_generic(state, src, dst, srcSizePtr, targetDstSize, LZ4_64bits() ? byU32 : byPtr); - } -} - - -int LZ4_compress_destSize(const char* src, char* dst, int* srcSizePtr, int targetDstSize) -{ -#if (HEAPMODE) - void* ctx = ALLOCATOR(1, sizeof(LZ4_stream_t)); /* malloc-calloc always properly aligned */ -#else - LZ4_stream_t ctxBody; - void* ctx = &ctxBody; -#endif - - int result = LZ4_compress_destSize_extState(ctx, src, dst, srcSizePtr, targetDstSize); - -#if (HEAPMODE) - FREEMEM(ctx); -#endif - return result; -} - - - -/******************************** -* Streaming functions -********************************/ - -LZ4_stream_t* LZ4_createStream(void) -{ - LZ4_stream_t* lz4s = (LZ4_stream_t*)ALLOCATOR(8, LZ4_STREAMSIZE_U64); - LZ4_STATIC_ASSERT(LZ4_STREAMSIZE >= sizeof(LZ4_stream_t_internal)); /* A compilation error here means LZ4_STREAMSIZE is not large enough */ - LZ4_resetStream(lz4s); - return lz4s; -} - -void LZ4_resetStream (LZ4_stream_t* LZ4_stream) -{ - MEM_INIT(LZ4_stream, 0, sizeof(LZ4_stream_t)); -} - -int LZ4_freeStream (LZ4_stream_t* LZ4_stream) -{ - FREEMEM(LZ4_stream); - return (0); -} - - -#define HASH_UNIT sizeof(size_t) -int LZ4_loadDict (LZ4_stream_t* LZ4_dict, const char* dictionary, int dictSize) -{ - LZ4_stream_t_internal* dict = (LZ4_stream_t_internal*) LZ4_dict; - const BYTE* p = (const BYTE*)dictionary; - const BYTE* const dictEnd = p + dictSize; - const BYTE* base; - - if ((dict->initCheck) || (dict->currentOffset > 1 GB)) /* Uninitialized structure, or reuse overflow */ - LZ4_resetStream(LZ4_dict); - - if (dictSize < (int)HASH_UNIT) - { - dict->dictionary = NULL; - dict->dictSize = 0; - return 0; - } - - if ((dictEnd - p) > 64 KB) p = dictEnd - 64 KB; - dict->currentOffset += 64 KB; - base = p - dict->currentOffset; - dict->dictionary = p; - dict->dictSize = (U32)(dictEnd - p); - dict->currentOffset += dict->dictSize; - - while (p <= dictEnd-HASH_UNIT) - { - LZ4_putPosition(p, dict->hashTable, byU32, base); - p+=3; - } - - return dict->dictSize; -} - - -static void LZ4_renormDictT(LZ4_stream_t_internal* LZ4_dict, const BYTE* src) -{ - if ((LZ4_dict->currentOffset > 0x80000000) || - ((size_t)LZ4_dict->currentOffset > (size_t)src)) /* address space overflow */ - { - /* rescale hash table */ - U32 delta = LZ4_dict->currentOffset - 64 KB; - const BYTE* dictEnd = LZ4_dict->dictionary + LZ4_dict->dictSize; - int i; - for (i=0; ihashTable[i] < delta) LZ4_dict->hashTable[i]=0; - else LZ4_dict->hashTable[i] -= delta; - } - LZ4_dict->currentOffset = 64 KB; - if (LZ4_dict->dictSize > 64 KB) LZ4_dict->dictSize = 64 KB; - LZ4_dict->dictionary = dictEnd - LZ4_dict->dictSize; - } -} - - -int LZ4_compress_fast_continue (LZ4_stream_t* LZ4_stream, const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration) -{ - LZ4_stream_t_internal* streamPtr = (LZ4_stream_t_internal*)LZ4_stream; - const BYTE* const dictEnd = streamPtr->dictionary + streamPtr->dictSize; - - const BYTE* smallest = (const BYTE*) source; - if (streamPtr->initCheck) return 0; /* Uninitialized structure detected */ - if ((streamPtr->dictSize>0) && (smallest>dictEnd)) smallest = dictEnd; - LZ4_renormDictT(streamPtr, smallest); - if (acceleration < 1) acceleration = ACCELERATION_DEFAULT; - - /* Check overlapping input/dictionary space */ - { - const BYTE* sourceEnd = (const BYTE*) source + inputSize; - if ((sourceEnd > streamPtr->dictionary) && (sourceEnd < dictEnd)) - { - streamPtr->dictSize = (U32)(dictEnd - sourceEnd); - if (streamPtr->dictSize > 64 KB) streamPtr->dictSize = 64 KB; - if (streamPtr->dictSize < 4) streamPtr->dictSize = 0; - streamPtr->dictionary = dictEnd - streamPtr->dictSize; - } - } - - /* prefix mode : source data follows dictionary */ - if (dictEnd == (const BYTE*)source) - { - int result; - if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) - result = LZ4_compress_generic(LZ4_stream, source, dest, inputSize, maxOutputSize, limitedOutput, byU32, withPrefix64k, dictSmall, acceleration); - else - result = LZ4_compress_generic(LZ4_stream, source, dest, inputSize, maxOutputSize, limitedOutput, byU32, withPrefix64k, noDictIssue, acceleration); - streamPtr->dictSize += (U32)inputSize; - streamPtr->currentOffset += (U32)inputSize; - return result; - } - - /* external dictionary mode */ - { - int result; - if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) - result = LZ4_compress_generic(LZ4_stream, source, dest, inputSize, maxOutputSize, limitedOutput, byU32, usingExtDict, dictSmall, acceleration); - else - result = LZ4_compress_generic(LZ4_stream, source, dest, inputSize, maxOutputSize, limitedOutput, byU32, usingExtDict, noDictIssue, acceleration); - streamPtr->dictionary = (const BYTE*)source; - streamPtr->dictSize = (U32)inputSize; - streamPtr->currentOffset += (U32)inputSize; - return result; - } -} - - -/* Hidden debug function, to force external dictionary mode */ -int LZ4_compress_forceExtDict (LZ4_stream_t* LZ4_dict, const char* source, char* dest, int inputSize) -{ - LZ4_stream_t_internal* streamPtr = (LZ4_stream_t_internal*)LZ4_dict; - int result; - const BYTE* const dictEnd = streamPtr->dictionary + streamPtr->dictSize; - - const BYTE* smallest = dictEnd; - if (smallest > (const BYTE*) source) smallest = (const BYTE*) source; - LZ4_renormDictT((LZ4_stream_t_internal*)LZ4_dict, smallest); - - result = LZ4_compress_generic(LZ4_dict, source, dest, inputSize, 0, notLimited, byU32, usingExtDict, noDictIssue, 1); - - streamPtr->dictionary = (const BYTE*)source; - streamPtr->dictSize = (U32)inputSize; - streamPtr->currentOffset += (U32)inputSize; - - return result; -} - - -int LZ4_saveDict (LZ4_stream_t* LZ4_dict, char* safeBuffer, int dictSize) -{ - LZ4_stream_t_internal* dict = (LZ4_stream_t_internal*) LZ4_dict; - const BYTE* previousDictEnd = dict->dictionary + dict->dictSize; - - if ((U32)dictSize > 64 KB) dictSize = 64 KB; /* useless to define a dictionary > 64 KB */ - if ((U32)dictSize > dict->dictSize) dictSize = dict->dictSize; - - memmove(safeBuffer, previousDictEnd - dictSize, dictSize); - - dict->dictionary = (const BYTE*)safeBuffer; - dict->dictSize = (U32)dictSize; - - return dictSize; -} - - - -/******************************* -* Decompression functions -*******************************/ -/* - * This generic decompression function cover all use cases. - * It shall be instantiated several times, using different sets of directives - * Note that it is essential 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 if dict == noDict */ - 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 size_t dec32table[] = {4, 1, 2, 1, 4, 4, 4, 4}; - const size_t dec64table[] = {0, 0, 0, (size_t)-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 */ - while (1) - { - unsigned token; - size_t length; - const BYTE* match; - - /* get literal length */ - 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-COPYLENGTH))) - { - 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 */ - match = cpy - LZ4_readLE16(ip); ip+=2; - if ((checkOffset) && (unlikely(match < lowLimit))) goto _output_error; /* Error : offset outside destination buffer */ - - /* get matchlength */ - length = token & ML_MASK; - if (length == ML_MASK) - { - unsigned s; - do - { - if ((endOnInput) && (ip > iend-LASTLITERALS)) goto _output_error; - s = *ip++; - length += s; - } while (s==255); - if ((safeDecode) && unlikely((size_t)(op+length)<(size_t)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 */ - match = dictEnd - (lowPrefix-match); - memmove(op, match, length); op += length; - } - else - { - /* match encompass external dictionary and current segment */ - size_t copySize = (size_t)(lowPrefix-match); - memcpy(op, dictEnd - copySize, copySize); - op += copySize; - copySize = length - copySize; - if (copySize > (size_t)(op-lowPrefix)) /* overlap within current segment */ - { - BYTE* const endOfMatch = op + copySize; - const BYTE* copyFrom = lowPrefix; - while (op < endOfMatch) *op++ = *copyFrom++; - } - else - { - memcpy(op, lowPrefix, copySize); - op += copySize; - } - } - continue; - } - - /* copy repeated sequence */ - cpy = op + length; - if (unlikely((op-match)<8)) - { - const size_t dec64 = dec64table[op-match]; - op[0] = match[0]; - op[1] = match[1]; - op[2] = match[2]; - op[3] = match[3]; - match += dec32table[op-match]; - LZ4_copy4(op+4, match); - op += 8; match -= dec64; - } else { LZ4_copy8(op, match); op+=8; match+=8; } - - if (unlikely(cpy>oend-12)) - { - if (cpy > oend-LASTLITERALS) goto _output_error; /* Error : last LASTLITERALS bytes must be literals */ - if (op < oend-8) - { - LZ4_wildCopy(op, match, oend-8); - match += (oend-8) - op; - op = oend-8; - } - while (opprefixSize = (size_t) dictSize; - lz4sd->prefixEnd = (const BYTE*) dictionary + dictSize; - lz4sd->externalDict = NULL; - lz4sd->extDictSize = 0; - return 1; -} - -/* -*_continue() : - These decoding functions allow decompression of multiple blocks in "streaming" mode. - Previously decoded blocks must still be available at the memory position where they were decoded. - If it's not possible, save the relevant part of decoded data into a safe buffer, - and indicate where it stands using LZ4_setStreamDecode() -*/ -int LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int compressedSize, int maxOutputSize) -{ - LZ4_streamDecode_t_internal* lz4sd = (LZ4_streamDecode_t_internal*) LZ4_streamDecode; - int result; - - if (lz4sd->prefixEnd == (BYTE*)dest) - { - result = LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, - endOnInputSize, full, 0, - usingExtDict, lz4sd->prefixEnd - lz4sd->prefixSize, lz4sd->externalDict, lz4sd->extDictSize); - if (result <= 0) return result; - lz4sd->prefixSize += result; - lz4sd->prefixEnd += result; - } - else - { - lz4sd->extDictSize = lz4sd->prefixSize; - lz4sd->externalDict = lz4sd->prefixEnd - lz4sd->extDictSize; - result = LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, - endOnInputSize, full, 0, - usingExtDict, (BYTE*)dest, lz4sd->externalDict, lz4sd->extDictSize); - if (result <= 0) return result; - lz4sd->prefixSize = result; - lz4sd->prefixEnd = (BYTE*)dest + result; - } - - return result; -} - -int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int originalSize) -{ - LZ4_streamDecode_t_internal* lz4sd = (LZ4_streamDecode_t_internal*) LZ4_streamDecode; - int result; - - if (lz4sd->prefixEnd == (BYTE*)dest) - { - result = LZ4_decompress_generic(source, dest, 0, originalSize, - endOnOutputSize, full, 0, - usingExtDict, lz4sd->prefixEnd - lz4sd->prefixSize, lz4sd->externalDict, lz4sd->extDictSize); - if (result <= 0) return result; - lz4sd->prefixSize += originalSize; - lz4sd->prefixEnd += originalSize; - } - else - { - lz4sd->extDictSize = lz4sd->prefixSize; - lz4sd->externalDict = (BYTE*)dest - lz4sd->extDictSize; - result = LZ4_decompress_generic(source, dest, 0, originalSize, - endOnOutputSize, full, 0, - usingExtDict, (BYTE*)dest, lz4sd->externalDict, lz4sd->extDictSize); - if (result <= 0) return result; - lz4sd->prefixSize = originalSize; - lz4sd->prefixEnd = (BYTE*)dest + originalSize; - } - - return result; -} - - -/* -Advanced decoding functions : -*_usingDict() : - These decoding functions work the same as "_continue" ones, - the dictionary must be explicitly provided within parameters -*/ - -FORCE_INLINE int LZ4_decompress_usingDict_generic(const char* source, char* dest, int compressedSize, int maxOutputSize, int safe, const char* dictStart, int dictSize) -{ - if (dictSize==0) - return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, safe, full, 0, noDict, (BYTE*)dest, NULL, 0); - if (dictStart+dictSize == dest) - { - if (dictSize >= (int)(64 KB - 1)) - return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, safe, full, 0, withPrefix64k, (BYTE*)dest-64 KB, NULL, 0); - return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, safe, full, 0, noDict, (BYTE*)dest-dictSize, NULL, 0); - } - return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, safe, full, 0, usingExtDict, (BYTE*)dest, (const BYTE*)dictStart, dictSize); -} - -int LZ4_decompress_safe_usingDict(const char* source, char* dest, int compressedSize, int maxOutputSize, const char* dictStart, int dictSize) -{ - return LZ4_decompress_usingDict_generic(source, dest, compressedSize, maxOutputSize, 1, dictStart, dictSize); -} - -int LZ4_decompress_fast_usingDict(const char* source, char* dest, int originalSize, const char* dictStart, int dictSize) -{ - return LZ4_decompress_usingDict_generic(source, dest, 0, originalSize, 0, dictStart, dictSize); -} - -/* debug function */ -int LZ4_decompress_safe_forceExtDict(const char* source, char* dest, int compressedSize, int maxOutputSize, const char* dictStart, int dictSize) -{ - return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, endOnInputSize, full, 0, usingExtDict, (BYTE*)dest, (const BYTE*)dictStart, dictSize); -} - - -/*************************************************** -* Obsolete Functions -***************************************************/ -/* obsolete compression functions */ -int LZ4_compress_limitedOutput(const char* source, char* dest, int inputSize, int maxOutputSize) { return LZ4_compress_default(source, dest, inputSize, maxOutputSize); } -int LZ4_compress(const char* source, char* dest, int inputSize) { return LZ4_compress_default(source, dest, inputSize, LZ4_compressBound(inputSize)); } -int LZ4_compress_limitedOutput_withState (void* state, const char* src, char* dst, int srcSize, int dstSize) { return LZ4_compress_fast_extState(state, src, dst, srcSize, dstSize, 1); } -int LZ4_compress_withState (void* state, const char* src, char* dst, int srcSize) { return LZ4_compress_fast_extState(state, src, dst, srcSize, LZ4_compressBound(srcSize), 1); } -int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_stream, const char* src, char* dst, int srcSize, int maxDstSize) { return LZ4_compress_fast_continue(LZ4_stream, src, dst, srcSize, maxDstSize, 1); } -int LZ4_compress_continue (LZ4_stream_t* LZ4_stream, const char* source, char* dest, int inputSize) { return LZ4_compress_fast_continue(LZ4_stream, source, dest, inputSize, LZ4_compressBound(inputSize), 1); } - -/* -These function names are deprecated and should no longer be used. -They are only provided here for compatibility with older user programs. -- LZ4_uncompress is totally equivalent to LZ4_decompress_fast -- LZ4_uncompress_unknownOutputSize is totally equivalent to LZ4_decompress_safe -*/ -int LZ4_uncompress (const char* source, char* dest, int outputSize) { return LZ4_decompress_fast(source, dest, outputSize); } -int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize) { return LZ4_decompress_safe(source, dest, isize, maxOutputSize); } - - -/* Obsolete Streaming functions */ - -int LZ4_sizeofStreamState() { return LZ4_STREAMSIZE; } - -static void LZ4_init(LZ4_stream_t_internal* lz4ds, BYTE* base) -{ - MEM_INIT(lz4ds, 0, LZ4_STREAMSIZE); - lz4ds->bufferStart = base; -} - -int LZ4_resetStreamState(void* state, char* inputBuffer) -{ - if ((((size_t)state) & 3) != 0) return 1; /* Error : pointer is not aligned on 4-bytes boundary */ - LZ4_init((LZ4_stream_t_internal*)state, (BYTE*)inputBuffer); - return 0; -} - -void* LZ4_create (char* inputBuffer) -{ - void* lz4ds = ALLOCATOR(8, LZ4_STREAMSIZE_U64); - LZ4_init ((LZ4_stream_t_internal*)lz4ds, (BYTE*)inputBuffer); - return lz4ds; -} - -char* LZ4_slideInputBuffer (void* LZ4_Data) -{ - LZ4_stream_t_internal* ctx = (LZ4_stream_t_internal*)LZ4_Data; - int dictSize = LZ4_saveDict((LZ4_stream_t*)LZ4_Data, (char*)ctx->bufferStart, 64 KB); - return (char*)(ctx->bufferStart + dictSize); -} - -/* Obsolete streaming decompression functions */ - -int LZ4_decompress_safe_withPrefix64k(const char* source, char* dest, int compressedSize, int maxOutputSize) -{ - return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, endOnInputSize, full, 0, withPrefix64k, (BYTE*)dest - 64 KB, NULL, 64 KB); -} - -int LZ4_decompress_fast_withPrefix64k(const char* source, char* dest, int originalSize) -{ - return LZ4_decompress_generic(source, dest, 0, originalSize, endOnOutputSize, full, 0, withPrefix64k, (BYTE*)dest - 64 KB, NULL, 64 KB); -} - -#endif /* LZ4_COMMONDEFS_ONLY */ - diff --git a/ext/lz4/lz4.h b/ext/lz4/lz4.h deleted file mode 100644 index 3e74002..0000000 --- a/ext/lz4/lz4.h +++ /dev/null @@ -1,360 +0,0 @@ -/* - LZ4 - Fast LZ compression algorithm - Header File - Copyright (C) 2011-2015, 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 source repository : https://github.com/Cyan4973/lz4 - - LZ4 public forum : https://groups.google.com/forum/#!forum/lz4c -*/ -#pragma once - -#if defined (__cplusplus) -extern "C" { -#endif - -/* - * lz4.h provides block compression functions, and gives full buffer control to programmer. - * If you need to generate inter-operable compressed data (respecting LZ4 frame specification), - * and can let the library handle its own memory, please use lz4frame.h instead. -*/ - -/************************************** -* 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 1 /* for tweaks, bug-fixes, or development */ -#define LZ4_VERSION_NUMBER (LZ4_VERSION_MAJOR *100*100 + LZ4_VERSION_MINOR *100 + LZ4_VERSION_RELEASE) -int LZ4_versionNumber (void); - -/************************************** -* Tuning parameter -**************************************/ -/* - * 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 - - -/************************************** -* Simple Functions -**************************************/ - -int LZ4_compress_default(const char* source, char* dest, int sourceSize, int maxDestSize); -int LZ4_decompress_safe (const char* source, char* dest, int compressedSize, int maxDecompressedSize); - -/* -LZ4_compress_default() : - Compresses 'sourceSize' bytes from buffer 'source' - into already allocated 'dest' buffer of size 'maxDestSize'. - Compression is guaranteed to succeed if 'maxDestSize' >= LZ4_compressBound(sourceSize). - It also runs faster, so it's a recommended setting. - If the function cannot compress 'source' into a more limited 'dest' budget, - compression stops *immediately*, and the function result is zero. - As a consequence, 'dest' content is not valid. - This function never writes outside 'dest' buffer, nor read outside 'source' buffer. - sourceSize : Max supported value is LZ4_MAX_INPUT_VALUE - maxDestSize : full or partial size of buffer 'dest' (which must be already allocated) - return : the number of bytes written into buffer 'dest' (necessarily <= maxOutputSize) - or 0 if compression fails - -LZ4_decompress_safe() : - compressedSize : is the precise full size of the compressed block. - maxDecompressedSize : is the size of destination buffer, which must be already allocated. - return : the number of bytes decompressed into destination buffer (necessarily <= maxDecompressedSize) - If destination buffer is not large enough, decoding will stop and output an error code (<0). - If the source stream is detected malformed, the function will stop decoding and return a negative result. - This function is protected against buffer overflow exploits, including malicious data packets. - It never writes outside output buffer, nor reads outside input buffer. -*/ - - -/************************************** -* Advanced Functions -**************************************/ -#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) - -/* -LZ4_compressBound() : - Provides the maximum size that LZ4 compression may output in a "worst case" scenario (input data not compressible) - This function is primarily useful for memory allocation purposes (destination buffer size). - Macro LZ4_COMPRESSBOUND() is also provided for compilation-time evaluation (stack memory allocation for example). - Note that LZ4_compress_default() compress faster when dest buffer size is >= LZ4_compressBound(srcSize) - inputSize : max supported value is LZ4_MAX_INPUT_SIZE - return : maximum output size in a "worst case" scenario - or 0, if input size is too large ( > LZ4_MAX_INPUT_SIZE) -*/ -int LZ4_compressBound(int inputSize); - -/* -LZ4_compress_fast() : - Same as LZ4_compress_default(), but allows to select an "acceleration" factor. - The larger the acceleration value, the faster the algorithm, but also the lesser the compression. - It's a trade-off. It can be fine tuned, with each successive value providing roughly +~3% to speed. - An acceleration value of "1" is the same as regular LZ4_compress_default() - Values <= 0 will be replaced by ACCELERATION_DEFAULT (see lz4.c), which is 1. -*/ -int LZ4_compress_fast (const char* source, char* dest, int sourceSize, int maxDestSize, int acceleration); - - -/* -LZ4_compress_fast_extState() : - Same compression function, just using an externally allocated memory space to store compression state. - Use LZ4_sizeofState() to know how much memory must be allocated, - and allocate it on 8-bytes boundaries (using malloc() typically). - Then, provide it as 'void* state' to compression function. -*/ -int LZ4_sizeofState(void); -int LZ4_compress_fast_extState (void* state, const char* source, char* dest, int inputSize, int maxDestSize, int acceleration); - - -/* -LZ4_compress_destSize() : - Reverse the logic, by compressing as much data as possible from 'source' buffer - into already allocated buffer 'dest' of size 'targetDestSize'. - This function either compresses the entire 'source' content into 'dest' if it's large enough, - or fill 'dest' buffer completely with as much data as possible from 'source'. - *sourceSizePtr : will be modified to indicate how many bytes where read from 'source' to fill 'dest'. - New value is necessarily <= old value. - return : Nb bytes written into 'dest' (necessarily <= targetDestSize) - or 0 if compression fails -*/ -int LZ4_compress_destSize (const char* source, char* dest, int* sourceSizePtr, int targetDestSize); - - -/* -LZ4_decompress_fast() : - originalSize : is the original and therefore uncompressed size - return : the number of bytes read from the source buffer (in other words, the compressed size) - If the source stream is detected malformed, the function will stop decoding and return a negative result. - Destination buffer must be already allocated. Its size must be a minimum of 'originalSize' bytes. - note : This function fully respect memory boundaries for properly formed compressed data. - It is a bit faster than LZ4_decompress_safe(). - However, it does not provide any protection against intentionally modified data stream (malicious input). - Use this function in trusted environment only (data to decode comes from a trusted source). -*/ -int LZ4_decompress_fast (const char* source, char* dest, int originalSize); - -/* -LZ4_decompress_safe_partial() : - This function decompress a compressed block of size 'compressedSize' at position 'source' - into destination buffer 'dest' of size 'maxDecompressedSize'. - The function tries to stop decompressing operation as soon as 'targetOutputSize' has been reached, - reducing decompression time. - return : the number of bytes decoded in the destination buffer (necessarily <= maxDecompressedSize) - Note : this number can be < 'targetOutputSize' should the compressed block to decode be smaller. - Always control how many bytes were decoded. - If the source stream is detected malformed, the function will stop decoding and return a negative result. - This function never writes outside of output buffer, and never reads outside of input buffer. It is therefore protected against malicious data packets -*/ -int LZ4_decompress_safe_partial (const char* source, char* dest, int compressedSize, int targetOutputSize, int maxDecompressedSize); - - -/*********************************************** -* Streaming Compression Functions -***********************************************/ -#define LZ4_STREAMSIZE_U64 ((1 << (LZ4_MEMORY_USAGE-3)) + 4) -#define LZ4_STREAMSIZE (LZ4_STREAMSIZE_U64 * sizeof(long long)) -/* - * LZ4_stream_t - * information structure to track an LZ4 stream. - * important : init this structure content before first use ! - * note : only allocated directly the structure if you are statically linking LZ4 - * If you are using liblz4 as a DLL, please use below construction methods instead. - */ -typedef struct { long long table[LZ4_STREAMSIZE_U64]; } LZ4_stream_t; - -/* - * LZ4_resetStream - * Use this function to init an allocated LZ4_stream_t structure - */ -void LZ4_resetStream (LZ4_stream_t* streamPtr); - -/* - * LZ4_createStream will allocate and initialize an LZ4_stream_t structure - * LZ4_freeStream releases its memory. - * In the context of a DLL (liblz4), please use these methods rather than the static struct. - * They are more future proof, in case of a change of LZ4_stream_t size. - */ -LZ4_stream_t* LZ4_createStream(void); -int LZ4_freeStream (LZ4_stream_t* streamPtr); - -/* - * LZ4_loadDict - * Use this function to load a static dictionary into LZ4_stream. - * Any previous data will be forgotten, only 'dictionary' will remain in memory. - * Loading a size of 0 is allowed. - * Return : dictionary size, in bytes (necessarily <= 64 KB) - */ -int LZ4_loadDict (LZ4_stream_t* streamPtr, const char* dictionary, int dictSize); - -/* - * LZ4_compress_fast_continue - * Compress buffer content 'src', using data from previously compressed blocks as dictionary to improve compression ratio. - * Important : Previous data blocks are assumed to still be present and unmodified ! - * 'dst' buffer must be already allocated. - * If maxDstSize >= LZ4_compressBound(srcSize), compression is guaranteed to succeed, and runs faster. - * If not, and if compressed data cannot fit into 'dst' buffer size, compression stops, and function returns a zero. - */ -int LZ4_compress_fast_continue (LZ4_stream_t* streamPtr, const char* src, char* dst, int srcSize, int maxDstSize, int acceleration); - -/* - * LZ4_saveDict - * If previously compressed data block is not guaranteed to remain available at its memory location - * save it into a safer place (char* safeBuffer) - * Note : you don't need to call LZ4_loadDict() afterwards, - * dictionary is immediately usable, you can therefore call LZ4_compress_fast_continue() - * Return : saved dictionary size in bytes (necessarily <= dictSize), or 0 if error - */ -int LZ4_saveDict (LZ4_stream_t* streamPtr, char* safeBuffer, int dictSize); - - -/************************************************ -* Streaming Decompression Functions -************************************************/ - -#define LZ4_STREAMDECODESIZE_U64 4 -#define LZ4_STREAMDECODESIZE (LZ4_STREAMDECODESIZE_U64 * sizeof(unsigned long long)) -typedef struct { unsigned long long table[LZ4_STREAMDECODESIZE_U64]; } LZ4_streamDecode_t; -/* - * LZ4_streamDecode_t - * information structure to track an LZ4 stream. - * init this structure content using LZ4_setStreamDecode or memset() before first use ! - * - * In the context of a DLL (liblz4) please prefer usage of construction methods below. - * They are more future proof, in case of a change of LZ4_streamDecode_t size in the future. - * LZ4_createStreamDecode will allocate and initialize an LZ4_streamDecode_t structure - * LZ4_freeStreamDecode releases its memory. - */ -LZ4_streamDecode_t* LZ4_createStreamDecode(void); -int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream); - -/* - * LZ4_setStreamDecode - * Use this function to instruct where to find the dictionary. - * Setting a size of 0 is allowed (same effect as reset). - * Return : 1 if OK, 0 if error - */ -int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize); - -/* -*_continue() : - These decoding functions allow decompression of multiple blocks in "streaming" mode. - Previously decoded blocks *must* remain available at the memory position where they were decoded (up to 64 KB) - In the case of a ring buffers, decoding buffer must be either : - - Exactly same size as encoding buffer, with same update rule (block boundaries at same positions) - In which case, the decoding & encoding ring buffer can have any size, including very small ones ( < 64 KB). - - Larger than encoding buffer, by a minimum of maxBlockSize more bytes. - maxBlockSize is implementation dependent. It's the maximum size you intend to compress into a single block. - In which case, encoding and decoding buffers do not need to be synchronized, - and encoding ring buffer can have any size, including small ones ( < 64 KB). - - _At least_ 64 KB + 8 bytes + maxBlockSize. - In which case, encoding and decoding buffers do not need to be synchronized, - and encoding ring buffer can have any size, including larger than decoding buffer. - Whenever these conditions are not possible, save the last 64KB of decoded data into a safe buffer, - and indicate where it is saved using LZ4_setStreamDecode() -*/ -int LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int compressedSize, int maxDecompressedSize); -int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int originalSize); - - -/* -Advanced decoding functions : -*_usingDict() : - These decoding functions work the same as - a combination of LZ4_setStreamDecode() followed by LZ4_decompress_x_continue() - They are stand-alone. They don't need nor update an LZ4_streamDecode_t structure. -*/ -int LZ4_decompress_safe_usingDict (const char* source, char* dest, int compressedSize, int maxDecompressedSize, const char* dictStart, int dictSize); -int LZ4_decompress_fast_usingDict (const char* source, char* dest, int originalSize, const char* dictStart, int dictSize); - - - -/************************************** -* Obsolete Functions -**************************************/ -/* Deprecate Warnings */ -/* Should these warnings messages be a problem, - it is generally possible to disable them, - with -Wno-deprecated-declarations for gcc - or _CRT_SECURE_NO_WARNINGS in Visual for example. - You can also define LZ4_DEPRECATE_WARNING_DEFBLOCK. */ -#ifndef LZ4_DEPRECATE_WARNING_DEFBLOCK -# define LZ4_DEPRECATE_WARNING_DEFBLOCK -# define LZ4_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) -# if (LZ4_GCC_VERSION >= 405) || defined(__clang__) -# define LZ4_DEPRECATED(message) __attribute__((deprecated(message))) -# elif (LZ4_GCC_VERSION >= 301) -# define LZ4_DEPRECATED(message) __attribute__((deprecated)) -# elif defined(_MSC_VER) -# define LZ4_DEPRECATED(message) __declspec(deprecated(message)) -# else -# pragma message("WARNING: You need to implement LZ4_DEPRECATED for this compiler") -# define LZ4_DEPRECATED(message) -# endif -#endif /* LZ4_DEPRECATE_WARNING_DEFBLOCK */ - -/* Obsolete compression functions */ -/* These functions are planned to start generate warnings by r131 approximately */ -int LZ4_compress (const char* source, char* dest, int sourceSize); -int LZ4_compress_limitedOutput (const char* source, char* dest, int sourceSize, int maxOutputSize); -int LZ4_compress_withState (void* state, const char* source, char* dest, int inputSize); -int LZ4_compress_limitedOutput_withState (void* state, const char* source, char* dest, int inputSize, int maxOutputSize); -int LZ4_compress_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize); -int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize, int maxOutputSize); - -/* Obsolete decompression functions */ -/* These function names are completely deprecated and must no longer be used. - They are only provided here for compatibility with older programs. - - LZ4_uncompress is the same as LZ4_decompress_fast - - LZ4_uncompress_unknownOutputSize is the same as LZ4_decompress_safe - These function prototypes are now disabled; uncomment them only if you really need them. - It is highly recommended to stop using these prototypes and migrate to maintained ones */ -/* int LZ4_uncompress (const char* source, char* dest, int outputSize); */ -/* int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize); */ - -/* Obsolete streaming functions; use new streaming interface whenever possible */ -LZ4_DEPRECATED("use LZ4_createStream() instead") void* LZ4_create (char* inputBuffer); -LZ4_DEPRECATED("use LZ4_createStream() instead") int LZ4_sizeofStreamState(void); -LZ4_DEPRECATED("use LZ4_resetStream() instead") int LZ4_resetStreamState(void* state, char* inputBuffer); -LZ4_DEPRECATED("use LZ4_saveDict() instead") char* LZ4_slideInputBuffer (void* state); - -/* Obsolete streaming decoding functions */ -LZ4_DEPRECATED("use LZ4_decompress_safe_usingDict() instead") int LZ4_decompress_safe_withPrefix64k (const char* src, char* dst, int compressedSize, int maxDstSize); -LZ4_DEPRECATED("use LZ4_decompress_fast_usingDict() instead") int LZ4_decompress_fast_withPrefix64k (const char* src, char* dst, int originalSize); - - -#if defined (__cplusplus) -} -#endif diff --git a/ext/miniupnpc/connecthostport.c b/ext/miniupnpc/connecthostport.c index 854203e..c12d7bd 100644 --- a/ext/miniupnpc/connecthostport.c +++ b/ext/miniupnpc/connecthostport.c @@ -1,12 +1,10 @@ -/* $Id: connecthostport.c,v 1.15 2015/10/09 16:26:19 nanard Exp $ */ +/* $Id: connecthostport.c,v 1.16 2016/12/16 08:57:53 nanard Exp $ */ /* Project : miniupnp * Author : Thomas Bernard - * Copyright (c) 2010-2015 Thomas Bernard + * Copyright (c) 2010-2016 Thomas Bernard * This software is subject to the conditions detailed in the * LICENCE file provided in this distribution. */ -#define _CRT_SECURE_NO_WARNINGS - /* use getaddrinfo() or gethostbyname() * uncomment the following line in order to use gethostbyname() */ #ifdef NO_GETADDRINFO @@ -102,13 +100,13 @@ int connecthostport(const char * host, unsigned short port, timeout.tv_usec = 0; if(setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(struct timeval)) < 0) { - PRINT_SOCKET_ERROR("setsockopt"); + 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"); + PRINT_SOCKET_ERROR("setsockopt SO_SNDTIMEO"); } #endif /* #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT */ dest.sin_family = AF_INET; diff --git a/ext/miniupnpc/minihttptestserver.c b/ext/miniupnpc/minihttptestserver.c index 6663bc0..d95dd7c 100644 --- a/ext/miniupnpc/minihttptestserver.c +++ b/ext/miniupnpc/minihttptestserver.c @@ -1,7 +1,7 @@ -/* $Id: minihttptestserver.c,v 1.19 2015/11/17 09:07:17 nanard Exp $ */ +/* $Id: minihttptestserver.c,v 1.20 2016/12/16 08:54:55 nanard Exp $ */ /* Project : miniUPnP * Author : Thomas Bernard - * Copyright (c) 2011-2015 Thomas Bernard + * Copyright (c) 2011-2016 Thomas Bernard * This software is subject to the conditions detailed in the * LICENCE file provided in this distribution. * */ @@ -611,7 +611,7 @@ int main(int argc, char * * argv) { if(pid < 0) { perror("wait"); } else { - printf("child(%d) terminated with status %d\n", pid, status); + printf("child(%d) terminated with status %d\n", (int)pid, status); } --child_to_wait_for; } @@ -648,7 +648,7 @@ int main(int argc, char * * argv) { if(pid < 0) { perror("wait"); } else { - printf("child(%d) terminated with status %d\n", pid, status); + printf("child(%d) terminated with status %d\n", (int)pid, status); } --child_to_wait_for; } diff --git a/ext/miniupnpc/minisoap.c b/ext/miniupnpc/minisoap.c index e2efd8f..7aa0213 100644 --- a/ext/miniupnpc/minisoap.c +++ b/ext/miniupnpc/minisoap.c @@ -1,4 +1,3 @@ -#define _CRT_SECURE_NO_WARNINGS /* $Id: minisoap.c,v 1.24 2015/10/26 17:05:07 nanard Exp $ */ /* Project : miniupnp * Author : Thomas Bernard @@ -20,6 +19,7 @@ #include #endif #include "minisoap.h" + #ifdef _WIN32 #define OS_STRING "Win32" #define MINIUPNPC_VERSION_STRING "2.0" @@ -124,3 +124,5 @@ int soapPostSubmit(int fd, #endif return httpWrite(fd, body, bodysize, headerbuf, headerssize); } + + diff --git a/ext/miniupnpc/minissdpc.c b/ext/miniupnpc/minissdpc.c index 0f7271e..06b11e8 100644 --- a/ext/miniupnpc/minissdpc.c +++ b/ext/miniupnpc/minissdpc.c @@ -1,11 +1,9 @@ -#define _CRT_SECURE_NO_WARNINGS - -/* $Id: minissdpc.c,v 1.31 2016/01/19 09:56:46 nanard Exp $ */ +/* $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-2015 Thomas Bernard + * copyright (c) 2005-2016 Thomas Bernard * This software is subjet to the conditions detailed in the * provided LICENCE file. */ /*#include */ @@ -13,6 +11,9 @@ #include #include #include +#if defined (__NetBSD__) +#include +#endif #if defined(_WIN32) || defined(__amigaos__) || defined(__amigaos4__) #ifdef _WIN32 #include @@ -72,6 +73,9 @@ struct sockaddr_un { #if !defined(HAS_IP_MREQN) && !defined(_WIN32) #include +#if defined(__sun) +#include +#endif #endif #if defined(HAS_IP_MREQN) && defined(NEED_STRUCT_IP_MREQN) @@ -168,7 +172,7 @@ connectToMiniSSDPD(const char * socketpath) { int s; struct sockaddr_un addr; -#ifdef MINIUPNPC_SET_SOCKET_TIMEOUT +#if defined(MINIUPNPC_SET_SOCKET_TIMEOUT) && !defined(__sun) struct timeval timeout; #endif /* #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT */ @@ -179,19 +183,20 @@ connectToMiniSSDPD(const char * socketpath) perror("socket(unix)"); return MINISSDPC_SOCKET_ERROR; } -#ifdef MINIUPNPC_SET_SOCKET_TIMEOUT +#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"); + 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"); + perror("setsockopt SO_SNDTIMEO unix"); } #endif /* #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT */ if(!socketpath) @@ -627,7 +632,7 @@ ssdpDiscoverDevices(const char * const deviceTypes[], unsigned int ifindex = if_nametoindex(multicastif); /* eth0, etc. */ if(setsockopt(sudp, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifindex, sizeof(ifindex)) < 0) { - PRINT_SOCKET_ERROR("setsockopt"); + PRINT_SOCKET_ERROR("setsockopt IPV6_MULTICAST_IF"); } #else #ifdef DEBUG @@ -642,7 +647,7 @@ ssdpDiscoverDevices(const char * const deviceTypes[], ((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"); + PRINT_SOCKET_ERROR("setsockopt IP_MULTICAST_IF"); } } else { #ifdef HAS_IP_MREQN @@ -652,7 +657,7 @@ ssdpDiscoverDevices(const char * const deviceTypes[], reqn.imr_ifindex = if_nametoindex(multicastif); if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_IF, (const char *)&reqn, sizeof(reqn)) < 0) { - PRINT_SOCKET_ERROR("setsockopt"); + PRINT_SOCKET_ERROR("setsockopt IP_MULTICAST_IF"); } #elif !defined(_WIN32) struct ifreq ifr; @@ -666,7 +671,7 @@ ssdpDiscoverDevices(const char * const deviceTypes[], 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"); + PRINT_SOCKET_ERROR("setsockopt IP_MULTICAST_IF"); } #else /* _WIN32 */ #ifdef DEBUG diff --git a/ext/miniupnpc/miniupnpc.c b/ext/miniupnpc/miniupnpc.c index 68d562f..2dc5c95 100644 --- a/ext/miniupnpc/miniupnpc.c +++ b/ext/miniupnpc/miniupnpc.c @@ -1,5 +1,3 @@ -#define _CRT_SECURE_NO_WARNINGS - /* $Id: miniupnpc.c,v 1.149 2016/02/09 09:50:46 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * Project : miniupnp diff --git a/ext/miniupnpc/miniupnpc.h b/ext/miniupnpc/miniupnpc.h index 0b5b473..4cc45f7 100644 --- a/ext/miniupnpc/miniupnpc.h +++ b/ext/miniupnpc/miniupnpc.h @@ -19,7 +19,7 @@ #define UPNPDISCOVER_MEMORY_ERROR (-102) /* versions : */ -#define MINIUPNPC_VERSION "2.0" +#define MINIUPNPC_VERSION "2.0.20161216" #define MINIUPNPC_API_VERSION 16 /* Source port: diff --git a/ext/miniupnpc/miniwget.c b/ext/miniupnpc/miniwget.c index 1af106d..93c8aa6 100644 --- a/ext/miniupnpc/miniwget.c +++ b/ext/miniupnpc/miniwget.c @@ -1,6 +1,4 @@ -#define _CRT_SECURE_NO_WARNINGS - -/* $Id: miniwget.c,v 1.75 2016/01/24 17:24:36 nanard Exp $ */ +/* $Id: miniwget.c,v 1.76 2016/12/16 08:54:04 nanard Exp $ */ /* Project : miniupnp * Website : http://miniupnp.free.fr/ * Author : Thomas Bernard @@ -50,6 +48,7 @@ #define MIN(x,y) (((x)<(y))?(x):(y)) #endif /* MIN */ + #ifdef _WIN32 #define OS_STRING "Win32" #define MINIUPNPC_VERSION_STRING "2.0" @@ -89,8 +88,10 @@ getHTTPResponse(int s, int * size, int * status_code) 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); @@ -187,8 +188,10 @@ getHTTPResponse(int s, int * size, int * status_code) *status_code = atoi(header_buf + sp + 1); else { +#ifdef DEBUG reason_phrase = header_buf + sp + 1; reason_phrase_len = i - sp - 1; +#endif break; } } diff --git a/ext/miniupnpc/minixml.c b/ext/miniupnpc/minixml.c index 5c79b3c..3e201ec 100644 --- a/ext/miniupnpc/minixml.c +++ b/ext/miniupnpc/minixml.c @@ -1,4 +1,3 @@ -#define _CRT_SECURE_NO_WARNINGS /* $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 diff --git a/ext/miniupnpc/minixmlvalid.c b/ext/miniupnpc/minixmlvalid.c index a86beba..dad1488 100644 --- a/ext/miniupnpc/minixmlvalid.c +++ b/ext/miniupnpc/minixmlvalid.c @@ -1,4 +1,3 @@ -#define _CRT_SECURE_NO_WARNINGS /* $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/ diff --git a/ext/miniupnpc/portlistingparse.c b/ext/miniupnpc/portlistingparse.c index 0e09278..d1954f5 100644 --- a/ext/miniupnpc/portlistingparse.c +++ b/ext/miniupnpc/portlistingparse.c @@ -1,7 +1,7 @@ -/* $Id: portlistingparse.c,v 1.9 2015/07/15 12:41:13 nanard Exp $ */ +/* $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-2015 Thomas Bernard + * (c) 2011-2016 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include @@ -55,7 +55,7 @@ startelt(void * d, const char * name, int l) pdata->curelt = PortMappingEltNone; for(i = 0; elements[i].str; i++) { - if(memcmp(name, elements[i].str, l) == 0) + if(strlen(elements[i].str) == (size_t)l && memcmp(name, elements[i].str, l) == 0) { pdata->curelt = elements[i].code; break; diff --git a/ext/miniupnpc/upnpc.c b/ext/miniupnpc/upnpc.c index 94f131c..8e7edad 100644 --- a/ext/miniupnpc/upnpc.c +++ b/ext/miniupnpc/upnpc.c @@ -1,4 +1,4 @@ -/* $Id: upnpc.c,v 1.114 2016/01/22 15:04:23 nanard Exp $ */ +/* $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 @@ -242,7 +242,7 @@ static void NewListRedirections(struct UPNPUrls * urls, * 2 - get extenal ip address * 3 - Add port mapping * 4 - get this port mapping from the IGD */ -static void SetRedirectAndTest(struct UPNPUrls * urls, +static int SetRedirectAndTest(struct UPNPUrls * urls, struct IGDdatas * data, const char * iaddr, const char * iport, @@ -262,13 +262,13 @@ static void SetRedirectAndTest(struct UPNPUrls * urls, if(!iaddr || !iport || !eport || !proto) { fprintf(stderr, "Wrong arguments\n"); - return; + return -1; } proto = protofix(proto); if(!proto) { fprintf(stderr, "invalid protocol\n"); - return; + return -1; } r = UPNP_GetExternalIPAddress(urls->controlURL, @@ -302,17 +302,19 @@ static void SetRedirectAndTest(struct UPNPUrls * urls, eport, proto, NULL/*remoteHost*/, intClient, intPort, NULL/*desc*/, NULL/*enabled*/, duration); - if(r!=UPNPCOMMAND_SUCCESS) + if(r!=UPNPCOMMAND_SUCCESS) { printf("GetSpecificPortMappingEntry() failed with code %d (%s)\n", r, strupnperror(r)); - else { + 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 void +static int RemoveRedirect(struct UPNPUrls * urls, struct IGDdatas * data, const char * eport, @@ -323,19 +325,25 @@ RemoveRedirect(struct UPNPUrls * urls, if(!proto || !eport) { fprintf(stderr, "invalid arguments\n"); - return; + return -1; } proto = protofix(proto); if(!proto) { fprintf(stderr, "protocol invalid\n"); - return; + return -1; } r = UPNP_DeletePortMapping(urls->controlURL, data->first.servicetype, eport, proto, remoteHost); - printf("UPNP_DeletePortMapping() returned : %d\n", r); + 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 void +static int RemoveRedirectRange(struct UPNPUrls * urls, struct IGDdatas * data, const char * ePortStart, char const * ePortEnd, @@ -349,16 +357,22 @@ RemoveRedirectRange(struct UPNPUrls * urls, if(!proto || !ePortStart || !ePortEnd) { fprintf(stderr, "invalid arguments\n"); - return; + return -1; } proto = protofix(proto); if(!proto) { fprintf(stderr, "protocol invalid\n"); - return; + return -1; } r = UPNP_DeletePortMappingRange(urls->controlURL, data->first.servicetype, ePortStart, ePortEnd, proto, manage); - printf("UPNP_DeletePortMappingRange() returned : %d\n", r); + 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 */ @@ -711,29 +725,33 @@ int main(int argc, char ** argv) NewListRedirections(&urls, &data); break; case 'a': - SetRedirectAndTest(&urls, &data, + if (SetRedirectAndTest(&urls, &data, commandargv[0], commandargv[1], commandargv[2], commandargv[3], (commandargc > 4)?commandargv[4]:"0", - description, 0); + description, 0) < 0) + retcode = 2; break; case 'd': - RemoveRedirect(&urls, &data, commandargv[0], commandargv[1], - commandargc > 2 ? commandargv[2] : NULL); + if (RemoveRedirect(&urls, &data, commandargv[0], commandargv[1], + commandargc > 2 ? commandargv[2] : NULL) < 0) + retcode = 2; break; case 'n': /* aNy */ - SetRedirectAndTest(&urls, &data, + if (SetRedirectAndTest(&urls, &data, commandargv[0], commandargv[1], commandargv[2], commandargv[3], (commandargc > 4)?commandargv[4]:"0", - description, 1); + description, 1) < 0) + retcode = 2; break; case 'N': if (commandargc < 3) fprintf(stderr, "too few arguments\n"); - RemoveRedirectRange(&urls, &data, commandargv[0], commandargv[1], commandargv[2], - commandargc > 3 ? commandargv[3] : NULL); + if (RemoveRedirectRange(&urls, &data, commandargv[0], commandargv[1], commandargv[2], + commandargc > 3 ? commandargv[3] : NULL) < 0) + retcode = 2; break; case 's': GetConnectionStatus(&urls, &data); @@ -749,17 +767,19 @@ int main(int argc, char ** argv) break; } else if(is_int(commandargv[i+1])){ /* 2nd parameter is an integer : */ - SetRedirectAndTest(&urls, &data, + if (SetRedirectAndTest(&urls, &data, lanaddr, commandargv[i], commandargv[i+1], commandargv[i+2], "0", - description, 0); + description, 0) < 0) + retcode = 2; i+=3; /* 3 parameters parsed */ } else { /* 2nd parameter not an integer : */ - SetRedirectAndTest(&urls, &data, + if (SetRedirectAndTest(&urls, &data, lanaddr, commandargv[i], commandargv[i], commandargv[i+1], "0", - description, 0); + description, 0) < 0) + retcode = 2; i+=2; /* 2 parameters parsed */ } } diff --git a/ext/miniupnpc/upnpcommands.c b/ext/miniupnpc/upnpcommands.c index 2b65651..3988e49 100644 --- a/ext/miniupnpc/upnpcommands.c +++ b/ext/miniupnpc/upnpcommands.c @@ -1,5 +1,3 @@ -#define _CRT_SECURE_NO_WARNINGS - /* $Id: upnpcommands.c,v 1.47 2016/03/07 12:26:48 nanard Exp $ */ /* Project : miniupnp * Author : Thomas Bernard diff --git a/ext/miniupnpc/upnpreplyparse.c b/ext/miniupnpc/upnpreplyparse.c index 88d77a6..5de5796 100644 --- a/ext/miniupnpc/upnpreplyparse.c +++ b/ext/miniupnpc/upnpreplyparse.c @@ -1,4 +1,3 @@ -#define _CRT_SECURE_NO_WARNINGS /* $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/ diff --git a/ext/x64-salsa2012-asm/README.md b/ext/x64-salsa2012-asm/README.md new file mode 100644 index 0000000..a69a1a6 --- /dev/null +++ b/ext/x64-salsa2012-asm/README.md @@ -0,0 +1,6 @@ +Blazingly fast X64 ASM implementation of Salsa20/12 +====== + +This is ripped from the [cnacl](https://github.com/cjdelisle/cnacl) source. The actual code is by Danial J. Bernstein and is in the public domain. + +This is included on Linux and Mac 64-bit builds and is significantly faster than the SSE intrinsics or C versions. It's used for packet encode/decode only since its use differs a bit from the regular Salsa20 C++ class. Specifically it lacks the ability to be called on multiple blocks, preferring instead to take a key and a single stream to encrypt and that's it. diff --git a/ext/x64-salsa2012-asm/salsa2012.h b/ext/x64-salsa2012-asm/salsa2012.h new file mode 100644 index 0000000..73e375e --- /dev/null +++ b/ext/x64-salsa2012-asm/salsa2012.h @@ -0,0 +1,16 @@ +#ifndef ZT_X64_SALSA2012_ASM +#define ZT_X64_SALSA2012_ASM + +#ifdef __cplusplus +extern "C" { +#endif + +// Generates Salsa20/12 key stream +// output, outlen, nonce, key (256-bit / 32-byte) +extern int zt_salsa2012_amd64_xmm6(unsigned char *, unsigned long long, const unsigned char *, const unsigned char *); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext/x64-salsa2012-asm/salsa2012.s b/ext/x64-salsa2012-asm/salsa2012.s new file mode 100644 index 0000000..699c89a --- /dev/null +++ b/ext/x64-salsa2012-asm/salsa2012.s @@ -0,0 +1,4488 @@ +# qhasm: enter zt_salsa2012_amd64_xmm6 +.text +.p2align 5 +.globl _zt_salsa2012_amd64_xmm6 +.globl zt_salsa2012_amd64_xmm6 +_zt_salsa2012_amd64_xmm6: +zt_salsa2012_amd64_xmm6: +mov %rsp,%r11 +and $31,%r11 +add $480,%r11 +sub %r11,%rsp + +# qhasm: r11_stack = r11_caller +# asm 1: movq r11_stack=stack64#1 +# asm 2: movq r11_stack=352(%rsp) +movq %r11,352(%rsp) + +# qhasm: r12_stack = r12_caller +# asm 1: movq r12_stack=stack64#2 +# asm 2: movq r12_stack=360(%rsp) +movq %r12,360(%rsp) + +# qhasm: r13_stack = r13_caller +# asm 1: movq r13_stack=stack64#3 +# asm 2: movq r13_stack=368(%rsp) +movq %r13,368(%rsp) + +# qhasm: r14_stack = r14_caller +# asm 1: movq r14_stack=stack64#4 +# asm 2: movq r14_stack=376(%rsp) +movq %r14,376(%rsp) + +# qhasm: r15_stack = r15_caller +# asm 1: movq r15_stack=stack64#5 +# asm 2: movq r15_stack=384(%rsp) +movq %r15,384(%rsp) + +# qhasm: rbx_stack = rbx_caller +# asm 1: movq rbx_stack=stack64#6 +# asm 2: movq rbx_stack=392(%rsp) +movq %rbx,392(%rsp) + +# qhasm: rbp_stack = rbp_caller +# asm 1: movq rbp_stack=stack64#7 +# asm 2: movq rbp_stack=400(%rsp) +movq %rbp,400(%rsp) + +# qhasm: bytes = arg2 +# asm 1: mov bytes=int64#6 +# asm 2: mov bytes=%r9 +mov %rsi,%r9 + +# qhasm: out = arg1 +# asm 1: mov out=int64#1 +# asm 2: mov out=%rdi +mov %rdi,%rdi + +# qhasm: m = out +# asm 1: mov m=int64#2 +# asm 2: mov m=%rsi +mov %rdi,%rsi + +# qhasm: iv = arg3 +# asm 1: mov iv=int64#3 +# asm 2: mov iv=%rdx +mov %rdx,%rdx + +# qhasm: k = arg4 +# asm 1: mov k=int64#8 +# asm 2: mov k=%r10 +mov %rcx,%r10 + +# qhasm: unsigned>? bytes - 0 +# asm 1: cmp $0, +jbe ._done + +# qhasm: a = 0 +# asm 1: mov $0,>a=int64#7 +# asm 2: mov $0,>a=%rax +mov $0,%rax + +# qhasm: i = bytes +# asm 1: mov i=int64#4 +# asm 2: mov i=%rcx +mov %r9,%rcx + +# qhasm: while (i) { *out++ = a; --i } +rep stosb + +# qhasm: out -= bytes +# asm 1: sub r11_stack=stack64#1 +# asm 2: movq r11_stack=352(%rsp) +movq %r11,352(%rsp) + +# qhasm: r12_stack = r12_caller +# asm 1: movq r12_stack=stack64#2 +# asm 2: movq r12_stack=360(%rsp) +movq %r12,360(%rsp) + +# qhasm: r13_stack = r13_caller +# asm 1: movq r13_stack=stack64#3 +# asm 2: movq r13_stack=368(%rsp) +movq %r13,368(%rsp) + +# qhasm: r14_stack = r14_caller +# asm 1: movq r14_stack=stack64#4 +# asm 2: movq r14_stack=376(%rsp) +movq %r14,376(%rsp) + +# qhasm: r15_stack = r15_caller +# asm 1: movq r15_stack=stack64#5 +# asm 2: movq r15_stack=384(%rsp) +movq %r15,384(%rsp) + +# qhasm: rbx_stack = rbx_caller +# asm 1: movq rbx_stack=stack64#6 +# asm 2: movq rbx_stack=392(%rsp) +movq %rbx,392(%rsp) + +# qhasm: rbp_stack = rbp_caller +# asm 1: movq rbp_stack=stack64#7 +# asm 2: movq rbp_stack=400(%rsp) +movq %rbp,400(%rsp) + +# qhasm: out = arg1 +# asm 1: mov out=int64#1 +# asm 2: mov out=%rdi +mov %rdi,%rdi + +# qhasm: m = arg2 +# asm 1: mov m=int64#2 +# asm 2: mov m=%rsi +mov %rsi,%rsi + +# qhasm: bytes = arg3 +# asm 1: mov bytes=int64#6 +# asm 2: mov bytes=%r9 +mov %rdx,%r9 + +# qhasm: iv = arg4 +# asm 1: mov iv=int64#3 +# asm 2: mov iv=%rdx +mov %rcx,%rdx + +# qhasm: k = arg5 +# asm 1: mov k=int64#8 +# asm 2: mov k=%r10 +mov %r8,%r10 + +# qhasm: unsigned>? bytes - 0 +# asm 1: cmp $0, +jbe ._done +# comment:fp stack unchanged by fallthrough + +# qhasm: start: +._start: + +# qhasm: in12 = *(uint32 *) (k + 20) +# asm 1: movl 20(in12=int64#4d +# asm 2: movl 20(in12=%ecx +movl 20(%r10),%ecx + +# qhasm: in1 = *(uint32 *) (k + 0) +# asm 1: movl 0(in1=int64#5d +# asm 2: movl 0(in1=%r8d +movl 0(%r10),%r8d + +# qhasm: in6 = *(uint32 *) (iv + 0) +# asm 1: movl 0(in6=int64#7d +# asm 2: movl 0(in6=%eax +movl 0(%rdx),%eax + +# qhasm: in11 = *(uint32 *) (k + 16) +# asm 1: movl 16(in11=int64#9d +# asm 2: movl 16(in11=%r11d +movl 16(%r10),%r11d + +# qhasm: ((uint32 *)&x1)[0] = in12 +# asm 1: movl x1=stack128#1 +# asm 2: movl x1=0(%rsp) +movl %ecx,0(%rsp) + +# qhasm: ((uint32 *)&x1)[1] = in1 +# asm 1: movl in8=int64#4 +# asm 2: mov $0,>in8=%rcx +mov $0,%rcx + +# qhasm: in13 = *(uint32 *) (k + 24) +# asm 1: movl 24(in13=int64#5d +# asm 2: movl 24(in13=%r8d +movl 24(%r10),%r8d + +# qhasm: in2 = *(uint32 *) (k + 4) +# asm 1: movl 4(in2=int64#7d +# asm 2: movl 4(in2=%eax +movl 4(%r10),%eax + +# qhasm: in7 = *(uint32 *) (iv + 4) +# asm 1: movl 4(in7=int64#3d +# asm 2: movl 4(in7=%edx +movl 4(%rdx),%edx + +# qhasm: ((uint32 *)&x2)[0] = in8 +# asm 1: movl x2=stack128#2 +# asm 2: movl x2=16(%rsp) +movl %ecx,16(%rsp) + +# qhasm: ((uint32 *)&x2)[1] = in13 +# asm 1: movl in4=int64#3d +# asm 2: movl 12(in4=%edx +movl 12(%r10),%edx + +# qhasm: in9 = 0 +# asm 1: mov $0,>in9=int64#4 +# asm 2: mov $0,>in9=%rcx +mov $0,%rcx + +# qhasm: in14 = *(uint32 *) (k + 28) +# asm 1: movl 28(in14=int64#5d +# asm 2: movl 28(in14=%r8d +movl 28(%r10),%r8d + +# qhasm: in3 = *(uint32 *) (k + 8) +# asm 1: movl 8(in3=int64#7d +# asm 2: movl 8(in3=%eax +movl 8(%r10),%eax + +# qhasm: ((uint32 *)&x3)[0] = in4 +# asm 1: movl x3=stack128#3 +# asm 2: movl x3=32(%rsp) +movl %edx,32(%rsp) + +# qhasm: ((uint32 *)&x3)[1] = in9 +# asm 1: movl in0=int64#3 +# asm 2: mov $1634760805,>in0=%rdx +mov $1634760805,%rdx + +# qhasm: in5 = 857760878 +# asm 1: mov $857760878,>in5=int64#4 +# asm 2: mov $857760878,>in5=%rcx +mov $857760878,%rcx + +# qhasm: in10 = 2036477234 +# asm 1: mov $2036477234,>in10=int64#5 +# asm 2: mov $2036477234,>in10=%r8 +mov $2036477234,%r8 + +# qhasm: in15 = 1797285236 +# asm 1: mov $1797285236,>in15=int64#7 +# asm 2: mov $1797285236,>in15=%rax +mov $1797285236,%rax + +# qhasm: ((uint32 *)&x0)[0] = in0 +# asm 1: movl x0=stack128#4 +# asm 2: movl x0=48(%rsp) +movl %edx,48(%rsp) + +# qhasm: ((uint32 *)&x0)[1] = in5 +# asm 1: movl z0=int6464#1 +# asm 2: movdqa z0=%xmm0 +movdqa 48(%rsp),%xmm0 + +# qhasm: z5 = z0[1,1,1,1] +# asm 1: pshufd $0x55,z5=int6464#2 +# asm 2: pshufd $0x55,z5=%xmm1 +pshufd $0x55,%xmm0,%xmm1 + +# qhasm: z10 = z0[2,2,2,2] +# asm 1: pshufd $0xaa,z10=int6464#3 +# asm 2: pshufd $0xaa,z10=%xmm2 +pshufd $0xaa,%xmm0,%xmm2 + +# qhasm: z15 = z0[3,3,3,3] +# asm 1: pshufd $0xff,z15=int6464#4 +# asm 2: pshufd $0xff,z15=%xmm3 +pshufd $0xff,%xmm0,%xmm3 + +# qhasm: z0 = z0[0,0,0,0] +# asm 1: pshufd $0x00,z0=int6464#1 +# asm 2: pshufd $0x00,z0=%xmm0 +pshufd $0x00,%xmm0,%xmm0 + +# qhasm: orig5 = z5 +# asm 1: movdqa orig5=stack128#5 +# asm 2: movdqa orig5=64(%rsp) +movdqa %xmm1,64(%rsp) + +# qhasm: orig10 = z10 +# asm 1: movdqa orig10=stack128#6 +# asm 2: movdqa orig10=80(%rsp) +movdqa %xmm2,80(%rsp) + +# qhasm: orig15 = z15 +# asm 1: movdqa orig15=stack128#7 +# asm 2: movdqa orig15=96(%rsp) +movdqa %xmm3,96(%rsp) + +# qhasm: orig0 = z0 +# asm 1: movdqa orig0=stack128#8 +# asm 2: movdqa orig0=112(%rsp) +movdqa %xmm0,112(%rsp) + +# qhasm: z1 = x1 +# asm 1: movdqa z1=int6464#1 +# asm 2: movdqa z1=%xmm0 +movdqa 0(%rsp),%xmm0 + +# qhasm: z6 = z1[2,2,2,2] +# asm 1: pshufd $0xaa,z6=int6464#2 +# asm 2: pshufd $0xaa,z6=%xmm1 +pshufd $0xaa,%xmm0,%xmm1 + +# qhasm: z11 = z1[3,3,3,3] +# asm 1: pshufd $0xff,z11=int6464#3 +# asm 2: pshufd $0xff,z11=%xmm2 +pshufd $0xff,%xmm0,%xmm2 + +# qhasm: z12 = z1[0,0,0,0] +# asm 1: pshufd $0x00,z12=int6464#4 +# asm 2: pshufd $0x00,z12=%xmm3 +pshufd $0x00,%xmm0,%xmm3 + +# qhasm: z1 = z1[1,1,1,1] +# asm 1: pshufd $0x55,z1=int6464#1 +# asm 2: pshufd $0x55,z1=%xmm0 +pshufd $0x55,%xmm0,%xmm0 + +# qhasm: orig6 = z6 +# asm 1: movdqa orig6=stack128#9 +# asm 2: movdqa orig6=128(%rsp) +movdqa %xmm1,128(%rsp) + +# qhasm: orig11 = z11 +# asm 1: movdqa orig11=stack128#10 +# asm 2: movdqa orig11=144(%rsp) +movdqa %xmm2,144(%rsp) + +# qhasm: orig12 = z12 +# asm 1: movdqa orig12=stack128#11 +# asm 2: movdqa orig12=160(%rsp) +movdqa %xmm3,160(%rsp) + +# qhasm: orig1 = z1 +# asm 1: movdqa orig1=stack128#12 +# asm 2: movdqa orig1=176(%rsp) +movdqa %xmm0,176(%rsp) + +# qhasm: z2 = x2 +# asm 1: movdqa z2=int6464#1 +# asm 2: movdqa z2=%xmm0 +movdqa 16(%rsp),%xmm0 + +# qhasm: z7 = z2[3,3,3,3] +# asm 1: pshufd $0xff,z7=int6464#2 +# asm 2: pshufd $0xff,z7=%xmm1 +pshufd $0xff,%xmm0,%xmm1 + +# qhasm: z13 = z2[1,1,1,1] +# asm 1: pshufd $0x55,z13=int6464#3 +# asm 2: pshufd $0x55,z13=%xmm2 +pshufd $0x55,%xmm0,%xmm2 + +# qhasm: z2 = z2[2,2,2,2] +# asm 1: pshufd $0xaa,z2=int6464#1 +# asm 2: pshufd $0xaa,z2=%xmm0 +pshufd $0xaa,%xmm0,%xmm0 + +# qhasm: orig7 = z7 +# asm 1: movdqa orig7=stack128#13 +# asm 2: movdqa orig7=192(%rsp) +movdqa %xmm1,192(%rsp) + +# qhasm: orig13 = z13 +# asm 1: movdqa orig13=stack128#14 +# asm 2: movdqa orig13=208(%rsp) +movdqa %xmm2,208(%rsp) + +# qhasm: orig2 = z2 +# asm 1: movdqa orig2=stack128#15 +# asm 2: movdqa orig2=224(%rsp) +movdqa %xmm0,224(%rsp) + +# qhasm: z3 = x3 +# asm 1: movdqa z3=int6464#1 +# asm 2: movdqa z3=%xmm0 +movdqa 32(%rsp),%xmm0 + +# qhasm: z4 = z3[0,0,0,0] +# asm 1: pshufd $0x00,z4=int6464#2 +# asm 2: pshufd $0x00,z4=%xmm1 +pshufd $0x00,%xmm0,%xmm1 + +# qhasm: z14 = z3[2,2,2,2] +# asm 1: pshufd $0xaa,z14=int6464#3 +# asm 2: pshufd $0xaa,z14=%xmm2 +pshufd $0xaa,%xmm0,%xmm2 + +# qhasm: z3 = z3[3,3,3,3] +# asm 1: pshufd $0xff,z3=int6464#1 +# asm 2: pshufd $0xff,z3=%xmm0 +pshufd $0xff,%xmm0,%xmm0 + +# qhasm: orig4 = z4 +# asm 1: movdqa orig4=stack128#16 +# asm 2: movdqa orig4=240(%rsp) +movdqa %xmm1,240(%rsp) + +# qhasm: orig14 = z14 +# asm 1: movdqa orig14=stack128#17 +# asm 2: movdqa orig14=256(%rsp) +movdqa %xmm2,256(%rsp) + +# qhasm: orig3 = z3 +# asm 1: movdqa orig3=stack128#18 +# asm 2: movdqa orig3=272(%rsp) +movdqa %xmm0,272(%rsp) + +# qhasm: bytesatleast256: +._bytesatleast256: + +# qhasm: in8 = ((uint32 *)&x2)[0] +# asm 1: movl in8=int64#3d +# asm 2: movl in8=%edx +movl 16(%rsp),%edx + +# qhasm: in9 = ((uint32 *)&x3)[1] +# asm 1: movl 4+in9=int64#4d +# asm 2: movl 4+in9=%ecx +movl 4+32(%rsp),%ecx + +# qhasm: ((uint32 *) &orig8)[0] = in8 +# asm 1: movl orig8=stack128#19 +# asm 2: movl orig8=288(%rsp) +movl %edx,288(%rsp) + +# qhasm: ((uint32 *) &orig9)[0] = in9 +# asm 1: movl orig9=stack128#20 +# asm 2: movl orig9=304(%rsp) +movl %ecx,304(%rsp) + +# qhasm: in8 += 1 +# asm 1: add $1,in9=int64#4 +# asm 2: mov in9=%rcx +mov %rdx,%rcx + +# qhasm: (uint64) in9 >>= 32 +# asm 1: shr $32,in9=int64#4 +# asm 2: mov in9=%rcx +mov %rdx,%rcx + +# qhasm: (uint64) in9 >>= 32 +# asm 1: shr $32,in9=int64#4 +# asm 2: mov in9=%rcx +mov %rdx,%rcx + +# qhasm: (uint64) in9 >>= 32 +# asm 1: shr $32,in9=int64#4 +# asm 2: mov in9=%rcx +mov %rdx,%rcx + +# qhasm: (uint64) in9 >>= 32 +# asm 1: shr $32,x2=stack128#2 +# asm 2: movl x2=16(%rsp) +movl %edx,16(%rsp) + +# qhasm: ((uint32 *)&x3)[1] = in9 +# asm 1: movl bytes_backup=stack64#8 +# asm 2: movq bytes_backup=408(%rsp) +movq %r9,408(%rsp) + +# qhasm: i = 12 +# asm 1: mov $12,>i=int64#3 +# asm 2: mov $12,>i=%rdx +mov $12,%rdx + +# qhasm: z5 = orig5 +# asm 1: movdqa z5=int6464#1 +# asm 2: movdqa z5=%xmm0 +movdqa 64(%rsp),%xmm0 + +# qhasm: z10 = orig10 +# asm 1: movdqa z10=int6464#2 +# asm 2: movdqa z10=%xmm1 +movdqa 80(%rsp),%xmm1 + +# qhasm: z15 = orig15 +# asm 1: movdqa z15=int6464#3 +# asm 2: movdqa z15=%xmm2 +movdqa 96(%rsp),%xmm2 + +# qhasm: z14 = orig14 +# asm 1: movdqa z14=int6464#4 +# asm 2: movdqa z14=%xmm3 +movdqa 256(%rsp),%xmm3 + +# qhasm: z3 = orig3 +# asm 1: movdqa z3=int6464#5 +# asm 2: movdqa z3=%xmm4 +movdqa 272(%rsp),%xmm4 + +# qhasm: z6 = orig6 +# asm 1: movdqa z6=int6464#6 +# asm 2: movdqa z6=%xmm5 +movdqa 128(%rsp),%xmm5 + +# qhasm: z11 = orig11 +# asm 1: movdqa z11=int6464#7 +# asm 2: movdqa z11=%xmm6 +movdqa 144(%rsp),%xmm6 + +# qhasm: z1 = orig1 +# asm 1: movdqa z1=int6464#8 +# asm 2: movdqa z1=%xmm7 +movdqa 176(%rsp),%xmm7 + +# qhasm: z7 = orig7 +# asm 1: movdqa z7=int6464#9 +# asm 2: movdqa z7=%xmm8 +movdqa 192(%rsp),%xmm8 + +# qhasm: z13 = orig13 +# asm 1: movdqa z13=int6464#10 +# asm 2: movdqa z13=%xmm9 +movdqa 208(%rsp),%xmm9 + +# qhasm: z2 = orig2 +# asm 1: movdqa z2=int6464#11 +# asm 2: movdqa z2=%xmm10 +movdqa 224(%rsp),%xmm10 + +# qhasm: z9 = orig9 +# asm 1: movdqa z9=int6464#12 +# asm 2: movdqa z9=%xmm11 +movdqa 304(%rsp),%xmm11 + +# qhasm: z0 = orig0 +# asm 1: movdqa z0=int6464#13 +# asm 2: movdqa z0=%xmm12 +movdqa 112(%rsp),%xmm12 + +# qhasm: z12 = orig12 +# asm 1: movdqa z12=int6464#14 +# asm 2: movdqa z12=%xmm13 +movdqa 160(%rsp),%xmm13 + +# qhasm: z4 = orig4 +# asm 1: movdqa z4=int6464#15 +# asm 2: movdqa z4=%xmm14 +movdqa 240(%rsp),%xmm14 + +# qhasm: z8 = orig8 +# asm 1: movdqa z8=int6464#16 +# asm 2: movdqa z8=%xmm15 +movdqa 288(%rsp),%xmm15 + +# qhasm: mainloop1: +._mainloop1: + +# qhasm: z10_stack = z10 +# asm 1: movdqa z10_stack=stack128#21 +# asm 2: movdqa z10_stack=320(%rsp) +movdqa %xmm1,320(%rsp) + +# qhasm: z15_stack = z15 +# asm 1: movdqa z15_stack=stack128#22 +# asm 2: movdqa z15_stack=336(%rsp) +movdqa %xmm2,336(%rsp) + +# qhasm: y4 = z12 +# asm 1: movdqa y4=int6464#2 +# asm 2: movdqa y4=%xmm1 +movdqa %xmm13,%xmm1 + +# qhasm: uint32323232 y4 += z0 +# asm 1: paddd r4=int6464#3 +# asm 2: movdqa r4=%xmm2 +movdqa %xmm1,%xmm2 + +# qhasm: uint32323232 y4 <<= 7 +# asm 1: pslld $7,>= 25 +# asm 1: psrld $25,y9=int6464#2 +# asm 2: movdqa y9=%xmm1 +movdqa %xmm7,%xmm1 + +# qhasm: uint32323232 y9 += z5 +# asm 1: paddd r9=int6464#3 +# asm 2: movdqa r9=%xmm2 +movdqa %xmm1,%xmm2 + +# qhasm: uint32323232 y9 <<= 7 +# asm 1: pslld $7,>= 25 +# asm 1: psrld $25,y8=int6464#2 +# asm 2: movdqa y8=%xmm1 +movdqa %xmm12,%xmm1 + +# qhasm: uint32323232 y8 += z4 +# asm 1: paddd r8=int6464#3 +# asm 2: movdqa r8=%xmm2 +movdqa %xmm1,%xmm2 + +# qhasm: uint32323232 y8 <<= 9 +# asm 1: pslld $9,>= 23 +# asm 1: psrld $23,y13=int6464#2 +# asm 2: movdqa y13=%xmm1 +movdqa %xmm0,%xmm1 + +# qhasm: uint32323232 y13 += z9 +# asm 1: paddd r13=int6464#3 +# asm 2: movdqa r13=%xmm2 +movdqa %xmm1,%xmm2 + +# qhasm: uint32323232 y13 <<= 9 +# asm 1: pslld $9,>= 23 +# asm 1: psrld $23,y12=int6464#2 +# asm 2: movdqa y12=%xmm1 +movdqa %xmm14,%xmm1 + +# qhasm: uint32323232 y12 += z8 +# asm 1: paddd r12=int6464#3 +# asm 2: movdqa r12=%xmm2 +movdqa %xmm1,%xmm2 + +# qhasm: uint32323232 y12 <<= 13 +# asm 1: pslld $13,>= 19 +# asm 1: psrld $19,y1=int6464#2 +# asm 2: movdqa y1=%xmm1 +movdqa %xmm11,%xmm1 + +# qhasm: uint32323232 y1 += z13 +# asm 1: paddd r1=int6464#3 +# asm 2: movdqa r1=%xmm2 +movdqa %xmm1,%xmm2 + +# qhasm: uint32323232 y1 <<= 13 +# asm 1: pslld $13,>= 19 +# asm 1: psrld $19,y0=int6464#2 +# asm 2: movdqa y0=%xmm1 +movdqa %xmm15,%xmm1 + +# qhasm: uint32323232 y0 += z12 +# asm 1: paddd r0=int6464#3 +# asm 2: movdqa r0=%xmm2 +movdqa %xmm1,%xmm2 + +# qhasm: uint32323232 y0 <<= 18 +# asm 1: pslld $18,>= 14 +# asm 1: psrld $14,z10=int6464#2 +# asm 2: movdqa z10=%xmm1 +movdqa 320(%rsp),%xmm1 + +# qhasm: z0_stack = z0 +# asm 1: movdqa z0_stack=stack128#21 +# asm 2: movdqa z0_stack=320(%rsp) +movdqa %xmm12,320(%rsp) + +# qhasm: y5 = z13 +# asm 1: movdqa y5=int6464#3 +# asm 2: movdqa y5=%xmm2 +movdqa %xmm9,%xmm2 + +# qhasm: uint32323232 y5 += z1 +# asm 1: paddd r5=int6464#13 +# asm 2: movdqa r5=%xmm12 +movdqa %xmm2,%xmm12 + +# qhasm: uint32323232 y5 <<= 18 +# asm 1: pslld $18,>= 14 +# asm 1: psrld $14,y14=int6464#3 +# asm 2: movdqa y14=%xmm2 +movdqa %xmm5,%xmm2 + +# qhasm: uint32323232 y14 += z10 +# asm 1: paddd r14=int6464#13 +# asm 2: movdqa r14=%xmm12 +movdqa %xmm2,%xmm12 + +# qhasm: uint32323232 y14 <<= 7 +# asm 1: pslld $7,>= 25 +# asm 1: psrld $25,z15=int6464#3 +# asm 2: movdqa z15=%xmm2 +movdqa 336(%rsp),%xmm2 + +# qhasm: z5_stack = z5 +# asm 1: movdqa z5_stack=stack128#22 +# asm 2: movdqa z5_stack=336(%rsp) +movdqa %xmm0,336(%rsp) + +# qhasm: y3 = z11 +# asm 1: movdqa y3=int6464#1 +# asm 2: movdqa y3=%xmm0 +movdqa %xmm6,%xmm0 + +# qhasm: uint32323232 y3 += z15 +# asm 1: paddd r3=int6464#13 +# asm 2: movdqa r3=%xmm12 +movdqa %xmm0,%xmm12 + +# qhasm: uint32323232 y3 <<= 7 +# asm 1: pslld $7,>= 25 +# asm 1: psrld $25,y2=int6464#1 +# asm 2: movdqa y2=%xmm0 +movdqa %xmm1,%xmm0 + +# qhasm: uint32323232 y2 += z14 +# asm 1: paddd r2=int6464#13 +# asm 2: movdqa r2=%xmm12 +movdqa %xmm0,%xmm12 + +# qhasm: uint32323232 y2 <<= 9 +# asm 1: pslld $9,>= 23 +# asm 1: psrld $23,y7=int6464#1 +# asm 2: movdqa y7=%xmm0 +movdqa %xmm2,%xmm0 + +# qhasm: uint32323232 y7 += z3 +# asm 1: paddd r7=int6464#13 +# asm 2: movdqa r7=%xmm12 +movdqa %xmm0,%xmm12 + +# qhasm: uint32323232 y7 <<= 9 +# asm 1: pslld $9,>= 23 +# asm 1: psrld $23,y6=int6464#1 +# asm 2: movdqa y6=%xmm0 +movdqa %xmm3,%xmm0 + +# qhasm: uint32323232 y6 += z2 +# asm 1: paddd r6=int6464#13 +# asm 2: movdqa r6=%xmm12 +movdqa %xmm0,%xmm12 + +# qhasm: uint32323232 y6 <<= 13 +# asm 1: pslld $13,>= 19 +# asm 1: psrld $19,y11=int6464#1 +# asm 2: movdqa y11=%xmm0 +movdqa %xmm4,%xmm0 + +# qhasm: uint32323232 y11 += z7 +# asm 1: paddd r11=int6464#13 +# asm 2: movdqa r11=%xmm12 +movdqa %xmm0,%xmm12 + +# qhasm: uint32323232 y11 <<= 13 +# asm 1: pslld $13,>= 19 +# asm 1: psrld $19,y10=int6464#1 +# asm 2: movdqa y10=%xmm0 +movdqa %xmm10,%xmm0 + +# qhasm: uint32323232 y10 += z6 +# asm 1: paddd r10=int6464#13 +# asm 2: movdqa r10=%xmm12 +movdqa %xmm0,%xmm12 + +# qhasm: uint32323232 y10 <<= 18 +# asm 1: pslld $18,>= 14 +# asm 1: psrld $14,z0=int6464#1 +# asm 2: movdqa z0=%xmm0 +movdqa 320(%rsp),%xmm0 + +# qhasm: z10_stack = z10 +# asm 1: movdqa z10_stack=stack128#21 +# asm 2: movdqa z10_stack=320(%rsp) +movdqa %xmm1,320(%rsp) + +# qhasm: y1 = z3 +# asm 1: movdqa y1=int6464#2 +# asm 2: movdqa y1=%xmm1 +movdqa %xmm4,%xmm1 + +# qhasm: uint32323232 y1 += z0 +# asm 1: paddd r1=int6464#13 +# asm 2: movdqa r1=%xmm12 +movdqa %xmm1,%xmm12 + +# qhasm: uint32323232 y1 <<= 7 +# asm 1: pslld $7,>= 25 +# asm 1: psrld $25,y15=int6464#2 +# asm 2: movdqa y15=%xmm1 +movdqa %xmm8,%xmm1 + +# qhasm: uint32323232 y15 += z11 +# asm 1: paddd r15=int6464#13 +# asm 2: movdqa r15=%xmm12 +movdqa %xmm1,%xmm12 + +# qhasm: uint32323232 y15 <<= 18 +# asm 1: pslld $18,>= 14 +# asm 1: psrld $14,z5=int6464#13 +# asm 2: movdqa z5=%xmm12 +movdqa 336(%rsp),%xmm12 + +# qhasm: z15_stack = z15 +# asm 1: movdqa z15_stack=stack128#22 +# asm 2: movdqa z15_stack=336(%rsp) +movdqa %xmm2,336(%rsp) + +# qhasm: y6 = z4 +# asm 1: movdqa y6=int6464#2 +# asm 2: movdqa y6=%xmm1 +movdqa %xmm14,%xmm1 + +# qhasm: uint32323232 y6 += z5 +# asm 1: paddd r6=int6464#3 +# asm 2: movdqa r6=%xmm2 +movdqa %xmm1,%xmm2 + +# qhasm: uint32323232 y6 <<= 7 +# asm 1: pslld $7,>= 25 +# asm 1: psrld $25,y2=int6464#2 +# asm 2: movdqa y2=%xmm1 +movdqa %xmm0,%xmm1 + +# qhasm: uint32323232 y2 += z1 +# asm 1: paddd r2=int6464#3 +# asm 2: movdqa r2=%xmm2 +movdqa %xmm1,%xmm2 + +# qhasm: uint32323232 y2 <<= 9 +# asm 1: pslld $9,>= 23 +# asm 1: psrld $23,y7=int6464#2 +# asm 2: movdqa y7=%xmm1 +movdqa %xmm12,%xmm1 + +# qhasm: uint32323232 y7 += z6 +# asm 1: paddd r7=int6464#3 +# asm 2: movdqa r7=%xmm2 +movdqa %xmm1,%xmm2 + +# qhasm: uint32323232 y7 <<= 9 +# asm 1: pslld $9,>= 23 +# asm 1: psrld $23,y3=int6464#2 +# asm 2: movdqa y3=%xmm1 +movdqa %xmm7,%xmm1 + +# qhasm: uint32323232 y3 += z2 +# asm 1: paddd r3=int6464#3 +# asm 2: movdqa r3=%xmm2 +movdqa %xmm1,%xmm2 + +# qhasm: uint32323232 y3 <<= 13 +# asm 1: pslld $13,>= 19 +# asm 1: psrld $19,y4=int6464#2 +# asm 2: movdqa y4=%xmm1 +movdqa %xmm5,%xmm1 + +# qhasm: uint32323232 y4 += z7 +# asm 1: paddd r4=int6464#3 +# asm 2: movdqa r4=%xmm2 +movdqa %xmm1,%xmm2 + +# qhasm: uint32323232 y4 <<= 13 +# asm 1: pslld $13,>= 19 +# asm 1: psrld $19,y0=int6464#2 +# asm 2: movdqa y0=%xmm1 +movdqa %xmm10,%xmm1 + +# qhasm: uint32323232 y0 += z3 +# asm 1: paddd r0=int6464#3 +# asm 2: movdqa r0=%xmm2 +movdqa %xmm1,%xmm2 + +# qhasm: uint32323232 y0 <<= 18 +# asm 1: pslld $18,>= 14 +# asm 1: psrld $14,z10=int6464#2 +# asm 2: movdqa z10=%xmm1 +movdqa 320(%rsp),%xmm1 + +# qhasm: z0_stack = z0 +# asm 1: movdqa z0_stack=stack128#21 +# asm 2: movdqa z0_stack=320(%rsp) +movdqa %xmm0,320(%rsp) + +# qhasm: y5 = z7 +# asm 1: movdqa y5=int6464#1 +# asm 2: movdqa y5=%xmm0 +movdqa %xmm8,%xmm0 + +# qhasm: uint32323232 y5 += z4 +# asm 1: paddd r5=int6464#3 +# asm 2: movdqa r5=%xmm2 +movdqa %xmm0,%xmm2 + +# qhasm: uint32323232 y5 <<= 18 +# asm 1: pslld $18,>= 14 +# asm 1: psrld $14,y11=int6464#1 +# asm 2: movdqa y11=%xmm0 +movdqa %xmm11,%xmm0 + +# qhasm: uint32323232 y11 += z10 +# asm 1: paddd r11=int6464#3 +# asm 2: movdqa r11=%xmm2 +movdqa %xmm0,%xmm2 + +# qhasm: uint32323232 y11 <<= 7 +# asm 1: pslld $7,>= 25 +# asm 1: psrld $25,z15=int6464#3 +# asm 2: movdqa z15=%xmm2 +movdqa 336(%rsp),%xmm2 + +# qhasm: z5_stack = z5 +# asm 1: movdqa z5_stack=stack128#22 +# asm 2: movdqa z5_stack=336(%rsp) +movdqa %xmm12,336(%rsp) + +# qhasm: y12 = z14 +# asm 1: movdqa y12=int6464#1 +# asm 2: movdqa y12=%xmm0 +movdqa %xmm3,%xmm0 + +# qhasm: uint32323232 y12 += z15 +# asm 1: paddd r12=int6464#13 +# asm 2: movdqa r12=%xmm12 +movdqa %xmm0,%xmm12 + +# qhasm: uint32323232 y12 <<= 7 +# asm 1: pslld $7,>= 25 +# asm 1: psrld $25,y8=int6464#1 +# asm 2: movdqa y8=%xmm0 +movdqa %xmm1,%xmm0 + +# qhasm: uint32323232 y8 += z11 +# asm 1: paddd r8=int6464#13 +# asm 2: movdqa r8=%xmm12 +movdqa %xmm0,%xmm12 + +# qhasm: uint32323232 y8 <<= 9 +# asm 1: pslld $9,>= 23 +# asm 1: psrld $23,y13=int6464#1 +# asm 2: movdqa y13=%xmm0 +movdqa %xmm2,%xmm0 + +# qhasm: uint32323232 y13 += z12 +# asm 1: paddd r13=int6464#13 +# asm 2: movdqa r13=%xmm12 +movdqa %xmm0,%xmm12 + +# qhasm: uint32323232 y13 <<= 9 +# asm 1: pslld $9,>= 23 +# asm 1: psrld $23,y9=int6464#1 +# asm 2: movdqa y9=%xmm0 +movdqa %xmm6,%xmm0 + +# qhasm: uint32323232 y9 += z8 +# asm 1: paddd r9=int6464#13 +# asm 2: movdqa r9=%xmm12 +movdqa %xmm0,%xmm12 + +# qhasm: uint32323232 y9 <<= 13 +# asm 1: pslld $13,>= 19 +# asm 1: psrld $19,y14=int6464#1 +# asm 2: movdqa y14=%xmm0 +movdqa %xmm13,%xmm0 + +# qhasm: uint32323232 y14 += z13 +# asm 1: paddd r14=int6464#13 +# asm 2: movdqa r14=%xmm12 +movdqa %xmm0,%xmm12 + +# qhasm: uint32323232 y14 <<= 13 +# asm 1: pslld $13,>= 19 +# asm 1: psrld $19,y10=int6464#1 +# asm 2: movdqa y10=%xmm0 +movdqa %xmm15,%xmm0 + +# qhasm: uint32323232 y10 += z9 +# asm 1: paddd r10=int6464#13 +# asm 2: movdqa r10=%xmm12 +movdqa %xmm0,%xmm12 + +# qhasm: uint32323232 y10 <<= 18 +# asm 1: pslld $18,>= 14 +# asm 1: psrld $14,y15=int6464#1 +# asm 2: movdqa y15=%xmm0 +movdqa %xmm9,%xmm0 + +# qhasm: uint32323232 y15 += z14 +# asm 1: paddd r15=int6464#13 +# asm 2: movdqa r15=%xmm12 +movdqa %xmm0,%xmm12 + +# qhasm: uint32323232 y15 <<= 18 +# asm 1: pslld $18,>= 14 +# asm 1: psrld $14,z0=int6464#13 +# asm 2: movdqa z0=%xmm12 +movdqa 320(%rsp),%xmm12 + +# qhasm: z5 = z5_stack +# asm 1: movdqa z5=int6464#1 +# asm 2: movdqa z5=%xmm0 +movdqa 336(%rsp),%xmm0 + +# qhasm: unsigned>? i -= 2 +# asm 1: sub $2, +ja ._mainloop1 + +# qhasm: uint32323232 z0 += orig0 +# asm 1: paddd in0=int64#3 +# asm 2: movd in0=%rdx +movd %xmm12,%rdx + +# qhasm: in1 = z1 +# asm 1: movd in1=int64#4 +# asm 2: movd in1=%rcx +movd %xmm7,%rcx + +# qhasm: in2 = z2 +# asm 1: movd in2=int64#5 +# asm 2: movd in2=%r8 +movd %xmm10,%r8 + +# qhasm: in3 = z3 +# asm 1: movd in3=int64#6 +# asm 2: movd in3=%r9 +movd %xmm4,%r9 + +# qhasm: z0 <<<= 96 +# asm 1: pshufd $0x39,in0=int64#3 +# asm 2: movd in0=%rdx +movd %xmm12,%rdx + +# qhasm: in1 = z1 +# asm 1: movd in1=int64#4 +# asm 2: movd in1=%rcx +movd %xmm7,%rcx + +# qhasm: in2 = z2 +# asm 1: movd in2=int64#5 +# asm 2: movd in2=%r8 +movd %xmm10,%r8 + +# qhasm: in3 = z3 +# asm 1: movd in3=int64#6 +# asm 2: movd in3=%r9 +movd %xmm4,%r9 + +# qhasm: z0 <<<= 96 +# asm 1: pshufd $0x39,in0=int64#3 +# asm 2: movd in0=%rdx +movd %xmm12,%rdx + +# qhasm: in1 = z1 +# asm 1: movd in1=int64#4 +# asm 2: movd in1=%rcx +movd %xmm7,%rcx + +# qhasm: in2 = z2 +# asm 1: movd in2=int64#5 +# asm 2: movd in2=%r8 +movd %xmm10,%r8 + +# qhasm: in3 = z3 +# asm 1: movd in3=int64#6 +# asm 2: movd in3=%r9 +movd %xmm4,%r9 + +# qhasm: z0 <<<= 96 +# asm 1: pshufd $0x39,in0=int64#3 +# asm 2: movd in0=%rdx +movd %xmm12,%rdx + +# qhasm: in1 = z1 +# asm 1: movd in1=int64#4 +# asm 2: movd in1=%rcx +movd %xmm7,%rcx + +# qhasm: in2 = z2 +# asm 1: movd in2=int64#5 +# asm 2: movd in2=%r8 +movd %xmm10,%r8 + +# qhasm: in3 = z3 +# asm 1: movd in3=int64#6 +# asm 2: movd in3=%r9 +movd %xmm4,%r9 + +# qhasm: (uint32) in0 ^= *(uint32 *) (m + 192) +# asm 1: xorl 192(in4=int64#3 +# asm 2: movd in4=%rdx +movd %xmm14,%rdx + +# qhasm: in5 = z5 +# asm 1: movd in5=int64#4 +# asm 2: movd in5=%rcx +movd %xmm0,%rcx + +# qhasm: in6 = z6 +# asm 1: movd in6=int64#5 +# asm 2: movd in6=%r8 +movd %xmm5,%r8 + +# qhasm: in7 = z7 +# asm 1: movd in7=int64#6 +# asm 2: movd in7=%r9 +movd %xmm8,%r9 + +# qhasm: z4 <<<= 96 +# asm 1: pshufd $0x39,in4=int64#3 +# asm 2: movd in4=%rdx +movd %xmm14,%rdx + +# qhasm: in5 = z5 +# asm 1: movd in5=int64#4 +# asm 2: movd in5=%rcx +movd %xmm0,%rcx + +# qhasm: in6 = z6 +# asm 1: movd in6=int64#5 +# asm 2: movd in6=%r8 +movd %xmm5,%r8 + +# qhasm: in7 = z7 +# asm 1: movd in7=int64#6 +# asm 2: movd in7=%r9 +movd %xmm8,%r9 + +# qhasm: z4 <<<= 96 +# asm 1: pshufd $0x39,in4=int64#3 +# asm 2: movd in4=%rdx +movd %xmm14,%rdx + +# qhasm: in5 = z5 +# asm 1: movd in5=int64#4 +# asm 2: movd in5=%rcx +movd %xmm0,%rcx + +# qhasm: in6 = z6 +# asm 1: movd in6=int64#5 +# asm 2: movd in6=%r8 +movd %xmm5,%r8 + +# qhasm: in7 = z7 +# asm 1: movd in7=int64#6 +# asm 2: movd in7=%r9 +movd %xmm8,%r9 + +# qhasm: z4 <<<= 96 +# asm 1: pshufd $0x39,in4=int64#3 +# asm 2: movd in4=%rdx +movd %xmm14,%rdx + +# qhasm: in5 = z5 +# asm 1: movd in5=int64#4 +# asm 2: movd in5=%rcx +movd %xmm0,%rcx + +# qhasm: in6 = z6 +# asm 1: movd in6=int64#5 +# asm 2: movd in6=%r8 +movd %xmm5,%r8 + +# qhasm: in7 = z7 +# asm 1: movd in7=int64#6 +# asm 2: movd in7=%r9 +movd %xmm8,%r9 + +# qhasm: (uint32) in4 ^= *(uint32 *) (m + 208) +# asm 1: xorl 208(in8=int64#3 +# asm 2: movd in8=%rdx +movd %xmm15,%rdx + +# qhasm: in9 = z9 +# asm 1: movd in9=int64#4 +# asm 2: movd in9=%rcx +movd %xmm11,%rcx + +# qhasm: in10 = z10 +# asm 1: movd in10=int64#5 +# asm 2: movd in10=%r8 +movd %xmm1,%r8 + +# qhasm: in11 = z11 +# asm 1: movd in11=int64#6 +# asm 2: movd in11=%r9 +movd %xmm6,%r9 + +# qhasm: z8 <<<= 96 +# asm 1: pshufd $0x39,in8=int64#3 +# asm 2: movd in8=%rdx +movd %xmm15,%rdx + +# qhasm: in9 = z9 +# asm 1: movd in9=int64#4 +# asm 2: movd in9=%rcx +movd %xmm11,%rcx + +# qhasm: in10 = z10 +# asm 1: movd in10=int64#5 +# asm 2: movd in10=%r8 +movd %xmm1,%r8 + +# qhasm: in11 = z11 +# asm 1: movd in11=int64#6 +# asm 2: movd in11=%r9 +movd %xmm6,%r9 + +# qhasm: z8 <<<= 96 +# asm 1: pshufd $0x39,in8=int64#3 +# asm 2: movd in8=%rdx +movd %xmm15,%rdx + +# qhasm: in9 = z9 +# asm 1: movd in9=int64#4 +# asm 2: movd in9=%rcx +movd %xmm11,%rcx + +# qhasm: in10 = z10 +# asm 1: movd in10=int64#5 +# asm 2: movd in10=%r8 +movd %xmm1,%r8 + +# qhasm: in11 = z11 +# asm 1: movd in11=int64#6 +# asm 2: movd in11=%r9 +movd %xmm6,%r9 + +# qhasm: z8 <<<= 96 +# asm 1: pshufd $0x39,in8=int64#3 +# asm 2: movd in8=%rdx +movd %xmm15,%rdx + +# qhasm: in9 = z9 +# asm 1: movd in9=int64#4 +# asm 2: movd in9=%rcx +movd %xmm11,%rcx + +# qhasm: in10 = z10 +# asm 1: movd in10=int64#5 +# asm 2: movd in10=%r8 +movd %xmm1,%r8 + +# qhasm: in11 = z11 +# asm 1: movd in11=int64#6 +# asm 2: movd in11=%r9 +movd %xmm6,%r9 + +# qhasm: (uint32) in8 ^= *(uint32 *) (m + 224) +# asm 1: xorl 224(in12=int64#3 +# asm 2: movd in12=%rdx +movd %xmm13,%rdx + +# qhasm: in13 = z13 +# asm 1: movd in13=int64#4 +# asm 2: movd in13=%rcx +movd %xmm9,%rcx + +# qhasm: in14 = z14 +# asm 1: movd in14=int64#5 +# asm 2: movd in14=%r8 +movd %xmm3,%r8 + +# qhasm: in15 = z15 +# asm 1: movd in15=int64#6 +# asm 2: movd in15=%r9 +movd %xmm2,%r9 + +# qhasm: z12 <<<= 96 +# asm 1: pshufd $0x39,in12=int64#3 +# asm 2: movd in12=%rdx +movd %xmm13,%rdx + +# qhasm: in13 = z13 +# asm 1: movd in13=int64#4 +# asm 2: movd in13=%rcx +movd %xmm9,%rcx + +# qhasm: in14 = z14 +# asm 1: movd in14=int64#5 +# asm 2: movd in14=%r8 +movd %xmm3,%r8 + +# qhasm: in15 = z15 +# asm 1: movd in15=int64#6 +# asm 2: movd in15=%r9 +movd %xmm2,%r9 + +# qhasm: z12 <<<= 96 +# asm 1: pshufd $0x39,in12=int64#3 +# asm 2: movd in12=%rdx +movd %xmm13,%rdx + +# qhasm: in13 = z13 +# asm 1: movd in13=int64#4 +# asm 2: movd in13=%rcx +movd %xmm9,%rcx + +# qhasm: in14 = z14 +# asm 1: movd in14=int64#5 +# asm 2: movd in14=%r8 +movd %xmm3,%r8 + +# qhasm: in15 = z15 +# asm 1: movd in15=int64#6 +# asm 2: movd in15=%r9 +movd %xmm2,%r9 + +# qhasm: z12 <<<= 96 +# asm 1: pshufd $0x39,in12=int64#3 +# asm 2: movd in12=%rdx +movd %xmm13,%rdx + +# qhasm: in13 = z13 +# asm 1: movd in13=int64#4 +# asm 2: movd in13=%rcx +movd %xmm9,%rcx + +# qhasm: in14 = z14 +# asm 1: movd in14=int64#5 +# asm 2: movd in14=%r8 +movd %xmm3,%r8 + +# qhasm: in15 = z15 +# asm 1: movd in15=int64#6 +# asm 2: movd in15=%r9 +movd %xmm2,%r9 + +# qhasm: (uint32) in12 ^= *(uint32 *) (m + 240) +# asm 1: xorl 240(bytes=int64#6 +# asm 2: movq bytes=%r9 +movq 408(%rsp),%r9 + +# qhasm: bytes -= 256 +# asm 1: sub $256,? bytes - 0 +# asm 1: cmp $0, +jbe ._done +# comment:fp stack unchanged by fallthrough + +# qhasm: bytesbetween1and255: +._bytesbetween1and255: + +# qhasm: unsignedctarget=int64#3 +# asm 2: mov ctarget=%rdx +mov %rdi,%rdx + +# qhasm: out = &tmp +# asm 1: leaq out=int64#1 +# asm 2: leaq out=%rdi +leaq 416(%rsp),%rdi + +# qhasm: i = bytes +# asm 1: mov i=int64#4 +# asm 2: mov i=%rcx +mov %r9,%rcx + +# qhasm: while (i) { *out++ = *m++; --i } +rep movsb + +# qhasm: out = &tmp +# asm 1: leaq out=int64#1 +# asm 2: leaq out=%rdi +leaq 416(%rsp),%rdi + +# qhasm: m = &tmp +# asm 1: leaq m=int64#2 +# asm 2: leaq m=%rsi +leaq 416(%rsp),%rsi +# comment:fp stack unchanged by fallthrough + +# qhasm: nocopy: +._nocopy: + +# qhasm: bytes_backup = bytes +# asm 1: movq bytes_backup=stack64#8 +# asm 2: movq bytes_backup=408(%rsp) +movq %r9,408(%rsp) + +# qhasm: diag0 = x0 +# asm 1: movdqa diag0=int6464#1 +# asm 2: movdqa diag0=%xmm0 +movdqa 48(%rsp),%xmm0 + +# qhasm: diag1 = x1 +# asm 1: movdqa diag1=int6464#2 +# asm 2: movdqa diag1=%xmm1 +movdqa 0(%rsp),%xmm1 + +# qhasm: diag2 = x2 +# asm 1: movdqa diag2=int6464#3 +# asm 2: movdqa diag2=%xmm2 +movdqa 16(%rsp),%xmm2 + +# qhasm: diag3 = x3 +# asm 1: movdqa diag3=int6464#4 +# asm 2: movdqa diag3=%xmm3 +movdqa 32(%rsp),%xmm3 + +# qhasm: a0 = diag1 +# asm 1: movdqa a0=int6464#5 +# asm 2: movdqa a0=%xmm4 +movdqa %xmm1,%xmm4 + +# qhasm: i = 12 +# asm 1: mov $12,>i=int64#4 +# asm 2: mov $12,>i=%rcx +mov $12,%rcx + +# qhasm: mainloop2: +._mainloop2: + +# qhasm: uint32323232 a0 += diag0 +# asm 1: paddd a1=int6464#6 +# asm 2: movdqa a1=%xmm5 +movdqa %xmm0,%xmm5 + +# qhasm: b0 = a0 +# asm 1: movdqa b0=int6464#7 +# asm 2: movdqa b0=%xmm6 +movdqa %xmm4,%xmm6 + +# qhasm: uint32323232 a0 <<= 7 +# asm 1: pslld $7,>= 25 +# asm 1: psrld $25,a2=int6464#5 +# asm 2: movdqa a2=%xmm4 +movdqa %xmm3,%xmm4 + +# qhasm: b1 = a1 +# asm 1: movdqa b1=int6464#7 +# asm 2: movdqa b1=%xmm6 +movdqa %xmm5,%xmm6 + +# qhasm: uint32323232 a1 <<= 9 +# asm 1: pslld $9,>= 23 +# asm 1: psrld $23,a3=int6464#6 +# asm 2: movdqa a3=%xmm5 +movdqa %xmm2,%xmm5 + +# qhasm: b2 = a2 +# asm 1: movdqa b2=int6464#7 +# asm 2: movdqa b2=%xmm6 +movdqa %xmm4,%xmm6 + +# qhasm: uint32323232 a2 <<= 13 +# asm 1: pslld $13,>= 19 +# asm 1: psrld $19,a4=int6464#5 +# asm 2: movdqa a4=%xmm4 +movdqa %xmm3,%xmm4 + +# qhasm: b3 = a3 +# asm 1: movdqa b3=int6464#7 +# asm 2: movdqa b3=%xmm6 +movdqa %xmm5,%xmm6 + +# qhasm: uint32323232 a3 <<= 18 +# asm 1: pslld $18,>= 14 +# asm 1: psrld $14,a5=int6464#6 +# asm 2: movdqa a5=%xmm5 +movdqa %xmm0,%xmm5 + +# qhasm: b4 = a4 +# asm 1: movdqa b4=int6464#7 +# asm 2: movdqa b4=%xmm6 +movdqa %xmm4,%xmm6 + +# qhasm: uint32323232 a4 <<= 7 +# asm 1: pslld $7,>= 25 +# asm 1: psrld $25,a6=int6464#5 +# asm 2: movdqa a6=%xmm4 +movdqa %xmm1,%xmm4 + +# qhasm: b5 = a5 +# asm 1: movdqa b5=int6464#7 +# asm 2: movdqa b5=%xmm6 +movdqa %xmm5,%xmm6 + +# qhasm: uint32323232 a5 <<= 9 +# asm 1: pslld $9,>= 23 +# asm 1: psrld $23,a7=int6464#6 +# asm 2: movdqa a7=%xmm5 +movdqa %xmm2,%xmm5 + +# qhasm: b6 = a6 +# asm 1: movdqa b6=int6464#7 +# asm 2: movdqa b6=%xmm6 +movdqa %xmm4,%xmm6 + +# qhasm: uint32323232 a6 <<= 13 +# asm 1: pslld $13,>= 19 +# asm 1: psrld $19,a0=int6464#5 +# asm 2: movdqa a0=%xmm4 +movdqa %xmm1,%xmm4 + +# qhasm: b7 = a7 +# asm 1: movdqa b7=int6464#7 +# asm 2: movdqa b7=%xmm6 +movdqa %xmm5,%xmm6 + +# qhasm: uint32323232 a7 <<= 18 +# asm 1: pslld $18,>= 14 +# asm 1: psrld $14,a1=int6464#6 +# asm 2: movdqa a1=%xmm5 +movdqa %xmm0,%xmm5 + +# qhasm: b0 = a0 +# asm 1: movdqa b0=int6464#7 +# asm 2: movdqa b0=%xmm6 +movdqa %xmm4,%xmm6 + +# qhasm: uint32323232 a0 <<= 7 +# asm 1: pslld $7,>= 25 +# asm 1: psrld $25,a2=int6464#5 +# asm 2: movdqa a2=%xmm4 +movdqa %xmm3,%xmm4 + +# qhasm: b1 = a1 +# asm 1: movdqa b1=int6464#7 +# asm 2: movdqa b1=%xmm6 +movdqa %xmm5,%xmm6 + +# qhasm: uint32323232 a1 <<= 9 +# asm 1: pslld $9,>= 23 +# asm 1: psrld $23,a3=int6464#6 +# asm 2: movdqa a3=%xmm5 +movdqa %xmm2,%xmm5 + +# qhasm: b2 = a2 +# asm 1: movdqa b2=int6464#7 +# asm 2: movdqa b2=%xmm6 +movdqa %xmm4,%xmm6 + +# qhasm: uint32323232 a2 <<= 13 +# asm 1: pslld $13,>= 19 +# asm 1: psrld $19,a4=int6464#5 +# asm 2: movdqa a4=%xmm4 +movdqa %xmm3,%xmm4 + +# qhasm: b3 = a3 +# asm 1: movdqa b3=int6464#7 +# asm 2: movdqa b3=%xmm6 +movdqa %xmm5,%xmm6 + +# qhasm: uint32323232 a3 <<= 18 +# asm 1: pslld $18,>= 14 +# asm 1: psrld $14,a5=int6464#6 +# asm 2: movdqa a5=%xmm5 +movdqa %xmm0,%xmm5 + +# qhasm: b4 = a4 +# asm 1: movdqa b4=int6464#7 +# asm 2: movdqa b4=%xmm6 +movdqa %xmm4,%xmm6 + +# qhasm: uint32323232 a4 <<= 7 +# asm 1: pslld $7,>= 25 +# asm 1: psrld $25,a6=int6464#5 +# asm 2: movdqa a6=%xmm4 +movdqa %xmm1,%xmm4 + +# qhasm: b5 = a5 +# asm 1: movdqa b5=int6464#7 +# asm 2: movdqa b5=%xmm6 +movdqa %xmm5,%xmm6 + +# qhasm: uint32323232 a5 <<= 9 +# asm 1: pslld $9,>= 23 +# asm 1: psrld $23,a7=int6464#6 +# asm 2: movdqa a7=%xmm5 +movdqa %xmm2,%xmm5 + +# qhasm: b6 = a6 +# asm 1: movdqa b6=int6464#7 +# asm 2: movdqa b6=%xmm6 +movdqa %xmm4,%xmm6 + +# qhasm: uint32323232 a6 <<= 13 +# asm 1: pslld $13,>= 19 +# asm 1: psrld $19,? i -= 4 +# asm 1: sub $4,a0=int6464#5 +# asm 2: movdqa a0=%xmm4 +movdqa %xmm1,%xmm4 + +# qhasm: b7 = a7 +# asm 1: movdqa b7=int6464#7 +# asm 2: movdqa b7=%xmm6 +movdqa %xmm5,%xmm6 + +# qhasm: uint32323232 a7 <<= 18 +# asm 1: pslld $18,b0=int6464#8,>b0=int6464#8 +# asm 2: pxor >b0=%xmm7,>b0=%xmm7 +pxor %xmm7,%xmm7 + +# qhasm: uint32323232 b7 >>= 14 +# asm 1: psrld $14, +ja ._mainloop2 + +# qhasm: uint32323232 diag0 += x0 +# asm 1: paddd in0=int64#4 +# asm 2: movd in0=%rcx +movd %xmm0,%rcx + +# qhasm: in12 = diag1 +# asm 1: movd in12=int64#5 +# asm 2: movd in12=%r8 +movd %xmm1,%r8 + +# qhasm: in8 = diag2 +# asm 1: movd in8=int64#6 +# asm 2: movd in8=%r9 +movd %xmm2,%r9 + +# qhasm: in4 = diag3 +# asm 1: movd in4=int64#7 +# asm 2: movd in4=%rax +movd %xmm3,%rax + +# qhasm: diag0 <<<= 96 +# asm 1: pshufd $0x39,in5=int64#4 +# asm 2: movd in5=%rcx +movd %xmm0,%rcx + +# qhasm: in1 = diag1 +# asm 1: movd in1=int64#5 +# asm 2: movd in1=%r8 +movd %xmm1,%r8 + +# qhasm: in13 = diag2 +# asm 1: movd in13=int64#6 +# asm 2: movd in13=%r9 +movd %xmm2,%r9 + +# qhasm: in9 = diag3 +# asm 1: movd in9=int64#7 +# asm 2: movd in9=%rax +movd %xmm3,%rax + +# qhasm: diag0 <<<= 96 +# asm 1: pshufd $0x39,in10=int64#4 +# asm 2: movd in10=%rcx +movd %xmm0,%rcx + +# qhasm: in6 = diag1 +# asm 1: movd in6=int64#5 +# asm 2: movd in6=%r8 +movd %xmm1,%r8 + +# qhasm: in2 = diag2 +# asm 1: movd in2=int64#6 +# asm 2: movd in2=%r9 +movd %xmm2,%r9 + +# qhasm: in14 = diag3 +# asm 1: movd in14=int64#7 +# asm 2: movd in14=%rax +movd %xmm3,%rax + +# qhasm: diag0 <<<= 96 +# asm 1: pshufd $0x39,in15=int64#4 +# asm 2: movd in15=%rcx +movd %xmm0,%rcx + +# qhasm: in11 = diag1 +# asm 1: movd in11=int64#5 +# asm 2: movd in11=%r8 +movd %xmm1,%r8 + +# qhasm: in7 = diag2 +# asm 1: movd in7=int64#6 +# asm 2: movd in7=%r9 +movd %xmm2,%r9 + +# qhasm: in3 = diag3 +# asm 1: movd in3=int64#7 +# asm 2: movd in3=%rax +movd %xmm3,%rax + +# qhasm: (uint32) in15 ^= *(uint32 *) (m + 60) +# asm 1: xorl 60(bytes=int64#6 +# asm 2: movq bytes=%r9 +movq 408(%rsp),%r9 + +# qhasm: in8 = ((uint32 *)&x2)[0] +# asm 1: movl in8=int64#4d +# asm 2: movl in8=%ecx +movl 16(%rsp),%ecx + +# qhasm: in9 = ((uint32 *)&x3)[1] +# asm 1: movl 4+in9=int64#5d +# asm 2: movl 4+in9=%r8d +movl 4+32(%rsp),%r8d + +# qhasm: in8 += 1 +# asm 1: add $1,in9=int64#5 +# asm 2: mov in9=%r8 +mov %rcx,%r8 + +# qhasm: (uint64) in9 >>= 32 +# asm 1: shr $32,x2=stack128#2 +# asm 2: movl x2=16(%rsp) +movl %ecx,16(%rsp) + +# qhasm: ((uint32 *)&x3)[1] = in9 +# asm 1: movl ? unsigned +ja ._bytesatleast65 +# comment:fp stack unchanged by jump + +# qhasm: goto bytesatleast64 if !unsigned< +jae ._bytesatleast64 + +# qhasm: m = out +# asm 1: mov m=int64#2 +# asm 2: mov m=%rsi +mov %rdi,%rsi + +# qhasm: out = ctarget +# asm 1: mov out=int64#1 +# asm 2: mov out=%rdi +mov %rdx,%rdi + +# qhasm: i = bytes +# asm 1: mov i=int64#4 +# asm 2: mov i=%rcx +mov %r9,%rcx + +# qhasm: while (i) { *out++ = *m++; --i } +rep movsb +# comment:fp stack unchanged by fallthrough + +# qhasm: bytesatleast64: +._bytesatleast64: +# comment:fp stack unchanged by fallthrough + +# qhasm: done: +._done: + +# qhasm: r11_caller = r11_stack +# asm 1: movq r11_caller=int64#9 +# asm 2: movq r11_caller=%r11 +movq 352(%rsp),%r11 + +# qhasm: r12_caller = r12_stack +# asm 1: movq r12_caller=int64#10 +# asm 2: movq r12_caller=%r12 +movq 360(%rsp),%r12 + +# qhasm: r13_caller = r13_stack +# asm 1: movq r13_caller=int64#11 +# asm 2: movq r13_caller=%r13 +movq 368(%rsp),%r13 + +# qhasm: r14_caller = r14_stack +# asm 1: movq r14_caller=int64#12 +# asm 2: movq r14_caller=%r14 +movq 376(%rsp),%r14 + +# qhasm: r15_caller = r15_stack +# asm 1: movq r15_caller=int64#13 +# asm 2: movq r15_caller=%r15 +movq 384(%rsp),%r15 + +# qhasm: rbx_caller = rbx_stack +# asm 1: movq rbx_caller=int64#14 +# asm 2: movq rbx_caller=%rbx +movq 392(%rsp),%rbx + +# qhasm: rbp_caller = rbp_stack +# asm 1: movq rbp_caller=int64#15 +# asm 2: movq rbp_caller=%rbp +movq 400(%rsp),%rbp + +# qhasm: leave +add %r11,%rsp +xor %rax,%rax +xor %rdx,%rdx +ret + +# qhasm: bytesatleast65: +._bytesatleast65: + +# qhasm: bytes -= 64 +# asm 1: sub $64,. + */ + #include "ZT_jniutils.h" #include "ZT_jnilookup.h" #include @@ -12,7 +30,7 @@ extern "C" { jobject createResultObject(JNIEnv *env, ZT_ResultCode code) { jclass resultClass = NULL; - + jobject resultObject = NULL; resultClass = lookup.findClass("com/zerotier/sdk/ResultCode"); @@ -49,14 +67,14 @@ jobject createResultObject(JNIEnv *env, ZT_ResultCode code) } jfieldID enumField = lookup.findStaticField(resultClass, fieldName.c_str(), "Lcom/zerotier/sdk/ResultCode;"); - if(env->ExceptionCheck() || enumField == NULL) + if(env->ExceptionCheck() || enumField == NULL) { LOGE("Error on FindStaticField"); return NULL; } resultObject = env->GetStaticObjectField(resultClass, enumField); - if(env->ExceptionCheck() || resultObject == NULL) + if(env->ExceptionCheck() || resultObject == NULL) { LOGE("Error on GetStaticObjectField"); } @@ -136,6 +154,8 @@ jobject createEvent(JNIEnv *env, ZT_Event event) case ZT_EVENT_TRACE: fieldName = "EVENT_TRACE"; break; + case ZT_EVENT_USER_MESSAGE: + break; } jfieldID enumField = lookup.findStaticField(eventClass, fieldName.c_str(), "Lcom/zerotier/sdk/Event;"); @@ -162,11 +182,11 @@ jobject createPeerRole(JNIEnv *env, ZT_PeerRole role) case ZT_PEER_ROLE_LEAF: fieldName = "PEER_ROLE_LEAF"; break; - case ZT_PEER_ROLE_RELAY: - fieldName = "PEER_ROLE_RELAY"; + case ZT_PEER_ROLE_MOON: + fieldName = "PEER_ROLE_MOON"; break; - case ZT_PEER_ROLE_ROOT: - fieldName = "PEER_ROLE_ROOTS"; + case ZT_PEER_ROLE_PLANET: + fieldName = "PEER_ROLE_PLANET"; break; } @@ -313,11 +333,20 @@ jobject newInetSocketAddress(JNIEnv *env, const sockaddr_storage &addr) return NULL; } - jobject inetAddressObject = newInetAddress(env, addr); + jobject inetAddressObject = NULL; - if(env->ExceptionCheck() || inetAddressObject == NULL) + if(addr.ss_family != 0) + { + inetAddressObject = newInetAddress(env, addr); + + if(env->ExceptionCheck() || inetAddressObject == NULL) + { + LOGE("Error creating new inet address"); + return NULL; + } + } + else { - LOGE("Error creating new inet address"); return NULL; } @@ -350,10 +379,9 @@ jobject newInetSocketAddress(JNIEnv *env, const sockaddr_storage &addr) break; default: { - LOGE("ERROR: addr.ss_family is not set or unknown"); break; } - }; + } jobject inetSocketAddressObject = env->NewObject(inetSocketAddressClass, inetSocketAddress_constructor, inetAddressObject, port); @@ -371,7 +399,6 @@ jobject newPeerPhysicalPath(JNIEnv *env, const ZT_PeerPhysicalPath &ppp) jfieldID addressField = NULL; jfieldID lastSendField = NULL; jfieldID lastReceiveField = NULL; - jfieldID activeField = NULL; jfieldID preferredField = NULL; jmethodID ppp_constructor = NULL; @@ -404,13 +431,6 @@ jobject newPeerPhysicalPath(JNIEnv *env, const ZT_PeerPhysicalPath &ppp) return NULL; } - activeField = lookup.findField(pppClass, "active", "Z"); - if(env->ExceptionCheck() || activeField == NULL) - { - LOGE("Error finding active field"); - return NULL; - } - preferredField = lookup.findField(pppClass, "preferred", "Z"); if(env->ExceptionCheck() || preferredField == NULL) { @@ -441,7 +461,6 @@ jobject newPeerPhysicalPath(JNIEnv *env, const ZT_PeerPhysicalPath &ppp) env->SetObjectField(pppObject, addressField, addressObject); env->SetLongField(pppObject, lastSendField, ppp.lastSend); env->SetLongField(pppObject, lastReceiveField, ppp.lastReceive); - env->SetBooleanField(pppObject, activeField, ppp.active); env->SetBooleanField(pppObject, preferredField, ppp.preferred); if(env->ExceptionCheck()) { @@ -451,15 +470,13 @@ jobject newPeerPhysicalPath(JNIEnv *env, const ZT_PeerPhysicalPath &ppp) return pppObject; } -jobject newPeer(JNIEnv *env, const ZT_Peer &peer) +jobject newPeer(JNIEnv *env, const ZT_Peer &peer) { LOGV("newPeer called"); jclass peerClass = NULL; jfieldID addressField = NULL; - jfieldID lastUnicastFrameField = NULL; - jfieldID lastMulticastFrameField = NULL; jfieldID versionMajorField = NULL; jfieldID versionMinorField = NULL; jfieldID versionRevField = NULL; @@ -483,20 +500,6 @@ jobject newPeer(JNIEnv *env, const ZT_Peer &peer) return NULL; } - lastUnicastFrameField = lookup.findField(peerClass, "lastUnicastFrame", "J"); - if(env->ExceptionCheck() || lastUnicastFrameField == NULL) - { - LOGE("Error finding lastUnicastFrame field of Peer object"); - return NULL; - } - - lastMulticastFrameField = lookup.findField(peerClass, "lastMulticastFrame", "J"); - if(env->ExceptionCheck() || lastMulticastFrameField == NULL) - { - LOGE("Error finding lastMulticastFrame field of Peer object"); - return NULL; - } - versionMajorField = lookup.findField(peerClass, "versionMajor", "I"); if(env->ExceptionCheck() || versionMajorField == NULL) { @@ -554,8 +557,6 @@ jobject newPeer(JNIEnv *env, const ZT_Peer &peer) } env->SetLongField(peerObject, addressField, (jlong)peer.address); - env->SetLongField(peerObject, lastUnicastFrameField, (jlong)peer.lastUnicastFrame); - env->SetLongField(peerObject, lastMulticastFrameField, (jlong)peer.lastMulticastFrame); env->SetIntField(peerObject, versionMajorField, peer.versionMajor); env->SetIntField(peerObject, versionMinorField, peer.versionMinor); env->SetIntField(peerObject, versionRevField, peer.versionRev); @@ -571,7 +572,7 @@ jobject newPeer(JNIEnv *env, const ZT_Peer &peer) jobjectArray arrayObject = env->NewObjectArray( peer.pathCount, peerPhysicalPathClass, NULL); - if(env->ExceptionCheck() || arrayObject == NULL) + if(env->ExceptionCheck() || arrayObject == NULL) { LOGE("Error creating PeerPhysicalPath[] array"); return NULL; @@ -609,6 +610,7 @@ jobject newNetworkConfig(JNIEnv *env, const ZT_VirtualNetworkConfig &vnetConfig) jfieldID portErrorField = NULL; jfieldID netconfRevisionField = NULL; jfieldID assignedAddressesField = NULL; + jfieldID routesField = NULL; vnetConfigClass = lookup.findClass("com/zerotier/sdk/VirtualNetworkConfig"); if(vnetConfigClass == NULL) @@ -709,13 +711,22 @@ jobject newNetworkConfig(JNIEnv *env, const ZT_VirtualNetworkConfig &vnetConfig) return NULL; } - assignedAddressesField = lookup.findField(vnetConfigClass, "assignedAddresses", "[Ljava/net/InetSocketAddress;"); + assignedAddressesField = lookup.findField(vnetConfigClass, "assignedAddresses", + "[Ljava/net/InetSocketAddress;"); if(env->ExceptionCheck() || assignedAddressesField == NULL) { LOGE("Error getting assignedAddresses field"); return NULL; } + routesField = lookup.findField(vnetConfigClass, "routes", + "[Lcom/zerotier/sdk/VirtualNetworkRoute;"); + if(env->ExceptionCheck() || routesField == NULL) + { + LOGE("Error getting routes field"); + return NULL; + } + env->SetLongField(vnetConfigObj, nwidField, vnetConfig.nwid); env->SetLongField(vnetConfigObj, macField, vnetConfig.mac); jstring nameStr = env->NewStringUTF(vnetConfig.name); @@ -773,6 +784,34 @@ jobject newNetworkConfig(JNIEnv *env, const ZT_VirtualNetworkConfig &vnetConfig) env->SetObjectField(vnetConfigObj, assignedAddressesField, assignedAddrArrayObj); + jclass virtualNetworkRouteClass = lookup.findClass("com/zerotier/sdk/VirtualNetworkRoute"); + if(env->ExceptionCheck() || virtualNetworkRouteClass == NULL) + { + LOGE("Error finding VirtualNetworkRoute class"); + return NULL; + } + + jobjectArray routesArrayObj = env->NewObjectArray( + vnetConfig.routeCount, virtualNetworkRouteClass, NULL); + if(env->ExceptionCheck() || routesArrayObj == NULL) + { + LOGE("Error creating VirtualNetworkRoute[] array"); + return NULL; + } + + for(unsigned int i = 0; i < vnetConfig.routeCount; ++i) + { + jobject routeObj = newVirtualNetworkRoute(env, vnetConfig.routes[i]); + env->SetObjectArrayElement(routesArrayObj, i, routeObj); + if(env->ExceptionCheck()) + { + LOGE("Error assigning VirtualNetworkRoute to array"); + return NULL; + } + } + + env->SetObjectField(vnetConfigObj, routesField, routesArrayObj); + return vnetConfigObj; } @@ -831,6 +870,72 @@ jobject newVersion(JNIEnv *env, int major, int minor, int rev) return versionObj; } +jobject newVirtualNetworkRoute(JNIEnv *env, const ZT_VirtualNetworkRoute &route) +{ + jclass virtualNetworkRouteClass = NULL; + jmethodID routeConstructor = NULL; + + virtualNetworkRouteClass = lookup.findClass("com/zerotier/sdk/VirtualNetworkRoute"); + if(env->ExceptionCheck() || virtualNetworkRouteClass == NULL) + { + return NULL; + } + + routeConstructor = lookup.findMethod(virtualNetworkRouteClass, "", "()V"); + if(env->ExceptionCheck() || routeConstructor == NULL) + { + return NULL; + } + + jobject routeObj = env->NewObject(virtualNetworkRouteClass, routeConstructor); + if(env->ExceptionCheck() || routeObj == NULL) + { + return NULL; + } + + jfieldID targetField = NULL; + jfieldID viaField = NULL; + jfieldID flagsField = NULL; + jfieldID metricField = NULL; + + targetField = lookup.findField(virtualNetworkRouteClass, "target", + "Ljava/net/InetSocketAddress;"); + if(env->ExceptionCheck() || targetField == NULL) + { + return NULL; + } + + viaField = lookup.findField(virtualNetworkRouteClass, "via", + "Ljava/net/InetSocketAddress;"); + if(env->ExceptionCheck() || targetField == NULL) + { + return NULL; + } + + flagsField = lookup.findField(virtualNetworkRouteClass, "flags", "I"); + if(env->ExceptionCheck() || flagsField == NULL) + { + return NULL; + } + + metricField = lookup.findField(virtualNetworkRouteClass, "metric", "I"); + if(env->ExceptionCheck() || metricField == NULL) + { + return NULL; + } + + jobject targetObj = newInetSocketAddress(env, route.target); + jobject viaObj = newInetSocketAddress(env, route.via); + + env->SetObjectField(routeObj, targetField, targetObj); + env->SetObjectField(routeObj, viaField, viaObj); + env->SetIntField(routeObj, flagsField, (jint)route.flags); + env->SetIntField(routeObj, metricField, (jint)route.metric); + + return routeObj; +} + #ifdef __cplusplus } -#endif \ No newline at end of file +#endif + diff --git a/java/jni/ZT_jniutils.h b/java/jni/ZT_jniutils.h index 34dfc47..e35d4f4 100644 --- a/java/jni/ZT_jniutils.h +++ b/java/jni/ZT_jniutils.h @@ -1,3 +1,21 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #ifndef ZT_jniutils_h_ #define ZT_jniutils_h_ #include @@ -42,6 +60,8 @@ jobject newNetworkConfig(JNIEnv *env, const ZT_VirtualNetworkConfig &config); jobject newVersion(JNIEnv *env, int major, int minor, int rev); +jobject newVirtualNetworkRoute(JNIEnv *env, const ZT_VirtualNetworkRoute &route); + #ifdef __cplusplus } #endif diff --git a/java/jni/com_zerotierone_sdk_Node.cpp b/java/jni/com_zerotierone_sdk_Node.cpp index 4d9a210..defbe7f 100644 --- a/java/jni/com_zerotierone_sdk_Node.cpp +++ b/java/jni/com_zerotierone_sdk_Node.cpp @@ -56,7 +56,12 @@ namespace { , eventListener(NULL) , frameListener(NULL) , configListener(NULL) - {} + , pathChecker(NULL) + , callbacks(NULL) + { + callbacks = (ZT_Node_Callbacks*)malloc(sizeof(ZT_Node_Callbacks)); + memset(callbacks, 0, sizeof(ZT_Node_Callbacks)); + } ~JniRef() { @@ -69,6 +74,10 @@ namespace { env->DeleteGlobalRef(eventListener); env->DeleteGlobalRef(frameListener); env->DeleteGlobalRef(configListener); + env->DeleteGlobalRef(pathChecker); + + free(callbacks); + callbacks = NULL; } uint64_t id; @@ -83,12 +92,16 @@ namespace { jobject eventListener; jobject frameListener; jobject configListener; + jobject pathChecker; + + ZT_Node_Callbacks *callbacks; }; int VirtualNetworkConfigFunctionCallback( ZT_Node *node, void *userData, + void *threadData, uint64_t nwid, void **, enum ZT_VirtualNetworkConfigOperation operation, @@ -130,13 +143,14 @@ namespace { } return env->CallIntMethod( - ref->configListener, - configListenerCallbackMethod, + ref->configListener, + configListenerCallbackMethod, (jlong)nwid, operationObject, networkConfigObject); } void VirtualNetworkFrameFunctionCallback(ZT_Node *node, void *userData, + void *threadData, uint64_t nwid, void**, uint64_t sourceMac, @@ -194,7 +208,8 @@ namespace { void EventCallback(ZT_Node *node, void *userData, - enum ZT_Event event, + void *threadData, + enum ZT_Event event, const void *data) { LOGV("EventCallback"); @@ -282,11 +297,14 @@ namespace { } } break; + case ZT_EVENT_USER_MESSAGE: + break; } } long DataStoreGetFunction(ZT_Node *node, void *userData, + void *threadData, const char *objectName, void *buffer, unsigned long bufferSize, @@ -339,7 +357,7 @@ namespace { objectName, buffer, bufferIndex, objectSizeObj); long retval = (long)env->CallLongMethod( - ref->dataStoreGetListener, dataStoreGetCallbackMethod, + ref->dataStoreGetListener, dataStoreGetCallbackMethod, nameStr, bufferObj, (jlong)bufferIndex, objectSizeObj); if(retval > 0) @@ -360,6 +378,7 @@ namespace { int DataStorePutFunction(ZT_Node *node, void *userData, + void *threadData, const char *objectName, const void *buffer, unsigned long bufferSize, @@ -426,6 +445,7 @@ namespace { int WirePacketSendFunction(ZT_Node *node, void *userData, + void *threadData, const struct sockaddr_storage *localAddress, const struct sockaddr_storage *remoteAddress, const void *buffer, @@ -454,7 +474,7 @@ namespace { LOGE("Couldn't find onSendPacketRequested method"); return -2; } - + jobject localAddressObj = NULL; if(memcmp(localAddress, &ZT_SOCKADDR_NULL, sizeof(sockaddr_storage)) != 0) { @@ -470,6 +490,165 @@ namespace { return retval; } + int PathCheckFunction(ZT_Node *node, + void *userPtr, + void *threadPtr, + uint64_t address, + const struct sockaddr_storage *localAddress, + const struct sockaddr_storage *remoteAddress) + { + JniRef *ref = (JniRef*)userPtr; + assert(ref->node == node); + + if(ref->pathChecker == NULL) { + return true; + } + + JNIEnv *env = NULL; + ref->jvm->GetEnv((void**)&env, JNI_VERSION_1_6); + + jclass pathCheckerClass = env->GetObjectClass(ref->pathChecker); + if(pathCheckerClass == NULL) + { + LOGE("Couldn't find class for PathChecker instance"); + return true; + } + + jmethodID pathCheckCallbackMethod = lookup.findMethod(pathCheckerClass, + "onPathCheck", "(JLjava/net/InetSocketAddress;Ljava/net/InetSocketAddress;)Z"); + if(pathCheckCallbackMethod == NULL) + { + LOGE("Couldn't find onPathCheck method implementation"); + return true; + } + + jobject localAddressObj = NULL; + jobject remoteAddressObj = NULL; + + if(memcmp(localAddress, &ZT_SOCKADDR_NULL, sizeof(sockaddr_storage)) != 0) + { + localAddressObj = newInetSocketAddress(env, *localAddress); + } + if(memcmp(remoteAddress, &ZT_SOCKADDR_NULL, sizeof(sockaddr_storage)) != 0) + { + remoteAddressObj = newInetSocketAddress(env, *remoteAddress); + } + + return env->CallBooleanMethod(ref->pathChecker, pathCheckCallbackMethod, address, localAddressObj, remoteAddressObj); + } + + int PathLookupFunction(ZT_Node *node, + void *userPtr, + void *threadPtr, + uint64_t address, + int ss_family, + struct sockaddr_storage *result) + { + JniRef *ref = (JniRef*)userPtr; + assert(ref->node == node); + + if(ref->pathChecker == NULL) { + return false; + } + + JNIEnv *env = NULL; + ref->jvm->GetEnv((void**)&env, JNI_VERSION_1_6); + + jclass pathCheckerClass = env->GetObjectClass(ref->pathChecker); + if(pathCheckerClass == NULL) + { + LOGE("Couldn't find class for PathChecker instance"); + return false; + } + + jmethodID pathLookupMethod = lookup.findMethod(pathCheckerClass, + "onPathLookup", "(JI)Ljava/net/InetSocketAddress;"); + if(pathLookupMethod == NULL) { + return false; + } + + jobject sockAddressObject = env->CallObjectMethod(ref->pathChecker, pathLookupMethod, address, ss_family); + if(sockAddressObject == NULL) + { + LOGE("Unable to call onPathLookup implementation"); + return false; + } + + jclass inetSockAddressClass = env->GetObjectClass(sockAddressObject); + if(inetSockAddressClass == NULL) + { + LOGE("Unable to find InetSocketAddress class"); + return false; + } + + jmethodID getAddressMethod = lookup.findMethod(inetSockAddressClass, "getAddress", "()Ljava/net/InetAddress;"); + if(getAddressMethod == NULL) + { + LOGE("Unable to find InetSocketAddress.getAddress() method"); + return false; + } + + jmethodID getPortMethod = lookup.findMethod(inetSockAddressClass, "getPort", "()I"); + if(getPortMethod == NULL) + { + LOGE("Unable to find InetSocketAddress.getPort() method"); + return false; + } + + jint port = env->CallIntMethod(sockAddressObject, getPortMethod); + jobject addressObject = env->CallObjectMethod(sockAddressObject, getAddressMethod); + + jclass inetAddressClass = lookup.findClass("java/net/InetAddress"); + if(inetAddressClass == NULL) + { + LOGE("Unable to find InetAddress class"); + return false; + } + + getAddressMethod = lookup.findMethod(inetAddressClass, "getAddress", "()[B"); + if(getAddressMethod == NULL) + { + LOGE("Unable to find InetAddress.getAddress() method"); + return false; + } + + jbyteArray addressBytes = (jbyteArray)env->CallObjectMethod(addressObject, getAddressMethod); + if(addressBytes == NULL) + { + LOGE("Unable to call InetAddress.getBytes()"); + return false; + } + + int addressSize = env->GetArrayLength(addressBytes); + if(addressSize == 4) + { + // IPV4 + sockaddr_in *addr = (sockaddr_in*)result; + addr->sin_family = AF_INET; + addr->sin_port = htons(port); + + void *data = env->GetPrimitiveArrayCritical(addressBytes, NULL); + memcpy(&addr->sin_addr, data, 4); + env->ReleasePrimitiveArrayCritical(addressBytes, data, 0); + } + else if (addressSize == 16) + { + // IPV6 + sockaddr_in6 *addr = (sockaddr_in6*)result; + addr->sin6_family = AF_INET6; + addr->sin6_port = htons(port); + void *data = env->GetPrimitiveArrayCritical(addressBytes, NULL); + memcpy(&addr->sin6_addr, data, 16); + env->ReleasePrimitiveArrayCritical(addressBytes, data, 0); + } + else + { + return false; + } + + return true; + } + typedef std::map NodeMap; static NodeMap nodeMap; ZeroTier::Mutex nodeMapMutex; @@ -487,7 +666,7 @@ namespace { } } -JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { lookup.setJavaVM(vm); return JNI_VERSION_1_6; @@ -602,17 +781,35 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_node_1init( } ref->eventListener = env->NewGlobalRef(tmp); + fid = lookup.findField( + cls, "pathChecker", "Lcom/zerotier/sdk/PathChecker;"); + if(fid == NULL) + { + LOGE("no path checker?"); + return NULL; + } + + tmp = env->GetObjectField(obj, fid); + if(tmp != NULL) + { + ref->pathChecker = env->NewGlobalRef(tmp); + } + + ref->callbacks->dataStoreGetFunction = &DataStoreGetFunction; + ref->callbacks->dataStorePutFunction = &DataStorePutFunction; + ref->callbacks->wirePacketSendFunction = &WirePacketSendFunction; + ref->callbacks->virtualNetworkFrameFunction = &VirtualNetworkFrameFunctionCallback; + ref->callbacks->virtualNetworkConfigFunction = &VirtualNetworkConfigFunctionCallback; + ref->callbacks->eventCallback = &EventCallback; + ref->callbacks->pathCheckFunction = &PathCheckFunction; + ref->callbacks->pathLookupFunction = &PathLookupFunction; + ZT_ResultCode rc = ZT_Node_new( &node, ref, - (uint64_t)now, - &DataStoreGetFunction, - &DataStorePutFunction, - &WirePacketSendFunction, - &VirtualNetworkFrameFunctionCallback, - &VirtualNetworkConfigFunctionCallback, NULL, - &EventCallback); + ref->callbacks, + (uint64_t)now); if(rc != ZT_RESULT_OK) { @@ -631,7 +828,7 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_node_1init( ZeroTier::Mutex::Lock lock(nodeMapMutex); ref->node = node; nodeMap.insert(std::make_pair(ref->id, ref)); - + return resultObject; } @@ -649,7 +846,7 @@ JNIEXPORT void JNICALL Java_com_zerotier_sdk_Node_node_1delete( NodeMap::iterator found; { - ZeroTier::Mutex::Lock lock(nodeMapMutex); + ZeroTier::Mutex::Lock lock(nodeMapMutex); found = nodeMap.find(nodeId); } @@ -675,9 +872,9 @@ JNIEXPORT void JNICALL Java_com_zerotier_sdk_Node_node_1delete( * Signature: (JJJJJII[B[J)Lcom/zerotier/sdk/ResultCode; */ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processVirtualNetworkFrame( - JNIEnv *env, jobject obj, - jlong id, - jlong in_now, + JNIEnv *env, jobject obj, + jlong id, + jlong in_now, jlong in_nwid, jlong in_sourceMac, jlong in_destMac, @@ -687,7 +884,7 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processVirtualNetworkFrame( jlongArray out_nextBackgroundTaskDeadline) { uint64_t nodeId = (uint64_t) id; - + ZT_Node *node = findNode(nodeId); if(node == NULL) { @@ -719,6 +916,7 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processVirtualNetworkFrame( ZT_ResultCode rc = ZT_Node_processVirtualNetworkFrame( node, + NULL, now, nwid, sourceMac, @@ -742,9 +940,9 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processVirtualNetworkFrame( * Signature: (JJLjava/net/InetSocketAddress;I[B[J)Lcom/zerotier/sdk/ResultCode; */ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processWirePacket( - JNIEnv *env, jobject obj, + JNIEnv *env, jobject obj, jlong id, - jlong in_now, + jlong in_now, jobject in_localAddress, jobject in_remoteAddress, jbyteArray in_packetData, @@ -810,7 +1008,7 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processWirePacket( jmethodID inetSock_getPort = lookup.findMethod( InetSocketAddressClass, "getPort", "()I"); - if(env->ExceptionCheck() || inetSock_getPort == NULL) + if(env->ExceptionCheck() || inetSock_getPort == NULL) { LOGE("Couldn't find getPort method on InetSocketAddress"); return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL); @@ -834,10 +1032,10 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processWirePacket( } unsigned int addrSize = env->GetArrayLength(remoteAddressArray); - + sockaddr_storage localAddress = {}; - + if(localAddrObj == NULL) { localAddress = ZT_SOCKADDR_NULL; @@ -923,13 +1121,14 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processWirePacket( ZT_ResultCode rc = ZT_Node_processWirePacket( node, + NULL, now, &localAddress, &remoteAddress, localData, packetLength, &nextBackgroundTaskDeadline); - if(rc != ZT_RESULT_OK) + if(rc != ZT_RESULT_OK) { LOGE("ZT_Node_processWirePacket returned: %d", rc); } @@ -949,7 +1148,7 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processWirePacket( * Signature: (JJ[J)Lcom/zerotier/sdk/ResultCode; */ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processBackgroundTasks( - JNIEnv *env, jobject obj, + JNIEnv *env, jobject obj, jlong id, jlong in_now, jlongArray out_nextBackgroundTaskDeadline) @@ -971,7 +1170,7 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processBackgroundTasks( uint64_t now = (uint64_t)in_now; uint64_t nextBackgroundTaskDeadline = 0; - ZT_ResultCode rc = ZT_Node_processBackgroundTasks(node, now, &nextBackgroundTaskDeadline); + ZT_ResultCode rc = ZT_Node_processBackgroundTasks(node, NULL, now, &nextBackgroundTaskDeadline); jlong *outDeadline = (jlong*)env->GetPrimitiveArrayCritical(out_nextBackgroundTaskDeadline, NULL); outDeadline[0] = (jlong)nextBackgroundTaskDeadline; @@ -998,7 +1197,7 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_join( uint64_t nwid = (uint64_t)in_nwid; - ZT_ResultCode rc = ZT_Node_join(node, nwid, NULL); + ZT_ResultCode rc = ZT_Node_join(node, nwid, NULL, NULL); return createResultObject(env, rc); } @@ -1021,8 +1220,8 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_leave( uint64_t nwid = (uint64_t)in_nwid; - ZT_ResultCode rc = ZT_Node_leave(node, nwid, NULL); - + ZT_ResultCode rc = ZT_Node_leave(node, nwid, NULL, NULL); + return createResultObject(env, rc); } @@ -1032,8 +1231,8 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_leave( * Signature: (JJJJ)Lcom/zerotier/sdk/ResultCode; */ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_multicastSubscribe( - JNIEnv *env, jobject obj, - jlong id, + JNIEnv *env, jobject obj, + jlong id, jlong in_nwid, jlong in_multicastGroup, jlong in_multicastAdi) @@ -1051,7 +1250,7 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_multicastSubscribe( unsigned long multicastAdi = (unsigned long)in_multicastAdi; ZT_ResultCode rc = ZT_Node_multicastSubscribe( - node, nwid, multicastGroup, multicastAdi); + node, NULL, nwid, multicastGroup, multicastAdi); return createResultObject(env, rc); } @@ -1062,8 +1261,8 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_multicastSubscribe( * Signature: (JJJJ)Lcom/zerotier/sdk/ResultCode; */ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_multicastUnsubscribe( - JNIEnv *env, jobject obj, - jlong id, + JNIEnv *env, jobject obj, + jlong id, jlong in_nwid, jlong in_multicastGroup, jlong in_multicastAdi) @@ -1086,6 +1285,54 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_multicastUnsubscribe( return createResultObject(env, rc); } +/* + * Class: com_zerotier_sdk_Node + * Method: orbit + * Signature: (JJJ)Lcom/zerotier/sdk/ResultCode; + */ +JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_orbit( + JNIEnv *env, jobject obj, + jlong id, + jlong in_moonWorldId, + jlong in_moonSeed) +{ + uint64_t nodeId = (uint64_t)id; + ZT_Node *node = findNode(nodeId); + if(node == NULL) + { + return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL); + } + + uint64_t moonWorldId = (uint64_t)in_moonWorldId; + uint64_t moonSeed = (uint64_t)in_moonSeed; + + ZT_ResultCode rc = ZT_Node_orbit(node, NULL, moonWorldId, moonSeed); + return createResultObject(env, rc); +} + +/* + * Class: com_zerotier_sdk_Node + * Method: deorbit + * Signature: (JJ)L/com/zerotier/sdk/ResultCode; + */ +JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_deorbit( + JNIEnv *env, jobject obj, + jlong id, + jlong in_moonWorldId) +{ + uint64_t nodeId = (uint64_t)id; + ZT_Node *node = findNode(nodeId); + if(node == NULL) + { + return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL); + } + + uint64_t moonWorldId = (uint64_t)in_moonWorldId; + + ZT_ResultCode rc = ZT_Node_deorbit(node, NULL, moonWorldId); + return createResultObject(env, rc); +} + /* * Class: com_zerotier_sdk_Node * Method: address @@ -1131,7 +1378,7 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_status { return NULL; } - + nodeStatusConstructor = lookup.findMethod( nodeStatusClass, "", "()V"); if(nodeStatusConstructor == NULL) @@ -1215,7 +1462,7 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_networkConfig( } ZT_VirtualNetworkConfig *vnetConfig = ZT_Node_networkConfig(node, nwid); - + jobject vnetConfigObject = newNetworkConfig(env, *vnetConfig); ZT_Node_freeQueryResult(node, vnetConfig); @@ -1257,7 +1504,7 @@ JNIEXPORT jobjectArray JNICALL Java_com_zerotier_sdk_Node_peers( } ZT_PeerList *peerList = ZT_Node_peers(node); - + if(peerList == NULL) { LOGE("ZT_Node_peers returned NULL"); @@ -1296,7 +1543,7 @@ JNIEXPORT jobjectArray JNICALL Java_com_zerotier_sdk_Node_peers( { jobject peerObj = newPeer(env, peerList->peers[i]); env->SetObjectArrayElement(peerArrayObj, i, peerObj); - if(env->ExceptionCheck()) + if(env->ExceptionCheck()) { LOGE("Error assigning Peer object to array"); break; diff --git a/java/src/com/zerotier/sdk/Node.java b/java/src/com/zerotier/sdk/Node.java index 4bc6e18..8e7d44e 100644 --- a/java/src/com/zerotier/sdk/Node.java +++ b/java/src/com/zerotier/sdk/Node.java @@ -74,6 +74,7 @@ public class Node { private final EventListener eventListener; private final VirtualNetworkFrameListener frameListener; private final VirtualNetworkConfigListener configListener; + private final PathChecker pathChecker; /** * Create a new ZeroTier One node @@ -88,6 +89,7 @@ public class Node { * @param eventListener User written instance of the {@link EventListener} interface to receive status updates and non-fatal error notices. This instance must be unique per Node object. * @param frameListener * @param configListener User written instance of the {@link VirtualNetworkConfigListener} interface to be called when virtual LANs are created, deleted, or their config parameters change. This instance must be unique per Node object. + * @param pathChecker User written instance of the {@link PathChecker} interface. Not required and can be null. */ public Node(long now, DataStoreGetListener getListener, @@ -95,7 +97,8 @@ public class Node { PacketSender sender, EventListener eventListener, VirtualNetworkFrameListener frameListener, - VirtualNetworkConfigListener configListener) throws NodeException + VirtualNetworkConfigListener configListener, + PathChecker pathChecker) throws NodeException { this.nodeId = now; @@ -105,6 +108,7 @@ public class Node { this.eventListener = eventListener; this.frameListener = frameListener; this.configListener = configListener; + this.pathChecker = pathChecker; ResultCode rc = node_init(now); if(rc != ResultCode.RESULT_OK) @@ -318,6 +322,34 @@ public class Node { return multicastUnsubscribe(nodeId, nwid, multicastGroup, multicastAdi); } + /** + * Add or update a moon + * + * Moons are persisted in the data store in moons.d/, so this can persist + * across invocations if the contents of moon.d are scanned and orbit is + * called for each on startup. + * + * @param moonWorldId Moon's world ID + * @param moonSeed If non-zero, the ZeroTier address of any member of the moon to query for moon definition + * @return Error if moon was invalid or failed to be added + */ + public ResultCode orbit( + long moonWorldId, + long moonSeed) { + return orbit(nodeId, moonWorldId, moonSeed); + } + + /** + * Remove a moon (does nothing if not present) + * + * @param moonWorldId World ID of moon to remove + * @return Error if anything bad happened + */ + public ResultCode deorbit( + long moonWorldId) { + return deorbit(nodeId, moonWorldId); + } + /** * Get this node's 40-bit ZeroTier address * @@ -420,6 +452,15 @@ public class Node { long multicastGroup, long multicastAdi); + private native ResultCode orbit( + long nodeId, + long moonWorldId, + long moonSeed); + + private native ResultCode deorbit( + long nodeId, + long moonWorldId); + private native long address(long nodeId); private native NodeStatus status(long nodeId); diff --git a/java/src/com/zerotier/sdk/PathChecker.java b/java/src/com/zerotier/sdk/PathChecker.java new file mode 100644 index 0000000..3e02f11 --- /dev/null +++ b/java/src/com/zerotier/sdk/PathChecker.java @@ -0,0 +1,45 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2017 ZeroTier, Inc. https://www.zerotier.com/ + */ + +package com.zerotier.sdk; + +import java.net.InetSocketAddress; + +public interface PathChecker { + /** + * Callback to check whether a path should be used for ZeroTier traffic + * + * This function must return true if the path should be used. + * + * If no path check function is specified, ZeroTier will still exclude paths + * that overlap with ZeroTier-assigned and managed IP address blocks. But the + * use of a path check function is recommended to ensure that recursion does + * not occur in cases where addresses are assigned by the OS or managed by + * an out of band mechanism like DHCP. The path check function should examine + * all configured ZeroTier interfaces and check to ensure that the supplied + * addresses will not result in ZeroTier traffic being sent over a ZeroTier + * interface (recursion). + * + * Obviously this is not required in configurations where this can't happen, + * such as network containers or embedded. + * + * @param ztAddress ZeroTier address or 0 for none/any + * @param localAddress Local interface address + * @param remoteAddress remote address + */ + boolean onPathCheck(long ztAddress, InetSocketAddress localAddress, InetSocketAddress remoteAddress); + + /** + * Function to get physical addresses for ZeroTier peers + * + * If provided this function will be occasionally called to get physical + * addresses that might be tried to reach a ZeroTier address. + * + * @param ztAddress ZeroTier address (least significant 40 bits) + * @param ss_family desired address family or -1 for any + * @return address and port of ztAddress or null + */ + InetSocketAddress onPathLookup(long ztAddress, int ss_family); +} diff --git a/java/src/com/zerotier/sdk/Peer.java b/java/src/com/zerotier/sdk/Peer.java index fb2d106..eb3d713 100644 --- a/java/src/com/zerotier/sdk/Peer.java +++ b/java/src/com/zerotier/sdk/Peer.java @@ -34,8 +34,6 @@ import java.util.ArrayList; */ public final class Peer { private long address; - private long lastUnicastFrame; - private long lastMulticastFrame; private int versionMajor; private int versionMinor; private int versionRev; @@ -52,20 +50,6 @@ public final class Peer { return address; } - /** - * Time we last received a unicast frame from this peer - */ - public final long lastUnicastFrame() { - return lastUnicastFrame; - } - - /** - * Time we last received a multicast rame from this peer - */ - public final long lastMulticastFrame() { - return lastMulticastFrame; - } - /** * Remote major version or -1 if not known */ diff --git a/java/src/com/zerotier/sdk/PeerPhysicalPath.java b/java/src/com/zerotier/sdk/PeerPhysicalPath.java index d64ea56..3f9a861 100644 --- a/java/src/com/zerotier/sdk/PeerPhysicalPath.java +++ b/java/src/com/zerotier/sdk/PeerPhysicalPath.java @@ -37,7 +37,6 @@ public final class PeerPhysicalPath { private long lastSend; private long lastReceive; private boolean fixed; - private boolean active; private boolean preferred; private PeerPhysicalPath() {} @@ -70,13 +69,6 @@ public final class PeerPhysicalPath { return fixed; } - /** - * Is path active? - */ - public final boolean isActive() { - return active; - } - /** * Is path preferred? */ diff --git a/java/src/com/zerotier/sdk/PeerRole.java b/java/src/com/zerotier/sdk/PeerRole.java index d7d55f0..fce183d 100644 --- a/java/src/com/zerotier/sdk/PeerRole.java +++ b/java/src/com/zerotier/sdk/PeerRole.java @@ -34,12 +34,12 @@ public enum PeerRole { PEER_ROLE_LEAF, /** - * relay node + * moon root */ - PEER_ROLE_RELAY, + PEER_ROLE_MOON, /** - * root server + * planetary root */ - PEER_ROLE_ROOT + PEER_ROLE_PLANET } \ No newline at end of file diff --git a/java/src/com/zerotier/sdk/VirtualNetworkConfig.java b/java/src/com/zerotier/sdk/VirtualNetworkConfig.java index fbcbd3a..64512da 100644 --- a/java/src/com/zerotier/sdk/VirtualNetworkConfig.java +++ b/java/src/com/zerotier/sdk/VirtualNetworkConfig.java @@ -50,6 +50,7 @@ public final class VirtualNetworkConfig implements Comparable. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +package com.zerotier.sdk; + +import java.net.InetSocketAddress; + +public final class VirtualNetworkRoute implements Comparable +{ + private VirtualNetworkRoute() { + target = null; + via = null; + flags = 0; + metric = 0; + } + + /** + * Target network / netmask bits (in port field) or NULL or 0.0.0.0/0 for default + */ + public InetSocketAddress target; + + /** + * Gateway IP address (port ignored) or NULL (family == 0) for LAN-local (no gateway) + */ + public InetSocketAddress via; + + /** + * Route flags + */ + public int flags; + + /** + * Route metric (not currently used) + */ + public int metric; + + + @Override + public int compareTo(VirtualNetworkRoute other) { + return target.toString().compareTo(other.target.toString()); + } + + public boolean equals(VirtualNetworkRoute other) { + boolean targetEquals; + if (target == null && other.target == null) { + targetEquals = true; + } + else if (target == null && other.target != null) { + targetEquals = false; + } + else if (target != null && other.target == null) { + targetEquals = false; + } + else { + targetEquals = target.equals(other.target); + } + + + boolean viaEquals; + if (via == null && other.via == null) { + viaEquals = true; + } + else if (via == null && other.via != null) { + viaEquals = false; + } + else if (via != null && other.via == null) { + viaEquals = false; + } + else { + viaEquals = via.equals(other.via); + } + + return viaEquals && + viaEquals && + flags == other.flags && + metric == other.metric; + } +} diff --git a/macui/ZeroTier One.xcodeproj/project.pbxproj b/macui/ZeroTier One.xcodeproj/project.pbxproj new file mode 100644 index 0000000..fc5cfc1 --- /dev/null +++ b/macui/ZeroTier One.xcodeproj/project.pbxproj @@ -0,0 +1,382 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 932D472F1D1CD499004BCFE2 /* ZeroTierIcon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 932D472E1D1CD499004BCFE2 /* ZeroTierIcon.icns */; }; + 932D47331D1CD861004BCFE2 /* PreferencesViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 932D47311D1CD861004BCFE2 /* PreferencesViewController.xib */; }; + 932D47371D1CDC9B004BCFE2 /* AboutViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 932D47351D1CDC9B004BCFE2 /* AboutViewController.xib */; }; + 93326BDE1CE7C816005CA2AC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 93326BDD1CE7C816005CA2AC /* Assets.xcassets */; }; + 93326BE11CE7C816005CA2AC /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 93326BDF1CE7C816005CA2AC /* MainMenu.xib */; }; + 93326BEB1CE7D9B9005CA2AC /* JoinNetworkViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 93326BE91CE7D9B9005CA2AC /* JoinNetworkViewController.xib */; }; + 93326BEF1CE7DA30005CA2AC /* ShowNetworksViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 93326BED1CE7DA30005CA2AC /* ShowNetworksViewController.xib */; }; + 93D1675F1D54191C00330C99 /* NodeStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D1675E1D54191C00330C99 /* NodeStatus.m */; }; + 93D167621D541BC200330C99 /* ServiceCom.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D167611D541BC200330C99 /* ServiceCom.m */; }; + 93D167661D54308200330C99 /* Network.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D167651D54308200330C99 /* Network.m */; }; + 93D167691D57E7EA00330C99 /* AboutViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D167681D57E7EA00330C99 /* AboutViewController.m */; }; + 93D1676D1D57EB8400330C99 /* PreferencesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D1676C1D57EB8400330C99 /* PreferencesViewController.m */; }; + 93D167701D57FD3800330C99 /* NetworkMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D1676F1D57FD3800330C99 /* NetworkMonitor.m */; }; + 93D167731D58093C00330C99 /* NetworkInfoCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D167721D58093C00330C99 /* NetworkInfoCell.m */; }; + 93D167761D580C3500330C99 /* ShowNetworksViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D167751D580C3500330C99 /* ShowNetworksViewController.m */; }; + 93D167791D5815E600330C99 /* JoinNetworkViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D167781D5815E600330C99 /* JoinNetworkViewController.m */; }; + 93D1677C1D58228A00330C99 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D1677B1D58228A00330C99 /* AppDelegate.m */; }; + 93D1679B1D58300F00330C99 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D1679A1D58300F00330C99 /* main.m */; }; + 93D1679D1D595F0000330C99 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93D1679C1D595F0000330C99 /* WebKit.framework */; }; + 93DAFB271D3F0BEE004D5417 /* about.html in Resources */ = {isa = PBXBuildFile; fileRef = 93DAFB261D3F0BEE004D5417 /* about.html */; }; + 93DAFE4B1CFE53CA00547CC4 /* AuthtokenCopy.m in Sources */ = {isa = PBXBuildFile; fileRef = 93DAFE4A1CFE53CA00547CC4 /* AuthtokenCopy.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 932D472E1D1CD499004BCFE2 /* ZeroTierIcon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = ZeroTierIcon.icns; sourceTree = ""; }; + 932D47311D1CD861004BCFE2 /* PreferencesViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PreferencesViewController.xib; sourceTree = ""; }; + 932D47351D1CDC9B004BCFE2 /* AboutViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AboutViewController.xib; sourceTree = ""; }; + 93326BD81CE7C816005CA2AC /* ZeroTier One.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "ZeroTier One.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 93326BDD1CE7C816005CA2AC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 93326BE01CE7C816005CA2AC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 93326BE21CE7C816005CA2AC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 93326BE91CE7D9B9005CA2AC /* JoinNetworkViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = JoinNetworkViewController.xib; sourceTree = ""; }; + 93326BED1CE7DA30005CA2AC /* ShowNetworksViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ShowNetworksViewController.xib; sourceTree = ""; }; + 93D1675D1D54191C00330C99 /* NodeStatus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NodeStatus.h; sourceTree = ""; }; + 93D1675E1D54191C00330C99 /* NodeStatus.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NodeStatus.m; sourceTree = ""; }; + 93D167601D541BC200330C99 /* ServiceCom.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ServiceCom.h; sourceTree = ""; }; + 93D167611D541BC200330C99 /* ServiceCom.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ServiceCom.m; sourceTree = ""; }; + 93D167641D54308200330C99 /* Network.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Network.h; sourceTree = ""; }; + 93D167651D54308200330C99 /* Network.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Network.m; sourceTree = ""; }; + 93D167671D57E7EA00330C99 /* AboutViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AboutViewController.h; sourceTree = ""; }; + 93D167681D57E7EA00330C99 /* AboutViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AboutViewController.m; sourceTree = ""; }; + 93D1676B1D57EB8400330C99 /* PreferencesViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PreferencesViewController.h; sourceTree = ""; }; + 93D1676C1D57EB8400330C99 /* PreferencesViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PreferencesViewController.m; sourceTree = ""; }; + 93D1676E1D57FD3800330C99 /* NetworkMonitor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NetworkMonitor.h; sourceTree = ""; }; + 93D1676F1D57FD3800330C99 /* NetworkMonitor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NetworkMonitor.m; sourceTree = ""; }; + 93D167711D58093C00330C99 /* NetworkInfoCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NetworkInfoCell.h; sourceTree = ""; }; + 93D167721D58093C00330C99 /* NetworkInfoCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NetworkInfoCell.m; sourceTree = ""; }; + 93D167741D580C3500330C99 /* ShowNetworksViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShowNetworksViewController.h; sourceTree = ""; }; + 93D167751D580C3500330C99 /* ShowNetworksViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShowNetworksViewController.m; sourceTree = ""; }; + 93D167771D5815E600330C99 /* JoinNetworkViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JoinNetworkViewController.h; sourceTree = ""; }; + 93D167781D5815E600330C99 /* JoinNetworkViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JoinNetworkViewController.m; sourceTree = ""; }; + 93D1677A1D58228A00330C99 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 93D1677B1D58228A00330C99 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 93D1679A1D58300F00330C99 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 93D1679C1D595F0000330C99 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; + 93DAFB261D3F0BEE004D5417 /* about.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = about.html; sourceTree = ""; }; + 93DAFE4A1CFE53CA00547CC4 /* AuthtokenCopy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AuthtokenCopy.m; sourceTree = ""; }; + 93DAFE4C1CFE53DA00547CC4 /* AuthtokenCopy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AuthtokenCopy.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 93326BD51CE7C816005CA2AC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 93D1679D1D595F0000330C99 /* WebKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 93326BCF1CE7C816005CA2AC = { + isa = PBXGroup; + children = ( + 93D1679C1D595F0000330C99 /* WebKit.framework */, + 93326BDA1CE7C816005CA2AC /* ZeroTier One */, + 93326BD91CE7C816005CA2AC /* Products */, + ); + sourceTree = ""; + }; + 93326BD91CE7C816005CA2AC /* Products */ = { + isa = PBXGroup; + children = ( + 93326BD81CE7C816005CA2AC /* ZeroTier One.app */, + ); + name = Products; + sourceTree = ""; + }; + 93326BDA1CE7C816005CA2AC /* ZeroTier One */ = { + isa = PBXGroup; + children = ( + 932D472E1D1CD499004BCFE2 /* ZeroTierIcon.icns */, + 93326BDD1CE7C816005CA2AC /* Assets.xcassets */, + 93326BDF1CE7C816005CA2AC /* MainMenu.xib */, + 93326BE21CE7C816005CA2AC /* Info.plist */, + 93DAFE4A1CFE53CA00547CC4 /* AuthtokenCopy.m */, + 93DAFE4C1CFE53DA00547CC4 /* AuthtokenCopy.h */, + 93D1676E1D57FD3800330C99 /* NetworkMonitor.h */, + 93D1676F1D57FD3800330C99 /* NetworkMonitor.m */, + 93DAFB261D3F0BEE004D5417 /* about.html */, + 93D1675D1D54191C00330C99 /* NodeStatus.h */, + 93D1675E1D54191C00330C99 /* NodeStatus.m */, + 93D167601D541BC200330C99 /* ServiceCom.h */, + 93D167611D541BC200330C99 /* ServiceCom.m */, + 93D167641D54308200330C99 /* Network.h */, + 93D167651D54308200330C99 /* Network.m */, + 93D167671D57E7EA00330C99 /* AboutViewController.h */, + 93D167681D57E7EA00330C99 /* AboutViewController.m */, + 932D47351D1CDC9B004BCFE2 /* AboutViewController.xib */, + 93D1676B1D57EB8400330C99 /* PreferencesViewController.h */, + 93D1676C1D57EB8400330C99 /* PreferencesViewController.m */, + 932D47311D1CD861004BCFE2 /* PreferencesViewController.xib */, + 93D167711D58093C00330C99 /* NetworkInfoCell.h */, + 93D167721D58093C00330C99 /* NetworkInfoCell.m */, + 93D167741D580C3500330C99 /* ShowNetworksViewController.h */, + 93D167751D580C3500330C99 /* ShowNetworksViewController.m */, + 93326BED1CE7DA30005CA2AC /* ShowNetworksViewController.xib */, + 93D167771D5815E600330C99 /* JoinNetworkViewController.h */, + 93D167781D5815E600330C99 /* JoinNetworkViewController.m */, + 93326BE91CE7D9B9005CA2AC /* JoinNetworkViewController.xib */, + 93D1677A1D58228A00330C99 /* AppDelegate.h */, + 93D1677B1D58228A00330C99 /* AppDelegate.m */, + 93D1679A1D58300F00330C99 /* main.m */, + ); + path = "ZeroTier One"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 93326BD71CE7C816005CA2AC /* ZeroTier One */ = { + isa = PBXNativeTarget; + buildConfigurationList = 93326BE51CE7C816005CA2AC /* Build configuration list for PBXNativeTarget "ZeroTier One" */; + buildPhases = ( + 93326BD41CE7C816005CA2AC /* Sources */, + 93326BD51CE7C816005CA2AC /* Frameworks */, + 93326BD61CE7C816005CA2AC /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "ZeroTier One"; + productName = "ZeroTier One"; + productReference = 93326BD81CE7C816005CA2AC /* ZeroTier One.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 93326BD01CE7C816005CA2AC /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0730; + LastUpgradeCheck = 0800; + ORGANIZATIONNAME = "ZeroTier, Inc"; + TargetAttributes = { + 93326BD71CE7C816005CA2AC = { + CreatedOnToolsVersion = 7.3; + }; + }; + }; + buildConfigurationList = 93326BD31CE7C816005CA2AC /* Build configuration list for PBXProject "ZeroTier One" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 93326BCF1CE7C816005CA2AC; + productRefGroup = 93326BD91CE7C816005CA2AC /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 93326BD71CE7C816005CA2AC /* ZeroTier One */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 93326BD61CE7C816005CA2AC /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 93DAFB271D3F0BEE004D5417 /* about.html in Resources */, + 93326BEF1CE7DA30005CA2AC /* ShowNetworksViewController.xib in Resources */, + 932D47371D1CDC9B004BCFE2 /* AboutViewController.xib in Resources */, + 93326BEB1CE7D9B9005CA2AC /* JoinNetworkViewController.xib in Resources */, + 93326BDE1CE7C816005CA2AC /* Assets.xcassets in Resources */, + 93326BE11CE7C816005CA2AC /* MainMenu.xib in Resources */, + 932D472F1D1CD499004BCFE2 /* ZeroTierIcon.icns in Resources */, + 932D47331D1CD861004BCFE2 /* PreferencesViewController.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 93326BD41CE7C816005CA2AC /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 93D1679B1D58300F00330C99 /* main.m in Sources */, + 93D167621D541BC200330C99 /* ServiceCom.m in Sources */, + 93D167761D580C3500330C99 /* ShowNetworksViewController.m in Sources */, + 93DAFE4B1CFE53CA00547CC4 /* AuthtokenCopy.m in Sources */, + 93D167701D57FD3800330C99 /* NetworkMonitor.m in Sources */, + 93D1675F1D54191C00330C99 /* NodeStatus.m in Sources */, + 93D167691D57E7EA00330C99 /* AboutViewController.m in Sources */, + 93D1676D1D57EB8400330C99 /* PreferencesViewController.m in Sources */, + 93D1677C1D58228A00330C99 /* AppDelegate.m in Sources */, + 93D167731D58093C00330C99 /* NetworkInfoCell.m in Sources */, + 93D167661D54308200330C99 /* Network.m in Sources */, + 93D167791D5815E600330C99 /* JoinNetworkViewController.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 93326BDF1CE7C816005CA2AC /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 93326BE01CE7C816005CA2AC /* Base */, + ); + name = MainMenu.xib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 93326BE31CE7C816005CA2AC /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 93326BE41CE7C816005CA2AC /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + }; + name = Release; + }; + 93326BE61CE7C816005CA2AC /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = "ZeroTier One/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + PRODUCT_BUNDLE_IDENTIFIER = "com.zerotier.ZeroTier-One"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "ZeroTier One/ZeroTier One-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 93326BE71CE7C816005CA2AC /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = "ZeroTier One/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + PRODUCT_BUNDLE_IDENTIFIER = "com.zerotier.ZeroTier-One"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "ZeroTier One/ZeroTier One-Bridging-Header.h"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 93326BD31CE7C816005CA2AC /* Build configuration list for PBXProject "ZeroTier One" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 93326BE31CE7C816005CA2AC /* Debug */, + 93326BE41CE7C816005CA2AC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 93326BE51CE7C816005CA2AC /* Build configuration list for PBXNativeTarget "ZeroTier One" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 93326BE61CE7C816005CA2AC /* Debug */, + 93326BE71CE7C816005CA2AC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 93326BD01CE7C816005CA2AC /* Project object */; +} diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/macui/ZeroTier One.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 69% rename from ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to macui/ZeroTier One.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 88f36fc..fd60338 100644 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/macui/ZeroTier One.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:ZeroTier One.xcodeproj"> diff --git a/macui/ZeroTier One/AboutViewController.h b/macui/ZeroTier One/AboutViewController.h new file mode 100644 index 0000000..d3d5bc1 --- /dev/null +++ b/macui/ZeroTier One/AboutViewController.h @@ -0,0 +1,33 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import +#import + +@interface AboutViewController : NSViewController + +@property (nonatomic, weak) IBOutlet WebView *webView; + +- (void)viewDidLoad; + +- (void)webView:(WebView *)webView decidePolicyForNavigationAction:(NSDictionary *)actionInformation + request:(NSURLRequest *)request + frame:(WebFrame *)frame +decisionListener:(id)listener; + +@end diff --git a/macui/ZeroTier One/AboutViewController.m b/macui/ZeroTier One/AboutViewController.m new file mode 100644 index 0000000..7f92977 --- /dev/null +++ b/macui/ZeroTier One/AboutViewController.m @@ -0,0 +1,57 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import "AboutViewController.h" + +@interface AboutViewController () + +@end + +@implementation AboutViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + [self.webView setWantsLayer:YES]; + self.webView.layer.borderWidth = 1.0f; + [self.webView.layer setCornerRadius:1.0f]; + self.webView.layer.masksToBounds = YES; + [self.webView.layer setBorderColor:[[NSColor darkGrayColor] CGColor]]; + self.webView.policyDelegate = self; + + NSBundle *bundle = [NSBundle mainBundle]; + NSURL *path = [bundle URLForResource:@"about" withExtension:@"html"]; + if(path) { + [self.webView.mainFrame loadRequest:[NSURLRequest requestWithURL:path]]; + } +} + +- (void)webView:(WebView *)webView decidePolicyForNavigationAction:(NSDictionary *)actionInformation + request:(NSURLRequest *)request + frame:(WebFrame *)frame +decisionListener:(id)listener +{ + if(request.URL != nil && request.URL.host != nil) { + [[NSWorkspace sharedWorkspace] openURL:request.URL]; + } + else { + [listener use]; + } +} + +@end diff --git a/macui/ZeroTier One/AboutViewController.xib b/macui/ZeroTier One/AboutViewController.xib new file mode 100644 index 0000000..a0df0fc --- /dev/null +++ b/macui/ZeroTier One/AboutViewController.xib @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macui/ZeroTier One/AppDelegate.h b/macui/ZeroTier One/AppDelegate.h new file mode 100644 index 0000000..a00cfba --- /dev/null +++ b/macui/ZeroTier One/AppDelegate.h @@ -0,0 +1,61 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import + +@class NetworkMonitor; +@class Network; +@class NodeStatus; + +@interface AppDelegate : NSObject + +@property (weak, nonatomic) IBOutlet NSWindow *window; + +@property (nonatomic) NSStatusItem *statusItem; + +@property (nonatomic) NSPopover *networkListPopover; +@property (nonatomic) NSPopover *joinNetworkPopover; +@property (nonatomic) NSPopover *preferencesPopover; +@property (nonatomic) NSPopover *aboutPopover; + +@property (nonatomic) id transientMonitor; + +@property (nonatomic) NetworkMonitor *monitor; + +@property (nonatomic) NSMutableArray *networks; + +@property (nonatomic) NodeStatus *status; + +- (void)buildMenu; + +- (void)onNetworkListUpdated:(NSNotification*)note; +- (void)onNodeStatusUpdated:(NSNotification*)note; + +- (void)showNetworks; +- (void)joinNetwork; +- (void)showPreferences; +- (void)showAbout; +- (void)quit; +- (void)toggleNetwork:(NSMenuItem*)sender; +- (void)copyNodeID; + +- (void)closeJoinNetworkPopover; + +- (void)darkModeChanged:(NSNotification*)note; + +@end diff --git a/macui/ZeroTier One/AppDelegate.m b/macui/ZeroTier One/AppDelegate.m new file mode 100644 index 0000000..ae3e042 --- /dev/null +++ b/macui/ZeroTier One/AppDelegate.m @@ -0,0 +1,378 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import "AppDelegate.h" +#import "NetworkMonitor.h" +#import "Network.h" +#import "NodeStatus.h" +#import "JoinNetworkViewController.h" +#import "ShowNetworksViewController.h" +#import "PreferencesViewController.h" +#import "AboutViewController.h" +#import "ServiceCom.h" + +@implementation AppDelegate + +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { + self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:-2.0f]; + self.networkListPopover = [[NSPopover alloc] init]; + self.joinNetworkPopover = [[NSPopover alloc] init]; + self.preferencesPopover = [[NSPopover alloc] init]; + self.aboutPopover = [[NSPopover alloc] init]; + self.transientMonitor = nil; + self.monitor = [[NetworkMonitor alloc] init]; + self.networks = [NSMutableArray array]; + self.status = nil; + + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSDictionary *defaultsDict = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:@"firstRun"]; + [defaults registerDefaults:defaultsDict]; + + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + + [nc addObserver:self + selector:@selector(onNetworkListUpdated:) + name:NetworkUpdateKey + object:nil]; + [nc addObserver:self + selector:@selector(onNodeStatusUpdated:) + name:StatusUpdateKey + object:nil]; + + NSString *osxMode = [defaults stringForKey:@"AppleInterfaceStyle"]; + + if(osxMode != nil && [osxMode isEqualToString:@"Dark"]) { + self.statusItem.image = [NSImage imageNamed:@"MenuBarIconMacWhite"]; + } + else { + self.statusItem.image = [NSImage imageNamed:@"MenuBarIconMac"]; + } + + [[NSDistributedNotificationCenter defaultCenter] addObserver:self + selector:@selector(darkModeChanged:) + name:@"AppleInterfaceThemeChangedNotification" + object:nil]; + + [self buildMenu]; + JoinNetworkViewController *jnvc = [[JoinNetworkViewController alloc] initWithNibName:@"JoinNetworkViewController" bundle:nil]; + jnvc.appDelegate = self; + self.joinNetworkPopover.contentViewController = jnvc; + self.joinNetworkPopover.behavior = NSPopoverBehaviorTransient; + + ShowNetworksViewController *showNetworksView = [[ShowNetworksViewController alloc] initWithNibName:@"ShowNetworksViewController" bundle:nil]; + showNetworksView.netMonitor = self.monitor; + self.networkListPopover.contentViewController = showNetworksView; + self.networkListPopover.behavior = NSPopoverBehaviorTransient; + + PreferencesViewController *prefsView = [[PreferencesViewController alloc] initWithNibName:@"PreferencesViewController" bundle:nil]; + self.preferencesPopover.contentViewController = prefsView; + self.preferencesPopover.behavior = NSPopoverBehaviorTransient; + + self.aboutPopover.contentViewController = [[AboutViewController alloc] initWithNibName:@"AboutViewController" bundle:nil]; + self.aboutPopover.behavior = NSPopoverBehaviorTransient; + + BOOL firstRun = [defaults boolForKey:@"firstRun"]; + + if(firstRun) { + [defaults setBool:NO forKey:@"firstRun"]; + [defaults synchronize]; + + [prefsView setLaunchAtLoginEnabled:YES]; + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + sleep(2); + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + [self showAbout]; + }]; + }); + } + + [self.monitor updateNetworkInfo]; + [self.monitor start]; +} + +- (void)applicationWillTerminate:(NSNotification *)aNotification { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [[NSDistributedNotificationCenter defaultCenter] removeObserver:self + name:@"AppleInterfaceThemeChangedNotification" + object:nil]; +} + +- (void)showNetworks { + NSButton *button = nil; + NSRect frame; + if ([self.statusItem respondsToSelector:@selector(button)]) { + button = self.statusItem.button; + frame = button.bounds; + } else if ([self.statusItem respondsToSelector:@selector(_button)]) { + button = [self.statusItem performSelector:@selector(_button)]; + frame = button.bounds; + } else { + NSLog(@"Can't get view. Uh oh."); + return; + } + + [self.networkListPopover showRelativeToRect:frame + ofView:button + preferredEdge:NSMinYEdge]; + + if(self.transientMonitor == nil) { + self.transientMonitor = + [NSEvent addGlobalMonitorForEventsMatchingMask:(NSLeftMouseDown|NSRightMouseDown|NSOtherMouseDown) + handler:^(NSEvent * _Nonnull e) { + [NSEvent removeMonitor:self.transientMonitor]; + self.transientMonitor = nil; + [self.networkListPopover close]; + }]; + } +} + +- (void)joinNetwork { + NSButton *button = nil; + NSRect frame; + if ([self.statusItem respondsToSelector:@selector(button)]) { + button = self.statusItem.button; + frame = button.bounds; + } else if ([self.statusItem respondsToSelector:@selector(_button)]) { + button = [self.statusItem performSelector:@selector(_button)]; + frame = button.bounds; + } else { + NSLog(@"Can't get view. Uh oh."); + return; + } + + [self.joinNetworkPopover showRelativeToRect:button.bounds + ofView:button + preferredEdge:NSMinYEdge]; + if(self.transientMonitor == nil) { + self.transientMonitor = + [NSEvent addGlobalMonitorForEventsMatchingMask:(NSLeftMouseDown|NSRightMouseDown|NSOtherMouseDown) + handler:^(NSEvent * _Nonnull e) { + [NSEvent removeMonitor:self.transientMonitor]; + self.transientMonitor = nil; + [self.joinNetworkPopover close]; + }]; + } +} + +- (void)showPreferences { + NSButton *button = nil; + NSRect frame; + if ([self.statusItem respondsToSelector:@selector(button)]) { + button = self.statusItem.button; + frame = button.bounds; + } else if ([self.statusItem respondsToSelector:@selector(_button)]) { + button = [self.statusItem performSelector:@selector(_button)]; + frame = button.bounds; + } else { + NSLog(@"Can't get view. Uh oh."); + return; + } + + [self.preferencesPopover showRelativeToRect:button.bounds + ofView:button + preferredEdge:NSMinYEdge]; + if(self.transientMonitor == nil) { + [NSEvent addGlobalMonitorForEventsMatchingMask:(NSLeftMouseDown|NSRightMouseDown|NSOtherMouseDown) + handler:^(NSEvent * _Nonnull e) { + [NSEvent removeMonitor:self.transientMonitor]; + self.transientMonitor = nil; + [self.preferencesPopover close]; + }]; + } +} + +- (void)showAbout { + NSButton *button = nil; + NSRect frame; + if ([self.statusItem respondsToSelector:@selector(button)]) { + button = self.statusItem.button; + frame = button.bounds; + } else if ([self.statusItem respondsToSelector:@selector(_button)]) { + button = [self.statusItem performSelector:@selector(_button)]; + frame = button.bounds; + } else { + NSLog(@"Can't get view. Uh oh."); + return; + } + + [self.aboutPopover showRelativeToRect:button.bounds + ofView:button + preferredEdge:NSMinYEdge]; + if(self.transientMonitor == nil) { + [NSEvent addGlobalMonitorForEventsMatchingMask:(NSLeftMouseDown|NSRightMouseDown|NSOtherMouseDown) + handler:^(NSEvent * _Nonnull e) { + [NSEvent removeMonitor:self.transientMonitor]; + self.transientMonitor = nil; + [self.aboutPopover close]; + }]; + } +} + +- (void)quit { + [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; +} + +- (void)onNetworkListUpdated:(NSNotification*)note { + NSArray *netList = [note.userInfo objectForKey:@"networks"]; + [(ShowNetworksViewController*)self.networkListPopover.contentViewController setNetworks:netList]; + self.networks = [netList mutableCopy]; + + [self buildMenu]; +} + +- (void)onNodeStatusUpdated:(NSNotification*)note { + NodeStatus *status = [note.userInfo objectForKey:@"status"]; + self.status = status; + + [self buildMenu]; +} + +- (void)buildMenu { + NSMenu *menu = [[NSMenu alloc] init]; + + if(self.status != nil) { + NSString *nodeId = @"Node ID: "; + nodeId = [nodeId stringByAppendingString:self.status.address]; + [menu addItem:[[NSMenuItem alloc] initWithTitle:nodeId + action:@selector(copyNodeID) + keyEquivalent:@""]]; + [menu addItem:[NSMenuItem separatorItem]]; + } + + [menu addItem:[[NSMenuItem alloc] initWithTitle:@"Network Details..." + action:@selector(showNetworks) + keyEquivalent:@"n"]]; + [menu addItem:[[NSMenuItem alloc] initWithTitle:@"Join Network..." + action:@selector(joinNetwork) + keyEquivalent:@"j"]]; + + [menu addItem:[NSMenuItem separatorItem]]; + + if([self.networks count] > 0) { + for(Network *net in self.networks) { + NSString *nwid = [NSString stringWithFormat:@"%10llx", net.nwid]; + NSString *networkName = @""; + if([net.name lengthOfBytesUsingEncoding:NSUTF8StringEncoding] == 0) { + networkName = nwid; + } + else { + networkName = [NSString stringWithFormat:@"%@ (%@)", nwid, net.name]; + } + + if(net.allowDefault && net.connected) { + networkName = [networkName stringByAppendingString:@" [default]"]; + } + + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:networkName + action:@selector(toggleNetwork:) + keyEquivalent:@""]; + if(net.connected) { + item.state = NSOnState; + } + else { + item.state = NSOffState; + } + + item.representedObject = net; + + [menu addItem:item]; + } + + [menu addItem:[NSMenuItem separatorItem]]; + } + + [menu addItem:[[NSMenuItem alloc] initWithTitle:@"About ZeroTier One..." + action:@selector(showAbout) + keyEquivalent:@""]]; + [menu addItem:[[NSMenuItem alloc] initWithTitle:@"Preferences..." + action:@selector(showPreferences) + keyEquivalent:@""]]; + + [menu addItem:[NSMenuItem separatorItem]]; + + [menu addItem:[[NSMenuItem alloc] initWithTitle:@"Quit" + action:@selector(quit) + keyEquivalent:@"q"]]; + + self.statusItem.menu = menu; +} + +- (void)toggleNetwork:(NSMenuItem*)sender { + Network *network = sender.representedObject; + NSString *nwid = [NSString stringWithFormat:@"%10llx", network.nwid]; + + if(network.connected) { + NSError *error = nil; + + [[ServiceCom sharedInstance] leaveNetwork:nwid error:&error]; + + if (error) { + NSAlert *alert = [NSAlert alertWithError:error]; + alert.alertStyle = NSCriticalAlertStyle; + [alert addButtonWithTitle:@"Ok"]; + + [alert runModal]; + } + } + else { + NSError *error = nil; + [[ServiceCom sharedInstance] joinNetwork:nwid + allowManaged:network.allowManaged + allowGlobal:network.allowGlobal + allowDefault:(network.allowDefault && ![Network defaultRouteExists:self.networks]) + error:&error]; + + if (error) { + NSAlert *alert = [NSAlert alertWithError:error]; + alert.alertStyle = NSCriticalAlertStyle; + [alert addButtonWithTitle:@"Ok"]; + + [alert runModal]; + } + } +} + +- (void)copyNodeID { + if(self.status != nil) { + NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; + [pasteboard declareTypes:[NSArray arrayWithObject:NSPasteboardTypeString] owner:nil]; + [pasteboard setString:self.status.address forType:NSPasteboardTypeString]; + } +} + +- (void)darkModeChanged:(NSNotification*)note { + NSString *osxMode = [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"]; + + if(osxMode != nil && [osxMode isEqualToString:@"Dark"]) { + self.statusItem.image = [NSImage imageNamed:@"MenuBarIconMacWhite"]; + } + else { + self.statusItem.image = [NSImage imageNamed:@"MenuBarIconMac"]; + } +} + +- (void)closeJoinNetworkPopover { + if (self.transientMonitor) { + [NSEvent removeMonitor:self.transientMonitor]; + self.transientMonitor = nil; + } + [self.joinNetworkPopover close]; +} + +@end diff --git a/macui/ZeroTier One/Assets.xcassets/AppIcon.appiconset/Contents.json b/macui/ZeroTier One/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..24c81d3 --- /dev/null +++ b/macui/ZeroTier One/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,59 @@ +{ + "images" : [ + { + "idiom" : "mac", + "size" : "16x16", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "16x16", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "32x32", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "32x32", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "128x128", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "128x128", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "256x256", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "256x256", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "ZeroTierIcon512x512.png", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "512x512", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/macui/ZeroTier One/Assets.xcassets/AppIcon.appiconset/ZeroTierIcon512x512.png b/macui/ZeroTier One/Assets.xcassets/AppIcon.appiconset/ZeroTierIcon512x512.png new file mode 100644 index 0000000..d225c2e Binary files /dev/null and b/macui/ZeroTier One/Assets.xcassets/AppIcon.appiconset/ZeroTierIcon512x512.png differ diff --git a/macui/ZeroTier One/Assets.xcassets/Contents.json b/macui/ZeroTier One/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/macui/ZeroTier One/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/macui/ZeroTier One/Assets.xcassets/MenuBarIconMac.imageset/Contents.json b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMac.imageset/Contents.json new file mode 100644 index 0000000..a680b58 --- /dev/null +++ b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMac.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "mac", + "filename" : "Menubar.png", + "scale" : "1x" + }, + { + "idiom" : "mac", + "filename" : "MenuBar@2x.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" + } +} \ No newline at end of file diff --git a/macui/ZeroTier One/Assets.xcassets/MenuBarIconMac.imageset/MenuBar@2x.png b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMac.imageset/MenuBar@2x.png new file mode 100644 index 0000000..9fd3d3d Binary files /dev/null and b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMac.imageset/MenuBar@2x.png differ diff --git a/macui/ZeroTier One/Assets.xcassets/MenuBarIconMac.imageset/Menubar.png b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMac.imageset/Menubar.png new file mode 100644 index 0000000..ee0d7e3 Binary files /dev/null and b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMac.imageset/Menubar.png differ diff --git a/macui/ZeroTier One/Assets.xcassets/MenuBarIconMacWhite.imageset/Contents.json b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMacWhite.imageset/Contents.json new file mode 100644 index 0000000..6173776 --- /dev/null +++ b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMacWhite.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "mac", + "filename" : "MenubarWhite.png", + "scale" : "1x" + }, + { + "idiom" : "mac", + "filename" : "MenubarWhite@2x.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" + } +} \ No newline at end of file diff --git a/macui/ZeroTier One/Assets.xcassets/MenuBarIconMacWhite.imageset/MenubarWhite.png b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMacWhite.imageset/MenubarWhite.png new file mode 100644 index 0000000..7049ae5 Binary files /dev/null and b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMacWhite.imageset/MenubarWhite.png differ diff --git a/macui/ZeroTier One/Assets.xcassets/MenuBarIconMacWhite.imageset/MenubarWhite@2x.png b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMacWhite.imageset/MenubarWhite@2x.png new file mode 100644 index 0000000..8c20e36 Binary files /dev/null and b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMacWhite.imageset/MenubarWhite@2x.png differ diff --git a/macui/ZeroTier One/AuthtokenCopy.h b/macui/ZeroTier One/AuthtokenCopy.h new file mode 100644 index 0000000..f0497cc --- /dev/null +++ b/macui/ZeroTier One/AuthtokenCopy.h @@ -0,0 +1,26 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef AuthtokenCopy_h +#define AuthtokenCopy_h + +#import + +NSString* getAdminAuthToken(AuthorizationRef authRef); + +#endif /* AuthtokenCopy_h */ diff --git a/macui/ZeroTier One/AuthtokenCopy.m b/macui/ZeroTier One/AuthtokenCopy.m new file mode 100644 index 0000000..a10350f --- /dev/null +++ b/macui/ZeroTier One/AuthtokenCopy.m @@ -0,0 +1,97 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import + +#import "AuthtokenCopy.h" + + +NSString* getAdminAuthToken(AuthorizationRef authRef) { + char *tool = "/bin/cat"; + char *args[] = { "/Library/Application Support/ZeroTier/One/authtoken.secret", NULL}; + FILE *pipe = nil; + char token[25]; + memset(token, 0, sizeof(char)*25); + + + OSStatus status = AuthorizationExecuteWithPrivileges(authRef, tool, kAuthorizationFlagDefaults, args, &pipe); + + if (status != errAuthorizationSuccess) { + NSLog(@"Reading authtoken failed!"); + + + switch(status) { + case errAuthorizationDenied: + NSLog(@"Autorization Denied"); + break; + case errAuthorizationCanceled: + NSLog(@"Authorization Canceled"); + break; + case errAuthorizationInternal: + NSLog(@"Authorization Internal"); + break; + case errAuthorizationBadAddress: + NSLog(@"Bad Address"); + break; + case errAuthorizationInvalidRef: + NSLog(@"Invalid Ref"); + break; + case errAuthorizationInvalidSet: + NSLog(@"Invalid Set"); + break; + case errAuthorizationInvalidTag: + NSLog(@"Invalid Tag"); + break; + case errAuthorizationInvalidFlags: + NSLog(@"Invalid Flags"); + break; + case errAuthorizationInvalidPointer: + NSLog(@"Invalid Pointer"); + break; + case errAuthorizationToolExecuteFailure: + NSLog(@"Tool Execute Failure"); + break; + case errAuthorizationToolEnvironmentError: + NSLog(@"Tool Environment Failure"); + break; + case errAuthorizationExternalizeNotAllowed: + NSLog(@"Externalize Not Allowed"); + break; + case errAuthorizationInteractionNotAllowed: + NSLog(@"Interaction Not Allowed"); + break; + case errAuthorizationInternalizeNotAllowed: + NSLog(@"Internalize Not Allowed"); + break; + default: + NSLog(@"Unknown Error"); + break; + } + + return @""; + } + + if(pipe != nil) { + fread(&token, sizeof(char), 24, pipe); + fclose(pipe); + + return [NSString stringWithUTF8String:token]; + } + + return @""; +} diff --git a/macui/ZeroTier One/Base.lproj/MainMenu.xib b/macui/ZeroTier One/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..6b6da2a --- /dev/null +++ b/macui/ZeroTier One/Base.lproj/MainMenu.xib @@ -0,0 +1,680 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/MacGap-Info.plist b/macui/ZeroTier One/Info.plist similarity index 74% rename from ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/MacGap-Info.plist rename to macui/ZeroTier One/Info.plist index 7f71ea2..e04b5f2 100644 --- a/ext/installfiles/mac/mac-ui-macgap1-wrapper/src/MacGap/MacGap-Info.plist +++ b/macui/ZeroTier One/Info.plist @@ -5,15 +5,15 @@ CFBundleDevelopmentRegion en CFBundleExecutable - ZeroTier One + $(EXECUTABLE_NAME) CFBundleIconFile - ZeroTierIcon + ZeroTierIcon.icns CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName - ZeroTier One + $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString @@ -21,19 +21,21 @@ CFBundleSignature ???? CFBundleVersion - 1 - LSApplicationCategoryType - public.app-category.utilities + 15 LSMinimumSystemVersion - ${MACOSX_DEPLOYMENT_TARGET} - NSMainNibFile - MainMenu - NSPrincipalClass - NSApplication + $(MACOSX_DEPLOYMENT_TARGET) + LSUIElement + NSAppTransportSecurity NSAllowsArbitraryLoads + NSHumanReadableCopyright + Copyright © 2016 ZeroTier, Inc. All rights reserved. + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication diff --git a/macui/ZeroTier One/JoinNetworkViewController.h b/macui/ZeroTier One/JoinNetworkViewController.h new file mode 100644 index 0000000..428959f --- /dev/null +++ b/macui/ZeroTier One/JoinNetworkViewController.h @@ -0,0 +1,40 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import + + +extern NSString * const JoinedNetworksKey; + +@class AppDelegate; + +@interface JoinNetworkViewController : NSViewController + +@property (nonatomic, weak) IBOutlet NSComboBox *network; +@property (nonatomic, weak) IBOutlet NSButton *joinButton; +@property (nonatomic, weak) IBOutlet NSButton *allowManagedCheckBox; +@property (nonatomic, weak) IBOutlet NSButton *allowGlobalCheckBox; +@property (nonatomic, weak) IBOutlet NSButton *allowDefaultCheckBox; +@property (nonatomic, weak) IBOutlet AppDelegate *appDelegate; + +@property (nonatomic) NSMutableArray *values; + +- (IBAction)onJoinClicked:(id)sender; + + +@end diff --git a/macui/ZeroTier One/JoinNetworkViewController.m b/macui/ZeroTier One/JoinNetworkViewController.m new file mode 100644 index 0000000..cae2654 --- /dev/null +++ b/macui/ZeroTier One/JoinNetworkViewController.m @@ -0,0 +1,184 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import "JoinNetworkViewController.h" +#import "ServiceCom.h" +#import "AppDelegate.h" + + +NSString * const JoinedNetworksKey = @"com.zerotier.one.joined-networks"; + +@interface NSString (extra) + +- (BOOL)contains:(NSString*)find; + +@end + +@implementation NSString (extra) + +- (BOOL)contains:(NSString*)find { + NSRange range = [self rangeOfString:find]; + return range.location != NSNotFound; +} + +@end + + +@implementation JoinNetworkViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do view setup here. + [self.network setDelegate:self]; + [self.network setDataSource:self]; +} + +- (void)viewWillAppear { + [super viewWillAppear]; + + self.allowManagedCheckBox.state = NSOnState; + self.allowGlobalCheckBox.state = NSOffState; + self.allowDefaultCheckBox.state = NSOffState; + + self.network.stringValue = @""; + + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + + NSMutableArray *vals = [[defaults stringArrayForKey:JoinedNetworksKey] mutableCopy]; + + if(vals) { + self.values = vals; + } +} + +- (void)viewWillDisappear { + [super viewWillDisappear]; + + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + + [defaults setObject:self.values forKey:JoinedNetworksKey]; +} + +- (IBAction)onJoinClicked:(id)sender { + NSString *networkId = self.network.stringValue; + + NSError *error = nil; + [[ServiceCom sharedInstance] joinNetwork:networkId + allowManaged:(self.allowManagedCheckBox.state == NSOnState) + allowGlobal:(self.allowGlobalCheckBox.state == NSOnState) + allowDefault:(self.allowDefaultCheckBox.state == NSOnState) + error:&error]; + + if(error) { + NSAlert *alert = [NSAlert alertWithError:error]; + alert.alertStyle = NSCriticalAlertStyle; + [alert addButtonWithTitle:@"Ok"]; + + [alert runModal]; + return; + } + + self.network.stringValue = @""; + + if(![self.values containsObject:networkId]) { + [self.values insertObject:networkId atIndex:0]; + + while([self.values count] > 20) { + [self.values removeLastObject]; + } + } + + [self.appDelegate closeJoinNetworkPopover]; +} + +// NSComboBoxDelegate methods + +- (void)controlTextDidChange:(NSNotification *)obj { + NSComboBox *cb = (NSComboBox*)obj.object; + NSString *value = cb.stringValue; + + NSString *allowedCharacters = @"abcdefABCDEF0123456789"; + + NSString *outValue = @""; + + for(int i = 0; i < [value length]; ++i) { + if(![allowedCharacters contains:[NSString stringWithFormat:@"%C", [value characterAtIndex:i]]]) { + NSBeep(); + } + else { + outValue = [outValue stringByAppendingString:[NSString stringWithFormat:@"%C", [value characterAtIndex:i]]]; + } + } + + if([outValue lengthOfBytesUsingEncoding:NSUTF8StringEncoding] == 16) { + self.joinButton.enabled = YES; + } + else { + if([outValue lengthOfBytesUsingEncoding:NSUTF8StringEncoding] > 16) { + NSRange range = {0, 16}; + range = [outValue rangeOfComposedCharacterSequencesForRange:range]; + outValue = [outValue substringWithRange:range]; + NSBeep(); + self.joinButton.enabled = YES; + } + else { + self.joinButton.enabled = NO; + } + } + + cb.stringValue = outValue; +} + +// end NSComboBoxDelegate methods + +// NSComboBoxDataSource methods + +- (NSInteger)numberOfItemsInComboBox:(NSComboBox *)aComboBox { + return [self.values count]; +} + +- (id)comboBox:(NSComboBox *)aComboBox objectValueForItemAtIndex:(NSInteger)index { + return [self.values objectAtIndex:index]; +} + +- (NSUInteger)comboBox:(NSComboBox *)aComboBox indexOfItemWithStringValue:(NSString *)string { + NSUInteger counter = 0; + + for(NSString *val in self.values) { + if([val isEqualToString:string]) { + return counter; + } + + counter += 1; + } + + return NSNotFound; +} + +- (NSString*)comboBox:(NSComboBox *)aComboBox completedString:(NSString *)string { + for(NSString *val in self.values) { + if([val hasPrefix:string]) { + return val; + } + } + return nil; +} + +// end NSComboBoxDataSource methods + +@end diff --git a/macui/ZeroTier One/JoinNetworkViewController.xib b/macui/ZeroTier One/JoinNetworkViewController.xib new file mode 100644 index 0000000..5916109 --- /dev/null +++ b/macui/ZeroTier One/JoinNetworkViewController.xib @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macui/ZeroTier One/Network.h b/macui/ZeroTier One/Network.h new file mode 100644 index 0000000..957ff8d --- /dev/null +++ b/macui/ZeroTier One/Network.h @@ -0,0 +1,68 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import + +enum NetworkStatus { + REQUESTING_CONFIGURATION, + OK, + ACCESS_DENIED, + NOT_FOUND, + PORT_ERROR, + CLIENT_TOO_OLD, +}; + +enum NetworkType { + PUBLIC, + PRIVATE, +}; + +@interface Network : NSObject + +@property (readonly) NSArray *assignedAddresses; +@property (readonly) BOOL bridge; +@property (readonly) BOOL broadcastEnabled; +@property (readonly) BOOL dhcp; +@property (readonly) NSString *mac; +@property (readonly) int mtu; +@property (readonly) int netconfRevision; +@property (readonly) NSString *name; +@property (readonly) UInt64 nwid; +@property (readonly) NSString *portDeviceName; +@property (readonly) int portError; +@property (readonly) enum NetworkStatus status; +@property (readonly) enum NetworkType type; +@property (readonly) BOOL allowManaged; +@property (readonly) BOOL allowGlobal; +@property (readonly) BOOL allowDefault; +@property (readonly) BOOL connected; // not persisted. set to YES if loaded via json + +- (id)initWithJsonData:(NSDictionary*)jsonData; +- (id)initWithCoder:(NSCoder *)aDecoder; +- (void)encodeWithCoder:(NSCoder *)aCoder; ++ (BOOL)defaultRouteExists:(NSArray*)netList; +- (NSString*)statusString; +- (NSString*)typeString; + +- (BOOL)hasSameNetworkId:(UInt64)networkId; + +- (BOOL)isEqualToNetwork:(Network*)network; +- (BOOL)isEqual:(id)object; +- (NSUInteger)hash; + +@end diff --git a/macui/ZeroTier One/Network.m b/macui/ZeroTier One/Network.m new file mode 100644 index 0000000..8474aca --- /dev/null +++ b/macui/ZeroTier One/Network.m @@ -0,0 +1,337 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import "Network.h" + +NSString *NetworkAddressesKey = @"addresses"; +NSString *NetworkBridgeKey = @"bridge"; +NSString *NetworkBroadcastKey = @"broadcast"; +NSString *NetworkDhcpKey = @"dhcp"; +NSString *NetworkMacKey = @"mac"; +NSString *NetworkMtuKey = @"mtu"; +NSString *NetworkMulticastKey = @"multicast"; +NSString *NetworkNameKey = @"name"; +NSString *NetworkNetconfKey = @"netconf"; +NSString *NetworkNwidKey = @"nwid"; +NSString *NetworkPortNameKey = @"port"; +NSString *NetworkPortErrorKey = @"portError"; +NSString *NetworkStatusKey = @"status"; +NSString *NetworkTypeKey = @"type"; +NSString *NetworkAllowManagedKey = @"allowManaged"; +NSString *NetworkAllowGlobalKey = @"allowGlobal"; +NSString *NetworkAllowDefaultKey = @"allowDefault"; + +@implementation Network + +- (id)initWithJsonData:(NSDictionary*)jsonData +{ + self = [super init]; + + if(self) { + if([jsonData objectForKey:@"assignedAddresses"]) { + _assignedAddresses = (NSArray*)[jsonData objectForKey:@"assignedAddresses"]; + } + + if([jsonData objectForKey:@"bridge"]) { + _bridge = [(NSNumber*)[jsonData objectForKey:@"bridge"] boolValue]; + } + + if([jsonData objectForKey:@"broadcastEnabled"]) { + _broadcastEnabled = [(NSNumber*)[jsonData objectForKey:@"broadcastEnabled"] boolValue]; + } + + if([jsonData objectForKey:@"dhcp"]) { + _dhcp = [(NSNumber*)[jsonData objectForKey:@"dhcp"] boolValue]; + } + + if([jsonData objectForKey:@"mac"]) { + _mac = (NSString*)[jsonData objectForKey:@"mac"]; + } + + if([jsonData objectForKey:@"mtu"]) { + _mtu = [(NSNumber*)[jsonData objectForKey:@"mtu"] intValue]; + } + + if([jsonData objectForKey:@"name"]) { + _name = (NSString*)[jsonData objectForKey:@"name"]; + } + + if([jsonData objectForKey:@"netconfRevision"]) { + _netconfRevision = [(NSNumber*)[jsonData objectForKey:@"netconfRevision"] intValue]; + } + + if([jsonData objectForKey:@"nwid"]) { + NSString *networkid = (NSString*)[jsonData objectForKey:@"nwid"]; + + NSScanner *scanner = [NSScanner scannerWithString:networkid]; + [scanner scanHexLongLong:&_nwid]; + } + + if([jsonData objectForKey:@"portDeviceName"]) { + _portDeviceName = (NSString*)[jsonData objectForKey:@"portDeviceName"]; + } + + if([jsonData objectForKey:@"portError"]) { + _portError = [(NSNumber*)[jsonData objectForKey:@"portError"] intValue]; + } + + if([jsonData objectForKey:@"allowManaged"]) { + _allowManaged = [(NSNumber*)[jsonData objectForKey:@"allowManaged"] boolValue]; + } + + if([jsonData objectForKey:@"allowGlobal"]) { + _allowGlobal = [(NSNumber*)[jsonData objectForKey:@"allowGlobal"] boolValue]; + } + + if([jsonData objectForKey:@"allowDefault"]) { + _allowDefault = [(NSNumber*)[jsonData objectForKey:@"allowDefault"] boolValue]; + } + + if([jsonData objectForKey:@"status"]) { + NSString *statusStr = (NSString*)[jsonData objectForKey:@"status"]; + if([statusStr isEqualToString:@"REQUESTING_CONFIGURATION"]) { + _status = REQUESTING_CONFIGURATION; + } + else if([statusStr isEqualToString:@"OK"]) { + _status = OK; + } + else if([statusStr isEqualToString:@"ACCESS_DENIED"]) { + _status = ACCESS_DENIED; + } + else if([statusStr isEqualToString:@"NOT_FOUND"]) { + _status = NOT_FOUND; + } + else if([statusStr isEqualToString:@"PORT_ERROR"]) { + _status = PORT_ERROR; + } + else if([statusStr isEqualToString:@"CLIENT_TOO_OLD"]) { + _status = CLIENT_TOO_OLD; + } + } + + if([jsonData objectForKey:@"type"]) { + NSString *typeStr = (NSString*)[jsonData objectForKey:@"type"]; + if([typeStr isEqualToString:@"PRIVATE"]) { + _type = PRIVATE; + } + else if([typeStr isEqualToString:@"PUBLIC"]) { + _type = PUBLIC; + } + } + + _connected = YES; + } + + return self; +} +- (id)initWithCoder:(NSCoder *)aDecoder +{ + self = [super init]; + + if(self) { + if([aDecoder containsValueForKey:NetworkAddressesKey]) { + _assignedAddresses = (NSArray*)[aDecoder decodeObjectForKey:NetworkAddressesKey]; + } + + if([aDecoder containsValueForKey:NetworkBridgeKey]) { + _bridge = [aDecoder decodeBoolForKey:NetworkBridgeKey]; + } + + if([aDecoder containsValueForKey:NetworkBroadcastKey]) { + _broadcastEnabled = [aDecoder decodeBoolForKey:NetworkBroadcastKey]; + } + + if([aDecoder containsValueForKey:NetworkDhcpKey]) { + _dhcp = [aDecoder decodeBoolForKey:NetworkDhcpKey]; + } + + if([aDecoder containsValueForKey:NetworkMacKey]) { + _mac = (NSString*)[aDecoder decodeObjectForKey:NetworkMacKey]; + } + + if([aDecoder containsValueForKey:NetworkMtuKey]) { + _mtu = (int)[aDecoder decodeIntegerForKey:NetworkMtuKey]; + } + + if([aDecoder containsValueForKey:NetworkNameKey]) { + _name = (NSString*)[aDecoder decodeObjectForKey:NetworkNameKey]; + } + + if([aDecoder containsValueForKey:NetworkNetconfKey]) { + _netconfRevision = (int)[aDecoder decodeIntegerForKey:NetworkNetconfKey]; + } + + if([aDecoder containsValueForKey:NetworkNwidKey]) { + _nwid = [(NSNumber*)[aDecoder decodeObjectForKey:NetworkNwidKey] unsignedLongLongValue]; + } + + if([aDecoder containsValueForKey:NetworkPortNameKey]) { + _portDeviceName = (NSString*)[aDecoder decodeObjectForKey:NetworkPortNameKey]; + } + + if([aDecoder containsValueForKey:NetworkPortErrorKey]) { + _portError = (int)[aDecoder decodeIntegerForKey:NetworkPortErrorKey]; + } + + if([aDecoder containsValueForKey:NetworkStatusKey]) { + _status = (enum NetworkStatus)[aDecoder decodeIntegerForKey:NetworkStatusKey]; + } + + if([aDecoder containsValueForKey:NetworkTypeKey]) { + _type = (enum NetworkType)[aDecoder decodeIntegerForKey:NetworkTypeKey]; + } + + if([aDecoder containsValueForKey:NetworkAllowManagedKey]) { + _allowManaged = [aDecoder decodeBoolForKey:NetworkAllowManagedKey]; + } + + if([aDecoder containsValueForKey:NetworkAllowGlobalKey]) { + _allowGlobal = [aDecoder decodeBoolForKey:NetworkAllowGlobalKey]; + } + + if([aDecoder containsValueForKey:NetworkAllowDefaultKey]) { + _allowDefault = [aDecoder decodeBoolForKey:NetworkAllowDefaultKey]; + } + + _connected = NO; + } + + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder +{ + [aCoder encodeObject:_assignedAddresses forKey:NetworkAddressesKey]; + [aCoder encodeBool:_bridge forKey:NetworkBridgeKey]; + [aCoder encodeBool:_broadcastEnabled forKey:NetworkBroadcastKey]; + [aCoder encodeBool:_dhcp forKey:NetworkDhcpKey]; + [aCoder encodeObject:_mac forKey:NetworkMacKey]; + [aCoder encodeInteger:_mtu forKey:NetworkMtuKey]; + [aCoder encodeObject:_name forKey:NetworkNameKey]; + [aCoder encodeInteger:_netconfRevision forKey:NetworkNetconfKey]; + [aCoder encodeObject:[NSNumber numberWithUnsignedLongLong:_nwid] + forKey:NetworkNwidKey]; + [aCoder encodeObject:_portDeviceName forKey:NetworkPortNameKey]; + [aCoder encodeInteger:_portError forKey:NetworkPortErrorKey]; + [aCoder encodeInteger:_status forKey:NetworkStatusKey]; + [aCoder encodeInteger:_type forKey:NetworkTypeKey]; + [aCoder encodeBool:_allowManaged forKey:NetworkAllowManagedKey]; + [aCoder encodeBool:_allowGlobal forKey:NetworkAllowGlobalKey]; + [aCoder encodeBool:_allowDefault forKey:NetworkAllowDefaultKey]; +} + ++ (BOOL)defaultRouteExists:(NSArray*)netList +{ + for(Network *net in netList) { + if (net.allowDefault && net.connected) { + return YES; + } + } + return NO; +} + +- (NSString*)statusString { + switch(_status) { + case REQUESTING_CONFIGURATION: + return @"REQUESTING_CONFIGURATION"; + case OK: + return @"OK"; + case ACCESS_DENIED: + return @"ACCESS_DENIED"; + case NOT_FOUND: + return @"NOT_FOUND"; + case PORT_ERROR: + return @"PORT_ERROR"; + case CLIENT_TOO_OLD: + return @"CLIENT_TOO_OLD"; + default: + return @""; + } +} + +- (NSString*)typeString { + switch(_type) { + case PUBLIC: + return @"PUBLIC"; + case PRIVATE: + return @"PRIVATE"; + default: + return @""; + } +} + +- (BOOL)hasSameNetworkId:(UInt64)networkId +{ + return self.nwid == networkId; +} + +- (BOOL)isEqualToNetwork:(Network*)network +{ + return [self.assignedAddresses isEqualToArray:network.assignedAddresses] && + self.bridge == network.bridge && + self.broadcastEnabled == network.broadcastEnabled && + self.dhcp == network.dhcp && + [self.mac isEqualToString:network.mac] && + self.mtu == network.mtu && + self.netconfRevision == network.netconfRevision && + [self.name isEqualToString:network.name] && + self.nwid == network.nwid && + [self.portDeviceName isEqualToString:network.portDeviceName] && + self.status == network.status && + self.type == network.type && + self.allowManaged == network.allowManaged && + self.allowGlobal == network.allowGlobal && + self.allowDefault == network.allowDefault && + self.connected == network.connected; +} + +- (BOOL)isEqual:(id)object +{ + if (self == object) { + return YES; + } + + if (![object isKindOfClass:[Network class]]) { + return NO; + } + + return [self isEqualToNetwork:object]; +} + +- (NSUInteger)hash +{ + return [self.assignedAddresses hash] ^ + self.bridge ^ + self.broadcastEnabled ^ + self.dhcp ^ + [self.mac hash] ^ + self.mtu ^ + self.netconfRevision ^ + [self.name hash] ^ + self.nwid ^ + [self.portDeviceName hash] ^ + self.portError ^ + self.status ^ + self.type ^ + self.allowManaged ^ + self.allowGlobal ^ + self.allowDefault ^ + self.connected; +} + +@end diff --git a/macui/ZeroTier One/NetworkInfoCell.h b/macui/ZeroTier One/NetworkInfoCell.h new file mode 100644 index 0000000..be9345d --- /dev/null +++ b/macui/ZeroTier One/NetworkInfoCell.h @@ -0,0 +1,50 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import + +@class ShowNetworksViewController; + +@interface NetworkInfoCell : NSTableCellView + +@property (weak, nonatomic) ShowNetworksViewController *parent; + +@property (weak, nonatomic) IBOutlet NSTextField *networkIdField; +@property (weak, nonatomic) IBOutlet NSTextField *networkNameField; +@property (weak, nonatomic) IBOutlet NSTextField *statusField; +@property (weak, nonatomic) IBOutlet NSTextField *typeField; +@property (weak, nonatomic) IBOutlet NSTextField *macField; +@property (weak, nonatomic) IBOutlet NSTextField *mtuField; +@property (weak, nonatomic) IBOutlet NSTextField *broadcastField; +@property (weak, nonatomic) IBOutlet NSTextField *bridgingField; +@property (weak, nonatomic) IBOutlet NSTextField *deviceField; +@property (weak, nonatomic) IBOutlet NSTextField *addressesField; +@property (weak, nonatomic) IBOutlet NSButton *allowManaged; +@property (weak, nonatomic) IBOutlet NSButton *allowGlobal; +@property (weak, nonatomic) IBOutlet NSButton *allowDefault; +@property (weak, nonatomic) IBOutlet NSButton *connectedCheckbox; +@property (weak, nonatomic) IBOutlet NSButton *deleteButton; + +- (IBAction)onConnectCheckStateChanged:(NSButton*)sender; +- (IBAction)deleteNetwork:(NSButton*)sender; +- (IBAction)onAllowStatusChanged:(NSButton*)sender; + +- (void)joinNetwork:(NSString*)nwid; +- (void)leaveNetwork:(NSString*)nwid; + +@end diff --git a/macui/ZeroTier One/NetworkInfoCell.m b/macui/ZeroTier One/NetworkInfoCell.m new file mode 100644 index 0000000..dc75cab --- /dev/null +++ b/macui/ZeroTier One/NetworkInfoCell.m @@ -0,0 +1,85 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import "NetworkInfoCell.h" +#import "ServiceCom.h" +#import "ShowNetworksViewController.h" +#import "Network.h" + +@implementation NetworkInfoCell + +- (void)drawRect:(NSRect)dirtyRect { + [super drawRect:dirtyRect]; + + // Drawing code here. +} + +- (IBAction)onConnectCheckStateChanged:(NSButton*)sender +{ + if(sender.state == NSOnState) { + [self joinNetwork:self.networkIdField.stringValue]; + } + else { + [self leaveNetwork:self.networkIdField.stringValue]; + } +} + +- (IBAction)deleteNetwork:(NSButton*)sender; +{ + [self leaveNetwork:self.networkIdField.stringValue]; + [self.parent deleteNetworkFromList:self.networkIdField.stringValue]; +} + +- (IBAction)onAllowStatusChanged:(NSButton*)sender +{ + [self joinNetwork:self.networkIdField.stringValue]; +} + +- (void)joinNetwork:(NSString*)nwid +{ + NSError *error = nil; + [[ServiceCom sharedInstance] joinNetwork:nwid + allowManaged:(self.allowManaged.state == NSOnState) + allowGlobal:(self.allowGlobal.state == NSOnState) + allowDefault:![Network defaultRouteExists:_parent.networkList] && (self.allowDefault.state == NSOnState) + error:&error]; + + if (error) { + NSAlert *alert = [NSAlert alertWithError:error]; + alert.alertStyle = NSCriticalAlertStyle; + [alert addButtonWithTitle:@"Ok"]; + + [alert runModal]; + } +} + +- (void)leaveNetwork:(NSString*)nwid +{ + NSError *error = nil; + [[ServiceCom sharedInstance] leaveNetwork:nwid error:&error]; + + if (error) { + NSAlert *alert = [NSAlert alertWithError:error]; + alert.alertStyle = NSCriticalAlertStyle; + [alert addButtonWithTitle:@"Ok"]; + + [alert runModal]; + } +} + +@end diff --git a/macui/ZeroTier One/NetworkMonitor.h b/macui/ZeroTier One/NetworkMonitor.h new file mode 100644 index 0000000..8cdec4e --- /dev/null +++ b/macui/ZeroTier One/NetworkMonitor.h @@ -0,0 +1,45 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import + +extern NSString * const NetworkUpdateKey; +extern NSString * const StatusUpdateKey; + +@class Network; + +@interface NetworkMonitor : NSObject +{ + NSMutableArray *_savedNetworks; + NSArray *_receivedNetworks; + NSMutableArray *_allNetworks; + + NSTimer *_timer; +} + +- (id)init; +- (void)dealloc; + +- (void)start; +- (void)stop; + +- (void)updateNetworkInfo; + +- (void)deleteSavedNetwork:(NSString*)networkId; + +@end diff --git a/macui/ZeroTier One/NetworkMonitor.m b/macui/ZeroTier One/NetworkMonitor.m new file mode 100644 index 0000000..7ed22c4 --- /dev/null +++ b/macui/ZeroTier One/NetworkMonitor.m @@ -0,0 +1,253 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import "NetworkMonitor.h" +#import "Network.h" +#import "ServiceCom.h" +#import "NodeStatus.h" + +@import AppKit; + + +NSString * const NetworkUpdateKey = @"com.zerotier.one.network-list"; +NSString * const StatusUpdateKey = @"com.zerotier.one.status"; + +@interface NetworkMonitor (private) + +- (NSString*)dataFile; +- (void)internal_updateNetworkInfo; +- (NSInteger)findNetworkWithID:(UInt64)networkId; +- (NSInteger)findSavedNetworkWithID:(UInt64)networkId; +- (void)saveNetworks; + +@end + +@implementation NetworkMonitor + +- (id)init +{ + self = [super init]; + if(self) + { + _savedNetworks = [NSMutableArray array]; + _receivedNetworks = [NSArray array]; + _allNetworks = [NSMutableArray array]; + _timer = nil; + } + + return self; +} + +- (void)dealloc +{ + [_timer invalidate]; +} + +- (void)start +{ + NSLog(@"ZeroTier monitor started"); + _timer = [NSTimer scheduledTimerWithTimeInterval:1.0f + target:self + selector:@selector(updateNetworkInfo) + userInfo:nil + repeats:YES]; +} + +- (void)stop +{ + NSLog(@"ZeroTier monitor stopped"); + [_timer invalidate]; + _timer = nil; +} + +- (void)updateNetworkInfo +{ + NSString *filePath = [self dataFile]; + + if([[NSFileManager defaultManager] fileExistsAtPath:filePath]) { + NSArray *networks = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + + if(networks != nil) { + _savedNetworks = [networks mutableCopy]; + } + } + + NSError *error = nil; + + [[ServiceCom sharedInstance] getNetworklist:^(NSArray *networkList) { + _receivedNetworks = networkList; + + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + [self internal_updateNetworkInfo]; + } ]; + } error:&error]; + + if(error) { + [self stop]; + + NSAlert *alert = [NSAlert alertWithError:error]; + alert.alertStyle = NSCriticalAlertStyle; + [alert addButtonWithTitle:@"Quit"]; + [alert addButtonWithTitle:@"Retry"]; + + NSModalResponse res = [alert runModal]; + + if(res == NSAlertFirstButtonReturn) { + [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; + } + else if(res == NSAlertSecondButtonReturn) { + [self start]; + return; + } + } + + [[ServiceCom sharedInstance] getNodeStatus:^(NodeStatus *status) { + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:status forKey:@"status"]; + + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + [[NSNotificationCenter defaultCenter] postNotificationName:StatusUpdateKey + object:nil + userInfo:userInfo]; + }]; + } error:&error]; + + if (error) { + [self stop]; + + NSAlert *alert = [NSAlert alertWithError:error]; + alert.alertStyle = NSCriticalAlertStyle; + [alert addButtonWithTitle:@"Quit"]; + [alert addButtonWithTitle:@"Retry"]; + + NSModalResponse res = [alert runModal]; + + if(res == NSAlertFirstButtonReturn) { + [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; + } + else if(res == NSAlertSecondButtonReturn) { + [self start]; + return; + } + } +} + +- (void)deleteSavedNetwork:(NSString*)networkId +{ + UInt64 nwid = 0; + NSScanner *scanner = [NSScanner scannerWithString:networkId]; + [scanner scanHexLongLong:&nwid]; + + NSInteger index = [self findNetworkWithID:nwid]; + + if(index != NSNotFound) { + [_allNetworks removeObjectAtIndex:index]; + } + + index = [self findSavedNetworkWithID:nwid]; + + if(index != NSNotFound) { + [_savedNetworks removeObjectAtIndex:index]; + } + + [self saveNetworks]; +} + +@end + +@implementation NetworkMonitor (private) +- (NSString*)dataFile +{ + NSURL *appSupport = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory + inDomains:NSUserDomainMask] objectAtIndex:0]; + + appSupport = [[[appSupport URLByAppendingPathComponent:@"ZeroTier"] URLByAppendingPathComponent:@"One"] URLByAppendingPathComponent:@"networkinfo.dat"]; + return appSupport.path; +} + +- (void)internal_updateNetworkInfo +{ + NSMutableArray *networks = [_savedNetworks mutableCopy]; + + for(Network *nw in _receivedNetworks) { + NSInteger index = [self findSavedNetworkWithID:nw.nwid]; + + if(index != NSNotFound) { + [networks setObject:nw atIndexedSubscript:index]; + } + else { + [networks addObject:nw]; + } + } + + [networks sortUsingComparator:^NSComparisonResult(Network *obj1, Network *obj2) { + if(obj1.nwid > obj2.nwid) { + return true; + } + return false; + }]; + + @synchronized(_allNetworks) { + _allNetworks = networks; + } + + [self saveNetworks]; + + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:networks forKey:@"networks"]; + + [[NSNotificationCenter defaultCenter] postNotificationName:NetworkUpdateKey + object:nil + userInfo:userInfo]; +} + +- (NSInteger)findNetworkWithID:(UInt64)networkId +{ + for(int i = 0; i < [_allNetworks count]; ++i) { + Network *nw = [_allNetworks objectAtIndex:i]; + + if(nw.nwid == networkId) { + return i; + } + } + + return NSNotFound; +} + + +- (NSInteger)findSavedNetworkWithID:(UInt64)networkId +{ + for(int i = 0; i < [_savedNetworks count]; ++i) { + Network *nw = [_savedNetworks objectAtIndex:i]; + + if(nw.nwid == networkId) { + return i; + } + } + + return NSNotFound; +} + +- (void)saveNetworks +{ + NSString *filePath = [self dataFile]; + + @synchronized(_allNetworks) { + [NSKeyedArchiver archiveRootObject:_allNetworks toFile:filePath]; + } +} + +@end diff --git a/macui/ZeroTier One/NodeStatus.h b/macui/ZeroTier One/NodeStatus.h new file mode 100644 index 0000000..eab5bfe --- /dev/null +++ b/macui/ZeroTier One/NodeStatus.h @@ -0,0 +1,35 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import + +@interface NodeStatus : NSObject + +@property (readonly) NSString *address; +@property (readonly) NSString *publicIdentity; +@property (readonly) BOOL online; +@property (readonly) BOOL tcpFallbackActive; +@property (readonly) int versionMajor; +@property (readonly) int versionMinor; +@property (readonly) int versionRev; +@property (readonly) NSString *version; +@property (readonly) UInt64 clock; + +- (id)initWithJsonData:(NSDictionary*)jsonData; + +@end diff --git a/macui/ZeroTier One/NodeStatus.m b/macui/ZeroTier One/NodeStatus.m new file mode 100644 index 0000000..3bae3c7 --- /dev/null +++ b/macui/ZeroTier One/NodeStatus.m @@ -0,0 +1,40 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#import "NodeStatus.h" + +@implementation NodeStatus + +- (id)initWithJsonData:(NSDictionary*)jsonData +{ + self = [super init]; + + if(self) { + _address = (NSString*)[jsonData objectForKey:@"address"]; + _publicIdentity = (NSString*)[jsonData objectForKey:@"publicIdentity"]; + _online = [(NSNumber*)[jsonData objectForKey:@"online"] boolValue]; + _tcpFallbackActive = [(NSNumber*)[jsonData objectForKey:@"tcpFallbackActive"] boolValue]; + _versionMajor = [(NSNumber*)[jsonData objectForKey:@"versionMajor"] intValue]; + _versionMinor = [(NSNumber*)[jsonData objectForKey:@"versionMinor"] intValue]; + _versionRev = [(NSNumber*)[jsonData objectForKey:@"versionRev"] intValue]; + _version = (NSString*)[jsonData objectForKey:@"version"]; + _clock = [(NSNumber*)[jsonData objectForKey:@"clock"] unsignedLongLongValue]; + } + + return self; +} +@end diff --git a/macui/ZeroTier One/PreferencesViewController.h b/macui/ZeroTier One/PreferencesViewController.h new file mode 100644 index 0000000..56d0fdb --- /dev/null +++ b/macui/ZeroTier One/PreferencesViewController.h @@ -0,0 +1,31 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import + +@interface PreferencesViewController : NSViewController + +@property (nonatomic, weak) IBOutlet NSButton *startupCheckBox; + +- (IBAction)onStartupCheckBoxChanged:(NSButton*)sender; + +- (BOOL)isLaunchAtStartup; +- (LSSharedFileListItemRef)itemRefInLoginItems; +- (void)setLaunchAtLoginEnabled:(BOOL)enabled; + +@end diff --git a/macui/ZeroTier One/PreferencesViewController.m b/macui/ZeroTier One/PreferencesViewController.m new file mode 100644 index 0000000..13927fb --- /dev/null +++ b/macui/ZeroTier One/PreferencesViewController.m @@ -0,0 +1,112 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import "PreferencesViewController.h" + +@interface PreferencesViewController () + +@end + +@implementation PreferencesViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + if([self isLaunchAtStartup]) { + self.startupCheckBox.state = NSOnState; + } + else { + self.startupCheckBox.state = NSOffState; + } +} + +- (IBAction)onStartupCheckBoxChanged:(NSButton *)sender +{ + if(sender.state == NSOnState) { + [self setLaunchAtLoginEnabled:YES]; + } + else { + [self setLaunchAtLoginEnabled:NO]; + } + +} + +- (void)setLaunchAtLoginEnabled:(BOOL)enabled +{ + LSSharedFileListRef loginItemsRef = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL); + + if (enabled) { + // Add the app to the LoginItems list. + CFURLRef appUrl = (__bridge CFURLRef)[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]; + LSSharedFileListItemRef itemRef = LSSharedFileListInsertItemURL(loginItemsRef, kLSSharedFileListItemLast, NULL, NULL, appUrl, NULL, NULL); + if (itemRef) CFRelease(itemRef); + } + else { + // Remove the app from the LoginItems list. + LSSharedFileListItemRef itemRef = [self itemRefInLoginItems]; + LSSharedFileListItemRemove(loginItemsRef,itemRef); + if (itemRef != nil) CFRelease(itemRef); + } +} + + +- (BOOL)isLaunchAtStartup { + // See if the app is currently in LoginItems. + LSSharedFileListItemRef itemRef = [self itemRefInLoginItems]; + // Store away that boolean. + BOOL isInList = itemRef != nil; + // Release the reference if it exists. + if (itemRef != nil) CFRelease(itemRef); + + return isInList; +} + +- (LSSharedFileListItemRef)itemRefInLoginItems { + LSSharedFileListItemRef itemRef = nil; + + NSString * appPath = [[NSBundle mainBundle] bundlePath]; + + // This will retrieve the path for the application + // For example, /Applications/test.app + CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:appPath]; + + // Create a reference to the shared file list. + LSSharedFileListRef loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL); + + if (loginItems) { + UInt32 seedValue; + //Retrieve the list of Login Items and cast them to + // a NSArray so that it will be easier to iterate. + NSArray *loginItemsArray = (__bridge NSArray *)LSSharedFileListCopySnapshot(loginItems, &seedValue); + for(int i = 0; i< [loginItemsArray count]; i++){ + LSSharedFileListItemRef currentItemRef = (__bridge LSSharedFileListItemRef)[loginItemsArray + objectAtIndex:i]; + //Resolve the item with URL + if (LSSharedFileListItemResolve(currentItemRef, 0, (CFURLRef*) &url, NULL) == noErr) { + NSString * urlPath = [(__bridge NSURL*)url path]; + if ([urlPath compare:appPath] == NSOrderedSame){ + itemRef = currentItemRef; + } + } + } + } + CFRelease(loginItems); + return itemRef; +} + +@end diff --git a/macui/ZeroTier One/PreferencesViewController.xib b/macui/ZeroTier One/PreferencesViewController.xib new file mode 100644 index 0000000..62aef4c --- /dev/null +++ b/macui/ZeroTier One/PreferencesViewController.xib @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macui/ZeroTier One/ServiceCom.h b/macui/ZeroTier One/ServiceCom.h new file mode 100644 index 0000000..c2b4692 --- /dev/null +++ b/macui/ZeroTier One/ServiceCom.h @@ -0,0 +1,40 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import + +@class NodeStatus; +@class Network; + +@interface ServiceCom : NSObject +{ + NSString *baseURL; + NSURLSession *session; + BOOL _isQuitting; + BOOL _resetKey; +} ++ (ServiceCom*)sharedInstance; + +- (id)init; + +- (void)getNetworklist:(void (^)(NSArray*))completionHandler error:(NSError* __autoreleasing *)error; +- (void)getNodeStatus:(void (^)(NodeStatus*))completionHandler error:(NSError*__autoreleasing*)error; +- (void)joinNetwork:(NSString*)networkId allowManaged:(BOOL)allowManaged allowGlobal:(BOOL)allowGlobal allowDefault:(BOOL)allowDefault error:(NSError*__autoreleasing*)error; +- (void)leaveNetwork:(NSString*)networkId error:(NSError*__autoreleasing*)error; + +@end diff --git a/macui/ZeroTier One/ServiceCom.m b/macui/ZeroTier One/ServiceCom.m new file mode 100644 index 0000000..75b9850 --- /dev/null +++ b/macui/ZeroTier One/ServiceCom.m @@ -0,0 +1,516 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import "ServiceCom.h" +#import "AuthtokenCopy.h" +#import "Network.h" +#import "NodeStatus.h" +@import AppKit; + +@interface ServiceCom (Private) + +- (NSString*)key; + +@end + +@implementation ServiceCom + ++ (ServiceCom*)sharedInstance { + static ServiceCom *sc = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sc = [[ServiceCom alloc] init]; + }); + return sc; +} + +- (id)init +{ + self = [super init]; + if(self) { + baseURL = @"http://127.0.0.1:9993"; + session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]]; + _isQuitting = NO; + _resetKey = NO; + } + + return self; +} + +- (NSString*)key:(NSError* __autoreleasing *)err +{ + static NSString *k = nil; + static NSUInteger resetCount = 0; + + @synchronized (self) { + if (_isQuitting) { + return @""; + } + + if (_resetKey && k != nil) { + k = nil; + ++resetCount; + NSLog(@"ResetCount: %lu", (unsigned long)resetCount); + if (resetCount > 10) { + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + NSAlert *alert = [NSAlert alertWithMessageText:@"Error obtaining Auth Token" + defaultButton:@"Quit" + alternateButton:@"Retry" + otherButton:nil + informativeTextWithFormat:@"Please ensure ZeroTier is installed correctly"]; + alert.alertStyle = NSCriticalAlertStyle; + + NSModalResponse res; + if (!_isQuitting) { + res = [alert runModal]; + } + else { + return; + } + + if(res == 1) { + _isQuitting = YES; + [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; + } + }]; + return @""; + } + } + + if (k == nil) { + NSError *error = nil; + NSURL *appSupportDir = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:false error:&error]; + + if (error) { + NSLog(@"Error: %@", error); + return @""; + } + + appSupportDir = [[appSupportDir URLByAppendingPathComponent:@"ZeroTier"] URLByAppendingPathComponent:@"One"]; + NSURL *authtokenURL = [appSupportDir URLByAppendingPathComponent:@"authtoken.secret"]; + + if (!_resetKey && [[NSFileManager defaultManager] fileExistsAtPath:[authtokenURL path]]) { + k = [NSString stringWithContentsOfURL:authtokenURL + encoding:NSUTF8StringEncoding + error:&error]; + + k = [k stringByReplacingOccurrencesOfString:@"\n" withString:@""]; + + if (error) { + NSLog(@"Error: %@", error); + k = nil; + *err = error; + return @""; + } + } + else { + _resetKey = NO; + NSURL *sysAppSupportDir = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory inDomain:NSSystemDomainMask appropriateForURL:nil create:false error:nil]; + + sysAppSupportDir = [[sysAppSupportDir URLByAppendingPathComponent:@"ZeroTier"] URLByAppendingPathComponent:@"One"]; + NSURL *sysAuthtokenURL = [sysAppSupportDir URLByAppendingPathComponent:@"authtoken.secret"]; + + if(![[NSFileManager defaultManager] fileExistsAtPath:[sysAuthtokenURL path]]) { + + } + + [[NSFileManager defaultManager] createDirectoryAtURL:appSupportDir + withIntermediateDirectories:YES + attributes:nil + error:&error]; + + if (error) { + NSLog(@"Error: %@", error); + *err = error; + k = nil; + return @""; + } + + AuthorizationRef authRef; + OSStatus status = AuthorizationCreate(nil, nil, kAuthorizationFlagDefaults, &authRef); + + if (status != errAuthorizationSuccess) { + NSLog(@"Authorization Failed! %d", status); + + NSDictionary *userInfo = @{ + NSLocalizedDescriptionKey: NSLocalizedString(@"Couldn't create AuthorizationRef", nil), + }; + *err = [NSError errorWithDomain:@"com.zerotier.one" code:-1 userInfo:userInfo]; + + return @""; + } + + AuthorizationItem authItem; + authItem.name = kAuthorizationRightExecute; + authItem.valueLength = 0; + authItem.flags = 0; + + AuthorizationRights authRights; + authRights.count = 1; + authRights.items = &authItem; + + AuthorizationFlags authFlags = kAuthorizationFlagDefaults | + kAuthorizationFlagInteractionAllowed | + kAuthorizationFlagPreAuthorize | + kAuthorizationFlagExtendRights; + + status = AuthorizationCopyRights(authRef, &authRights, nil, authFlags, nil); + + if (status != errAuthorizationSuccess) { + NSLog(@"Authorization Failed! %d", status); + NSDictionary *userInfo = @{ + NSLocalizedDescriptionKey: NSLocalizedString(@"Couldn't copy authorization rights", nil), + }; + *err = [NSError errorWithDomain:@"com.zerotier.one" code:-1 userInfo:userInfo]; + return @""; + } + + NSString *localKey = getAdminAuthToken(authRef); + AuthorizationFree(authRef, kAuthorizationFlagDestroyRights); + + if (localKey != nil && [localKey lengthOfBytesUsingEncoding:NSUTF8StringEncoding] > 0) { + k = localKey; + + [localKey writeToURL:authtokenURL + atomically:YES + encoding:NSUTF8StringEncoding + error:&error]; + + if (error) { + NSLog(@"Error writing token to disk: %@", error); + *err = error; + } + } + } + } + + if (k == nil) { + NSDictionary *userInfo = @{ + NSLocalizedDescriptionKey: NSLocalizedString(@"Unknown error finding authorization key", nil), + }; + *err = [NSError errorWithDomain:@"com.zerotier.one" code:-1 userInfo:userInfo]; + + return @""; + } + } + return k; +} + +- (void)getNetworklist:(void (^)(NSArray *))completionHandler error:(NSError *__autoreleasing*)error +{ + NSString* key = [self key:error]; + if(*error) { + return; + } + + NSString *urlString = [[baseURL stringByAppendingString:@"/network?auth="] stringByAppendingString:key]; + + NSURL *url = [NSURL URLWithString:urlString]; + NSURLSessionDataTask *task = + [session dataTaskWithURL:url + completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable err) { + + if (err) { + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + NSAlert *alert = [NSAlert alertWithError:err]; + alert.alertStyle = NSCriticalAlertStyle; + [alert addButtonWithTitle:@"Quit"]; + [alert addButtonWithTitle:@"Retry"]; + + NSModalResponse res; + if (!_isQuitting) { + res = [alert runModal]; + } + else { + return; + } + + if(res == NSAlertFirstButtonReturn) { + [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; + _isQuitting = YES; + } + }]; + return; + } + + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response; + NSInteger status = [httpResponse statusCode]; + + NSError *err2; + + if (status == 200) { + NSArray *json = [NSJSONSerialization JSONObjectWithData:data + options:0 + error:&err2]; + if (err) { + NSLog(@"Error fetching network list: %@", err2); + + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + NSAlert *alert = [NSAlert alertWithError:err2]; + alert.alertStyle = NSCriticalAlertStyle; + [alert addButtonWithTitle:@"Quit"]; + [alert addButtonWithTitle:@"Retry"]; + + NSModalResponse res; + if (!_isQuitting) { + res = [alert runModal]; + } + else { + return; + } + + if(res == NSAlertFirstButtonReturn) { + _isQuitting = YES; + [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; + } + }]; + return; + } + + NSMutableArray *networks = [[NSMutableArray alloc] init]; + for(NSDictionary *dict in json) { + [networks addObject:[[Network alloc] initWithJsonData:dict]]; + } + + completionHandler(networks); + } + else if (status == 401) { + self->_resetKey = YES; + } + }]; + [task resume]; +} + +- (void)getNodeStatus:(void (^)(NodeStatus*))completionHandler error:(NSError*__autoreleasing*)error +{ + NSString *key = [self key:error]; + if(*error) { + return; + } + + NSString *urlString = [[baseURL stringByAppendingString:@"/status?auth="] stringByAppendingString:key]; + + NSURL *url = [NSURL URLWithString:urlString]; + NSURLSessionDataTask *task = + [session dataTaskWithURL:url + completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable err) { + + if(err) { + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + NSAlert *alert = [NSAlert alertWithError:err]; + alert.alertStyle = NSCriticalAlertStyle; + [alert addButtonWithTitle:@"Quit"]; + [alert addButtonWithTitle:@"Retry"]; + + NSModalResponse res; + if (!_isQuitting) { + res = [alert runModal]; + } + else { + return; + } + + if(res == NSAlertFirstButtonReturn) { + [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; + _isQuitting = YES; + } + }]; + return; + } + + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response; + NSInteger status = [httpResponse statusCode]; + + NSError *err2; + if(status == 200) { + NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data + options:0 + error:&err2]; + + if(err2) { + NSLog(@"Error fetching node status: %@", err2); + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + NSAlert *alert = [NSAlert alertWithError:err2]; + alert.alertStyle = NSCriticalAlertStyle; + [alert addButtonWithTitle:@"Quit"]; + [alert addButtonWithTitle:@"Retry"]; + + NSModalResponse res; + if (!_isQuitting) { + res = [alert runModal]; + } + else { + return; + } + + if(res == NSAlertFirstButtonReturn) { + [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; + _isQuitting = YES; + } + }]; + return; + } + + NodeStatus *status = [[NodeStatus alloc] initWithJsonData:json]; + + completionHandler(status); + } + else if (status == 401) { + self->_resetKey = YES; + } + }]; + [task resume]; +} + +- (void)joinNetwork:(NSString*)networkId allowManaged:(BOOL)allowManaged allowGlobal:(BOOL)allowGlobal allowDefault:(BOOL)allowDefault error:(NSError *__autoreleasing*)error +{ + NSString *key = [self key:error]; + if(*error) { + return; + } + + NSString *urlString = [[[[baseURL stringByAppendingString:@"/network/"] + stringByAppendingString:networkId] + stringByAppendingString:@"?auth="] + stringByAppendingString:key]; + + NSURL *url = [NSURL URLWithString:urlString]; + + NSMutableDictionary *jsonDict = [NSMutableDictionary dictionary]; + [jsonDict setObject:[NSNumber numberWithBool:allowManaged] forKey:@"allowManaged"]; + [jsonDict setObject:[NSNumber numberWithBool:allowGlobal] forKey:@"allowGlobal"]; + [jsonDict setObject:[NSNumber numberWithBool:allowDefault] forKey:@"allowDefault"]; + + NSError *err = nil; + + NSData *json = [NSJSONSerialization dataWithJSONObject:jsonDict + options:0 + error:&err]; + + if(err) { + NSLog(@"Error creating json data: %@", err); + *error = err; + return; + } + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; + request.HTTPMethod = @"POST"; + request.HTTPBody = json; + [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; + + NSURLSessionDataTask *task = + [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable err) { + if(err) { + NSLog(@"Error posting join request: %@", err); + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + NSAlert *alert = [NSAlert alertWithError:err]; + alert.alertStyle = NSCriticalAlertStyle; + [alert addButtonWithTitle:@"Quit"]; + [alert addButtonWithTitle:@"Retry"]; + + NSModalResponse res; + if (!_isQuitting) { + res = [alert runModal]; + } + else { + return; + } + + if(res == NSAlertFirstButtonReturn) { + [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; + _isQuitting = YES; + } + }]; + } + + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response; + NSInteger status = [httpResponse statusCode]; + + if(status == 200) { + NSLog(@"join ok"); + } + else if (status == 401) { + self->_resetKey = YES; + } + else { + NSLog(@"join error: %ld", (long)status); + } + }]; + [task resume]; +} + +- (void)leaveNetwork:(NSString*)networkId error:(NSError*__autoreleasing*)error +{ + NSString *key = [self key:error]; + if(*error) { + return; + } + + NSString *urlString = [[[[baseURL stringByAppendingString:@"/network/"] + stringByAppendingString:networkId] + stringByAppendingString:@"?auth="] + stringByAppendingString:key]; + + NSURL *url = [NSURL URLWithString:urlString]; + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; + request.HTTPMethod = @"DELETE"; + + NSURLSessionDataTask *task = + [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable err) { + if(err) { + NSLog(@"Error posting delete request: %@", err); + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + NSAlert *alert = [NSAlert alertWithError:err]; + alert.alertStyle = NSCriticalAlertStyle; + [alert addButtonWithTitle:@"Quit"]; + [alert addButtonWithTitle:@"Retry"]; + + NSModalResponse res; + if (!_isQuitting) { + res = [alert runModal]; + } + else { + return; + } + + if(res == NSAlertFirstButtonReturn) { + [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; + _isQuitting = YES; + } + }]; + return; + } + + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response; + NSInteger status = httpResponse.statusCode; + + if(status == 200) { + NSLog(@"leave ok"); + } + else if (status == 401) { + self->_resetKey = YES; + } + else { + NSLog(@"leave error: %ld", status); + } + }]; + [task resume]; +} + +@end diff --git a/macui/ZeroTier One/ShowNetworksViewController.h b/macui/ZeroTier One/ShowNetworksViewController.h new file mode 100644 index 0000000..6138958 --- /dev/null +++ b/macui/ZeroTier One/ShowNetworksViewController.h @@ -0,0 +1,36 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import + +@class NetworkMonitor; +@class Network; + +@interface ShowNetworksViewController : NSViewController + +@property (nonatomic) NSMutableArray *networkList; +@property (nonatomic) NetworkMonitor *netMonitor; +@property (nonatomic) BOOL visible; + +@property (weak, nonatomic) IBOutlet NSTableView *tableView; + +- (void)deleteNetworkFromList:(NSString*)nwid; +- (void)setNetworks:(NSArray*)list; + + +@end diff --git a/macui/ZeroTier One/ShowNetworksViewController.m b/macui/ZeroTier One/ShowNetworksViewController.m new file mode 100644 index 0000000..903a4b4 --- /dev/null +++ b/macui/ZeroTier One/ShowNetworksViewController.m @@ -0,0 +1,181 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import "ShowNetworksViewController.h" +#import "NetworkMonitor.h" +#import "NetworkInfoCell.h" +#import "Network.h" + +BOOL hasNetworkWithID(NSArray *list, UInt64 nwid) +{ + for(Network *n in list) { + if(n.nwid == nwid) { + return YES; + } + } + + return NO; +} + +@interface ShowNetworksViewController () + +@end + +@implementation ShowNetworksViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.networkList = [NSMutableArray array]; + + [self.tableView setDelegate:self]; + [self.tableView setDataSource:self]; + [self.tableView setBackgroundColor:[NSColor clearColor]]; +} + +- (void)viewWillAppear { + [super viewWillAppear]; + self.visible = YES; +} + +- (void)viewWillDisappear { + [super viewWillDisappear]; + self.visible = NO; +} + +- (NSInteger)findNetworkWithID:(UInt64)networkId +{ + for(int i = 0; i < [_networkList count]; ++i) { + Network *nw = [_networkList objectAtIndex:i]; + + if(nw.nwid == networkId) { + return i; + } + } + + return NSNotFound; +} + + +- (void)deleteNetworkFromList:(NSString *)nwid { + [self.netMonitor deleteSavedNetwork:nwid]; + + UInt64 netid = 0; + NSScanner *scanner = [NSScanner scannerWithString:nwid]; + [scanner scanHexLongLong:&netid]; + for (Network *n in _networkList) { + if (n.nwid == netid) { + NSInteger index = [self findNetworkWithID:netid]; + + if (index != NSNotFound) { + [_networkList removeObjectAtIndex:index]; + [_tableView reloadData]; + } + } + } +} + +- (void)setNetworks:(NSArray *)list { + for (Network *n in list) { + if ([_networkList containsObject:n]) { + // don't need to do anything here. Already an identical object in the list + continue; + } + else { + // network not in the list based on equality. Did an object change? or is it a new item? + if (hasNetworkWithID(_networkList, n.nwid)) { + + for (int i = 0; i < [_networkList count]; ++i) { + Network *n2 = [_networkList objectAtIndex:i]; + if (n.nwid == n2.nwid) + { + [_networkList replaceObjectAtIndex:i withObject:n]; + [_tableView reloadDataForRowIndexes:[NSIndexSet indexSetWithIndex:i] + columnIndexes:[NSIndexSet indexSetWithIndex:0]]; + } + } + } + else { + [_networkList addObject:n]; + [_tableView reloadData]; + } + } + } +} + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView { + return [_networkList count]; +} + +- (NSView*)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +{ + NetworkInfoCell *cell = (NetworkInfoCell*)[tableView makeViewWithIdentifier:@"NetworkInfoCell" + owner:nil]; + Network *network = [_networkList objectAtIndex:row]; + cell.parent = self; + cell.networkIdField.stringValue = [NSString stringWithFormat:@"%10llx", network.nwid]; + cell.networkNameField.stringValue = network.name; + cell.statusField.stringValue = [network statusString]; + cell.typeField.stringValue = [network typeString]; + cell.mtuField.stringValue = [NSString stringWithFormat:@"%d", network.mtu]; + cell.macField.stringValue = network.mac; + cell.broadcastField.stringValue = network.broadcastEnabled ? @"ENABLED" : @"DISABLED"; + cell.bridgingField.stringValue = network.bridge ? @"ENABLED" : @"DISABLED"; + cell.deviceField.stringValue = network.portDeviceName; + + if(network.connected) { + cell.connectedCheckbox.state = NSOnState; + + if(network.allowDefault) { + cell.allowDefault.enabled = YES; + cell.allowDefault.state = NSOnState; + } + else { + cell.allowDefault.state = NSOffState; + + if([Network defaultRouteExists:_networkList]) { + cell.allowDefault.enabled = NO; + } + else { + cell.allowDefault.enabled = YES; + } + } + + cell.allowGlobal.enabled = YES; + cell.allowManaged.enabled = YES; + } + else { + cell.connectedCheckbox.state = NSOffState; + cell.allowDefault.enabled = NO; + cell.allowGlobal.enabled = NO; + cell.allowManaged.enabled = NO; + } + + cell.allowGlobal.state = network.allowGlobal ? NSOnState : NSOffState; + cell.allowManaged.state = network.allowManaged ? NSOnState : NSOffState; + + cell.addressesField.stringValue = @""; + + for(NSString *addr in network.assignedAddresses) { + cell.addressesField.stringValue = [[cell.addressesField.stringValue stringByAppendingString:addr] stringByAppendingString:@"\n"]; + } + + return cell; +} + +@end diff --git a/macui/ZeroTier One/ShowNetworksViewController.xib b/macui/ZeroTier One/ShowNetworksViewController.xib new file mode 100644 index 0000000..62ac289 --- /dev/null +++ b/macui/ZeroTier One/ShowNetworksViewController.xib @@ -0,0 +1,371 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ext/installfiles/mac/mac-ui-macgap1-wrapper/bin/ZeroTier One.app/Contents/Resources/ZeroTierIcon.icns b/macui/ZeroTier One/ZeroTierIcon.icns similarity index 100% rename from ext/installfiles/mac/mac-ui-macgap1-wrapper/bin/ZeroTier One.app/Contents/Resources/ZeroTierIcon.icns rename to macui/ZeroTier One/ZeroTierIcon.icns diff --git a/macui/ZeroTier One/about.html b/macui/ZeroTier One/about.html new file mode 100644 index 0000000..4fa41d7 --- /dev/null +++ b/macui/ZeroTier One/about.html @@ -0,0 +1,65 @@ + + + + + + +
+
+
+
+
+ +
+

Getting Started

+ +

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

+ +

Create a Network

+

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

+ +

For more information, visit zerotier.com.

+ +
+ + \ No newline at end of file diff --git a/macui/ZeroTier One/main.m b/macui/ZeroTier One/main.m new file mode 100644 index 0000000..108a6bd --- /dev/null +++ b/macui/ZeroTier One/main.m @@ -0,0 +1,23 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import + +int main(int argc, const char * argv[]) { + return NSApplicationMain(argc, argv); +} diff --git a/make-bsd.mk b/make-bsd.mk new file mode 100644 index 0000000..39c6cef --- /dev/null +++ b/make-bsd.mk @@ -0,0 +1,156 @@ +# This requires GNU make, which is typically "gmake" on BSD systems + +INCLUDES= +DEFS= +LIBS= + +include objects.mk +OBJS+=osdep/BSDEthernetTap.o ext/http-parser/http_parser.o + +# Build with ZT_ENABLE_CLUSTER=1 to build with cluster support +ifeq ($(ZT_ENABLE_CLUSTER),1) + DEFS+=-DZT_ENABLE_CLUSTER +endif + +# "make debug" is a shortcut for this +ifeq ($(ZT_DEBUG),1) + DEFS+=-DZT_TRACE + CFLAGS+=-Wall -g -pthread $(INCLUDES) $(DEFS) + LDFLAGS+= + STRIP=echo + # The following line enables optimization for the crypto code, since + # C25519 in particular is almost UNUSABLE in heavy testing without it. +node/Salsa20.o node/SHA512.o node/C25519.o node/Poly1305.o: CFLAGS = -Wall -O2 -g -pthread $(INCLUDES) $(DEFS) +else + CFLAGS?=-O3 -fstack-protector + CFLAGS+=-Wall -fPIE -fvisibility=hidden -fstack-protector -pthread $(INCLUDES) -DNDEBUG $(DEFS) + LDFLAGS+=-pie -Wl,-z,relro,-z,now + STRIP=strip --strip-all +endif + +# Determine system build architecture from compiler target +CC_MACH=$(shell $(CC) -dumpmachine | cut -d '-' -f 1) +ZT_ARCHITECTURE=999 +ifeq ($(CC_MACH),x86_64) + ZT_ARCHITECTURE=2 + ZT_USE_X64_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),amd64) + ZT_ARCHITECTURE=2 + ZT_USE_X64_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),i386) + ZT_ARCHITECTURE=1 +endif +ifeq ($(CC_MACH),i686) + ZT_ARCHITECTURE=1 +endif +ifeq ($(CC_MACH),arm) + ZT_ARCHITECTURE=3 + override DEFS+=-DZT_NO_TYPE_PUNNING + ZT_USE_ARM32_NEON_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),armel) + ZT_ARCHITECTURE=3 + override DEFS+=-DZT_NO_TYPE_PUNNING + ZT_USE_ARM32_NEON_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),armhf) + ZT_ARCHITECTURE=3 + override DEFS+=-DZT_NO_TYPE_PUNNING + ZT_USE_ARM32_NEON_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),armv6) + ZT_ARCHITECTURE=3 + override DEFS+=-DZT_NO_TYPE_PUNNING + ZT_USE_ARM32_NEON_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),armv6zk) + ZT_ARCHITECTURE=3 + override DEFS+=-DZT_NO_TYPE_PUNNING + ZT_USE_ARM32_NEON_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),armv6kz) + ZT_ARCHITECTURE=3 + override DEFS+=-DZT_NO_TYPE_PUNNING + ZT_USE_ARM32_NEON_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),armv7) + ZT_ARCHITECTURE=3 + override DEFS+=-DZT_NO_TYPE_PUNNING + ZT_USE_ARM32_NEON_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),arm64) + ZT_ARCHITECTURE=4 + override DEFS+=-DZT_NO_TYPE_PUNNING +endif +ifeq ($(CC_MACH),aarch64) + ZT_ARCHITECTURE=4 + override DEFS+=-DZT_NO_TYPE_PUNNING +endif +ifeq ($(CC_MACH),mipsel) + ZT_ARCHITECTURE=5 + override DEFS+=-DZT_NO_TYPE_PUNNING +endif +ifeq ($(CC_MACH),mips) + ZT_ARCHITECTURE=5 + override DEFS+=-DZT_NO_TYPE_PUNNING +endif +ifeq ($(CC_MACH),mips64) + ZT_ARCHITECTURE=6 + override DEFS+=-DZT_NO_TYPE_PUNNING +endif +ifeq ($(CC_MACH),mips64el) + ZT_ARCHITECTURE=6 + override DEFS+=-DZT_NO_TYPE_PUNNING +endif + +# Fail if system architecture could not be determined +ifeq ($(ZT_ARCHITECTURE),999) +ERR=$(error FATAL: architecture could not be determined from $(CC) -dumpmachine: $CC_MACH) +.PHONY: err +err: ; $(ERR) +endif + +# Build faster crypto on some targets +ifeq ($(ZT_USE_X64_ASM_SALSA2012),1) + override DEFS+=-DZT_USE_X64_ASM_SALSA2012 + override OBJS+=ext/x64-salsa2012-asm/salsa2012.o +endif +ifeq ($(ZT_USE_ARM32_NEON_ASM_SALSA2012),1) + override DEFS+=-DZT_USE_ARM32_NEON_ASM_SALSA2012 + override OBJS+=ext/arm32-neon-salsa2012-asm/salsa2012.o +endif + +override DEFS+=-DZT_BUILD_PLATFORM=$(ZT_BUILD_PLATFORM) -DZT_BUILD_ARCHITECTURE=$(ZT_ARCHITECTURE) -DZT_SOFTWARE_UPDATE_DEFAULT="\"disable\"" + +CXXFLAGS+=$(CFLAGS) -fno-rtti -std=c++11 #-D_GLIBCXX_USE_C99 -D_GLIBCXX_USE_C99_MATH -D_GLIBCXX_USE_C99_MATH_TR1 + +all: one + +one: $(OBJS) service/OneService.o one.o + $(CXX) $(CXXFLAGS) $(LDFLAGS) -o zerotier-one $(OBJS) service/OneService.o one.o $(LIBS) + $(STRIP) zerotier-one + ln -sf zerotier-one zerotier-idtool + ln -sf zerotier-one zerotier-cli + +selftest: $(OBJS) selftest.o + $(CXX) $(CXXFLAGS) $(LDFLAGS) -o zerotier-selftest selftest.o $(OBJS) $(LIBS) + $(STRIP) zerotier-selftest + +clean: + rm -rf *.o node/*.o controller/*.o osdep/*.o service/*.o ext/http-parser/*.o build-* zerotier-one zerotier-idtool zerotier-selftest zerotier-cli ZeroTierOneInstaller-* $(OBJS) + +debug: FORCE + make -j 4 ZT_DEBUG=1 + +install: one + rm -f /usr/local/sbin/zerotier-one + cp zerotier-one /usr/local/sbin + ln -sf /usr/local/sbin/zerotier-one /usr/local/sbin/zerotier-cli + ln -sf /usr/local/sbin/zerotier-one /usr/local/bin/zerotier-idtool + +uninstall: FORCE + rm -rf /usr/local/sbin/zerotier-one /usr/local/sbin/zerotier-cli /usr/local/bin/zerotier-idtool /var/db/zerotier-one/zerotier-one.port /var/db/zerotier-one/zerotier-one.pid /var/db/zerotier-one/iddb.d + +FORCE: diff --git a/make-freebsd.mk b/make-freebsd.mk deleted file mode 100644 index e7bd9fd..0000000 --- a/make-freebsd.mk +++ /dev/null @@ -1,65 +0,0 @@ -CC=cc -CXX=c++ - -INCLUDES= -DEFS= -LIBS= - -include objects.mk -OBJS+=osdep/BSDEthernetTap.o ext/lz4/lz4.o ext/json-parser/json.o ext/http-parser/http_parser.o - -# "make official" is a shortcut for this -ifeq ($(ZT_OFFICIAL_RELEASE),1) - DEFS+=-DZT_OFFICIAL_RELEASE -endif - -# 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. -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?=-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 - -CXXFLAGS+=$(CFLAGS) -fno-rtti - -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 - -# No installer on FreeBSD yet -#installer: one FORCE -# ./buildinstaller.sh - -clean: - rm -rf *.o node/*.o controller/*.o osdep/*.o service/*.o ext/http-parser/*.o ext/lz4/*.o ext/json-parser/*.o build-* zerotier-one zerotier-idtool zerotier-selftest zerotier-cli ZeroTierOneInstaller-* - -debug: FORCE - make -j 4 ZT_DEBUG=1 - -#official: FORCE -# make -j 4 ZT_OFFICIAL_RELEASE=1 -# ./buildinstaller.sh - -FORCE: diff --git a/make-linux.mk b/make-linux.mk index acc22a6..87d29af 100644 --- a/make-linux.mk +++ b/make-linux.mk @@ -1,24 +1,3 @@ -# -# Makefile for ZeroTier One on Linux -# -# This is confirmed to work on distributions newer than CentOS 6 (the -# one used for reference builds) and on 32 and 64 bit x86 and ARM -# machines. It should also work on other 'normal' machines and recent -# distributions. Editing might be required for tiny devices or weird -# distros. -# -# Targets -# one: zerotier-one and symlinks (cli and idtool) -# manpages: builds manpages, requires 'ronn' or nodeJS (will use either) -# all: builds 'one' and 'manpages' -# selftest: zerotier-selftest -# debug: builds 'one' and 'selftest' with tracing and debug flags -# clean: removes all built files, objects, other trash -# distclean: removes a few other things that might be present -# debian: build DEB packages; deb dev tools must be present -# redhat: build RPM packages; rpm dev tools must be present -# - # 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) @@ -28,8 +7,6 @@ ifeq ($(origin CXX),default) CXX=$(shell if [ -e /usr/bin/clang++ ]; then echo clang++; else echo g++; fi) endif -#UNAME_M=$(shell $(CC) -dumpmachine | cut -d '-' -f 1) - INCLUDES?= DEFS?=-D_FORTIFY_SOURCE=2 LDLIBS?= @@ -37,80 +14,60 @@ DESTDIR?= include objects.mk -# On Linux we auto-detect the presence of some libraries and if present we -# link against the system version. This works with our package build images. -ifeq ($(wildcard /usr/include/lz4.h),) - OBJS+=ext/lz4/lz4.o +# 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 - LDLIBS+=-llz4 - DEFS+=-DZT_USE_SYSTEM_LZ4 + 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/http_parser.h),) - OBJS+=ext/http-parser/http_parser.o +ifeq ($(wildcard /usr/include/natpmp.h),) + OBJS+=ext/libnatpmp/natpmp.o ext/libnatpmp/getgateway.o else - LDLIBS+=-lhttp_parser - DEFS+=-DZT_USE_SYSTEM_HTTP_PARSER -endif -ifeq ($(wildcard /usr/include/json-parser/json.h),) - OBJS+=ext/json-parser/json.o -else - LDLIBS+=-ljsonparser - DEFS+=-DZT_USE_SYSTEM_JSON_PARSER -endif - -ifeq ($(ZT_USE_MINIUPNPC),1) - OBJS+=osdep/PortMapper.o - - DEFS+=-DZT_USE_MINIUPNPC - - # Auto-detect libminiupnpc at least v2.0 - 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 - - # Auto-detect libnatpmp - ifeq ($(wildcard /usr/include/natpmp.h),) - OBJS+=ext/libnatpmp/natpmp.o ext/libnatpmp/getgateway.o - else - LDLIBS+=-lnatpmp - DEFS+=-DZT_USE_SYSTEM_NATPMP - endif -endif - -ifeq ($(ZT_ENABLE_NETWORK_CONTROLLER),1) - DEFS+=-DZT_ENABLE_NETWORK_CONTROLLER - LDLIBS+=-L/usr/local/lib -lsqlite3 - OBJS+=controller/SqliteNetworkController.o + 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 -pthread $(INCLUDES) $(DEFS) - LDFLAGS= + 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! -ext/lz4/lz4.o node/Salsa20.o node/SHA512.o node/C25519.o node/Poly1305.o: CFLAGS = -Wall -O2 -g -pthread $(INCLUDES) $(DEFS) +node/Salsa20.o node/SHA512.o node/C25519.o node/Poly1305.o: CFLAGS = -Wall -O2 -g -pthread $(INCLUDES) $(DEFS) else - CFLAGS?=-O3 -fstack-protector-strong + CFLAGS?=-O3 -fstack-protector override CFLAGS+=-Wall -fPIE -pthread $(INCLUDES) -DNDEBUG $(DEFS) - CXXFLAGS?=-O3 -fstack-protector-strong - override CXXFLAGS+=-Wall -Wno-unused-result -Wreorder -fPIE -fno-rtti -pthread $(INCLUDES) -DNDEBUG $(DEFS) - LDFLAGS=-pie -Wl,-z,relro,-z,now + 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 @@ -121,7 +78,118 @@ endif #LDFLAGS= #STRIP=echo -all: one manpages +# Determine system build architecture from compiler target +CC_MACH=$(shell $(CC) -dumpmachine | cut -d '-' -f 1) +ZT_ARCHITECTURE=999 +ifeq ($(CC_MACH),x86_64) + ZT_ARCHITECTURE=2 + ZT_USE_X64_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),amd64) + ZT_ARCHITECTURE=2 + ZT_USE_X64_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),i386) + ZT_ARCHITECTURE=1 +endif +ifeq ($(CC_MACH),i686) + ZT_ARCHITECTURE=1 +endif +ifeq ($(CC_MACH),arm) + ZT_ARCHITECTURE=3 + override DEFS+=-DZT_NO_TYPE_PUNNING + ZT_USE_ARM32_NEON_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),armel) + ZT_ARCHITECTURE=3 + override DEFS+=-DZT_NO_TYPE_PUNNING + ZT_USE_ARM32_NEON_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),armhf) + ZT_ARCHITECTURE=3 + override DEFS+=-DZT_NO_TYPE_PUNNING + ZT_USE_ARM32_NEON_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),armv6) + ZT_ARCHITECTURE=3 + override DEFS+=-DZT_NO_TYPE_PUNNING + ZT_USE_ARM32_NEON_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),armv6zk) + ZT_ARCHITECTURE=3 + override DEFS+=-DZT_NO_TYPE_PUNNING + ZT_USE_ARM32_NEON_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),armv6kz) + ZT_ARCHITECTURE=3 + override DEFS+=-DZT_NO_TYPE_PUNNING + ZT_USE_ARM32_NEON_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),armv7) + ZT_ARCHITECTURE=3 + override DEFS+=-DZT_NO_TYPE_PUNNING + ZT_USE_ARM32_NEON_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),arm64) + ZT_ARCHITECTURE=4 + override DEFS+=-DZT_NO_TYPE_PUNNING +endif +ifeq ($(CC_MACH),aarch64) + ZT_ARCHITECTURE=4 + override DEFS+=-DZT_NO_TYPE_PUNNING +endif +ifeq ($(CC_MACH),mipsel) + ZT_ARCHITECTURE=5 + override DEFS+=-DZT_NO_TYPE_PUNNING +endif +ifeq ($(CC_MACH),mips) + ZT_ARCHITECTURE=5 + override DEFS+=-DZT_NO_TYPE_PUNNING +endif +ifeq ($(CC_MACH),mips64) + ZT_ARCHITECTURE=6 + override DEFS+=-DZT_NO_TYPE_PUNNING +endif +ifeq ($(CC_MACH),mips64el) + ZT_ARCHITECTURE=6 + override DEFS+=-DZT_NO_TYPE_PUNNING +endif + +# Fail if system architecture could not be determined +ifeq ($(ZT_ARCHITECTURE),999) +ERR=$(error FATAL: architecture could not be determined from $(CC) -dumpmachine: $CC_MACH) +.PHONY: err +err: ; $(ERR) +endif + +# Disable software updates by default on Linux since that is normally done with package management +override DEFS+=-DZT_BUILD_PLATFORM=1 -DZT_BUILD_ARCHITECTURE=$(ZT_ARCHITECTURE) -DZT_SOFTWARE_UPDATE_DEFAULT="\"disable\"" + +# Build faster crypto on some targets +ifeq ($(ZT_USE_X64_ASM_SALSA2012),1) + override DEFS+=-DZT_USE_X64_ASM_SALSA2012 + override OBJS+=ext/x64-salsa2012-asm/salsa2012.o +endif +ifeq ($(ZT_USE_ARM32_NEON_ASM_SALSA2012),1) + override DEFS+=-DZT_USE_ARM32_NEON_ASM_SALSA2012 + override OBJS+=ext/arm32-neon-salsa2012-asm/salsa2012.o +endif + +# Static builds, which are currently done for a number of Linux targets +ifeq ($(ZT_STATIC),1) + override LDFLAGS+=-static + ifeq ($(ZT_ARCHITECTURE),3) + ifeq ($(ZT_ARM_SOFTFLOAT),1) + override CFLAGS+=-march=armv5te -mfloat-abi=soft -msoft-float -mno-unaligned-access -marm + override CXXFLAGS+=-march=armv5te -mfloat-abi=soft -msoft-float -mno-unaligned-access -marm + else + override CFLAGS+=-march=armv6kz -mcpu=arm1176jzf-s -mfpu=vfp -mfloat-abi=hard -mno-unaligned-access -marm + override CXXFLAGS+=-march=armv6kz -mcpu=arm1176jzf-s -mfpu=vfp -mfloat-abi=hard -mno-unaligned-access -marm + endif + endif +endif + +all: one one: $(OBJS) service/OneService.o one.o osdep/LinuxEthernetTap.o $(CXX) $(CXXFLAGS) $(LDFLAGS) -o zerotier-one $(OBJS) service/OneService.o one.o osdep/LinuxEthernetTap.o $(LDLIBS) @@ -139,13 +207,9 @@ manpages: FORCE doc: manpages clean: FORCE - rm -rf *.so *.o node/*.o controller/*.o osdep/*.o service/*.o ext/http-parser/*.o ext/lz4/*.o ext/json-parser/*.o ext/miniupnpc/*.o ext/libnatpmp/*.o $(OBJS) zerotier-one zerotier-idtool zerotier-cli zerotier-selftest build-* ZeroTierOneInstaller-* *.deb *.rpm .depend doc/*.1 doc/*.2 doc/*.8 debian/files debian/zerotier-one*.debhelper debian/zerotier-one.substvars debian/*.log debian/zerotier-one + 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 - rm -rf doc/node_modules - find linux-build-farm -type f -name '*.deb' -print0 | xargs -0 rm -fv - find linux-build-farm -type f -name '*.rpm' -print0 | xargs -0 rm -fv - find linux-build-farm -type f -name 'zt1-src.tar.gz' | xargs rm -fv realclean: distclean @@ -201,10 +265,10 @@ uninstall: FORCE # These are just for convenience for building Linux packages -debian: distclean - debuild -I -i -us -uc +debian: FORCE + debuild -I -i -us -uc -nc -b -redhat: distclean +redhat: FORCE rpmbuild -ba zerotier-one.spec FORCE: diff --git a/make-mac.mk b/make-mac.mk index e821c4c..6676f45 100644 --- a/make-mac.mk +++ b/make-mac.mk @@ -1,55 +1,49 @@ -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 - +CC=clang +CXX=clang++ INCLUDES= DEFS= LIBS= -ARCH_FLAGS=-arch x86_64 - -include objects.mk -OBJS+=osdep/OSXEthernetTap.o ext/lz4/lz4.o ext/json-parser/json.o ext/http-parser/http_parser.o - -# Disable codesign since open source users will not have ZeroTier's certs +ARCH_FLAGS= CODESIGN=echo PRODUCTSIGN=echo CODESIGN_APP_CERT= CODESIGN_INSTALLER_CERT= -# Build with libminiupnpc by default for Mac -- desktops/laptops almost always want this -ZT_USE_MINIUPNPC?=1 +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) -# For internal use only -- signs everything with ZeroTier's developer cert +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_OFFICIAL_RELEASE -DZT_AUTO_UPDATE + DEFS+=-DZT_SOFTWARE_UPDATE_DEFAULT="\"apply\"" ZT_USE_MINIUPNPC=1 CODESIGN=codesign PRODUCTSIGN=productsign - CODESIGN_APP_CERT="Developer ID Application: ZeroTier Networks LLC (8ZD9JUCZ4V)" - CODESIGN_INSTALLER_CERT="Developer ID Installer: ZeroTier Networks LLC (8ZD9JUCZ4V)" + 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 -ifeq ($(ZT_AUTO_UPDATE),1) - DEFS+=-DZT_AUTO_UPDATE -endif +# Use fast ASM Salsa20/12 for x64 processors +DEFS+=-DZT_USE_X64_ASM_SALSA2012 +OBJS+=ext/x64-salsa2012-asm/salsa2012.o -ifeq ($(ZT_USE_MINIUPNPC),1) - 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 -endif - -ifeq ($(ZT_ENABLE_NETWORK_CONTROLLER),1) - DEFS+=-DZT_ENABLE_NETWORK_CONTROLLER - LIBS+=-L/usr/local/lib -lsqlite3 - OBJS+=controller/SqliteNetworkController.o -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) @@ -58,16 +52,19 @@ ifeq ($(ZT_DEBUG),1) 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) +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) -fno-rtti +CXXFLAGS=$(CFLAGS) -std=c++11 -stdlib=libc++ -all: one +all: one macui + +ext/x64-salsa2012-asm/salsa2012.o: + $(CC) $(CFLAGS) -c ext/x64-salsa2012-asm/salsa2012.s -o ext/x64-salsa2012-asm/salsa2012.o one: $(OBJS) service/OneService.o one.o $(CXX) $(CXXFLAGS) -o zerotier-one $(OBJS) service/OneService.o one.o $(LIBS) @@ -75,11 +72,14 @@ one: $(OBJS) service/OneService.o one.o ln -sf zerotier-one zerotier-idtool ln -sf zerotier-one zerotier-cli $(CODESIGN) -f -s $(CODESIGN_APP_CERT) zerotier-one - $(CODESIGN) -vvv zerotier-one -cli: FORCE - $(CXX) -Os -mmacosx-version-min=10.7 -std=c++11 -stdlib=libc++ -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 +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) @@ -91,18 +91,22 @@ mac-dist-pkg: FORCE 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 ext/lz4/*.o ext/json-parser/*.o $(OBJS) zerotier-one zerotier-idtool zerotier-selftest zerotier-cli zerotier ZeroTierOneInstaller-* mkworld doc/node_modules + 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 - rm -rf doc/node_modules + +realclean: clean # For those building from source -- installs signed binary tap driver in system ZT home install-mac-tap: FORCE diff --git a/node/Address.hpp b/node/Address.hpp index 9bf5605..4a5883b 100644 --- a/node/Address.hpp +++ b/node/Address.hpp @@ -38,57 +38,26 @@ namespace ZeroTier { class Address { public: - Address() - throw() : - _a(0) - { - } - - Address(const Address &a) - throw() : - _a(a._a) - { - } - - Address(uint64_t a) - throw() : - _a(a & 0xffffffffffULL) - { - } - - Address(const char *s) - throw() - { - unsigned char foo[ZT_ADDRESS_LENGTH]; - setTo(foo,Utils::unhex(s,foo,ZT_ADDRESS_LENGTH)); - } - - Address(const std::string &s) - throw() - { - unsigned char foo[ZT_ADDRESS_LENGTH]; - setTo(foo,Utils::unhex(s.c_str(),foo,ZT_ADDRESS_LENGTH)); - } + 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) - throw() { setTo(bits,len); } inline Address &operator=(const Address &a) - throw() { _a = a._a; return *this; } inline Address &operator=(const uint64_t a) - throw() { _a = (a & 0xffffffffffULL); return *this; @@ -99,7 +68,6 @@ public: * @param len Length of array */ inline void setTo(const void *bits,unsigned int len) - throw() { if (len < ZT_ADDRESS_LENGTH) { _a = 0; @@ -119,7 +87,6 @@ public: * @param len Length of array */ inline void copyTo(void *bits,unsigned int len) const - throw() { if (len < ZT_ADDRESS_LENGTH) return; @@ -138,7 +105,6 @@ public: */ template inline void appendTo(Buffer &b) const - throw(std::out_of_range) { unsigned char *p = (unsigned char *)b.appendField(ZT_ADDRESS_LENGTH); *(p++) = (unsigned char)((_a >> 32) & 0xff); @@ -152,7 +118,6 @@ public: * @return Integer containing address (0 to 2^40) */ inline uint64_t toInt() const - throw() { return _a; } @@ -161,7 +126,6 @@ public: * @return Hash code for use with Hashtable */ inline unsigned long hashCode() const - throw() { return (unsigned long)_a; } @@ -188,12 +152,12 @@ public: /** * @return True if this address is not zero */ - inline operator bool() const throw() { return (_a != 0); } + inline operator bool() const { return (_a != 0); } /** * Set to null/zero */ - inline void zero() throw() { _a = 0; } + inline void zero() { _a = 0; } /** * Check if this address is reserved @@ -205,7 +169,6 @@ public: * @return True if address is reserved and may not be used */ inline bool isReserved() const - throw() { return ((!_a)||((_a >> 32) == ZT_ADDRESS_RESERVED_PREFIX)); } @@ -214,21 +177,21 @@ public: * @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 throw() { return (unsigned char)((_a >> (32 - (i * 8))) & 0xff); } + inline unsigned char operator[](unsigned int i) const { return (unsigned char)((_a >> (32 - (i * 8))) & 0xff); } - inline bool operator==(const uint64_t &a) const throw() { return (_a == (a & 0xffffffffffULL)); } - inline bool operator!=(const uint64_t &a) const throw() { return (_a != (a & 0xffffffffffULL)); } - inline bool operator>(const uint64_t &a) const throw() { return (_a > (a & 0xffffffffffULL)); } - inline bool operator<(const uint64_t &a) const throw() { return (_a < (a & 0xffffffffffULL)); } - inline bool operator>=(const uint64_t &a) const throw() { return (_a >= (a & 0xffffffffffULL)); } - inline bool operator<=(const uint64_t &a) const throw() { 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 uint64_t &a) const { return (_a <= (a & 0xffffffffffULL)); } - inline bool operator==(const Address &a) const throw() { return (_a == a._a); } - inline bool operator!=(const Address &a) const throw() { return (_a != a._a); } - inline bool operator>(const Address &a) const throw() { return (_a > a._a); } - inline bool operator<(const Address &a) const throw() { return (_a < a._a); } - inline bool operator>=(const Address &a) const throw() { return (_a >= a._a); } - inline bool operator<=(const Address &a) const throw() { 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); } + inline bool operator<=(const Address &a) const { return (_a <= a._a); } private: uint64_t _a; diff --git a/node/AtomicCounter.hpp b/node/AtomicCounter.hpp index b499377..a0f29ba 100644 --- a/node/AtomicCounter.hpp +++ b/node/AtomicCounter.hpp @@ -20,11 +20,9 @@ #define ZT_ATOMICCOUNTER_HPP #include "Constants.hpp" -#include "Mutex.hpp" #include "NonCopyable.hpp" -#ifdef __WINDOWS__ -// will replace this whole class eventually once it's ubiquitous +#ifndef __GNUC__ #include #endif @@ -36,75 +34,34 @@ namespace ZeroTier { class AtomicCounter : NonCopyable { public: - /** - * Initialize counter at zero - */ AtomicCounter() - throw() { _v = 0; } - inline operator int() const - throw() - { -#ifdef __GNUC__ - return __sync_or_and_fetch(const_cast (&_v),0); -#else -#ifdef __WINDOWS__ - return (int)_v; -#else - _l.lock(); - int v = _v; - _l.unlock(); - return v; -#endif -#endif - } - inline int operator++() - throw() { #ifdef __GNUC__ return __sync_add_and_fetch(&_v,1); #else -#ifdef __WINDOWS__ return ++_v; -#else - _l.lock(); - int v = ++_v; - _l.unlock(); - return v; -#endif #endif } inline int operator--() - throw() { #ifdef __GNUC__ return __sync_sub_and_fetch(&_v,1); #else -#ifdef __WINDOWS__ return --_v; -#else - _l.lock(); - int v = --_v; - _l.unlock(); - return v; -#endif #endif } private: -#ifdef __WINDOWS__ - std::atomic_int _v; -#else +#ifdef __GNUC__ int _v; -#ifndef __GNUC__ -#warning Neither __WINDOWS__ nor __GNUC__ so AtomicCounter using Mutex - Mutex _l; -#endif +#else + std::atomic_int _v; #endif }; diff --git a/node/BinarySemaphore.hpp b/node/BinarySemaphore.hpp deleted file mode 100644 index 315d2b0..0000000 --- a/node/BinarySemaphore.hpp +++ /dev/null @@ -1,97 +0,0 @@ -/* - * 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_BINARYSEMAPHORE_HPP -#define ZT_BINARYSEMAPHORE_HPP - -#include -#include -#include - -#include "Constants.hpp" -#include "NonCopyable.hpp" - -#ifdef __WINDOWS__ - -#include - -namespace ZeroTier { - -class BinarySemaphore : NonCopyable -{ -public: - BinarySemaphore() throw() { _sem = CreateSemaphore(NULL,0,1,NULL); } - ~BinarySemaphore() { CloseHandle(_sem); } - inline void wait() { WaitForSingleObject(_sem,INFINITE); } - inline void post() { ReleaseSemaphore(_sem,1,NULL); } -private: - HANDLE _sem; -}; - -} // namespace ZeroTier - -#else // !__WINDOWS__ - -#include - -namespace ZeroTier { - -class BinarySemaphore : NonCopyable -{ -public: - BinarySemaphore() - { - pthread_mutex_init(&_mh,(const pthread_mutexattr_t *)0); - pthread_cond_init(&_cond,(const pthread_condattr_t *)0); - _f = false; - } - - ~BinarySemaphore() - { - pthread_cond_destroy(&_cond); - pthread_mutex_destroy(&_mh); - } - - inline void wait() - { - pthread_mutex_lock(const_cast (&_mh)); - while (!_f) - pthread_cond_wait(const_cast (&_cond),const_cast (&_mh)); - _f = false; - pthread_mutex_unlock(const_cast (&_mh)); - } - - inline void post() - { - pthread_mutex_lock(const_cast (&_mh)); - _f = true; - pthread_mutex_unlock(const_cast (&_mh)); - pthread_cond_signal(const_cast (&_cond)); - } - -private: - pthread_cond_t _cond; - pthread_mutex_t _mh; - volatile bool _f; -}; - -} // namespace ZeroTier - -#endif // !__WINDOWS__ - -#endif diff --git a/node/Buffer.hpp b/node/Buffer.hpp index 0b17159..37f39e7 100644 --- a/node/Buffer.hpp +++ b/node/Buffer.hpp @@ -61,11 +61,11 @@ public: // STL container idioms typedef unsigned char value_type; typedef unsigned char * pointer; - typedef const unsigned char * const_pointer; - typedef unsigned char & reference; - typedef const unsigned char & const_reference; - typedef unsigned char * iterator; - typedef const unsigned char * const_iterator; + 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; @@ -79,8 +79,7 @@ public: inline const_reverse_iterator rbegin() const { return const_reverse_iterator(begin()); } inline const_reverse_iterator rend() const { return const_reverse_iterator(end()); } - Buffer() - throw() : + Buffer() : _l(0) { } @@ -419,87 +418,70 @@ public: /** * Set buffer data length to zero */ - inline void clear() - throw() - { - _l = 0; - } + inline void clear() { _l = 0; } /** * Zero buffer up to size() */ - inline void zero() - throw() - { - memset(_b,0,_l); - } + inline void zero() { memset(_b,0,_l); } /** * Zero unused capacity area */ - inline void zeroUnused() - throw() - { - memset(_b + _l,0,C - _l); - } + inline void zeroUnused() { memset(_b + _l,0,C - _l); } /** * Unconditionally and securely zero buffer's underlying memory */ - inline void burn() - throw() - { - Utils::burn(_b,sizeof(_b)); - } + inline void burn() { Utils::burn(_b,sizeof(_b)); } /** * @return Constant pointer to data in buffer */ - inline const void *data() const throw() { return _b; } + 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 throw() { return _l; } + inline unsigned int size() const { return _l; } /** * @return Capacity of buffer */ - inline unsigned int capacity() const throw() { return C; } + inline unsigned int capacity() const { return C; } template inline bool operator==(const Buffer &b) const - throw() { return ((_l == b._l)&&(!memcmp(_b,b._b,_l))); } template inline bool operator!=(const Buffer &b) const - throw() { return ((_l != b._l)||(memcmp(_b,b._b,_l))); } template inline bool operator<(const Buffer &b) const - throw() { return (memcmp(_b,b._b,std::min(_l,b._l)) < 0); } template inline bool operator>(const Buffer &b) const - throw() { return (b < *this); } template inline bool operator<=(const Buffer &b) const - throw() { return !(b < *this); } template inline bool operator>=(const Buffer &b) const - throw() { return !(*this < b); } diff --git a/node/Capability.cpp b/node/Capability.cpp new file mode 100644 index 0000000..c178e56 --- /dev/null +++ b/node/Capability.cpp @@ -0,0 +1,65 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "Capability.hpp" +#include "RuntimeEnvironment.hpp" +#include "Identity.hpp" +#include "Topology.hpp" +#include "Switch.hpp" +#include "Network.hpp" + +namespace ZeroTier { + +int Capability::verify(const RuntimeEnvironment *RR,void *tPtr) const +{ + try { + // There must be at least one entry, and sanity check for bad chain max length + if ((_maxCustodyChainLength < 1)||(_maxCustodyChainLength > ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH)) + return -1; + + // Validate all entries in chain of custody + Buffer<(sizeof(Capability) * 2)> tmp; + this->serialize(tmp,true); + for(unsigned int c=0;c<_maxCustodyChainLength;++c) { + if (c == 0) { + if ((!_custody[c].to)||(!_custody[c].from)||(_custody[c].from != Network::controllerFor(_nwid))) + return -1; // the first entry must be present and from the network's controller + } else { + if (!_custody[c].to) + return 0; // all previous entries were valid, so we are valid + else if ((!_custody[c].from)||(_custody[c].from != _custody[c-1].to)) + return -1; // otherwise if we have another entry it must be from the previous holder in the chain + } + + const Identity id(RR->topology->getIdentity(tPtr,_custody[c].from)); + if (id) { + if (!id.verify(tmp.data(),tmp.size(),_custody[c].signature)) + return -1; + } else { + RR->sw->requestWhois(tPtr,_custody[c].from); + return 1; + } + } + + // We reached max custody chain length and everything was valid + return 0; + } catch ( ... ) {} + return -1; +} + +} // namespace ZeroTier diff --git a/node/Capability.hpp b/node/Capability.hpp new file mode 100644 index 0000000..454723a --- /dev/null +++ b/node/Capability.hpp @@ -0,0 +1,472 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ZT_CAPABILITY_HPP +#define ZT_CAPABILITY_HPP + +#include +#include +#include + +#include "Constants.hpp" +#include "Credential.hpp" +#include "Address.hpp" +#include "C25519.hpp" +#include "Utils.hpp" +#include "Buffer.hpp" +#include "Identity.hpp" +#include "../include/ZeroTierOne.h" + +namespace ZeroTier { + +class RuntimeEnvironment; + +/** + * A set of grouped and signed network flow rules + * + * On the sending side the sender does the following for each packet: + * + * (1) Evaluates its capabilities in ascending order of ID to determine + * which capability allows it to transmit this packet. + * (2) If it has not done so lately, it then sends this capability to the + * receving peer ("presents" it). + * (3) The sender then sends the packet. + * + * On the receiving side the receiver evaluates the capabilities presented + * by the sender. If any valid un-expired capability allows this packet it + * is accepted. + * + * Note that this is after evaluation of network scope rules and only if + * network scope rules do not deliver an explicit match. + * + * Capabilities support a chain of custody. This is currently unused but + * in the future would allow the publication of capabilities that can be + * handed off between nodes. Limited transferrability of capabilities is + * a feature of true capability based security. + */ +class Capability : public Credential +{ +public: + static inline Credential::Type credentialType() { return Credential::CREDENTIAL_TYPE_CAPABILITY; } + + Capability() + { + memset(this,0,sizeof(Capability)); + } + + /** + * @param id Capability ID + * @param nwid Network ID + * @param ts Timestamp (at controller) + * @param mccl Maximum custody chain length (1 to create non-transferrable capability) + * @param rules Network flow rules for this capability + * @param ruleCount Number of flow rules + */ + Capability(uint32_t id,uint64_t nwid,uint64_t ts,unsigned int mccl,const ZT_VirtualNetworkRule *rules,unsigned int ruleCount) + { + memset(this,0,sizeof(Capability)); + _nwid = nwid; + _ts = ts; + _id = id; + _maxCustodyChainLength = (mccl > 0) ? ((mccl < ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH) ? mccl : (unsigned int)ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH) : 1; + _ruleCount = (ruleCount < ZT_MAX_CAPABILITY_RULES) ? ruleCount : ZT_MAX_CAPABILITY_RULES; + if (_ruleCount) + memcpy(_rules,rules,sizeof(ZT_VirtualNetworkRule) * _ruleCount); + } + + /** + * @return Rules -- see ruleCount() for size of array + */ + inline const ZT_VirtualNetworkRule *rules() const { return _rules; } + + /** + * @return Number of rules in rules() + */ + inline unsigned int ruleCount() const { return _ruleCount; } + + /** + * @return ID and evaluation order of this capability in network + */ + inline uint32_t id() const { return _id; } + + /** + * @return Network ID for which this capability was issued + */ + inline uint64_t networkId() const { return _nwid; } + + /** + * @return Timestamp + */ + inline uint64_t timestamp() const { return _ts; } + + /** + * @return Last 'to' address in chain of custody + */ + inline Address issuedTo() const + { + Address i2; + for(unsigned int i=0;i tmp; + this->serialize(tmp,true); + _custody[i].to = to; + _custody[i].from = from.address(); + _custody[i].signature = from.sign(tmp.data(),tmp.size()); + return true; + } + } + } catch ( ... ) {} + return false; + } + + /** + * Verify this capability's chain of custody and signatures + * + * @param RR Runtime environment to provide for peer lookup, etc. + * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or chain + */ + int verify(const RuntimeEnvironment *RR,void *tPtr) const; + + template + static inline void serializeRules(Buffer &b,const ZT_VirtualNetworkRule *rules,unsigned int ruleCount) + { + for(unsigned int i=0;i + static inline void deserializeRules(const Buffer &b,unsigned int &p,ZT_VirtualNetworkRule *rules,unsigned int &ruleCount,const unsigned int maxRuleCount) + { + while ((ruleCount < maxRuleCount)&&(p < b.size())) { + rules[ruleCount].t = (uint8_t)b[p++]; + const unsigned int fieldLen = (unsigned int)b[p++]; + switch((ZT_VirtualNetworkRuleType)(rules[ruleCount].t & 0x3f)) { + default: + break; + case ZT_NETWORK_RULE_ACTION_TEE: + case ZT_NETWORK_RULE_ACTION_WATCH: + case ZT_NETWORK_RULE_ACTION_REDIRECT: + rules[ruleCount].v.fwd.address = b.template at(p); + rules[ruleCount].v.fwd.flags = b.template at(p + 8); + rules[ruleCount].v.fwd.length = b.template at(p + 12); + break; + case ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS: + case ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS: + rules[ruleCount].v.zt = Address(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH).toInt(); + break; + case ZT_NETWORK_RULE_MATCH_VLAN_ID: + rules[ruleCount].v.vlanId = b.template at(p); + break; + case ZT_NETWORK_RULE_MATCH_VLAN_PCP: + rules[ruleCount].v.vlanPcp = (uint8_t)b[p]; + break; + case ZT_NETWORK_RULE_MATCH_VLAN_DEI: + rules[ruleCount].v.vlanDei = (uint8_t)b[p]; + break; + case ZT_NETWORK_RULE_MATCH_MAC_SOURCE: + case ZT_NETWORK_RULE_MATCH_MAC_DEST: + memcpy(rules[ruleCount].v.mac,b.field(p,6),6); + break; + case ZT_NETWORK_RULE_MATCH_IPV4_SOURCE: + case ZT_NETWORK_RULE_MATCH_IPV4_DEST: + memcpy(&(rules[ruleCount].v.ipv4.ip),b.field(p,4),4); + rules[ruleCount].v.ipv4.mask = (uint8_t)b[p + 4]; + break; + case ZT_NETWORK_RULE_MATCH_IPV6_SOURCE: + case ZT_NETWORK_RULE_MATCH_IPV6_DEST: + memcpy(rules[ruleCount].v.ipv6.ip,b.field(p,16),16); + rules[ruleCount].v.ipv6.mask = (uint8_t)b[p + 16]; + break; + case ZT_NETWORK_RULE_MATCH_IP_TOS: + rules[ruleCount].v.ipTos.mask = (uint8_t)b[p]; + rules[ruleCount].v.ipTos.value[0] = (uint8_t)b[p+1]; + rules[ruleCount].v.ipTos.value[1] = (uint8_t)b[p+2]; + break; + case ZT_NETWORK_RULE_MATCH_IP_PROTOCOL: + rules[ruleCount].v.ipProtocol = (uint8_t)b[p]; + break; + case ZT_NETWORK_RULE_MATCH_ETHERTYPE: + rules[ruleCount].v.etherType = b.template at(p); + break; + case ZT_NETWORK_RULE_MATCH_ICMP: + rules[ruleCount].v.icmp.type = (uint8_t)b[p]; + rules[ruleCount].v.icmp.code = (uint8_t)b[p+1]; + rules[ruleCount].v.icmp.flags = (uint8_t)b[p+2]; + break; + case ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE: + case ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE: + rules[ruleCount].v.port[0] = b.template at(p); + rules[ruleCount].v.port[1] = b.template at(p + 2); + break; + case ZT_NETWORK_RULE_MATCH_CHARACTERISTICS: + rules[ruleCount].v.characteristics = b.template at(p); + break; + case ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE: + rules[ruleCount].v.frameSize[0] = b.template at(p); + rules[ruleCount].v.frameSize[1] = b.template at(p + 2); + break; + case ZT_NETWORK_RULE_MATCH_RANDOM: + rules[ruleCount].v.randomProbability = b.template at(p); + break; + case ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE: + case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND: + case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR: + case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR: + case ZT_NETWORK_RULE_MATCH_TAGS_EQUAL: + case ZT_NETWORK_RULE_MATCH_TAG_SENDER: + case ZT_NETWORK_RULE_MATCH_TAG_RECEIVER: + rules[ruleCount].v.tag.id = b.template at(p); + rules[ruleCount].v.tag.value = b.template at(p + 4); + break; + } + p += fieldLen; + ++ruleCount; + } + } + + template + inline void serialize(Buffer &b,const bool forSign = false) const + { + if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + + // These are the same between Tag and Capability + b.append(_nwid); + b.append(_ts); + b.append(_id); + + b.append((uint16_t)_ruleCount); + serializeRules(b,_rules,_ruleCount); + b.append((uint8_t)_maxCustodyChainLength); + + if (!forSign) { + for(unsigned int i=0;;++i) { + if ((i < _maxCustodyChainLength)&&(i < ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH)&&(_custody[i].to)) { + _custody[i].to.appendTo(b); + _custody[i].from.appendTo(b); + b.append((uint8_t)1); // 1 == Ed25519 signature + b.append((uint16_t)ZT_C25519_SIGNATURE_LEN); // length of signature + b.append(_custody[i].signature.data,ZT_C25519_SIGNATURE_LEN); + } else { + b.append((unsigned char)0,ZT_ADDRESS_LENGTH); // zero 'to' terminates chain + break; + } + } + } + + // This is the size of any additional fields, currently 0. + b.append((uint16_t)0); + + if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + } + + template + inline unsigned int deserialize(const Buffer &b,unsigned int startAt = 0) + { + memset(this,0,sizeof(Capability)); + + unsigned int p = startAt; + + _nwid = b.template at(p); p += 8; + _ts = b.template at(p); p += 8; + _id = b.template at(p); p += 4; + + const unsigned int rc = b.template at(p); p += 2; + if (rc > ZT_MAX_CAPABILITY_RULES) + throw std::runtime_error("rule overflow"); + deserializeRules(b,p,_rules,_ruleCount,rc); + + _maxCustodyChainLength = (unsigned int)b[p++]; + if ((_maxCustodyChainLength < 1)||(_maxCustodyChainLength > ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH)) + throw std::runtime_error("invalid max custody chain length"); + + for(unsigned int i=0;;++i) { + const Address to(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; + if (!to) + break; + if ((i >= _maxCustodyChainLength)||(i >= ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH)) + throw std::runtime_error("unterminated custody chain"); + _custody[i].to = to; + _custody[i].from.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; + if (b[p++] == 1) { + if (b.template at(p) != ZT_C25519_SIGNATURE_LEN) + throw std::runtime_error("invalid signature"); + p += 2; + memcpy(_custody[i].signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); p += ZT_C25519_SIGNATURE_LEN; + } else { + p += 2 + b.template at(p); + } + } + + p += 2 + b.template at(p); + if (p > b.size()) + throw std::runtime_error("extended field overflow"); + + return (p - startAt); + } + + // Provides natural sort order by ID + inline bool operator<(const Capability &c) const { return (_id < c._id); } + + inline bool operator==(const Capability &c) const { return (memcmp(this,&c,sizeof(Capability)) == 0); } + inline bool operator!=(const Capability &c) const { return (memcmp(this,&c,sizeof(Capability)) != 0); } + +private: + uint64_t _nwid; + uint64_t _ts; + uint32_t _id; + + unsigned int _maxCustodyChainLength; + + unsigned int _ruleCount; + ZT_VirtualNetworkRule _rules[ZT_MAX_CAPABILITY_RULES]; + + struct { + Address to; + Address from; + C25519::Signature signature; + } _custody[ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH]; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/CertificateOfMembership.cpp b/node/CertificateOfMembership.cpp index 55537fd..9bf7021 100644 --- a/node/CertificateOfMembership.cpp +++ b/node/CertificateOfMembership.cpp @@ -17,6 +17,10 @@ */ #include "CertificateOfMembership.hpp" +#include "RuntimeEnvironment.hpp" +#include "Topology.hpp" +#include "Switch.hpp" +#include "Network.hpp" namespace ZeroTier { @@ -152,6 +156,9 @@ bool CertificateOfMembership::agreesWith(const CertificateOfMembership &other) c 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. @@ -182,7 +189,7 @@ bool CertificateOfMembership::agreesWith(const CertificateOfMembership &other) c bool CertificateOfMembership::sign(const Identity &with) { - uint64_t *const buf = new uint64_t[_qualifierCount * 3]; + 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); @@ -193,38 +200,32 @@ bool CertificateOfMembership::sign(const Identity &with) try { _signature = with.sign(buf,ptr * sizeof(uint64_t)); _signedBy = with.address(); - delete [] buf; return true; } catch ( ... ) { _signedBy.zero(); - delete [] buf; return false; } } -bool CertificateOfMembership::verify(const Identity &id) const +int CertificateOfMembership::verify(const RuntimeEnvironment *RR,void *tPtr) const { - if (!_signedBy) - return false; - if (id.address() != _signedBy) - return false; + if ((!_signedBy)||(_signedBy != Network::controllerFor(networkId()))||(_qualifierCount > ZT_NETWORK_COM_MAX_QUALIFIERS)) + return -1; - uint64_t *const buf = new uint64_t[_qualifierCount * 3]; + 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); } - - bool valid = false; - try { - valid = id.verify(buf,ptr * sizeof(uint64_t),_signature); - delete [] buf; - } catch ( ... ) { - delete [] buf; - } - return valid; + return (id.verify(buf,ptr * sizeof(uint64_t),_signature) ? 0 : -1); } } // namespace ZeroTier diff --git a/node/CertificateOfMembership.hpp b/node/CertificateOfMembership.hpp index 0342bc3..dfccb13 100644 --- a/node/CertificateOfMembership.hpp +++ b/node/CertificateOfMembership.hpp @@ -27,6 +27,7 @@ #include #include "Constants.hpp" +#include "Credential.hpp" #include "Buffer.hpp" #include "Address.hpp" #include "C25519.hpp" @@ -34,22 +35,14 @@ #include "Utils.hpp" /** - * Default window of time for certificate agreement - * - * Right now we use time for 'revision' so this is the maximum time divergence - * between two certs for them to agree. It comes out to five minutes, which - * gives a lot of margin for error if the controller hiccups or its clock - * drifts but causes de-authorized peers to fall off fast enough. + * Maximum number of qualifiers allowed in a COM (absolute max: 65535) */ -#define ZT_NETWORK_COM_DEFAULT_REVISION_MAX_DELTA (ZT_NETWORK_AUTOCONF_DELAY * 5) - -/** - * Maximum number of qualifiers in a COM - */ -#define ZT_NETWORK_COM_MAX_QUALIFIERS 16 +#define ZT_NETWORK_COM_MAX_QUALIFIERS 8 namespace ZeroTier { +class RuntimeEnvironment; + /** * Certificate of network membership * @@ -76,25 +69,16 @@ namespace ZeroTier { * This is a memcpy()'able structure and is safe (in a crash sense) to modify * without locks. */ -class CertificateOfMembership +class CertificateOfMembership : public Credential { public: - /** - * Certificate type codes, used in serialization - * - * Only one so far, and only one hopefully there shall be for quite some - * time. - */ - enum Type - { - COM_UINT64_ED25519 = 1 // tuples of unsigned 64's signed with Ed25519 - }; + static inline Credential::Type credentialType() { return Credential::CREDENTIAL_TYPE_COM; } /** * Reserved qualifier IDs * - * IDs below 65536 should be considered reserved for future global - * assignment here. + * 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. @@ -102,36 +86,27 @@ public: enum ReservedId { /** - * Revision number of certificate - * - * Certificates may differ in revision number by a designated max - * delta. Differences wider than this cause certificates not to agree. + * Timestamp of certificate */ - COM_RESERVED_ID_REVISION = 0, + COM_RESERVED_ID_TIMESTAMP = 0, /** * Network ID for which certificate was issued - * - * maxDelta here is zero, since this must match. */ COM_RESERVED_ID_NETWORK_ID = 1, /** * ZeroTier address to whom certificate was issued - * - * maxDelta will be 0xffffffffffffffff here since it's permitted to differ - * from peers obviously. */ COM_RESERVED_ID_ISSUED_TO = 2 }; /** - * Create an empty certificate + * Create an empty certificate of membership */ - CertificateOfMembership() : - _qualifierCount(0) + CertificateOfMembership() { - memset(_signature.data,0,_signature.size()); + memset(this,0,sizeof(CertificateOfMembership)); } CertificateOfMembership(const CertificateOfMembership &c) @@ -142,16 +117,16 @@ public: /** * Create from required fields common to all networks * - * @param revision Revision number of certificate + * @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 revision,uint64_t revisionMaxDelta,uint64_t nwid,const Address &issuedTo) + CertificateOfMembership(uint64_t timestamp,uint64_t timestampMaxDelta,uint64_t nwid,const Address &issuedTo) { - _qualifiers[0].id = COM_RESERVED_ID_REVISION; - _qualifiers[0].value = revision; - _qualifiers[0].maxDelta = revisionMaxDelta; + _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; @@ -168,22 +143,6 @@ public: return *this; } -#ifdef ZT_SUPPORT_OLD_STYLE_NETCONF - /** - * Create from string-serialized data - * - * @param s String-serialized COM - */ - CertificateOfMembership(const char *s) { fromString(s); } - - /** - * Create from string-serialized data - * - * @param s String-serialized COM - */ - CertificateOfMembership(const std::string &s) { fromString(s.c_str()); } -#endif // ZT_SUPPORT_OLD_STYLE_NETCONF - /** * Create from binary-serialized COM in buffer * @@ -199,48 +158,23 @@ public: /** * @return True if there's something here */ - inline operator bool() const throw() { return (_qualifierCount != 0); } + inline operator bool() const { return (_qualifierCount != 0); } /** - * Check for presence of all required fields common to all networks - * - * @return True if all required fields are present + * @return Credential ID, always 0 for COMs */ - inline bool hasRequiredFields() const - { - if (_qualifierCount < 3) - return false; - if (_qualifiers[0].id != COM_RESERVED_ID_REVISION) - return false; - if (_qualifiers[1].id != COM_RESERVED_ID_NETWORK_ID) - return false; - if (_qualifiers[2].id != COM_RESERVED_ID_ISSUED_TO) - return false; - return true; - } + inline uint32_t id() const { return 0; } /** - * @return Maximum delta for mandatory revision field or 0 if field missing + * @return Timestamp for this cert and maximum delta for timestamp */ - inline uint64_t revisionMaxDelta() const + inline uint64_t timestamp() const { for(unsigned int i=0;i<_qualifierCount;++i) { - if (_qualifiers[i].id == COM_RESERVED_ID_REVISION) - return _qualifiers[i].maxDelta; - } - return 0ULL; - } - - /** - * @return Revision number for this cert - */ - inline uint64_t revision() const - { - for(unsigned int i=0;i<_qualifierCount;++i) { - if (_qualifiers[i].id == COM_RESERVED_ID_REVISION) + if (_qualifiers[i].id == COM_RESERVED_ID_TIMESTAMP) return _qualifiers[i].value; } - return 0ULL; + return 0; } /** @@ -321,27 +255,28 @@ public: bool sign(const Identity &with); /** - * Verify certificate against an identity + * Verify this COM and its signature * - * @param id Identity to verify against - * @return True if certificate is signed by this identity and verification was successful + * @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 */ - bool verify(const Identity &id) const; + int verify(const RuntimeEnvironment *RR,void *tPtr) const; /** * @return True if signed */ - inline bool isSigned() const throw() { return (_signedBy); } + inline bool isSigned() const { return (_signedBy); } /** * @return Address that signed this certificate or null address if none */ - inline const Address &signedBy() const throw() { return _signedBy; } + inline const Address &signedBy() const { return _signedBy; } template inline void serialize(Buffer &b) const { - b.append((unsigned char)COM_UINT64_ED25519); + b.append((uint8_t)1); b.append((uint16_t)_qualifierCount); for(unsigned int i=0;i<_qualifierCount;++i) { b.append(_qualifiers[i].id); @@ -361,8 +296,8 @@ public: _qualifierCount = 0; _signedBy.zero(); - if (b[p++] != COM_UINT64_ED25519) - throw std::invalid_argument("invalid type"); + 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; @@ -394,7 +329,6 @@ public: } inline bool operator==(const CertificateOfMembership &c) const - throw() { if (_signedBy != c._signedBy) return false; @@ -408,7 +342,7 @@ public: } return (_signature == c._signature); } - inline bool operator!=(const CertificateOfMembership &c) const throw() { return (!(*this == c)); } + inline bool operator!=(const CertificateOfMembership &c) const { return (!(*this == c)); } private: struct _Qualifier diff --git a/node/CertificateOfOwnership.cpp b/node/CertificateOfOwnership.cpp new file mode 100644 index 0000000..2bd181e --- /dev/null +++ b/node/CertificateOfOwnership.cpp @@ -0,0 +1,63 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "CertificateOfOwnership.hpp" +#include "RuntimeEnvironment.hpp" +#include "Identity.hpp" +#include "Topology.hpp" +#include "Switch.hpp" +#include "Network.hpp" + +namespace ZeroTier { + +int CertificateOfOwnership::verify(const RuntimeEnvironment *RR,void *tPtr) const +{ + if ((!_signedBy)||(_signedBy != Network::controllerFor(_networkId))) + return -1; + const Identity id(RR->topology->getIdentity(tPtr,_signedBy)); + if (!id) { + RR->sw->requestWhois(tPtr,_signedBy); + return 1; + } + try { + Buffer<(sizeof(CertificateOfOwnership) + 64)> tmp; + this->serialize(tmp,true); + return (id.verify(tmp.data(),tmp.size(),_signature) ? 0 : -1); + } catch ( ... ) { + return -1; + } +} + +bool CertificateOfOwnership::_owns(const CertificateOfOwnership::Thing &t,const void *v,unsigned int l) const +{ + for(unsigned int i=0,j=_thingCount;i(v)[k] != _thingValues[i][k]) + break; + ++k; + } + if (k == l) + return true; + } + } + return false; +} + +} // namespace ZeroTier diff --git a/node/CertificateOfOwnership.hpp b/node/CertificateOfOwnership.hpp new file mode 100644 index 0000000..93be64d --- /dev/null +++ b/node/CertificateOfOwnership.hpp @@ -0,0 +1,237 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ZT_CERTIFICATEOFOWNERSHIP_HPP +#define ZT_CERTIFICATEOFOWNERSHIP_HPP + +#include +#include +#include +#include + +#include "Constants.hpp" +#include "Credential.hpp" +#include "C25519.hpp" +#include "Address.hpp" +#include "Identity.hpp" +#include "Buffer.hpp" +#include "InetAddress.hpp" +#include "MAC.hpp" + +// Max things per CertificateOfOwnership +#define ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS 16 + +// Maximum size of a thing's value field in bytes +#define ZT_CERTIFICATEOFOWNERSHIP_MAX_THING_VALUE_SIZE 16 + +namespace ZeroTier { + +class RuntimeEnvironment; + +/** + * Certificate indicating ownership of a network identifier + */ +class CertificateOfOwnership : public Credential +{ +public: + static inline Credential::Type credentialType() { return Credential::CREDENTIAL_TYPE_COO; } + + enum Thing + { + THING_NULL = 0, + THING_MAC_ADDRESS = 1, + THING_IPV4_ADDRESS = 2, + THING_IPV6_ADDRESS = 3 + }; + + CertificateOfOwnership() + { + memset(this,0,sizeof(CertificateOfOwnership)); + } + + CertificateOfOwnership(const uint64_t nwid,const uint64_t ts,const Address &issuedTo,const uint32_t id) : + _networkId(nwid), + _ts(ts), + _flags(0), + _id(id), + _thingCount(0), + _issuedTo(issuedTo) + { + } + + inline uint64_t networkId() const { return _networkId; } + inline uint64_t timestamp() const { return _ts; } + inline uint32_t id() const { return _id; } + inline unsigned int thingCount() const { return (unsigned int)_thingCount; } + + inline Thing thingType(const unsigned int i) const { return (Thing)_thingTypes[i]; } + inline const uint8_t *thingValue(const unsigned int i) const { return _thingValues[i]; } + + inline const Address &issuedTo() const { return _issuedTo; } + + inline bool owns(const InetAddress &ip) const + { + if (ip.ss_family == AF_INET) + return this->_owns(THING_IPV4_ADDRESS,&(reinterpret_cast(&ip)->sin_addr.s_addr),4); + if (ip.ss_family == AF_INET6) + return this->_owns(THING_IPV6_ADDRESS,reinterpret_cast(&ip)->sin6_addr.s6_addr,16); + return false; + } + + inline bool owns(const MAC &mac) const + { + uint8_t tmp[6]; + mac.copyTo(tmp,6); + return this->_owns(THING_MAC_ADDRESS,tmp,6); + } + + inline void addThing(const InetAddress &ip) + { + if (_thingCount >= ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS) return; + if (ip.ss_family == AF_INET) { + _thingTypes[_thingCount] = THING_IPV4_ADDRESS; + memcpy(_thingValues[_thingCount],&(reinterpret_cast(&ip)->sin_addr.s_addr),4); + ++_thingCount; + } else if (ip.ss_family == AF_INET6) { + _thingTypes[_thingCount] = THING_IPV6_ADDRESS; + memcpy(_thingValues[_thingCount],reinterpret_cast(&ip)->sin6_addr.s6_addr,16); + ++_thingCount; + } + } + + inline void addThing(const MAC &mac) + { + if (_thingCount >= ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS) return; + _thingTypes[_thingCount] = THING_MAC_ADDRESS; + mac.copyTo(_thingValues[_thingCount],6); + ++_thingCount; + } + + /** + * @param signer Signing identity, must have private key + * @return True if signature was successful + */ + inline bool sign(const Identity &signer) + { + if (signer.hasPrivate()) { + Buffer tmp; + _signedBy = signer.address(); + this->serialize(tmp,true); + _signature = signer.sign(tmp.data(),tmp.size()); + return true; + } + return false; + } + + /** + * @param RR Runtime environment to allow identity lookup for signedBy + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature + */ + int verify(const RuntimeEnvironment *RR,void *tPtr) const; + + template + inline void serialize(Buffer &b,const bool forSign = false) const + { + if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + + b.append(_networkId); + b.append(_ts); + b.append(_flags); + b.append(_id); + b.append((uint16_t)_thingCount); + for(unsigned int i=0,j=_thingCount;i + inline unsigned int deserialize(const Buffer &b,unsigned int startAt = 0) + { + unsigned int p = startAt; + + memset(this,0,sizeof(CertificateOfOwnership)); + + _networkId = b.template at(p); p += 8; + _ts = b.template at(p); p += 8; + _flags = b.template at(p); p += 8; + _id = b.template at(p); p += 4; + _thingCount = b.template at(p); p += 2; + for(unsigned int i=0,j=_thingCount;i(p) != ZT_C25519_SIGNATURE_LEN) + throw std::runtime_error("invalid signature length"); + p += 2; + memcpy(_signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); p += ZT_C25519_SIGNATURE_LEN; + } else { + p += 2 + b.template at(p); + } + + p += 2 + b.template at(p); + if (p > b.size()) + throw std::runtime_error("extended field overflow"); + + return (p - startAt); + } + + // Provides natural sort order by ID + inline bool operator<(const CertificateOfOwnership &coo) const { return (_id < coo._id); } + + inline bool operator==(const CertificateOfOwnership &coo) const { return (memcmp(this,&coo,sizeof(CertificateOfOwnership)) == 0); } + inline bool operator!=(const CertificateOfOwnership &coo) const { return (memcmp(this,&coo,sizeof(CertificateOfOwnership)) != 0); } + +private: + bool _owns(const Thing &t,const void *v,unsigned int l) const; + + uint64_t _networkId; + uint64_t _ts; + uint64_t _flags; + uint32_t _id; + uint16_t _thingCount; + uint8_t _thingTypes[ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS]; + uint8_t _thingValues[ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS][ZT_CERTIFICATEOFOWNERSHIP_MAX_THING_VALUE_SIZE]; + Address _issuedTo; + Address _signedBy; + C25519::Signature _signature; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/CertificateOfRepresentation.hpp b/node/CertificateOfRepresentation.hpp new file mode 100644 index 0000000..710ee57 --- /dev/null +++ b/node/CertificateOfRepresentation.hpp @@ -0,0 +1,180 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ZT_CERTIFICATEOFREPRESENTATION_HPP +#define ZT_CERTIFICATEOFREPRESENTATION_HPP + +#include "Constants.hpp" +#include "Credential.hpp" +#include "Address.hpp" +#include "C25519.hpp" +#include "Identity.hpp" +#include "Buffer.hpp" + +/** + * Maximum number of addresses allowed in a COR + */ +#define ZT_CERTIFICATEOFREPRESENTATION_MAX_ADDRESSES ZT_MAX_UPSTREAMS + +namespace ZeroTier { + +/** + * A signed enumeration of a node's roots (planet and moons) + * + * This is sent as part of HELLO and attests to which roots a node trusts + * to represent it on the network. Federated roots (moons) can send these + * further upstream to tell global roots which nodes they represent, making + * them reachable via federated roots if they are not reachable directly. + * + * As of 1.2.0 this is sent but not used. Right now nodes still always + * announce to planetary roots no matter what. In the future this can be + * used to implement even better fault tolerance for federation for the + * no roots are reachable case as well as a "privacy mode" where federated + * roots can shield nodes entirely and p2p connectivity behind them can + * be disabled. This will be desirable for a number of use cases. + */ +class CertificateOfRepresentation : public Credential +{ +public: + static inline Credential::Type credentialType() { return Credential::CREDENTIAL_TYPE_COR; } + + CertificateOfRepresentation() + { + memset(this,0,sizeof(CertificateOfRepresentation)); + } + + inline uint32_t id() const { return 0; } + inline uint64_t timestamp() const { return _timestamp; } + inline const Address &representative(const unsigned int i) const { return _reps[i]; } + inline unsigned int repCount() const { return _repCount; } + + inline void clear() + { + memset(this,0,sizeof(CertificateOfRepresentation)); + } + + /** + * Add a representative if space remains + * + * @param r Representative to add + * @return True if representative was added + */ + inline bool addRepresentative(const Address &r) + { + if (_repCount < ZT_CERTIFICATEOFREPRESENTATION_MAX_ADDRESSES) { + _reps[_repCount++] = r; + return true; + } + return false; + } + + /** + * Sign this COR with my identity + * + * @param myIdentity This node's identity + * @param ts COR timestamp for establishing new vs. old + */ + inline void sign(const Identity &myIdentity,const uint64_t ts) + { + _timestamp = ts; + Buffer tmp; + this->serialize(tmp,true); + _signature = myIdentity.sign(tmp.data(),tmp.size()); + } + + /** + * Verify this COR's signature + * + * @param senderIdentity Identity of sender of COR + * @return True if COR is valid + */ + inline bool verify(const Identity &senderIdentity) + { + try { + Buffer tmp; + this->serialize(tmp,true); + return senderIdentity.verify(tmp.data(),tmp.size(),_signature.data,ZT_C25519_SIGNATURE_LEN); + } catch ( ... ) { + return false; + } + } + + template + inline void serialize(Buffer &b,const bool forSign = false) const + { + if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + + b.append((uint64_t)_timestamp); + b.append((uint16_t)_repCount); + for(unsigned int i=0;i<_repCount;++i) + _reps[i].appendTo(b); + + if (!forSign) { + b.append((uint8_t)1); // 1 == Ed25519 signature + b.append((uint16_t)ZT_C25519_SIGNATURE_LEN); + b.append(_signature.data,ZT_C25519_SIGNATURE_LEN); + } + + b.append((uint16_t)0); // size of any additional fields, currently 0 + + if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + } + + template + inline unsigned int deserialize(const Buffer &b,unsigned int startAt = 0) + { + clear(); + + unsigned int p = startAt; + + _timestamp = b.template at(p); p += 8; + const unsigned int rc = b.template at(p); p += 2; + for(unsigned int i=0;i ZT_CERTIFICATEOFREPRESENTATION_MAX_ADDRESSES) ? ZT_CERTIFICATEOFREPRESENTATION_MAX_ADDRESSES : rc; + + if (b[p++] == 1) { + if (b.template at(p) == ZT_C25519_SIGNATURE_LEN) { + p += 2; + memcpy(_signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); + p += ZT_C25519_SIGNATURE_LEN; + } else throw std::runtime_error("invalid signature"); + } else { + p += 2 + b.template at(p); + } + + p += 2 + b.template at(p); + if (p > b.size()) + throw std::runtime_error("extended field overflow"); + + return (p - startAt); + } + +private: + uint64_t _timestamp; + Address _reps[ZT_CERTIFICATEOFREPRESENTATION_MAX_ADDRESSES]; + unsigned int _repCount; + C25519::Signature _signature; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Cluster.cpp b/node/Cluster.cpp index f590ad1..54206f9 100644 --- a/node/Cluster.cpp +++ b/node/Cluster.cpp @@ -44,6 +44,7 @@ #include "Packet.hpp" #include "Switch.hpp" #include "Node.hpp" +#include "Network.hpp" #include "Array.hpp" namespace ZeroTier { @@ -254,7 +255,7 @@ void Cluster::handleIncomingStateMessage(const void *msg,unsigned int len) // 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.encrypt12(polykey,polykey,sizeof(polykey)); + s20.crypt12(polykey,polykey,sizeof(polykey)); // Compute 16-byte MAC char mac[ZT_POLY1305_MAC_LEN]; @@ -266,7 +267,7 @@ void Cluster::handleIncomingStateMessage(const void *msg,unsigned int len) // Decrypt! dmsg.setSize(len - 24); - s20.decrypt12(reinterpret_cast(msg) + 24,const_cast(dmsg.data()),dmsg.size()); + s20.crypt12(reinterpret_cast(msg) + 24,const_cast(dmsg.data()),dmsg.size()); } if (dmsg.size() < 4) @@ -341,17 +342,20 @@ void Cluster::handleIncomingStateMessage(const void *msg,unsigned int len) Identity id; ptr += id.deserialize(dmsg,ptr); if (id) { - RR->topology->saveIdentity(id); - { Mutex::Lock _l(_remotePeers_m); - _remotePeers[std::pair(id.address(),(unsigned int)fromMemberId)] = RR->node->now(); + _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;isendViaCluster(q[i]->fromPeerAddress,q[i]->toPeerAddress,q[i]->data,q[i]->len,q[i]->unite); + this->relayViaCluster(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); @@ -361,7 +365,7 @@ void Cluster::handleIncomingStateMessage(const void *msg,unsigned int len) 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->hasClusterOptimalPath(RR->node->now())) ) { + if ( (peer) && (peer->hasLocalClusterOptimalPath(RR->node->now())) ) { Buffer<1024> buf; peer->identity().serialize(buf); Mutex::Lock _l2(_members[fromMemberId].lock); @@ -396,7 +400,7 @@ void Cluster::handleIncomingStateMessage(const void *msg,unsigned int len) SharedPtr localPeer(RR->topology->getPeerNoCache(localPeerAddress)); if ((localPeer)&&(numRemotePeerPaths > 0)) { InetAddress bestLocalV4,bestLocalV6; - localPeer->getBestActiveAddresses(now,bestLocalV4,bestLocalV6); + localPeer->getRendezvousAddresses(now,bestLocalV4,bestLocalV6); InetAddress bestRemoteV4,bestRemoteV6; for(unsigned int i=0;isw->send(rendezvousForLocal,true,0); + RR->sw->send((void *)0,rendezvousForLocal,true); } } } break; @@ -466,9 +470,18 @@ void Cluster::handleIncomingStateMessage(const void *msg,unsigned int len) 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(outp,true,0); + 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); @@ -494,7 +507,84 @@ void Cluster::broadcastHavePeer(const Identity &id) } } -void Cluster::sendViaCluster(const Address &fromPeerAddress,const Address &toPeerAddress,const void *data,unsigned int len,bool unite) +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; @@ -502,87 +592,101 @@ void Cluster::sendViaCluster(const Address &fromPeerAddress,const Address &toPee const uint64_t now = RR->node->now(); uint64_t mostRecentTs = 0; - unsigned int mostRecentMemberId = 0xffffffff; + int mostRecentMemberId = -1; { Mutex::Lock _l2(_remotePeers_m); - std::map< std::pair,uint64_t >::const_iterator rpe(_remotePeers.lower_bound(std::pair(toPeerAddress,0))); + 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 > mostRecentTs) { - mostRecentTs = rpe->second; - mostRecentMemberId = rpe->first.second; + else if (rpe->second.lastHavePeerReceived > mostRecentTs) { + mostRecentTs = rpe->second.lastHavePeerReceived; + mostRecentMemberId = (int)rpe->first.second; } ++rpe; } } - const uint64_t age = now - mostRecentTs; - if (age >= (ZT_PEER_ACTIVITY_TIMEOUT / 3)) { - const bool enqueueAndWait = ((age >= ZT_PEER_ACTIVITY_TIMEOUT)||(mostRecentMemberId > 0xffff)); + 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). - char tmp[ZT_ADDRESS_LENGTH]; - toPeerAddress.copyTo(tmp,ZT_ADDRESS_LENGTH); + bool sendWantPeer = true; { - 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); + 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("sendViaCluster %s -> %s enqueueing to wait for HAVE_PEER",fromPeerAddress.toString().c_str(),toPeerAddress.toString().c_str()); + 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; } } - Buffer<1024> buf; - if (unite) { - InetAddress v4,v6; - if (fromPeerAddress) { - SharedPtr fromPeer(RR->topology->getPeerNoCache(fromPeerAddress)); - if (fromPeer) - fromPeer->getBestActiveAddresses(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 (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) - v4.serialize(buf); + ++addrCount; 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("sendViaCluster 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(*i1,*i2,data,len); - return; - } + ++addrCount; + if (addrCount) { + toPeerAddress.appendTo(buf); + fromPeerAddress.appendTo(buf); + buf.append(addrCount); + if (v4) + v4.serialize(buf); + if (v6) + v6.serialize(buf); } } - TRACE("sendViaCluster 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); - return; + { + 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); + } } } @@ -644,8 +748,8 @@ void Cluster::doPeriodicTasks() _lastCleanedRemotePeers = now; Mutex::Lock _l(_remotePeers_m); - for(std::map< std::pair,uint64_t >::iterator rp(_remotePeers.begin());rp!=_remotePeers.end();) { - if ((now - rp->second) >= ZT_PEER_ACTIVITY_TIMEOUT) + 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; } @@ -719,7 +823,9 @@ bool Cluster::findBetterEndpoint(InetAddress &redirectTo,const Address &peerAddr 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) { @@ -731,7 +837,9 @@ bool Cluster::findBetterEndpoint(InetAddress &redirectTo,const Address &peerAddr 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; } } @@ -754,6 +862,19 @@ bool Cluster::findBetterEndpoint(InetAddress &redirectTo,const Address &peerAddr } } +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(); @@ -833,10 +954,10 @@ void Cluster::_flush(uint16_t memberId) // 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.encrypt12(polykey,polykey,sizeof(polykey)); + s20.crypt12(polykey,polykey,sizeof(polykey)); // Encrypt m.q in place - s20.encrypt12(reinterpret_cast(m.q.data()) + 24,const_cast(reinterpret_cast(m.q.data())) + 24,m.q.size() - 24); + 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]; @@ -860,7 +981,7 @@ void Cluster::_flush(uint16_t memberId) void Cluster::_doREMOTE_WHOIS(uint64_t fromMemberId,const Packet &remotep) { if (remotep.payloadLength() >= ZT_ADDRESS_LENGTH) { - Identity queried(RR->topology->getIdentity(Address(remotep.payload(),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); diff --git a/node/Cluster.hpp b/node/Cluster.hpp index dafbf42..08e32a9 100644 --- a/node/Cluster.hpp +++ b/node/Cluster.hpp @@ -88,6 +88,11 @@ */ #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; @@ -216,14 +221,13 @@ public: /** * Replicate a network config for a network we belong to: - * <[8] 64-bit network ID> - * <[2] 16-bit length of network config> - * <[...] serialized network config> + * <[...] 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. * - * TODO: not implemented yet! + * 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 }; @@ -268,7 +272,38 @@ public: void broadcastHavePeer(const Identity &id); /** - * Send this packet via another node in this cluster if another node has this peer + * 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 @@ -280,7 +315,7 @@ public: * @param len Length of packet or fragment * @param unite If true, also request proxy unite across cluster */ - void sendViaCluster(const Address &fromPeerAddress,const Address &toPeerAddress,const void *data,unsigned int len,bool unite); + void relayViaCluster(const Address &fromPeerAddress,const Address &toPeerAddress,const void *data,unsigned int len,bool unite); /** * Send a distributed query to other cluster members @@ -323,6 +358,12 @@ public: */ 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) * @@ -391,7 +432,15 @@ private: std::vector _memberIds; Mutex _memberIds_m; - std::map< std::pair,uint64_t > _remotePeers; // we need ordered behavior and lower_bound here + 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; diff --git a/node/Constants.hpp b/node/Constants.hpp index dc36b3a..93184ef 100644 --- a/node/Constants.hpp +++ b/node/Constants.hpp @@ -179,16 +179,16 @@ */ #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 -/** - * Overriding granularity for timer tasks to prevent CPU-intensive thrashing on every packet - */ -#define ZT_CORE_TIMER_TASK_GRANULARITY 500 - /** * How long to remember peer records in RAM if they haven't been used */ @@ -202,7 +202,7 @@ /** * Maximum identity WHOIS retries (each attempt tries consulting a different peer) */ -#define ZT_MAX_WHOIS_RETRIES 3 +#define ZT_MAX_WHOIS_RETRIES 4 /** * Transmit queue entry timeout @@ -214,6 +214,11 @@ */ #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) * @@ -221,16 +226,31 @@ */ #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 * @@ -239,30 +259,49 @@ #define ZT_MULTICAST_TRANSMIT_TIMEOUT 5000 /** - * Default maximum number of peers to address with a single multicast (if unspecified in network config) + * Delay between checks of peer pings, etc., and also related housekeeping tasks */ -#define ZT_MULTICAST_DEFAULT_LIMIT 32 +#define ZT_PING_CHECK_INVERVAL 5000 /** - * How frequently to send a zero-byte UDP keepalive packet - * - * There are NATs with timeouts as short as 20 seconds, so this turns out - * to be needed. + * How frequently to send heartbeats over in-use paths */ -#define ZT_NAT_KEEPALIVE_DELAY 19000 +#define ZT_PATH_HEARTBEAT_PERIOD 14000 /** - * Delay between scans of the topology active peer DB for peers that need ping - * - * This is also how often pings will be retried to upstream peers (relays, roots) - * constantly until something is heard. + * Paths are considered inactive if they have not received traffic in this long */ -#define ZT_PING_CHECK_INVERVAL 9500 +#define ZT_PATH_ALIVE_TIMEOUT 45000 /** - * Delay between ordinary case pings of direct links + * Minimum time between attempts to check dead paths to see if they can be re-awakened */ -#define ZT_PEER_DIRECT_PING_DELAY 60000 +#define ZT_PATH_MIN_REACTIVATE_INTERVAL 2500 + +/** + * Do not accept HELLOs over a given path more often than this + */ +#define ZT_PATH_HELLO_RATE_LIMIT 1000 + +/** + * Delay between full-fledge pings of directly connected peers + */ +#define ZT_PEER_PING_PERIOD 60000 + +/** + * Paths are considered expired if they have not sent us a real packet in this long + */ +#define ZT_PEER_PATH_EXPIRATION ((ZT_PEER_PING_PERIOD * 4) + 3000) + +/** + * Send a full HELLO every this often (ms) + */ +#define ZT_PEER_SEND_FULL_HELLO_EVERY (ZT_PEER_PING_PERIOD * 2) + +/** + * How often to retry expired paths that we're still remembering + */ +#define ZT_PEER_EXPIRED_PATH_TRIAL_PERIOD (ZT_PEER_PING_PERIOD * 10) /** * Timeout for overall peer activity (measured from last receive) @@ -270,19 +309,14 @@ #define ZT_PEER_ACTIVITY_TIMEOUT 500000 /** - * Timeout for path activity + * General rate limit timeout for multiple packet types (HELLO, etc.) */ -#define ZT_PATH_ACTIVITY_TIMEOUT ZT_PEER_ACTIVITY_TIMEOUT +#define ZT_PEER_GENERAL_INBOUND_RATE_LIMIT 500 /** - * No answer timeout to trigger dead path detection + * General limit for max RTT for requests over the network */ -#define ZT_PEER_DEAD_PATH_DETECTION_NO_ANSWER_TIMEOUT 2000 - -/** - * Probation threshold after which a path becomes dead - */ -#define ZT_PEER_DEAD_PATH_DETECTION_MAX_PROBATION 3 +#define ZT_GENERAL_RTT_LIMIT 5000 /** * Delay between requests for updated network autoconf information @@ -302,19 +336,9 @@ #define ZT_MIN_UNITE_INTERVAL 30000 /** - * Delay between initial direct NAT-t packet and more aggressive techniques - * - * This may also be a delay before sending the first packet if we determine - * that we should wait for the remote to initiate rendezvous first. + * How often should peers try memorized or statically defined paths? */ -#define ZT_NAT_T_TACTICAL_ESCALATION_DELAY 1000 - -/** - * How long (max) to remember network certificates of membership? - * - * This only applies to networks we don't belong to. - */ -#define ZT_PEER_NETWORK_COM_EXPIRATION 3600000 +#define ZT_TRY_MEMORIZED_PATH_INTERVAL 30000 /** * Sanity limit on maximum bridge routes @@ -330,7 +354,7 @@ /** * If there is no known route, spam to up to this many active bridges */ -#define ZT_MAX_BRIDGE_SPAM 16 +#define ZT_MAX_BRIDGE_SPAM 32 /** * Interval between direct path pushes in milliseconds @@ -357,22 +381,54 @@ #define ZT_PUSH_DIRECT_PATHS_MAX_PER_SCOPE_AND_FAMILY 4 /** - * Enable support for old Dictionary based network configs + * Time horizon for VERB_NETWORK_CREDENTIALS cutoff */ -#define ZT_SUPPORT_OLD_STYLE_NETCONF 1 +#define ZT_PEER_CREDENTIALS_CUTOFF_TIME 60000 /** - * A test pseudo-network-ID that can be joined - * - * Joining this network ID will result in a network with no IP addressing - * and default parameters. No network configuration master will be consulted - * and instead a static config will be used. This is used in built-in testnet - * scenarios and can also be used for external testing. - * - * This is an impossible real network ID since 0xff is a reserved address - * prefix. + * Maximum number of VERB_NETWORK_CREDENTIALS within cutoff time */ -#define ZT_TEST_NETWORK_ID 0xffffffffffffffffULL +#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) @@ -383,6 +439,11 @@ #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 diff --git a/node/Credential.hpp b/node/Credential.hpp new file mode 100644 index 0000000..0ae2a0a --- /dev/null +++ b/node/Credential.hpp @@ -0,0 +1,58 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ZT_CREDENTIAL_HPP +#define ZT_CREDENTIAL_HPP + +#include +#include +#include + +#include +#include +#include +#include + +#include "Constants.hpp" + +namespace ZeroTier { + +/** + * Base class for credentials + */ +class Credential +{ +public: + /** + * Do not change type code IDs -- these are used in Revocation objects and elsewhere + */ + enum Type + { + CREDENTIAL_TYPE_NULL = 0, + CREDENTIAL_TYPE_COM = 1, // CertificateOfMembership + CREDENTIAL_TYPE_CAPABILITY = 2, + CREDENTIAL_TYPE_TAG = 3, + CREDENTIAL_TYPE_COO = 4, // CertificateOfOwnership + CREDENTIAL_TYPE_COR = 5, // CertificateOfRepresentation + CREDENTIAL_TYPE_REVOCATION = 6 + }; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/DeferredPackets.cpp b/node/DeferredPackets.cpp deleted file mode 100644 index 192b407..0000000 --- a/node/DeferredPackets.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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 "DeferredPackets.hpp" -#include "IncomingPacket.hpp" -#include "RuntimeEnvironment.hpp" -#include "Node.hpp" - -namespace ZeroTier { - -DeferredPackets::DeferredPackets(const RuntimeEnvironment *renv) : - RR(renv), - _waiting(0), - _die(false) -{ -} - -DeferredPackets::~DeferredPackets() -{ - _q_m.lock(); - _die = true; - _q_m.unlock(); - - for(;;) { - _q_s.post(); - - _q_m.lock(); - if (_waiting <= 0) { - _q_m.unlock(); - break; - } else { - _q_m.unlock(); - } - } -} - -bool DeferredPackets::enqueue(IncomingPacket *pkt) -{ - { - Mutex::Lock _l(_q_m); - if (_q.size() >= ZT_DEFFEREDPACKETS_MAX) - return false; - _q.push_back(*pkt); - } - _q_s.post(); - return true; -} - -int DeferredPackets::process() -{ - std::list pkt; - - _q_m.lock(); - - if (_die) { - _q_m.unlock(); - return -1; - } - - while (_q.empty()) { - ++_waiting; - _q_m.unlock(); - _q_s.wait(); - _q_m.lock(); - --_waiting; - if (_die) { - _q_m.unlock(); - return -1; - } - } - - // Move item from _q list to a dummy list here to avoid copying packet - pkt.splice(pkt.end(),_q,_q.begin()); - - _q_m.unlock(); - - try { - pkt.front().tryDecode(RR,true); - } catch ( ... ) {} // drop invalids - - return 1; -} - -} // namespace ZeroTier diff --git a/node/DeferredPackets.hpp b/node/DeferredPackets.hpp deleted file mode 100644 index a985539..0000000 --- a/node/DeferredPackets.hpp +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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_DEFERREDPACKETS_HPP -#define ZT_DEFERREDPACKETS_HPP - -#include - -#include "Constants.hpp" -#include "SharedPtr.hpp" -#include "Mutex.hpp" -#include "DeferredPackets.hpp" -#include "BinarySemaphore.hpp" - -/** - * Maximum number of deferred packets - */ -#define ZT_DEFFEREDPACKETS_MAX 256 - -namespace ZeroTier { - -class IncomingPacket; -class RuntimeEnvironment; - -/** - * Deferred packets - * - * IncomingPacket can defer its decoding this way by enqueueing itself here. - * When this is done, deferredDecode() is called later. This is done for - * operations that may be expensive to allow them to potentially be handled - * in the background or rate limited to maintain quality of service for more - * routine operations. - */ -class DeferredPackets -{ -public: - DeferredPackets(const RuntimeEnvironment *renv); - ~DeferredPackets(); - - /** - * Enqueue a packet - * - * @param pkt Packet to process later (possibly in the background) - * @return False if queue is full - */ - bool enqueue(IncomingPacket *pkt); - - /** - * Wait for and then process a deferred packet - * - * If we are shutting down (in destructor), this returns -1 and should - * not be called again. Otherwise it returns the number of packets - * processed. - * - * @return Number processed or -1 if shutting down - */ - int process(); - -private: - std::list _q; - const RuntimeEnvironment *const RR; - volatile int _waiting; - volatile bool _die; - Mutex _q_m; - BinarySemaphore _q_s; -}; - -} // namespace ZeroTier - -#endif diff --git a/node/Dictionary.hpp b/node/Dictionary.hpp index 59fc4bb..0db13b6 100644 --- a/node/Dictionary.hpp +++ b/node/Dictionary.hpp @@ -61,15 +61,23 @@ public: Dictionary(const char *s) { - Utils::scopy(_d,sizeof(_d),s); + if (s) { + Utils::scopy(_d,sizeof(_d),s); + } else { + _d[0] = (char)0; + } } Dictionary(const char *s,unsigned int len) { - if (len > (C-1)) - len = C-1; - memcpy(_d,s,len); - _d[len] = (char)0; + 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) @@ -91,7 +99,12 @@ public: */ inline bool load(const char *s) { - return Utils::scopy(_d,sizeof(_d),s); + if (s) { + return Utils::scopy(_d,sizeof(_d),s); + } else { + _d[0] = (char)0; + return true; + } } /** @@ -163,12 +176,12 @@ public: j = 0; esc = false; ++p; - while ((*p != 0)&&(*p != '\r')&&(*p != '\n')) { + while ((*p != 0)&&(*p != 13)&&(*p != 10)) { if (esc) { esc = false; switch(*p) { - case 'r': dest[j++] = '\r'; break; - case 'n': dest[j++] = '\n'; break; + case 'r': dest[j++] = 13; break; + case 'n': dest[j++] = 10; break; case '0': dest[j++] = (char)0; break; case 'e': dest[j++] = '='; break; default: dest[j++] = *p; break; @@ -194,7 +207,7 @@ public: dest[j] = (char)0; return j; } else { - while ((*p)&&(*p != '\r')&&(*p != '\n')) { + while ((*p)&&(*p != 13)&&(*p != 10)) { if (++p == eof) { dest[0] = (char)0; return -1; @@ -286,7 +299,7 @@ public: unsigned int j = i; if (j > 0) { - _d[j++] = '\n'; + _d[j++] = (char)10; if (j == C) { _d[i] = (char)0; return false; @@ -313,8 +326,8 @@ public: while ( ((vlen < 0)&&(*p)) || (k < vlen) ) { switch(*p) { case 0: - case '\r': - case '\n': + case 13: + case 10: case '\\': case '=': _d[j++] = '\\'; @@ -324,8 +337,8 @@ public: } switch(*p) { case 0: _d[j++] = '0'; break; - case '\r': _d[j++] = 'r'; break; - case '\n': _d[j++] = 'n'; break; + case 13: _d[j++] = 'r'; break; + case 10: _d[j++] = 'n'; break; case '\\': _d[j++] = '\\'; break; case '=': _d[j++] = 'e'; break; } @@ -403,56 +416,14 @@ public: 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 Dictionary data as a 0-terminated C-string - */ - inline const char *data() const { return _d; } - /** * @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]; }; diff --git a/node/Hashtable.hpp b/node/Hashtable.hpp index f06b223..66f2990 100644 --- a/node/Hashtable.hpp +++ b/node/Hashtable.hpp @@ -103,9 +103,9 @@ public: friend class Hashtable::Iterator; /** - * @param bc Initial capacity in buckets (default: 128, must be nonzero) + * @param bc Initial capacity in buckets (default: 64, must be nonzero) */ - Hashtable(unsigned long bc = 128) : + Hashtable(unsigned long bc = 64) : _t(reinterpret_cast<_Bucket **>(::malloc(sizeof(_Bucket *) * bc))), _bc(bc), _s(0) @@ -362,7 +362,7 @@ private: template static inline unsigned long _hc(const O &obj) { - return obj.hashCode(); + return (unsigned long)obj.hashCode(); } static inline unsigned long _hc(const uint64_t i) { diff --git a/node/Identity.cpp b/node/Identity.cpp index 6f89a1e..d1b21e9 100644 --- a/node/Identity.cpp +++ b/node/Identity.cpp @@ -45,8 +45,8 @@ static inline void _computeMemoryHardHash(const void *publicKey,unsigned int pub // 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.encrypt20((char *)genmem,(char *)genmem,64); + Salsa20 s20(digest,(char *)digest + 32); + s20.crypt20((char *)genmem,(char *)genmem,64); for(unsigned long i=64;i &b,bool includePrivate = false) const { _address.appendTo(b); - b.append((unsigned char)IDENTITY_TYPE_C25519); + 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()); @@ -257,7 +244,7 @@ public: _address.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; - if (b[p++] != IDENTITY_TYPE_C25519) + 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()); @@ -295,6 +282,24 @@ public: 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 */ diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp index 37af842..303160e 100644 --- a/node/IncomingPacket.cpp +++ b/node/IncomingPacket.cpp @@ -36,11 +36,15 @@ #include "World.hpp" #include "Cluster.hpp" #include "Node.hpp" -#include "DeferredPackets.hpp" +#include "CertificateOfMembership.hpp" +#include "CertificateOfRepresentation.hpp" +#include "Capability.hpp" +#include "Tag.hpp" +#include "Revocation.hpp" namespace ZeroTier { -bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR,bool deferred) +bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR,void *tPtr) { const Address sourceAddress(source()); @@ -52,159 +56,157 @@ bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR,bool deferred) // 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(_remoteAddress,trustedPathId())) { + 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(),_remoteAddress.toString().c_str(),trustedPathId()); + 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(),_remoteAddress.toString().c_str(),trustedPathId(),_remoteAddress.toString().c_str()); + 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)) { - // Unencrypted HELLOs require some potentially expensive verification, so - // do this in the background if background processing is enabled. - if ((RR->dpEnabled > 0)&&(!deferred)) { - RR->dp->enqueue(this); - return true; // 'handled' via deferring to background thread(s) - } else { - // A null pointer for peer to _doHELLO() tells it to run its own - // special internal authentication logic. This is done for unencrypted - // HELLOs to learn new identities, etc. - SharedPtr tmp; - return _doHELLO(RR,tmp); - } + // Only HELLO is allowed in the clear, but will still have a MAC + return _doHELLO(RR,tPtr,false); } - SharedPtr peer(RR->topology->getPeer(sourceAddress)); + const SharedPtr peer(RR->topology->getPeer(tPtr,sourceAddress)); if (peer) { if (!trusted) { if (!dearmor(peer->key())) { - TRACE("dropped packet from %s(%s), MAC authentication failed (size: %u)",sourceAddress.toString().c_str(),_remoteAddress.toString().c_str(),size()); + //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()) { - TRACE("dropped packet from %s(%s), compressed data invalid",sourceAddress.toString().c_str(),_remoteAddress.toString().c_str()); + //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(),_remoteAddress.toString().c_str()); + //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(_localAddress,_remoteAddress,hops(),packetId(),v,0,Packet::VERB_NOP); + peer->received(tPtr,_path,hops(),packetId(),v,0,Packet::VERB_NOP,false); return true; - case Packet::VERB_HELLO: return _doHELLO(RR,peer); - case Packet::VERB_ERROR: return _doERROR(RR,peer); - case Packet::VERB_OK: return _doOK(RR,peer); - case Packet::VERB_WHOIS: return _doWHOIS(RR,peer); - case Packet::VERB_RENDEZVOUS: return _doRENDEZVOUS(RR,peer); - case Packet::VERB_FRAME: return _doFRAME(RR,peer); - case Packet::VERB_EXT_FRAME: return _doEXT_FRAME(RR,peer); - case Packet::VERB_ECHO: return _doECHO(RR,peer); - case Packet::VERB_MULTICAST_LIKE: return _doMULTICAST_LIKE(RR,peer); - case Packet::VERB_NETWORK_MEMBERSHIP_CERTIFICATE: return _doNETWORK_MEMBERSHIP_CERTIFICATE(RR,peer); - case Packet::VERB_NETWORK_CONFIG_REQUEST: return _doNETWORK_CONFIG_REQUEST(RR,peer); - case Packet::VERB_NETWORK_CONFIG_REFRESH: return _doNETWORK_CONFIG_REFRESH(RR,peer); - case Packet::VERB_MULTICAST_GATHER: return _doMULTICAST_GATHER(RR,peer); - case Packet::VERB_MULTICAST_FRAME: return _doMULTICAST_FRAME(RR,peer); - case Packet::VERB_PUSH_DIRECT_PATHS: return _doPUSH_DIRECT_PATHS(RR,peer); - case Packet::VERB_CIRCUIT_TEST: return _doCIRCUIT_TEST(RR,peer); - case Packet::VERB_CIRCUIT_TEST_REPORT: return _doCIRCUIT_TEST_REPORT(RR,peer); - case Packet::VERB_REQUEST_PROOF_OF_WORK: return _doREQUEST_PROOF_OF_WORK(RR,peer); + 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(sourceAddress); + 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(),_remoteAddress.toString().c_str()); + 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,const SharedPtr &peer) +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(),_remoteAddress.toString().c_str(),Packet::verbString(inReVerb)); + //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) { - SharedPtr network(RR->node->network(at(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD))); + 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) { - SharedPtr network(RR->node->network(at(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD))); + 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: - if (RR->topology->isRoot(peer->identity())) - RR->node->postEvent(ZT_EVENT_FATAL_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: { - /* Note: certificates are public so it's safe to push them to anyone - * who asks. We won't communicate unless we also get a certificate - * from the remote that agrees. */ - SharedPtr network(RR->node->network(at(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD))); - if ((network)&&(network->hasConfig())&&(network->config().com)) { - Packet outp(peer->address(),RR->identity.address(),Packet::VERB_NETWORK_MEMBERSHIP_CERTIFICATE); - network->config().com.serialize(outp); - outp.armor(peer->key(),true); - RR->node->putPacket(_localAddress,_remoteAddress,outp.data(),outp.size()); - } + // 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_: { - SharedPtr network(RR->node->network(at(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD))); + // 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: { - uint64_t nwid = at(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD); - 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",nwid,peer->address().toString().c_str(),mg.toString().c_str()); - RR->mc->remove(nwid,mg,peer->address()); + // 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(_localAddress,_remoteAddress,hops(),packetId(),Packet::VERB_ERROR,inRePacketId,inReVerb); + 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(),_remoteAddress.toString().c_str()); + 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,SharedPtr &peer) +bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool alreadyAuthenticated) { - /* Note: this is the only packet ever sent in the clear, and it's also - * the only packet that we authenticate via a different path. Authentication - * occurs here and is based on the validity of the identity and the - * integrity of the packet's MAC, but it must be done after we check - * the identity since HELLO is a mechanism for learning new identities - * in the first place. */ - 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]; @@ -212,53 +214,44 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,SharedPtr &peer 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; - InetAddress externalSurfaceAddress; - uint64_t worldId = ZT_WORLD_ID_NULL; - uint64_t worldTimestamp = 0; - { - unsigned int ptr = ZT_PROTO_VERB_HELLO_IDX_IDENTITY + id.deserialize(*this,ZT_PROTO_VERB_HELLO_IDX_IDENTITY); - if (ptr < size()) // ZeroTier One < 1.0.3 did not include physical destination address info - ptr += externalSurfaceAddress.deserialize(*this,ptr); - if ((ptr + 16) <= size()) { // older versions also did not include World IDs or timestamps - worldId = at(ptr); ptr += 8; - worldTimestamp = at(ptr); - } - } + 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(),_remoteAddress.toString().c_str()); + 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 not for sending address",fromAddress.toString().c_str(),_remoteAddress.toString().c_str()); + TRACE("dropped HELLO from %s(%s): identity does not match packet source address",fromAddress.toString().c_str(),_path->address().toString().c_str()); return true; } - if (!peer) { // peer == NULL is the normal case here - peer = RR->topology->getPeer(id.address()); - if (peer) { - // We already have an identity with this address -- check for collisions - + 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 - unsigned char key[ZT_PEER_SECRET_KEY_LENGTH]; + // 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(),_remoteAddress.toString().c_str()); + 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((unsigned char)Packet::VERB_HELLO); + outp.append((uint8_t)Packet::VERB_HELLO); outp.append((uint64_t)pid); - outp.append((unsigned char)Packet::ERROR_IDENTITY_COLLISION); - outp.armor(key,true); - RR->node->putPacket(_localAddress,_remoteAddress,outp.data(),outp.size()); + 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(),_remoteAddress.toString().c_str()); + 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(),_remoteAddress.toString().c_str()); + TRACE("rejected HELLO from %s(%s): key agreement failed",id.address().toString().c_str(),_path->address().toString().c_str()); } return true; @@ -266,37 +259,89 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,SharedPtr &peer // 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(),_remoteAddress.toString().c_str()); + 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 { - // We don't already have an identity with this address -- validate and learn it + } // else if alreadyAuthenticated then continue at // VALID + } else { + // We don't already have an identity with this address -- validate and learn it - // Check identity proof of work - if (!id.locallyValidate()) { - TRACE("dropped HELLO from %s(%s): identity invalid",id.address().toString().c_str(),_remoteAddress.toString().c_str()); - return true; - } - - // Check packet integrity and authentication - 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(),_remoteAddress.toString().c_str()); - return true; - } - peer = RR->topology->addPeer(newPeer); - - // Continue at // VALID + // 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; } - // VALID -- if we made it here, packet passed identity and authenticity checks! + // 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 } - if (externalSurfaceAddress) - RR->sa->iam(id.address(),_localAddress,_remoteAddress,externalSurfaceAddress,RR->topology->isRoot(id),RR->node->now()); + // 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); @@ -306,8 +351,9 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,SharedPtr &peer 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) { - _remoteAddress.serialize(outp); + _path->address().serialize(outp); } else { /* LEGACY COMPATIBILITY HACK: * @@ -332,114 +378,143 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,SharedPtr &peer * 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(_remoteAddress); + InetAddress tmpa(_path->address()); tmpa.setPort(0); tmpa.serialize(outp); } - if ((worldId != ZT_WORLD_ID_NULL)&&(RR->topology->worldTimestamp() > worldTimestamp)&&(worldId == RR->topology->worldId())) { - World w(RR->topology->world()); - const unsigned int sizeAt = outp.size(); - outp.addSize(2); // make room for 16-bit size field - w.serialize(outp,false); - outp.setAt(sizeAt,(uint16_t)(outp.size() - (sizeAt + 2))); - } else { - outp.append((uint16_t)0); // no world update needed + 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))); - outp.armor(peer->key(),true); - RR->node->putPacket(_localAddress,_remoteAddress,outp.data(),outp.size()); + 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(_localAddress,_remoteAddress,hops(),pid,Packet::VERB_HELLO,0,Packet::VERB_NOP); + 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(),_remoteAddress.toString().c_str()); + 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,const SharedPtr &peer) +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); - //TRACE("%s(%s): OK(%s)",source().toString().c_str(),_remoteAddress.toString().c_str(),Packet::verbString(inReVerb)); + 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 unsigned int latency = std::min((unsigned int)(RR->node->now() - at(ZT_PROTO_VERB_HELLO__OK__IDX_TIMESTAMP)),(unsigned int)0xffff); + 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(),_remoteAddress.toString().c_str()); + TRACE("%s(%s): OK(HELLO) dropped, protocol version too old",source().toString().c_str(),_path->address().toString().c_str()); return true; } - const bool trusted = RR->topology->isRoot(peer->identity()); - InetAddress externalSurfaceAddress; unsigned int ptr = ZT_PROTO_VERB_HELLO__OK__IDX_REVISION + 2; - if (ptr < size()) // ZeroTier One < 1.0.3 did not include this field + + // Get reported external surface address if present + if (ptr < size()) ptr += externalSurfaceAddress.deserialize(*this,ptr); - if ((trusted)&&((ptr + 2) <= size())) { // older versions also did not include this field, and right now we only use if from a root - World worldUpdate; - const unsigned int worldLen = at(ptr); ptr += 2; - if (worldLen > 0) { - World w; - w.deserialize(*this,ptr); - RR->topology->worldUpdateIfValid(w); + + // 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; } } - TRACE("%s(%s): OK(HELLO), version %u.%u.%u, latency %u, reported external address %s",source().toString().c_str(),_remoteAddress.toString().c_str(),vMajor,vMinor,vRevision,latency,((externalSurfaceAddress) ? externalSurfaceAddress.toString().c_str() : "(none)")); + // 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; + } - peer->addDirectLatencyMeasurment(latency); +#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) - RR->sa->iam(peer->address(),_localAddress,_remoteAddress,externalSurfaceAddress,trusted,RR->node->now()); + 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->isRoot(peer->identity())) { + case Packet::VERB_WHOIS: + if (RR->topology->isUpstream(peer->identity())) { const Identity id(*this,ZT_PROTO_VERB_WHOIS__OK__IDX_IDENTITY); - // Right now we can skip this since OK(WHOIS) is only accepted from - // roots. In the future it should be done if we query less trusted - // sources. - //if (id.locallyValidate()) - RR->sw->doAnythingWaitingForPeer(RR->topology->addPeer(SharedPtr(new Peer(RR,RR->identity,id)))); + RR->sw->doAnythingWaitingForPeer(tPtr,RR->topology->addPeer(tPtr,SharedPtr(new Peer(RR,RR->identity,id)))); } - } break; + break; case Packet::VERB_NETWORK_CONFIG_REQUEST: { - const SharedPtr nw(RR->node->network(at(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_NETWORK_ID))); - if ((nw)&&(nw->controller() == peer->address())) { - const unsigned int nclen = at(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT_LEN); - if (nclen) { - Dictionary dconf((const char *)field(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT,nclen),nclen); - NetworkConfig nconf; - if (nconf.fromDictionary(dconf)) { - nw->setConfiguration(nconf,true); - TRACE("got network configuration for network %.16llx from %s",(unsigned long long)nw->id(),source().toString().c_str()); - } - } - } + 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_ECHO: { - //} break; - case Packet::VERB_MULTICAST_GATHER: { const uint64_t nwid = at(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_NETWORK_ID); - 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(),_remoteAddress.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(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)); + 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: { @@ -447,363 +522,523 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,const SharedPtr &p 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(),_remoteAddress.toString().c_str(),nwid,mg.toString().c_str(),flags); + //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); - unsigned int offset = 0; + const SharedPtr network(RR->node->network(nwid)); + if (network) { + unsigned int offset = 0; - if ((flags & 0x01) != 0) { - // OK(MULTICAST_FRAME) includes certificate of membership update - CertificateOfMembership com; - offset += com.deserialize(*this,ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_COM_AND_GATHER_RESULTS); - peer->validateAndSetNetworkMembershipCertificate(nwid,com); - } + 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(RR->node->now(),nwid,mg,field(offset,count * 5),count,totalKnown); + 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(_localAddress,_remoteAddress,hops(),packetId(),Packet::VERB_OK,inRePacketId,inReVerb); + 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(),_remoteAddress.toString().c_str()); + 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,const SharedPtr &peer) +bool IncomingPacket::_doWHOIS(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { try { - if (payloadLength() == ZT_ADDRESS_LENGTH) { - Identity queried(RR->topology->getIdentity(Address(payload(),ZT_ADDRESS_LENGTH))); - if (queried) { - Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK); - outp.append((unsigned char)Packet::VERB_WHOIS); - outp.append(packetId()); - queried.serialize(outp,false); - outp.armor(peer->key(),true); - RR->node->putPacket(_localAddress,_remoteAddress,outp.data(),outp.size()); + 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 } - } else { - TRACE("dropped WHOIS from %s(%s): missing or invalid address",source().toString().c_str(),_remoteAddress.toString().c_str()); } - peer->received(_localAddress,_remoteAddress,hops(),packetId(),Packet::VERB_WHOIS,0,Packet::VERB_NOP); + + 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(),_remoteAddress.toString().c_str()); + 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,const SharedPtr &peer) +bool IncomingPacket::_doRENDEZVOUS(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { try { - if (RR->topology->isUpstream(peer->identity())) { + 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 withPeer(RR->topology->getPeer(with)); - if (withPeer) { + 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))) { - peer->received(_localAddress,_remoteAddress,hops(),packetId(),Packet::VERB_RENDEZVOUS,0,Packet::VERB_NOP); - - InetAddress atAddr(field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRESS,addrlen),addrlen,port); - TRACE("RENDEZVOUS from %s says %s might be at %s, starting NAT-t",peer->address().toString().c_str(),with.toString().c_str(),atAddr.toString().c_str()); - if (RR->node->shouldUsePathForZeroTierTraffic(_localAddress,atAddr)) - RR->sw->rendezvous(withPeer,_localAddress,atAddr); + 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(),_remoteAddress.toString().c_str()); + TRACE("dropped corrupt RENDEZVOUS from %s(%s) (bad address or port)",peer->address().toString().c_str(),_path->address().toString().c_str()); } } else { - RR->sw->requestWhois(with); - TRACE("ignored RENDEZVOUS from %s(%s) to meet unknown peer %s",peer->address().toString().c_str(),_remoteAddress.toString().c_str(),with.toString().c_str()); + 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()); } - } else { - TRACE("ignored RENDEZVOUS from %s(%s): not a root server or a network relay",peer->address().toString().c_str(),_remoteAddress.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(),_remoteAddress.toString().c_str()); + 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,const SharedPtr &peer) +bool IncomingPacket::_doFRAME(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { try { - const SharedPtr network(RR->node->network(at(ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID))); + 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 (size() > ZT_PROTO_VERB_FRAME_IDX_PAYLOAD) { - if (!network->isAllowed(peer)) { - TRACE("dropped FRAME from %s(%s): not a member of private network %.16llx",peer->address().toString().c_str(),_remoteAddress.toString().c_str(),(unsigned long long)network->id()); - _sendErrorNeedCertificate(RR,peer,network->id()); - return true; + 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); } - - const unsigned int etherType = at(ZT_PROTO_VERB_FRAME_IDX_ETHERTYPE); - if (!network->config().permitsEtherType(etherType)) { - TRACE("dropped FRAME from %s(%s): ethertype %.4x not allowed on %.16llx",peer->address().toString().c_str(),_remoteAddress.toString().c_str(),(unsigned int)etherType,(unsigned long long)network->id()); - return true; - } - - const unsigned int payloadLen = size() - ZT_PROTO_VERB_FRAME_IDX_PAYLOAD; - RR->node->putFrame(network->id(),network->userPtr(),MAC(peer->address(),network->id()),network->mac(),etherType,0,field(ZT_PROTO_VERB_FRAME_IDX_PAYLOAD,payloadLen),payloadLen); + } 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); } - - peer->received(_localAddress,_remoteAddress,hops(),packetId(),Packet::VERB_FRAME,0,Packet::VERB_NOP); } else { - TRACE("dropped FRAME from %s(%s): we are not connected to network %.16llx",source().toString().c_str(),_remoteAddress.toString().c_str(),at(ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID)); + 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(),_remoteAddress.toString().c_str()); + 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,const SharedPtr &peer) +bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { try { - SharedPtr network(RR->node->network(at(ZT_PROTO_VERB_EXT_FRAME_IDX_NETWORK_ID))); + 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 flags = (*this)[ZT_PROTO_VERB_EXT_FRAME_IDX_FLAGS]; - - unsigned int comLen = 0; - if ((flags & 0x01) != 0) { - CertificateOfMembership com; - comLen = com.deserialize(*this,ZT_PROTO_VERB_EXT_FRAME_IDX_COM); - peer->validateAndSetNetworkMembershipCertificate(network->id(),com); - } - - if (!network->isAllowed(peer)) { - TRACE("dropped EXT_FRAME from %s(%s): not a member of private network %.16llx",peer->address().toString().c_str(),_remoteAddress.toString().c_str(),network->id()); - _sendErrorNeedCertificate(RR,peer,network->id()); - return true; - } - - // Everything after flags must be adjusted based on the length - // of the certificate, if there was one... - const unsigned int etherType = at(comLen + ZT_PROTO_VERB_EXT_FRAME_IDX_ETHERTYPE); - if (!network->config().permitsEtherType(etherType)) { - TRACE("dropped EXT_FRAME from %s(%s): ethertype %.4x not allowed on network %.16llx",peer->address().toString().c_str(),_remoteAddress.toString().c_str(),(unsigned int)etherType,(unsigned long long)network->id()); - return true; - } - 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); - - if (to.isMulticast()) { - TRACE("dropped EXT_FRAME from %s@%s(%s) to %s: destination is multicast, must use MULTICAST_FRAME",from.toString().c_str(),peer->address().toString().c_str(),_remoteAddress.toString().c_str(),to.toString().c_str()); - return true; - } + 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",from.toString().c_str(),peer->address().toString().c_str(),_remoteAddress.toString().c_str(),to.toString().c_str()); + 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; } - if (from != MAC(peer->address(),network->id())) { - 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(),_remoteAddress.toString().c_str(),to.toString().c_str(),network->id()); - return true; - } - } else if (to != network->mac()) { - 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(),_remoteAddress.toString().c_str(),to.toString().c_str(),network->id()); - 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; } - - const unsigned int payloadLen = size() - (comLen + ZT_PROTO_VERB_EXT_FRAME_IDX_PAYLOAD); - RR->node->putFrame(network->id(),network->userPtr(),from,to,etherType,0,field(comLen + ZT_PROTO_VERB_EXT_FRAME_IDX_PAYLOAD,payloadLen),payloadLen); } - peer->received(_localAddress,_remoteAddress,hops(),packetId(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP); + 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(),_remoteAddress.toString().c_str(),at(ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID)); + 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(),_remoteAddress.toString().c_str()); + 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,const SharedPtr &peer) +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); - RR->node->putPacket(_localAddress,_remoteAddress,outp.data(),outp.size()); - peer->received(_localAddress,_remoteAddress,hops(),pid,Packet::VERB_ECHO,0,Packet::VERB_NOP); + 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(),_remoteAddress.toString().c_str()); + 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,const SharedPtr &peer) +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); - const MulticastGroup group(MAC(field(ptr + 8,6),6),at(ptr + 14)); - RR->mc->add(now,nwid,group,peer->address()); + + 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(_localAddress,_remoteAddress,hops(),packetId(),Packet::VERB_MULTICAST_LIKE,0,Packet::VERB_NOP); + 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(),_remoteAddress.toString().c_str()); + TRACE("dropped MULTICAST_LIKE from %s(%s): unexpected exception",source().toString().c_str(),_path->address().toString().c_str()); } return true; } -bool IncomingPacket::_doNETWORK_MEMBERSHIP_CERTIFICATE(const RuntimeEnvironment *RR,const SharedPtr &peer) +bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { try { - CertificateOfMembership com; - - unsigned int ptr = ZT_PACKET_IDX_PAYLOAD; - while (ptr < size()) { - ptr += com.deserialize(*this,ptr); - peer->validateAndSetNetworkMembershipCertificate(com.networkId(),com); + 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; } - peer->received(_localAddress,_remoteAddress,hops(),packetId(),Packet::VERB_NETWORK_MEMBERSHIP_CERTIFICATE,0,Packet::VERB_NOP); + 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 ( ... ) { - TRACE("dropped NETWORK_MEMBERSHIP_CERTIFICATE from %s(%s): unexpected exception",source().toString().c_str(),_remoteAddress.toString().c_str()); + //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,const SharedPtr &peer) +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 metaDataLength = at(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN); - const char *metaDataBytes = (const char *)field(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT,metaDataLength); - const Dictionary metaData(metaDataBytes,metaDataLength); - - //const uint64_t haveRevision = ((ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT + metaDataLength + 8) <= size()) ? at(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT + metaDataLength) : 0ULL; - - const unsigned int h = hops(); - const uint64_t pid = packetId(); - peer->received(_localAddress,_remoteAddress,h,pid,Packet::VERB_NETWORK_CONFIG_REQUEST,0,Packet::VERB_NOP); + const unsigned int hopCount = hops(); + const uint64_t requestPacketId = packetId(); if (RR->localNetworkController) { - NetworkConfig netconf; - switch(RR->localNetworkController->doNetworkConfigRequest((h > 0) ? InetAddress() : _remoteAddress,RR->identity,peer->identity(),nwid,metaData,netconf)) { - - case NetworkController::NETCONF_QUERY_OK: { - Dictionary dconf; - if (netconf.toDictionary(dconf,metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_VERSION,0) < 6)) { - Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK); - outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST); - outp.append(pid); - outp.append(nwid); - const unsigned int dlen = dconf.sizeBytes(); - outp.append((uint16_t)dlen); - outp.append((const void *)dconf.data(),dlen); - outp.compress(); - RR->sw->send(outp,true,0); - } - } break; - - case NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND: { - Packet outp(peer->address(),RR->identity.address(),Packet::VERB_ERROR); - outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST); - outp.append(pid); - outp.append((unsigned char)Packet::ERROR_OBJ_NOT_FOUND); - outp.append(nwid); - outp.armor(peer->key(),true); - RR->node->putPacket(_localAddress,_remoteAddress,outp.data(),outp.size()); - } break; - - case NetworkController::NETCONF_QUERY_ACCESS_DENIED: { - Packet outp(peer->address(),RR->identity.address(),Packet::VERB_ERROR); - outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST); - outp.append(pid); - outp.append((unsigned char)Packet::ERROR_NETWORK_ACCESS_DENIED_); - outp.append(nwid); - outp.armor(peer->key(),true); - RR->node->putPacket(_localAddress,_remoteAddress,outp.data(),outp.size()); - } break; - - case NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR: - // TRACE("NETWORK_CONFIG_REQUEST failed: internal error: %s",netconf.get("error","(unknown)").c_str()); - break; - - case NetworkController::NETCONF_QUERY_IGNORE: - break; - - default: - TRACE("NETWORK_CONFIG_REQUEST failed: invalid return value from NetworkController::doNetworkConfigRequest()"); - break; - - } + 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(pid); + outp.append(requestPacketId); outp.append((unsigned char)Packet::ERROR_UNSUPPORTED_OPERATION); outp.append(nwid); - outp.armor(peer->key(),true); - RR->node->putPacket(_localAddress,_remoteAddress,outp.data(),outp.size()); + 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 ( ... ) { - TRACE("dropped NETWORK_CONFIG_REQUEST from %s(%s): unexpected exception",source().toString().c_str(),_remoteAddress.toString().c_str()); + //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_REFRESH(const RuntimeEnvironment *RR,const SharedPtr &peer) +bool IncomingPacket::_doNETWORK_CONFIG(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { try { - unsigned int ptr = ZT_PACKET_IDX_PAYLOAD; - while ((ptr + 8) <= size()) { - uint64_t nwid = at(ptr); - SharedPtr nw(RR->node->network(nwid)); - if ((nw)&&(peer->address() == nw->controller())) - nw->requestConfiguration(); - ptr += 8; + 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(_localAddress,_remoteAddress,hops(),packetId(),Packet::VERB_NETWORK_CONFIG_REFRESH,0,Packet::VERB_NOP); + 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(),_remoteAddress.toString().c_str()); + 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,const SharedPtr &peer) +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()); - if (gatherLimit) { + 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()); @@ -811,25 +1046,26 @@ bool IncomingPacket::_doMULTICAST_GATHER(const RuntimeEnvironment *RR,const Shar 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) { - outp.armor(peer->key(),true); - RR->node->putPacket(_localAddress,_remoteAddress,outp.data(),outp.size()); + 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(_localAddress,_remoteAddress,hops(),packetId(),Packet::VERB_MULTICAST_GATHER,0,Packet::VERB_NOP); + 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",source().toString().c_str(),_remoteAddress.toString().c_str()); + 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,const SharedPtr &peer) +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); @@ -841,16 +1077,23 @@ bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment *RR,const Share 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); - peer->validateAndSetNetworkMembershipCertificate(nwid,com); + if (com) + network->addCredential(tPtr,com); } - // Check membership after we've read any included COM, since - // that cert might be what we needed. - if (!network->isAllowed(peer)) { - TRACE("dropped MULTICAST_FRAME from %s(%s): not a member of private network %.16llx",peer->address().toString().c_str(),_remoteAddress.toString().c_str(),(unsigned long long)network->id()); - _sendErrorNeedCertificate(RR,peer,network->id()); + 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; } @@ -870,30 +1113,36 @@ bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment *RR,const Share 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 payloadLen = size() - (offset + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FRAME); + const unsigned int frameLen = size() - (offset + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FRAME); - //TRACE("<address().toString().c_str(),flags,payloadLen); + //TRACE("<address().toString().c_str(),flags,frameLen); - if ((payloadLen > 0)&&(payloadLen <= ZT_IF_MTU)) { + 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(),_remoteAddress.toString().c_str(),to.toString().c_str()); + 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(),_remoteAddress.toString().c_str(),to.toString().c_str()); + 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(),network->id())) { + 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(),_remoteAddress.toString().c_str(),to.toString().c_str(),network->id()); + 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; } } - RR->node->putFrame(network->id(),network->userPtr(),from,to.mac(),etherType,0,field(offset + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FRAME,payloadLen),payloadLen); + 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) { @@ -905,27 +1154,31 @@ bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment *RR,const Share 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); - RR->node->putPacket(_localAddress,_remoteAddress,outp.data(),outp.size()); + outp.armor(peer->key(),true,_path->nextOutgoingCounter()); + _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } } - } // else ignore -- not a member of this network - peer->received(_localAddress,_remoteAddress,hops(),packetId(),Packet::VERB_MULTICAST_FRAME,0,Packet::VERB_NOP); + 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(),_remoteAddress.toString().c_str()); + 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,const SharedPtr &peer) +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->shouldRespondToDirectPathPush(now)) { - TRACE("dropped PUSH_DIRECT_PATHS from %s(%s): circuit breaker tripped",source().toString().c_str(),_remoteAddress.toString().c_str()); + 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; } @@ -947,38 +1200,34 @@ bool IncomingPacket::_doPUSH_DIRECT_PATHS(const RuntimeEnvironment *RR,const Sha 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->setClusterOptimalPathForAddressFamily(a); - } else { - redundant = peer->hasActivePathTo(now,a); - } - - if ( ((flags & ZT_PUSH_DIRECT_PATHS_FLAG_FORGET_PATH) == 0) && (!redundant) && (RR->node->shouldUsePathForZeroTierTraffic(_localAddress,a)) ) { + const InetAddress a(field(ptr,4),4,at(ptr + 4)); + if ( + ((flags & ZT_PUSH_DIRECT_PATHS_FLAG_FORGET_PATH) == 0) && // not being told to forget + (!( ((flags & ZT_PUSH_DIRECT_PATHS_FLAG_CLUSTER_REDIRECT) == 0) && (peer->hasActivePathTo(now,a)) )) && // not already known + (RR->node->shouldUsePathForZeroTierTraffic(tPtr,peer->address(),_path->localAddress(),a)) ) // should use path + { + if ((flags & ZT_PUSH_DIRECT_PATHS_FLAG_CLUSTER_REDIRECT) != 0) + peer->setClusterPreferred(a); if (++countPerScope[(int)a.ipScope()][0] <= ZT_PUSH_DIRECT_PATHS_MAX_PER_SCOPE_AND_FAMILY) { TRACE("attempting to contact %s at pushed direct path %s",peer->address().toString().c_str(),a.toString().c_str()); - peer->sendHELLO(InetAddress(),a,now); + 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->setClusterOptimalPathForAddressFamily(a); - } else { - redundant = peer->hasActivePathTo(now,a); - } - - if ( ((flags & ZT_PUSH_DIRECT_PATHS_FLAG_FORGET_PATH) == 0) && (!redundant) && (RR->node->shouldUsePathForZeroTierTraffic(_localAddress,a)) ) { + const InetAddress a(field(ptr,16),16,at(ptr + 16)); + if ( + ((flags & ZT_PUSH_DIRECT_PATHS_FLAG_FORGET_PATH) == 0) && // not being told to forget + (!( ((flags & ZT_PUSH_DIRECT_PATHS_FLAG_CLUSTER_REDIRECT) == 0) && (peer->hasActivePathTo(now,a)) )) && // not already known + (RR->node->shouldUsePathForZeroTierTraffic(tPtr,peer->address(),_path->localAddress(),a)) ) // should use path + { + if ((flags & ZT_PUSH_DIRECT_PATHS_FLAG_CLUSTER_REDIRECT) != 0) + peer->setClusterPreferred(a); if (++countPerScope[(int)a.ipScope()][1] <= ZT_PUSH_DIRECT_PATHS_MAX_PER_SCOPE_AND_FAMILY) { TRACE("attempting to contact %s at pushed direct path %s",peer->address().toString().c_str(),a.toString().c_str()); - peer->sendHELLO(InetAddress(),a,now); + 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()); } @@ -988,20 +1237,20 @@ bool IncomingPacket::_doPUSH_DIRECT_PATHS(const RuntimeEnvironment *RR,const Sha ptr += addrLen; } - peer->received(_localAddress,_remoteAddress,hops(),packetId(),Packet::VERB_PUSH_DIRECT_PATHS,0,Packet::VERB_NOP); + 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(),_remoteAddress.toString().c_str()); + 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,const SharedPtr &peer) +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(originatorAddress)); + SharedPtr originator(RR->topology->getPeer(tPtr,originatorAddress)); if (!originator) { - RR->sw->requestWhois(originatorAddress); + RR->sw->requestWhois(tPtr,originatorAddress); return false; } @@ -1012,7 +1261,7 @@ bool IncomingPacket::_doCIRCUIT_TEST(const RuntimeEnvironment *RR,const SharedPt // Tracks total length of variable length fields, initialized to originator credential length below unsigned int vlf; - // Originator credentials + // 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) { @@ -1031,7 +1280,8 @@ bool IncomingPacket::_doCIRCUIT_TEST(const RuntimeEnvironment *RR,const SharedPt // 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(),_remoteAddress.toString().c_str(),originatorAddress.toString().c_str()); + 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; @@ -1040,46 +1290,24 @@ bool IncomingPacket::_doCIRCUIT_TEST(const RuntimeEnvironment *RR,const SharedPt // into the one we send along to next hops. const unsigned int lengthOfSignedPortionAndSignature = 29 + vlf; - // Get previous hop's credential, if any - const unsigned int previousHopCredentialLength = at(ZT_PACKET_IDX_PAYLOAD + 29 + vlf); - CertificateOfMembership previousHopCom; - if (previousHopCredentialLength >= 1) { - switch((*this)[ZT_PACKET_IDX_PAYLOAD + 31 + vlf]) { - case 0x01: { // network certificate of membership for previous hop - const unsigned int phcl = previousHopCom.deserialize(*this,ZT_PACKET_IDX_PAYLOAD + 32 + vlf); - if (phcl != (previousHopCredentialLength - 1)) { - TRACE("dropped CIRCUIT_TEST from %s(%s): previous hop COM invalid (%u != %u)",source().toString().c_str(),_remoteAddress.toString().c_str(),phcl,(previousHopCredentialLength - 1)); - return true; - } - } break; - default: break; - } - } - vlf += previousHopCredentialLength; + // Add length of second "additional fields" section. + vlf += at(ZT_PACKET_IDX_PAYLOAD + 29 + vlf); + + uint64_t reportFlags = 0; // Check credentials (signature already verified) - NetworkConfig originatorCredentialNetworkConfig; if (originatorCredentialNetworkId) { - if (Network::controllerFor(originatorCredentialNetworkId) == originatorAddress) { - SharedPtr nw(RR->node->network(originatorCredentialNetworkId)); - if ((nw)&&(nw->hasConfig())) { - originatorCredentialNetworkConfig = nw->config(); - if ( ( (originatorCredentialNetworkConfig.isPublic()) || (peer->address() == originatorAddress) || ((originatorCredentialNetworkConfig.com)&&(previousHopCom)&&(originatorCredentialNetworkConfig.com.agreesWith(previousHopCom))) ) ) { - TRACE("CIRCUIT_TEST %.16llx received from hop %s(%s) and originator %s with valid network ID credential %.16llx (verified from originator and next hop)",testId,source().toString().c_str(),_remoteAddress.toString().c_str(),originatorAddress.toString().c_str(),originatorCredentialNetworkId); - } else { - TRACE("dropped CIRCUIT_TEST from %s(%s): originator %s specified network ID %.16llx as credential, and previous hop %s did not supply a valid COM",source().toString().c_str(),_remoteAddress.toString().c_str(),originatorAddress.toString().c_str(),originatorCredentialNetworkId,peer->address().toString().c_str()); - return true; - } - } else { - TRACE("dropped CIRCUIT_TEST from %s(%s): originator %s specified network ID %.16llx as credential, and we are not a member",source().toString().c_str(),_remoteAddress.toString().c_str(),originatorAddress.toString().c_str(),originatorCredentialNetworkId); - return true; - } - } else { - TRACE("dropped CIRCUIT_TEST from %s(%s): originator %s specified network ID as credential, is not controller for %.16llx",source().toString().c_str(),_remoteAddress.toString().c_str(),originatorAddress.toString().c_str(),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(),_remoteAddress.toString().c_str(),originatorAddress.toString().c_str()); + 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; } @@ -1095,11 +1323,11 @@ bool IncomingPacket::_doCIRCUIT_TEST(const RuntimeEnvironment *RR,const SharedPt for(unsigned int h=0;h nhp(RR->topology->getPeer(nextHop[h])); + SharedPtr nhp(RR->topology->getPeer(tPtr,nextHop[h])); if (nhp) { - Path *const rp = nhp->getBestPath(now); - if (rp) - nextHopBestPathAddress[h] = rp->address(); + SharedPtr nhbp(nhp->getBestPath(now,false)); + if ((nhbp)&&(nhbp->alive(now))) + nextHopBestPathAddress[h] = nhbp->address(); } } } @@ -1118,32 +1346,26 @@ bool IncomingPacket::_doCIRCUIT_TEST(const RuntimeEnvironment *RR,const SharedPt 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)0); // flags, currently unused + outp.append((uint64_t)reportFlags); outp.append((uint64_t)packetId()); peer->address().appendTo(outp); outp.append((uint8_t)hops()); - _localAddress.serialize(outp); - _remoteAddress.serialize(outp); - outp.append((uint16_t)0); // no additional fields + _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(outp,true,0); + RR->sw->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); - const unsigned int previousHopCredentialPos = outp.size(); - outp.append((uint16_t)0); // no previous hop credentials: default - if ((originatorCredentialNetworkConfig)&&(!originatorCredentialNetworkConfig.isPublic())&&(originatorCredentialNetworkConfig.com)) { - outp.append((uint8_t)0x01); // COM - originatorCredentialNetworkConfig.com.serialize(outp); - outp.setAt(previousHopCredentialPos,(uint16_t)(outp.size() - (previousHopCredentialPos + 2))); - } + outp.append((uint16_t)0); // no additional fields if (remainingHopsPtr < size()) outp.append(field(remainingHopsPtr,size() - remainingHopsPtr),size() - remainingHopsPtr); @@ -1151,19 +1373,19 @@ bool IncomingPacket::_doCIRCUIT_TEST(const RuntimeEnvironment *RR,const SharedPt if (RR->identity.address() != nextHop[h]) { // next hops that loop back to the current hop are not valid outp.newInitializationVector(); outp.setDestination(nextHop[h]); - RR->sw->send(outp,true,originatorCredentialNetworkId); + RR->sw->send(tPtr,outp,true); } } } - peer->received(_localAddress,_remoteAddress,hops(),packetId(),Packet::VERB_CIRCUIT_TEST,0,Packet::VERB_NOP); + 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(),_remoteAddress.toString().c_str()); + 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,const SharedPtr &peer) +bool IncomingPacket::_doCIRCUIT_TEST_REPORT(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { try { ZT_CircuitTestReport report; @@ -1173,7 +1395,6 @@ bool IncomingPacket::_doCIRCUIT_TEST_REPORT(const RuntimeEnvironment *RR,const S 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.remoteTimestamp = at(ZT_PACKET_IDX_PAYLOAD + 16); 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 @@ -1188,176 +1409,61 @@ bool IncomingPacket::_doCIRCUIT_TEST_REPORT(const RuntimeEnvironment *RR,const S 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 + } - unsigned int nhptr = ZT_PACKET_IDX_PAYLOAD + 58 + receivedOnLocalAddressLen + receivedFromRemoteAddressLen; - nhptr += at(nhptr) + 2; // add "additional field" length, which right now will be zero - - report.nextHopCount = (*this)[nhptr++]; + 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,nhptr); + report.nextHops[h].address = Address(field(ptr,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH).toInt(); ptr += ZT_ADDRESS_LENGTH; + ptr += reinterpret_cast(&(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(),_remoteAddress.toString().c_str()); + TRACE("dropped CIRCUIT_TEST_REPORT from %s(%s): unexpected exception",source().toString().c_str(),_path->address().toString().c_str()); } return true; } -bool IncomingPacket::_doREQUEST_PROOF_OF_WORK(const RuntimeEnvironment *RR,const SharedPtr &peer) +bool IncomingPacket::_doUSER_MESSAGE(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { try { - // If this were allowed from anyone, it would itself be a DOS vector. Right - // now we only allow it from roots and controllers of networks you have joined. - bool allowed = RR->topology->isRoot(peer->identity()); - if (!allowed) { - std::vector< SharedPtr > allNetworks(RR->node->allNetworks()); - for(std::vector< SharedPtr >::const_iterator n(allNetworks.begin());n!=allNetworks.end();++n) { - if (peer->address() == (*n)->controller()) { - allowed = true; - break; - } - } - } - - if (allowed) { - const uint64_t pid = packetId(); - const unsigned int difficulty = (*this)[ZT_PACKET_IDX_PAYLOAD + 1]; - const unsigned int challengeLength = at(ZT_PACKET_IDX_PAYLOAD + 2); - if (challengeLength > ZT_PROTO_MAX_PACKET_LENGTH) - return true; // sanity check, drop invalid size - const unsigned char *challenge = field(ZT_PACKET_IDX_PAYLOAD + 4,challengeLength); - - switch((*this)[ZT_PACKET_IDX_PAYLOAD]) { - - // Salsa20/12+SHA512 hashcash - case 0x01: { - if (difficulty <= 14) { - unsigned char result[16]; - computeSalsa2012Sha512ProofOfWork(difficulty,challenge,challengeLength,result); - TRACE("PROOF_OF_WORK computed for %s: difficulty==%u, challengeLength==%u, result: %.16llx%.16llx",peer->address().toString().c_str(),difficulty,challengeLength,Utils::ntoh(*(reinterpret_cast(result))),Utils::ntoh(*(reinterpret_cast(result + 8)))); - Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK); - outp.append((unsigned char)Packet::VERB_REQUEST_PROOF_OF_WORK); - outp.append(pid); - outp.append((uint16_t)sizeof(result)); - outp.append(result,sizeof(result)); - outp.armor(peer->key(),true); - RR->node->putPacket(_localAddress,_remoteAddress,outp.data(),outp.size()); - } else { - Packet outp(peer->address(),RR->identity.address(),Packet::VERB_ERROR); - outp.append((unsigned char)Packet::VERB_REQUEST_PROOF_OF_WORK); - outp.append(pid); - outp.append((unsigned char)Packet::ERROR_INVALID_REQUEST); - outp.armor(peer->key(),true); - RR->node->putPacket(_localAddress,_remoteAddress,outp.data(),outp.size()); - } - } break; - - default: - TRACE("dropped REQUEST_PROOF_OF_WORK from %s(%s): unrecognized proof of work type",peer->address().toString().c_str(),_remoteAddress.toString().c_str()); - break; - } - - peer->received(_localAddress,_remoteAddress,hops(),pid,Packet::VERB_REQUEST_PROOF_OF_WORK,0,Packet::VERB_NOP); - } else { - TRACE("dropped REQUEST_PROOF_OF_WORK from %s(%s): not trusted enough",peer->address().toString().c_str(),_remoteAddress.toString().c_str()); + 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 REQUEST_PROOF_OF_WORK from %s(%s): unexpected exception",peer->address().toString().c_str(),_remoteAddress.toString().c_str()); + TRACE("dropped CIRCUIT_TEST_REPORT from %s(%s): unexpected exception",source().toString().c_str(),_path->address().toString().c_str()); } return true; } -void IncomingPacket::computeSalsa2012Sha512ProofOfWork(unsigned int difficulty,const void *challenge,unsigned int challengeLength,unsigned char result[16]) +void IncomingPacket::_sendErrorNeedCredentials(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer,const uint64_t nwid) { - unsigned char salsabuf[131072]; // 131072 == protocol constant, size of memory buffer for this proof of work function - char candidatebuf[ZT_PROTO_MAX_PACKET_LENGTH + 256]; - unsigned char shabuf[ZT_SHA512_DIGEST_LEN]; - const uint64_t s20iv = 0; // zero IV for Salsa20 - char *const candidate = (char *)(( ((uintptr_t)&(candidatebuf[0])) | 0xf ) + 1); // align to 16-byte boundary to ensure that uint64_t type punning of initial nonce is okay - Salsa20 s20; - unsigned int d; - unsigned char *p; - - Utils::getSecureRandom(candidate,16); - memcpy(candidate + 16,challenge,challengeLength); - - if (difficulty > 512) - difficulty = 512; // sanity check - -try_salsa2012sha512_again: - ++*(reinterpret_cast(candidate)); - - SHA512::hash(shabuf,candidate,16 + challengeLength); - s20.init(shabuf,256,&s20iv); - memset(salsabuf,0,sizeof(salsabuf)); - s20.encrypt12(salsabuf,salsabuf,sizeof(salsabuf)); - SHA512::hash(shabuf,salsabuf,sizeof(salsabuf)); - - d = difficulty; - p = shabuf; - while (d >= 8) { - if (*(p++)) - goto try_salsa2012sha512_again; - d -= 8; + 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); } - if (d > 0) { - if ( ((((unsigned int)*p) << d) & 0xff00) != 0 ) - goto try_salsa2012sha512_again; - } - - memcpy(result,candidate,16); -} - -bool IncomingPacket::testSalsa2012Sha512ProofOfWorkResult(unsigned int difficulty,const void *challenge,unsigned int challengeLength,const unsigned char proposedResult[16]) -{ - unsigned char salsabuf[131072]; // 131072 == protocol constant, size of memory buffer for this proof of work function - char candidate[ZT_PROTO_MAX_PACKET_LENGTH + 256]; - unsigned char shabuf[ZT_SHA512_DIGEST_LEN]; - const uint64_t s20iv = 0; // zero IV for Salsa20 - Salsa20 s20; - unsigned int d; - unsigned char *p; - - if (difficulty > 512) - difficulty = 512; // sanity check - - memcpy(candidate,proposedResult,16); - memcpy(candidate + 16,challenge,challengeLength); - - SHA512::hash(shabuf,candidate,16 + challengeLength); - s20.init(shabuf,256,&s20iv); - memset(salsabuf,0,sizeof(salsabuf)); - s20.encrypt12(salsabuf,salsabuf,sizeof(salsabuf)); - SHA512::hash(shabuf,salsabuf,sizeof(salsabuf)); - - d = difficulty; - p = shabuf; - while (d >= 8) { - if (*(p++)) - return false; - d -= 8; - } - if (d > 0) { - if ( ((((unsigned int)*p) << d) & 0xff00) != 0 ) - return false; - } - - return true; -} - -void IncomingPacket::_sendErrorNeedCertificate(const RuntimeEnvironment *RR,const SharedPtr &peer,uint64_t nwid) -{ - Packet outp(source(),RR->identity.address(),Packet::VERB_ERROR); - outp.append((unsigned char)verb()); - outp.append(packetId()); - outp.append((unsigned char)Packet::ERROR_NEED_MEMBERSHIP_CERTIFICATE); - outp.append(nwid); - outp.armor(peer->key(),true); - RR->node->putPacket(_localAddress,_remoteAddress,outp.data(),outp.size()); } } // namespace ZeroTier diff --git a/node/IncomingPacket.hpp b/node/IncomingPacket.hpp index cd0b7dc..3d4a2e0 100644 --- a/node/IncomingPacket.hpp +++ b/node/IncomingPacket.hpp @@ -22,7 +22,7 @@ #include #include "Packet.hpp" -#include "InetAddress.hpp" +#include "Path.hpp" #include "Utils.hpp" #include "MulticastGroup.hpp" #include "Peer.hpp" @@ -56,59 +56,40 @@ class IncomingPacket : public Packet public: IncomingPacket() : Packet(), - _receiveTime(0), - _localAddress(), - _remoteAddress() + _receiveTime(0) { } - IncomingPacket(const IncomingPacket &p) - { - // All fields including InetAddress are memcpy'able - memcpy(this,&p,sizeof(IncomingPacket)); - } - /** * Create a new packet-in-decode * * @param data Packet data * @param len Packet length - * @param localAddress Local interface address - * @param remoteAddress Address from which packet came + * @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 InetAddress &localAddress,const InetAddress &remoteAddress,uint64_t now) : + IncomingPacket(const void *data,unsigned int len,const SharedPtr &path,uint64_t now) : Packet(data,len), _receiveTime(now), - _localAddress(localAddress), - _remoteAddress(remoteAddress) + _path(path) { } - inline IncomingPacket &operator=(const IncomingPacket &p) - { - // All fields including InetAddress are memcpy'able - memcpy(this,&p,sizeof(IncomingPacket)); - return *this; - } - /** * Init packet-in-decode in place * * @param data Packet data * @param len Packet length - * @param localAddress Local interface address - * @param remoteAddress Address from which packet came + * @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 InetAddress &localAddress,const InetAddress &remoteAddress,uint64_t now) + inline void init(const void *data,unsigned int len,const SharedPtr &path,uint64_t now) { copyFrom(data,len); _receiveTime = now; - _localAddress = localAddress; - _remoteAddress = remoteAddress; + _path = path; } /** @@ -118,76 +99,45 @@ public: * 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. The only exception is deferred decoding. In this - * case true is returned to indicate to the normal decode path that it is - * finished with the packet. The packet will have added itself to the - * deferred queue and will expect tryDecode() to be called one more time - * with deferred set to true. - * - * Deferred decoding is performed by DeferredPackets.cpp and should not be - * done elsewhere. Under deferred decoding packets only get one shot and - * so the return value of tryDecode() is ignored. + * may no longer be valid. * * @param RR Runtime environment - * @param deferred If true, this is a deferred decode and the return is ignored + * @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,bool deferred); + bool tryDecode(const RuntimeEnvironment *RR,void *tPtr); /** * @return Time of packet receipt / start of decode */ inline uint64_t receiveTime() const throw() { return _receiveTime; } - /** - * Compute the Salsa20/12+SHA512 proof of work function - * - * @param difficulty Difficulty in bits (max: 64) - * @param challenge Challenge string - * @param challengeLength Length of challenge in bytes (max allowed: ZT_PROTO_MAX_PACKET_LENGTH) - * @param result Buffer to fill with 16-byte result - */ - static void computeSalsa2012Sha512ProofOfWork(unsigned int difficulty,const void *challenge,unsigned int challengeLength,unsigned char result[16]); - - /** - * Verify the result of Salsa20/12+SHA512 proof of work - * - * @param difficulty Difficulty in bits (max: 64) - * @param challenge Challenge bytes - * @param challengeLength Length of challenge in bytes (max allowed: ZT_PROTO_MAX_PACKET_LENGTH) - * @param proposedResult Result supplied by client - * @return True if result is valid - */ - static bool testSalsa2012Sha512ProofOfWorkResult(unsigned int difficulty,const void *challenge,unsigned int challengeLength,const unsigned char proposedResult[16]); - private: // These are called internally to handle packet contents once it has // been authenticated, decrypted, decompressed, and classified. - bool _doERROR(const RuntimeEnvironment *RR,const SharedPtr &peer); - bool _doHELLO(const RuntimeEnvironment *RR,SharedPtr &peer); // can be called with NULL peer, while all others cannot - bool _doOK(const RuntimeEnvironment *RR,const SharedPtr &peer); - bool _doWHOIS(const RuntimeEnvironment *RR,const SharedPtr &peer); - bool _doRENDEZVOUS(const RuntimeEnvironment *RR,const SharedPtr &peer); - bool _doFRAME(const RuntimeEnvironment *RR,const SharedPtr &peer); - bool _doEXT_FRAME(const RuntimeEnvironment *RR,const SharedPtr &peer); - bool _doECHO(const RuntimeEnvironment *RR,const SharedPtr &peer); - bool _doMULTICAST_LIKE(const RuntimeEnvironment *RR,const SharedPtr &peer); - bool _doNETWORK_MEMBERSHIP_CERTIFICATE(const RuntimeEnvironment *RR,const SharedPtr &peer); - bool _doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,const SharedPtr &peer); - bool _doNETWORK_CONFIG_REFRESH(const RuntimeEnvironment *RR,const SharedPtr &peer); - bool _doMULTICAST_GATHER(const RuntimeEnvironment *RR,const SharedPtr &peer); - bool _doMULTICAST_FRAME(const RuntimeEnvironment *RR,const SharedPtr &peer); - bool _doPUSH_DIRECT_PATHS(const RuntimeEnvironment *RR,const SharedPtr &peer); - bool _doCIRCUIT_TEST(const RuntimeEnvironment *RR,const SharedPtr &peer); - bool _doCIRCUIT_TEST_REPORT(const RuntimeEnvironment *RR,const SharedPtr &peer); - bool _doREQUEST_PROOF_OF_WORK(const RuntimeEnvironment *RR,const SharedPtr &peer); + 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); - // Send an ERROR_NEED_MEMBERSHIP_CERTIFICATE to a peer indicating that an updated cert is needed to communicate - void _sendErrorNeedCertificate(const RuntimeEnvironment *RR,const SharedPtr &peer,uint64_t nwid); + void _sendErrorNeedCredentials(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer,const uint64_t nwid); uint64_t _receiveTime; - InetAddress _localAddress; - InetAddress _remoteAddress; + SharedPtr _path; }; } // namespace ZeroTier diff --git a/node/InetAddress.cpp b/node/InetAddress.cpp index 3f6b9be..7d22eea 100644 --- a/node/InetAddress.cpp +++ b/node/InetAddress.cpp @@ -113,7 +113,7 @@ void InetAddress::set(const std::string &ip,unsigned int port) 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 { + } 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); @@ -236,8 +236,14 @@ InetAddress InetAddress::netmask() const case AF_INET6: { uint64_t nm[2]; const unsigned int bits = netmaskBits(); - nm[0] = Utils::hton((uint64_t)((bits >= 64) ? 0xffffffffffffffffULL : (0xffffffffffffffffULL << (64 - bits)))); - nm[1] = Utils::hton((uint64_t)((bits <= 64) ? 0ULL : (0xffffffffffffffffULL << (128 - bits)))); + 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; } diff --git a/node/InetAddress.hpp b/node/InetAddress.hpp index e03deb7..c37fa62 100644 --- a/node/InetAddress.hpp +++ b/node/InetAddress.hpp @@ -300,6 +300,19 @@ struct InetAddress : public sockaddr_storage */ 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() * @@ -356,7 +369,6 @@ struct InetAddress : public sockaddr_storage * @return pointer to raw address bytes or NULL if not available */ inline const void *rawIpData() const - throw() { switch(ss_family) { case AF_INET: return (const void *)&(reinterpret_cast(this)->sin_addr.s_addr); @@ -365,6 +377,25 @@ struct InetAddress : public sockaddr_storage } } + /** + * @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() * @@ -383,6 +414,25 @@ struct InetAddress : public sockaddr_storage 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 */ @@ -399,6 +449,30 @@ struct InetAddress : public sockaddr_storage 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 */ diff --git a/node/Membership.cpp b/node/Membership.cpp new file mode 100644 index 0000000..2d0471f --- /dev/null +++ b/node/Membership.cpp @@ -0,0 +1,231 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "Membership.hpp" +#include "RuntimeEnvironment.hpp" +#include "Peer.hpp" +#include "Topology.hpp" +#include "Switch.hpp" +#include "Packet.hpp" +#include "Node.hpp" + +#define ZT_CREDENTIAL_PUSH_EVERY (ZT_NETWORK_AUTOCONF_DELAY / 3) + +namespace ZeroTier { + +Membership::Membership() : + _lastUpdatedMulticast(0), + _lastPushedCom(0), + _comRevocationThreshold(0), + _revocations(4), + _remoteTags(4), + _remoteCaps(4), + _remoteCoos(4) +{ + resetPushState(); +} + +void Membership::pushCredentials(const RuntimeEnvironment *RR,void *tPtr,const uint64_t now,const Address &peerAddress,const NetworkConfig &nconf,int localCapabilityIndex,const bool force) +{ + bool sendCom = ( (nconf.com) && ( ((now - _lastPushedCom) >= ZT_CREDENTIAL_PUSH_EVERY) || (force) ) ); + + const Capability *sendCap; + if (localCapabilityIndex >= 0) { + sendCap = &(nconf.capabilities[localCapabilityIndex]); + if ( ((now - _localCredLastPushed.cap[localCapabilityIndex]) >= ZT_CREDENTIAL_PUSH_EVERY) || (force) ) + _localCredLastPushed.cap[localCapabilityIndex] = now; + else sendCap = (const Capability *)0; + } else sendCap = (const Capability *)0; + + const Tag *sendTags[ZT_MAX_NETWORK_TAGS]; + unsigned int sendTagCount = 0; + for(unsigned int t=0;t= ZT_CREDENTIAL_PUSH_EVERY) || (force) ) { + _localCredLastPushed.tag[t] = now; + sendTags[sendTagCount++] = &(nconf.tags[t]); + } + } + + const CertificateOfOwnership *sendCoos[ZT_MAX_CERTIFICATES_OF_OWNERSHIP]; + unsigned int sendCooCount = 0; + for(unsigned int c=0;c= ZT_CREDENTIAL_PUSH_EVERY) || (force) ) { + _localCredLastPushed.coo[c] = now; + sendCoos[sendCooCount++] = &(nconf.certificatesOfOwnership[c]); + } + } + + unsigned int tagPtr = 0; + unsigned int cooPtr = 0; + while ((tagPtr < sendTagCount)||(cooPtr < sendCooCount)||(sendCom)||(sendCap)) { + Packet outp(peerAddress,RR->identity.address(),Packet::VERB_NETWORK_CREDENTIALS); + + if (sendCom) { + sendCom = false; + nconf.com.serialize(outp); + _lastPushedCom = now; + } + outp.append((uint8_t)0x00); + + if (sendCap) { + outp.append((uint16_t)1); + sendCap->serialize(outp); + sendCap = (const Capability *)0; + } else outp.append((uint16_t)0); + + const unsigned int tagCountAt = outp.size(); + outp.addSize(2); + unsigned int thisPacketTagCount = 0; + while ((tagPtr < sendTagCount)&&((outp.size() + sizeof(Tag) + 16) < ZT_PROTO_MAX_PACKET_LENGTH)) { + sendTags[tagPtr++]->serialize(outp); + ++thisPacketTagCount; + } + outp.setAt(tagCountAt,(uint16_t)thisPacketTagCount); + + // No revocations, these propagate differently + outp.append((uint16_t)0); + + const unsigned int cooCountAt = outp.size(); + outp.addSize(2); + unsigned int thisPacketCooCount = 0; + while ((cooPtr < sendCooCount)&&((outp.size() + sizeof(CertificateOfOwnership) + 16) < ZT_PROTO_MAX_PACKET_LENGTH)) { + sendCoos[cooPtr++]->serialize(outp); + ++thisPacketCooCount; + } + outp.setAt(cooCountAt,(uint16_t)thisPacketCooCount); + + outp.compress(); + RR->sw->send(tPtr,outp,true); + } +} + +Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment *RR,void *tPtr,const NetworkConfig &nconf,const CertificateOfMembership &com) +{ + const uint64_t newts = com.timestamp(); + if (newts <= _comRevocationThreshold) { + TRACE("addCredential(CertificateOfMembership) for %s on %.16llx REJECTED (revoked)",com.issuedTo().toString().c_str(),com.networkId()); + return ADD_REJECTED; + } + + const uint64_t oldts = _com.timestamp(); + if (newts < oldts) { + TRACE("addCredential(CertificateOfMembership) for %s on %.16llx REJECTED (older than current)",com.issuedTo().toString().c_str(),com.networkId()); + return ADD_REJECTED; + } + if ((newts == oldts)&&(_com == com)) { + TRACE("addCredential(CertificateOfMembership) for %s on %.16llx ACCEPTED (redundant)",com.issuedTo().toString().c_str(),com.networkId()); + return ADD_ACCEPTED_REDUNDANT; + } + + switch(com.verify(RR,tPtr)) { + default: + TRACE("addCredential(CertificateOfMembership) for %s on %.16llx REJECTED (invalid signature or object)",com.issuedTo().toString().c_str(),com.networkId()); + return ADD_REJECTED; + case 0: + TRACE("addCredential(CertificateOfMembership) for %s on %.16llx ACCEPTED (new)",com.issuedTo().toString().c_str(),com.networkId()); + _com = com; + return ADD_ACCEPTED_NEW; + case 1: + return ADD_DEFERRED_FOR_WHOIS; + } +} + +// Template out addCredential() for many cred types to avoid copypasta +template +static Membership::AddCredentialResult _addCredImpl(Hashtable &remoteCreds,const Hashtable &revocations,const RuntimeEnvironment *RR,void *tPtr,const NetworkConfig &nconf,const C &cred) +{ + C *rc = remoteCreds.get(cred.id()); + if (rc) { + if (rc->timestamp() > cred.timestamp()) { + TRACE("addCredential(type==%d) for %s on %.16llx REJECTED (older than credential we have)",(int)C::credentialType(),cred.issuedTo().toString().c_str(),cred.networkId()); + return Membership::ADD_REJECTED; + } + if (*rc == cred) { + //TRACE("addCredential(type==%d) for %s on %.16llx ACCEPTED (redundant)",(int)C::credentialType(),cred.issuedTo().toString().c_str(),cred.networkId()); + return Membership::ADD_ACCEPTED_REDUNDANT; + } + } + + const uint64_t *const rt = revocations.get(Membership::credentialKey(C::credentialType(),cred.id())); + if ((rt)&&(*rt >= cred.timestamp())) { + TRACE("addCredential(type==%d) for %s on %.16llx REJECTED (timestamp below revocation threshold)",(int)C::credentialType(),cred.issuedTo().toString().c_str(),cred.networkId()); + return Membership::ADD_REJECTED; + } + + switch(cred.verify(RR,tPtr)) { + default: + TRACE("addCredential(type==%d) for %s on %.16llx REJECTED (invalid)",(int)C::credentialType(),cred.issuedTo().toString().c_str(),cred.networkId()); + return Membership::ADD_REJECTED; + case 0: + TRACE("addCredential(type==%d) for %s on %.16llx ACCEPTED (new)",(int)C::credentialType(),cred.issuedTo().toString().c_str(),cred.networkId()); + if (!rc) + rc = &(remoteCreds[cred.id()]); + *rc = cred; + return Membership::ADD_ACCEPTED_NEW; + case 1: + return Membership::ADD_DEFERRED_FOR_WHOIS; + } +} + +Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment *RR,void *tPtr,const NetworkConfig &nconf,const Tag &tag) { return _addCredImpl(_remoteTags,_revocations,RR,tPtr,nconf,tag); } +Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment *RR,void *tPtr,const NetworkConfig &nconf,const Capability &cap) { return _addCredImpl(_remoteCaps,_revocations,RR,tPtr,nconf,cap); } +Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment *RR,void *tPtr,const NetworkConfig &nconf,const CertificateOfOwnership &coo) { return _addCredImpl(_remoteCoos,_revocations,RR,tPtr,nconf,coo); } + +Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment *RR,void *tPtr,const NetworkConfig &nconf,const Revocation &rev) +{ + uint64_t *rt; + switch(rev.verify(RR,tPtr)) { + default: + return ADD_REJECTED; + case 0: { + const Credential::Type ct = rev.type(); + switch(ct) { + case Credential::CREDENTIAL_TYPE_COM: + if (rev.threshold() > _comRevocationThreshold) { + _comRevocationThreshold = rev.threshold(); + return ADD_ACCEPTED_NEW; + } + return ADD_ACCEPTED_REDUNDANT; + case Credential::CREDENTIAL_TYPE_CAPABILITY: + case Credential::CREDENTIAL_TYPE_TAG: + case Credential::CREDENTIAL_TYPE_COO: + rt = &(_revocations[credentialKey(ct,rev.credentialId())]); + if (*rt < rev.threshold()) { + *rt = rev.threshold(); + return ADD_ACCEPTED_NEW; + } + return ADD_ACCEPTED_REDUNDANT; + default: + return ADD_REJECTED; + } + } + case 1: + return ADD_DEFERRED_FOR_WHOIS; + } +} + +void Membership::clean(const uint64_t now,const NetworkConfig &nconf) +{ + _cleanCredImpl(nconf,_remoteTags); + _cleanCredImpl(nconf,_remoteCaps); + _cleanCredImpl(nconf,_remoteCoos); +} + +} // namespace ZeroTier diff --git a/node/Membership.hpp b/node/Membership.hpp new file mode 100644 index 0000000..0bc8f33 --- /dev/null +++ b/node/Membership.hpp @@ -0,0 +1,272 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ZT_MEMBERSHIP_HPP +#define ZT_MEMBERSHIP_HPP + +#include + +#include "Constants.hpp" +#include "../include/ZeroTierOne.h" +#include "Credential.hpp" +#include "Hashtable.hpp" +#include "CertificateOfMembership.hpp" +#include "Capability.hpp" +#include "Tag.hpp" +#include "Revocation.hpp" +#include "NetworkConfig.hpp" + +#define ZT_MEMBERSHIP_CRED_ID_UNUSED 0xffffffffffffffffULL + +namespace ZeroTier { + +class RuntimeEnvironment; +class Network; + +/** + * A container for certificates of membership and other network credentials + * + * This is essentially a relational join between Peer and Network. + * + * This class is not thread safe. It must be locked externally. + */ +class Membership +{ +public: + enum AddCredentialResult + { + ADD_REJECTED, + ADD_ACCEPTED_NEW, + ADD_ACCEPTED_REDUNDANT, + ADD_DEFERRED_FOR_WHOIS + }; + + Membership(); + + /** + * Send COM and other credentials to this peer if needed + * + * This checks last pushed times for our COM and for other credentials and + * sends VERB_NETWORK_CREDENTIALS if the recipient might need them. + * + * @param RR Runtime environment + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param now Current time + * @param peerAddress Address of member peer (the one that this Membership describes) + * @param nconf My network config + * @param localCapabilityIndex Index of local capability to include (in nconf.capabilities[]) or -1 if none + * @param force If true, send objects regardless of last push time + */ + void pushCredentials(const RuntimeEnvironment *RR,void *tPtr,const uint64_t now,const Address &peerAddress,const NetworkConfig &nconf,int localCapabilityIndex,const bool force); + + /** + * Check whether we should push MULTICAST_LIKEs to this peer, and update last sent time if true + * + * @param now Current time + * @return True if we should update multicasts + */ + inline bool multicastLikeGate(const uint64_t now) + { + if ((now - _lastUpdatedMulticast) >= ZT_MULTICAST_ANNOUNCE_PERIOD) { + _lastUpdatedMulticast = now; + return true; + } + return false; + } + + /** + * Check whether the peer represented by this Membership should be allowed on this network at all + * + * @param nconf Our network config + * @return True if this peer is allowed on this network at all + */ + inline bool isAllowedOnNetwork(const NetworkConfig &nconf) const + { + if (nconf.isPublic()) return true; + if (_com.timestamp() <= _comRevocationThreshold) return false; + return nconf.com.agreesWith(_com); + } + + /** + * Check whether the peer represented by this Membership owns a given resource + * + * @tparam Type of resource: InetAddress or MAC + * @param nconf Our network config + * @param r Resource to check + * @return True if this peer has a certificate of ownership for the given resource + */ + template + inline bool hasCertificateOfOwnershipFor(const NetworkConfig &nconf,const T &r) const + { + uint32_t *k = (uint32_t *)0; + CertificateOfOwnership *v = (CertificateOfOwnership *)0; + Hashtable< uint32_t,CertificateOfOwnership >::Iterator i(*(const_cast< Hashtable< uint32_t,CertificateOfOwnership> *>(&_remoteCoos))); + while (i.next(k,v)) { + if (_isCredentialTimestampValid(nconf,*v)&&(v->owns(r))) + return true; + } + return false; + } + + /** + * Get a remote member's tag (if we have it) + * + * @param nconf Network configuration + * @param id Tag ID + * @return Pointer to tag or NULL if not found + */ + inline const Tag *getTag(const NetworkConfig &nconf,const uint32_t id) const + { + const Tag *const t = _remoteTags.get(id); + return (((t)&&(_isCredentialTimestampValid(nconf,*t))) ? t : (Tag *)0); + } + + /** + * Validate and add a credential if signature is okay and it's otherwise good + */ + AddCredentialResult addCredential(const RuntimeEnvironment *RR,void *tPtr,const NetworkConfig &nconf,const CertificateOfMembership &com); + + /** + * Validate and add a credential if signature is okay and it's otherwise good + */ + AddCredentialResult addCredential(const RuntimeEnvironment *RR,void *tPtr,const NetworkConfig &nconf,const Tag &tag); + + /** + * Validate and add a credential if signature is okay and it's otherwise good + */ + AddCredentialResult addCredential(const RuntimeEnvironment *RR,void *tPtr,const NetworkConfig &nconf,const Capability &cap); + + /** + * Validate and add a credential if signature is okay and it's otherwise good + */ + AddCredentialResult addCredential(const RuntimeEnvironment *RR,void *tPtr,const NetworkConfig &nconf,const CertificateOfOwnership &coo); + + /** + * Validate and add a credential if signature is okay and it's otherwise good + */ + AddCredentialResult addCredential(const RuntimeEnvironment *RR,void *tPtr,const NetworkConfig &nconf,const Revocation &rev); + + /** + * Clean internal databases of stale entries + * + * @param now Current time + * @param nconf Current network configuration + */ + void clean(const uint64_t now,const NetworkConfig &nconf); + + /** + * Reset last pushed time for local credentials + * + * This is done when we update our network configuration and our credentials have changed + */ + inline void resetPushState() + { + _lastPushedCom = 0; + memset(&_localCredLastPushed,0,sizeof(_localCredLastPushed)); + } + + /** + * Generates a key for the internal use in indexing credentials by type and credential ID + */ + static uint64_t credentialKey(const Credential::Type &t,const uint32_t i) { return (((uint64_t)t << 32) | (uint64_t)i); } + +private: + template + inline bool _isCredentialTimestampValid(const NetworkConfig &nconf,const C &remoteCredential) const + { + const uint64_t ts = remoteCredential.timestamp(); + if (((ts >= nconf.timestamp) ? (ts - nconf.timestamp) : (nconf.timestamp - ts)) <= nconf.credentialTimeMaxDelta) { + const uint64_t *threshold = _revocations.get(credentialKey(C::credentialType(),remoteCredential.id())); + return ((!threshold)||(ts > *threshold)); + } + return false; + } + + template + void _cleanCredImpl(const NetworkConfig &nconf,Hashtable &remoteCreds) + { + uint32_t *k = (uint32_t *)0; + C *v = (C *)0; + typename Hashtable::Iterator i(remoteCreds); + while (i.next(k,v)) { + if (!_isCredentialTimestampValid(nconf,*v)) + remoteCreds.erase(*k); + } + } + + // Last time we pushed MULTICAST_LIKE(s) + uint64_t _lastUpdatedMulticast; + + // Last time we pushed our COM to this peer + uint64_t _lastPushedCom; + + // Revocation threshold for COM or 0 if none + uint64_t _comRevocationThreshold; + + // Remote member's latest network COM + CertificateOfMembership _com; + + // Revocations by credentialKey() + Hashtable< uint64_t,uint64_t > _revocations; + + // Remote credentials that we have received from this member (and that are valid) + Hashtable< uint32_t,Tag > _remoteTags; + Hashtable< uint32_t,Capability > _remoteCaps; + Hashtable< uint32_t,CertificateOfOwnership > _remoteCoos; + + // Time we last pushed our local credentials to this member + struct { + uint64_t tag[ZT_MAX_NETWORK_TAGS]; + uint64_t cap[ZT_MAX_NETWORK_CAPABILITIES]; + uint64_t coo[ZT_MAX_CERTIFICATES_OF_OWNERSHIP]; + } _localCredLastPushed; + +public: + class CapabilityIterator + { + public: + CapabilityIterator(Membership &m,const NetworkConfig &nconf) : + _hti(m._remoteCaps), + _k((uint32_t *)0), + _c((Capability *)0), + _m(m), + _nconf(nconf) + { + } + + inline Capability *next() + { + while (_hti.next(_k,_c)) { + if (_m._isCredentialTimestampValid(_nconf,*_c)) + return _c; + } + return (Capability *)0; + } + + private: + Hashtable< uint32_t,Capability >::Iterator _hti; + uint32_t *_k; + Capability *_c; + Membership &_m; + const NetworkConfig &_nconf; + }; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/MulticastGroup.hpp b/node/MulticastGroup.hpp index dbf3899..be4e808 100644 --- a/node/MulticastGroup.hpp +++ b/node/MulticastGroup.hpp @@ -60,16 +60,6 @@ public: { } - MulticastGroup(const char *s) - { - fromString(s); - } - - MulticastGroup(const std::string &s) - { - fromString(s.c_str()); - } - /** * Derive the multicast group used for address resolution (ARP/NDP) for an IP * @@ -106,22 +96,6 @@ public: return std::string(buf); } - /** - * Parse a human-readable multicast group - * - * @param s Multicast group in hex MAC/ADI format - */ - inline void fromString(const char *s) - { - char hex[17]; - unsigned int hexlen = 0; - while ((*s)&&(*s != '/')&&(hexlen < (sizeof(hex) - 1))) - hex[hexlen++] = *s; - hex[hexlen] = (char)0; - _mac.fromString(hex); - _adi = (*s == '/') ? (uint32_t)Utils::hexStrToULong(s + 1) : (uint32_t)0; - } - /** * @return Multicast address */ diff --git a/node/Multicaster.cpp b/node/Multicaster.cpp index e1d4567..8e534b5 100644 --- a/node/Multicaster.cpp +++ b/node/Multicaster.cpp @@ -34,8 +34,8 @@ namespace ZeroTier { Multicaster::Multicaster(const RuntimeEnvironment *renv) : RR(renv), - _groups(1024), - _groups_m() + _groups(256), + _gatherAuth(256) { } @@ -43,14 +43,14 @@ Multicaster::~Multicaster() { } -void Multicaster::addMultiple(uint64_t now,uint64_t nwid,const MulticastGroup &mg,const void *addresses,unsigned int count,unsigned int totalKnown) +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(now,nwid,mg,gs,Address(p,5)); + _add(tPtr,now,nwid,mg,gs,Address(p,5)); p += 5; } } @@ -152,10 +152,11 @@ std::vector
Multicaster::getMembers(uint64_t nwid,const MulticastGroup } void Multicaster::send( - const CertificateOfMembership *com, + void *tPtr, unsigned int limit, uint64_t now, uint64_t nwid, + bool disableCompression, const std::vector
&alwaysSendTo, const MulticastGroup &mg, const MAC &src, @@ -194,7 +195,7 @@ void Multicaster::send( RR, now, nwid, - com, + disableCompression, limit, 1, // we'll still gather a little from peers to keep multicast list fresh src, @@ -207,7 +208,7 @@ void Multicaster::send( for(std::vector
::const_iterator ast(alwaysSendTo.begin());ast!=alwaysSendTo.end();++ast) { if (*ast != RR->identity.address()) { - out.sendOnly(RR,*ast); // optimization: don't use dedup log if it's a one-pass send + out.sendOnly(RR,tPtr,*ast); // optimization: don't use dedup log if it's a one-pass send if (++count >= limit) break; } @@ -217,7 +218,7 @@ void Multicaster::send( 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,ma); // optimization: don't use dedup log if it's a one-pass send + out.sendOnly(RR,tPtr,ma); // optimization: don't use dedup log if it's a one-pass send ++count; } } @@ -226,35 +227,38 @@ void Multicaster::send( if ((gs.members.empty())||((now - gs.lastExplicitGather) >= ZT_MULTICAST_EXPLICIT_GATHER_DELAY)) { gs.lastExplicitGather = now; - SharedPtr explicitGatherPeers[2]; - explicitGatherPeers[0] = RR->topology->getBestRoot(); - const Address nwidc(Network::controllerFor(nwid)); - if (nwidc != RR->identity.address()) - explicitGatherPeers[1] = RR->topology->getPeer(nwidc); - for(unsigned int k=0;k<2;++k) { - const SharedPtr &p = explicitGatherPeers[k]; - if (!p) - continue; - //TRACE(">>MC upstream GATHER up to %u for group %.16llx/%s",gatherLimit,nwid,mg.toString().c_str()); - const CertificateOfMembership *com = (CertificateOfMembership *)0; - { - SharedPtr nw(RR->node->network(nwid)); - if ((nw)&&(nw->hasConfig())&&(nw->config().com)&&(nw->config().isPrivate())&&(p->needsOurNetworkMembershipCertificate(nwid,now,true))) - com = &(nw->config().com); + 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; + } } + } - Packet outp(p->address(),RR->identity.address(),Packet::VERB_MULTICAST_GATHER); + 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)); + 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->sw->send(outp,true,0); + RR->node->expectReplyTo(outp.packetId()); + RR->sw->send(tPtr,outp,true); } - gatherLimit = 0; } gs.txQueue.push_back(OutboundMulticast()); @@ -264,7 +268,7 @@ void Multicaster::send( RR, now, nwid, - com, + disableCompression, limit, gatherLimit, src, @@ -277,7 +281,7 @@ void Multicaster::send( for(std::vector
::const_iterator ast(alwaysSendTo.begin());ast!=alwaysSendTo.end();++ast) { if (*ast != RR->identity.address()) { - out.sendAndLog(RR,*ast); + out.sendAndLog(RR,tPtr,*ast); if (++count >= limit) break; } @@ -287,7 +291,7 @@ void Multicaster::send( 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,ma); + out.sendAndLog(RR,tPtr,ma); ++count; } } @@ -301,43 +305,63 @@ void Multicaster::send( void Multicaster::clean(uint64_t now) { - Mutex::Lock _l(_groups_m); + { + 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; + } - 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; + 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; } - ++reader; + } + + if (count) { + s->members.resize(count); + } else if (s->txQueue.empty()) { + _groups.erase(*k); + } else { + s->members.clear(); } } + } - 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::_add(uint64_t now,uint64_t nwid,const MulticastGroup &mg,MulticastGroupStatus &gs,const Address &member) +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 @@ -360,7 +384,7 @@ void Multicaster::_add(uint64_t now,uint64_t nwid,const MulticastGroup &mg,Multi if (tx->atLimit()) gs.txQueue.erase(tx++); else { - tx->sendIfNew(RR,member); + tx->sendIfNew(RR,tPtr,member); if (tx->atLimit()) gs.txQueue.erase(tx++); else ++tx; diff --git a/node/Multicaster.hpp b/node/Multicaster.hpp index c43c8d9..f646a5b 100644 --- a/node/Multicaster.hpp +++ b/node/Multicaster.hpp @@ -90,10 +90,10 @@ public: * @param mg Multicast group * @param member New member address */ - inline void add(uint64_t now,uint64_t nwid,const MulticastGroup &mg,const Address &member) + inline void add(void *tPtr,uint64_t now,uint64_t nwid,const MulticastGroup &mg,const Address &member) { Mutex::Lock _l(_groups_m); - _add(now,nwid,mg,_groups[Multicaster::Key(nwid,mg)],member); + _add(tPtr,now,nwid,mg,_groups[Multicaster::Key(nwid,mg)],member); } /** @@ -101,6 +101,7 @@ public: * * 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 @@ -108,7 +109,7 @@ public: * @param count Number of addresses * @param totalKnown Total number of known addresses as reported by peer */ - void addMultiple(uint64_t now,uint64_t nwid,const MulticastGroup &mg,const void *addresses,unsigned int count,unsigned int totalKnown); + 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) @@ -150,10 +151,11 @@ public: /** * Send a multicast * - * @param com Certificate of membership to include or NULL for none + * @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) @@ -162,10 +164,11 @@ public: * @param len Length of packet data */ void send( - const CertificateOfMembership *com, + void *tPtr, unsigned int limit, uint64_t now, uint64_t nwid, + bool disableCompression, const std::vector
&alwaysSendTo, const MulticastGroup &mg, const MAC &src, @@ -181,12 +184,52 @@ public: */ 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(uint64_t now,uint64_t nwid,const MulticastGroup &mg,MulticastGroupStatus &gs,const Address &member); + 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 diff --git a/node/Network.cpp b/node/Network.cpp index 2511664..b7f25f7 100644 --- a/node/Network.cpp +++ b/node/Network.cpp @@ -22,24 +22,663 @@ #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 "Packet.hpp" #include "Buffer.hpp" +#include "Packet.hpp" #include "NetworkController.hpp" #include "Node.hpp" +#include "Peer.hpp" +#include "Cluster.hpp" -#include "../version.h" +// 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,uint64_t nwid,void *uptr) : +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), @@ -47,43 +686,38 @@ Network::Network(const RuntimeEnvironment *renv,uint64_t nwid,void *uptr) : _netconfFailure(NETCONF_FAILURE_NONE), _portError(0) { - char confn[128],mcdbn[128]; + for(int i=0;inode->dataStoreDelete(mcdbn); - - if (_id == ZT_TEST_NETWORK_ID) { - applyConfiguration(NetworkConfig::createTestNetworkConfig(RR->identity.address())); - - // Save a one-byte CR to persist membership in the test network - RR->node->dataStorePut(confn,"\n",1,false); - } else { - bool gotConf = false; - try { - std::string conf(RR->node->dataStoreGet(confn)); - if (conf.length()) { - Dictionary dconf(conf.c_str()); - NetworkConfig nconf; - if (nconf.fromDictionary(dconf)) { - this->setConfiguration(nconf,false); - _lastConfigUpdate = 0; // we still want to re-request a new config from the network - gotConf = true; - } + bool gotConf = false; + Dictionary *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 - - if (!gotConf) { - // Save a one-byte CR to persist membership while we request a real netconf - RR->node->dataStorePut(confn,"\n",1,false); } + } 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(_id,&_uPtr,ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP,&ctmp); + _portError = RR->node->configureVirtualNetworkPort(tPtr,_id,&_uPtr,ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP,&ctmp); _portInitialized = true; } } @@ -95,14 +729,242 @@ Network::~Network() char n[128]; if (_destroyed) { - RR->node->configureVirtualNetworkPort(_id,&_uPtr,ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY,&ctmp); + // This is done in Node::leave() so we can pass tPtr + //RR->node->configureVirtualNetworkPort((void *)0,_id,&_uPtr,ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY,&ctmp); Utils::snprintf(n,sizeof(n),"networks.d/%.16llx.conf",_id); - RR->node->dataStoreDelete(n); + RR->node->dataStoreDelete((void *)0,n); } else { - RR->node->configureVirtualNetworkPort(_id,&_uPtr,ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DOWN,&ctmp); + 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); @@ -110,145 +972,364 @@ bool Network::subscribedToMulticastGroup(const MulticastGroup &mg,bool includeBr return true; else if (includeBridgedGroups) return _multicastGroupsBehindMe.contains(mg); - else return false; + return false; } -void Network::multicastSubscribe(const MulticastGroup &mg) +void Network::multicastSubscribe(void *tPtr,const MulticastGroup &mg) { - { - Mutex::Lock _l(_lock); - if (std::binary_search(_myMulticastGroups.begin(),_myMulticastGroups.end(),mg)) - return; - _myMulticastGroups.push_back(mg); - std::sort(_myMulticastGroups.begin(),_myMulticastGroups.end()); + 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); } - _announceMulticastGroups(); } void Network::multicastUnsubscribe(const MulticastGroup &mg) { Mutex::Lock _l(_lock); - std::vector nmg; - for(std::vector::const_iterator i(_myMulticastGroups.begin());i!=_myMulticastGroups.end();++i) { - if (*i != mg) - nmg.push_back(*i); - } - if (nmg.size() != _myMulticastGroups.size()) - _myMulticastGroups.swap(nmg); + std::vector::iterator i(std::lower_bound(_myMulticastGroups.begin(),_myMulticastGroups.end(),mg)); + if ( (i != _myMulticastGroups.end()) && (*i == mg) ) + _myMulticastGroups.erase(i); } -bool Network::tryAnnounceMulticastGroupsTo(const SharedPtr &peer) +uint64_t Network::handleConfigChunk(void *tPtr,const uint64_t packetId,const Address &source,const Buffer &chunk,unsigned int ptr) { - Mutex::Lock _l(_lock); - if ( - (_isAllowed(peer)) || - (peer->address() == this->controller()) || - (RR->topology->isRoot(peer->identity())) - ) { - _announceMulticastGroupsTo(peer,_allMulticastGroups()); - return true; - } - return false; -} + if (_destroyed) + return 0; -bool Network::applyConfiguration(const NetworkConfig &conf) -{ - if (_destroyed) // sanity check - return false; - try { - if ((conf.networkId == _id)&&(conf.issuedTo == RR->identity.address())) { - ZT_VirtualNetworkConfig ctmp; - bool portInitialized; - { - Mutex::Lock _l(_lock); - _config = conf; - _lastConfigUpdate = RR->node->now(); - _netconfFailure = NETCONF_FAILURE_NONE; - _externalConfig(&ctmp); - portInitialized = _portInitialized; - _portInitialized = true; + 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; } - _portError = RR->node->configureVirtualNetworkPort(_id,&_uPtr,(portInitialized) ? ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE : ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP,&ctmp); - return true; + + 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("ignored invalid configuration for network %.16llx (configuration contains mismatched network ID or issued-to address)",(unsigned long long)_id); + 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; + } } - } catch (std::exception &exc) { - TRACE("ignored invalid configuration for network %.16llx (%s)",(unsigned long long)_id,exc.what()); - } catch ( ... ) { - TRACE("ignored invalid configuration for network %.16llx (unknown exception)",(unsigned long long)_id); } - return false; + + if (nc) { + this->setConfiguration(tPtr,*nc,true); + delete nc; + return configUpdateId; + } else { + return 0; + } + + return 0; } -int Network::setConfiguration(const NetworkConfig &nconf,bool saveToDisk) +int Network::setConfiguration(void *tPtr,const NetworkConfig &nconf,bool saveToDisk) { + if (_destroyed) + return 0; + + // _lock is NOT locked when this is called try { - { + if ((nconf.issuedTo != RR->identity.address())||(nconf.networkId != _id)) + return 0; // invalid config that is not for us or not for this network + if (_config == nconf) + return 1; // OK config, but duplicate of what we already have + + ZT_VirtualNetworkConfig ctmp; + bool oldPortInitialized; + { // do things that require lock here, but unlock before calling callbacks Mutex::Lock _l(_lock); - if (_config == nconf) - return 1; // OK config, but duplicate of what we already have + + _config = nconf; + _lastConfigUpdate = RR->node->now(); + _netconfFailure = NETCONF_FAILURE_NONE; + + oldPortInitialized = _portInitialized; + _portInitialized = true; + + _externalConfig(&ctmp); + + Address *a = (Address *)0; + Membership *m = (Membership *)0; + Hashtable::Iterator i(_memberships); + while (i.next(a,m)) + m->resetPushState(); } - if (applyConfiguration(nconf)) { - if (saveToDisk) { + + _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); - Dictionary d; - if (nconf.toDictionary(d,false)) - RR->node->dataStorePut(n,(const void *)d.data(),d.sizeBytes(),true); - } - return 2; // OK and configuration has changed + 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 Network::requestConfiguration(void *tPtr) { - if (_id == ZT_TEST_NETWORK_ID) // pseudo-network-ID, uses locally generated static config + if (_destroyed) return; - Dictionary rmd; + /* 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 (controller() == RR->identity.address()) { + if (ctrl == RR->identity.address()) { if (RR->localNetworkController) { - NetworkConfig nconf; - switch(RR->localNetworkController->doNetworkConfigRequest(InetAddress(),RR->identity,RR->identity,_id,rmd,nconf)) { - case NetworkController::NETCONF_QUERY_OK: - this->setConfiguration(nconf,true); - return; - case NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND: - this->setNotFound(); - return; - case NetworkController::NETCONF_QUERY_ACCESS_DENIED: - this->setAccessDenied(); - return; - default: - return; - } + RR->localNetworkController->request(_id,InetAddress(),0xffffffffffffffffULL,RR->identity,rmd); } else { this->setNotFound(); - return; } + return; } - TRACE("requesting netconf for network %.16llx from controller %s",(unsigned long long)_id,controller().toString().c_str()); + TRACE("requesting netconf for network %.16llx from controller %s",(unsigned long long)_id,ctrl.toString().c_str()); - Packet outp(controller(),RR->identity.address(),Packet::VERB_NETWORK_CONFIG_REQUEST); + 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); - outp.append((_config) ? (uint64_t)_config.revision : (uint64_t)0); + if (_config) { + outp.append((uint64_t)_config.revision); + outp.append((uint64_t)_config.timestamp); + } else { + outp.append((unsigned char)0,16); + } outp.compress(); - RR->sw->send(outp,true,0); + RR->node->expectReplyTo(outp.packetId()); + RR->sw->send(tPtr,outp,true); +} + +bool Network::gate(void *tPtr,const SharedPtr &peer) +{ + const uint64_t now = RR->node->now(); + Mutex::Lock _l(_lock); + try { + if (_config) { + Membership *m = _memberships.get(peer->address()); + if ( (_config.isPublic()) || ((m)&&(m->isAllowedOnNetwork(_config))) ) { + if (!m) + m = &(_membership(peer->address())); + if (m->multicastLikeGate(now)) { + m->pushCredentials(RR,tPtr,now,peer->address(),_config,-1,false); + _announceMulticastGroupsTo(tPtr,peer->address(),_allMulticastGroups()); + } + return true; + } + } + } catch ( ... ) { + TRACE("gate() check failed for peer %s: unexpected exception",peer->address().toString().c_str()); + } + return false; } void Network::clean() @@ -268,6 +1349,17 @@ void Network::clean() _multicastGroupsBehindMe.erase(*mg); } } + + { + Address *a = (Address *)0; + Membership *m = (Membership *)0; + Hashtable::Iterator i(_memberships); + while (i.next(a,m)) { + if (!RR->topology->getPeerNoCache(*a)) + _memberships.erase(*a); + else m->clean(now,_config); + } + } } void Network::learnBridgeRoute(const MAC &mac,const Address &addr) @@ -307,13 +1399,59 @@ void Network::learnBridgeRoute(const MAC &mac,const Address &addr) } } -void Network::learnBridgedMulticastGroup(const MulticastGroup &mg,uint64_t now) +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()) - _announceMulticastGroups(); + _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() @@ -350,6 +1488,7 @@ void Network::_externalConfig(ZT_VirtualNetworkConfig *ec) const 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; @@ -378,96 +1517,100 @@ void Network::_externalConfig(ZT_VirtualNetworkConfig *ec) const } } -bool Network::_isAllowed(const SharedPtr &peer) const +void Network::_sendUpdatesToMembers(void *tPtr,const MulticastGroup *const newMulticastGroup) { // Assumes _lock is locked - try { - if (!_config) - return false; - if (_config.isPublic()) - return true; - return ((_config.com)&&(peer->networkMembershipCertificatesAgree(_id,_config.com))); - } catch (std::exception &exc) { - TRACE("isAllowed() check failed for peer %s: unexpected exception: %s",peer->address().toString().c_str(),exc.what()); - } catch ( ... ) { - TRACE("isAllowed() check failed for peer %s: unexpected exception: unknown exception",peer->address().toString().c_str()); - } - return false; // default position on any failure -} + const uint64_t now = RR->node->now(); -class _MulticastAnnounceAll -{ -public: - _MulticastAnnounceAll(const RuntimeEnvironment *renv,Network *nw) : - _now(renv->node->now()), - _controller(nw->controller()), - _network(nw), - _anchors(nw->config().anchors()), - _rootAddresses(renv->topology->rootAddresses()) - {} - inline void operator()(Topology &t,const SharedPtr &p) - { - if ( (_network->_isAllowed(p)) || // FIXME: this causes multicast LIKEs for public networks to get spammed - (p->address() == _controller) || - (std::find(_rootAddresses.begin(),_rootAddresses.end(),p->address()) != _rootAddresses.end()) || - (std::find(_anchors.begin(),_anchors.end(),p->address()) != _anchors.end()) ) { - peers.push_back(p); - } - } - std::vector< SharedPtr > peers; -private: - const uint64_t _now; - const Address _controller; - Network *const _network; - const std::vector
_anchors; - const std::vector
_rootAddresses; -}; -void Network::_announceMulticastGroups() -{ - // Assumes _lock is locked - std::vector allMulticastGroups(_allMulticastGroups()); - _MulticastAnnounceAll gpfunc(RR,this); - RR->topology->eachPeer<_MulticastAnnounceAll &>(gpfunc); - for(std::vector< SharedPtr >::const_iterator i(gpfunc.peers.begin());i!=gpfunc.peers.end();++i) - _announceMulticastGroupsTo(*i,allMulticastGroups); -} + std::vector groups; + if (newMulticastGroup) + groups.push_back(*newMulticastGroup); + else groups = _allMulticastGroups(); -void Network::_announceMulticastGroupsTo(const SharedPtr &peer,const std::vector &allMulticastGroups) const -{ - // Assumes _lock is locked + if ((newMulticastGroup)||((now - _lastAnnouncedMulticastGroupsUpstream) >= ZT_MULTICAST_ANNOUNCE_PERIOD)) { + if (!newMulticastGroup) + _lastAnnouncedMulticastGroupsUpstream = now; - // We push COMs ahead of MULTICAST_LIKE since they're used for access control -- a COM is a public - // credential so "over-sharing" isn't really an issue (and we only do so with roots). - if ((_config)&&(_config.com)&&(!_config.isPublic())&&(peer->needsOurNetworkMembershipCertificate(_id,RR->node->now(),true))) { - Packet outp(peer->address(),RR->identity.address(),Packet::VERB_NETWORK_MEMBERSHIP_CERTIFICATE); - _config.com.serialize(outp); - RR->sw->send(outp,true,0); - } - - { - Packet outp(peer->address(),RR->identity.address(),Packet::VERB_MULTICAST_LIKE); - - for(std::vector::const_iterator mg(allMulticastGroups.begin());mg!=allMulticastGroups.end();++mg) { - if ((outp.size() + 18) >= ZT_UDP_DEFAULT_PAYLOAD_MTU) { - RR->sw->send(outp,true,0); - outp.reset(peer->address(),RR->identity.address(),Packet::VERB_MULTICAST_LIKE); + // 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); } - - // network ID, MAC, ADI - outp.append((uint64_t)_id); - mg->mac().appendTo(outp); - outp.append((uint32_t)mg->adi()); + _announceMulticastGroupsTo(tPtr,*a,groups); } - if (outp.size() > ZT_PROTO_MIN_PACKET_LENGTH) - RR->sw->send(outp,true,0); + // Also announce to controller, and send COM to simplify and generalize behavior even though in theory it does not need it + const Address c(controller()); + if ( (std::find(upstreams.begin(),upstreams.end(),c) == upstreams.end()) && (!_memberships.contains(c)) ) { + if (_config.com) { + Packet outp(c,RR->identity.address(),Packet::VERB_NETWORK_CREDENTIALS); + _config.com.serialize(outp); + outp.append((uint8_t)0x00); + outp.append((uint16_t)0); // no capabilities + outp.append((uint16_t)0); // no tags + outp.append((uint16_t)0); // no revocations + outp.append((uint16_t)0); // no certificates of ownership + RR->sw->send(tPtr,outp,true); + } + _announceMulticastGroupsTo(tPtr,c,groups); + } + } + + // Make sure that all "network anchors" have Membership records so we will + // push multicasts to them. + const std::vector
anchors(_config.anchors()); + for(std::vector
::const_iterator a(anchors.begin());a!=anchors.end();++a) + _membership(*a); + + // Send credentials and multicast LIKEs to members, upstreams, and controller + { + Address *a = (Address *)0; + Membership *m = (Membership *)0; + Hashtable::Iterator i(_memberships); + while (i.next(a,m)) { + m->pushCredentials(RR,tPtr,now,*a,_config,-1,false); + if ( ( m->multicastLikeGate(now) || (newMulticastGroup) ) && (m->isAllowedOnNetwork(_config)) ) + _announceMulticastGroupsTo(tPtr,*a,groups); + } + } +} + +void Network::_announceMulticastGroupsTo(void *tPtr,const Address &peer,const std::vector &allMulticastGroups) +{ + // Assumes _lock is locked + Packet outp(peer,RR->identity.address(),Packet::VERB_MULTICAST_LIKE); + + for(std::vector::const_iterator mg(allMulticastGroups.begin());mg!=allMulticastGroups.end();++mg) { + if ((outp.size() + 24) >= ZT_PROTO_MAX_PACKET_LENGTH) { + outp.compress(); + RR->sw->send(tPtr,outp,true); + outp.reset(peer,RR->identity.address(),Packet::VERB_MULTICAST_LIKE); + } + + // network ID, MAC, ADI + outp.append((uint64_t)_id); + mg->mac().appendTo(outp); + outp.append((uint32_t)mg->adi()); + } + + if (outp.size() > ZT_PROTO_MIN_PACKET_LENGTH) { + outp.compress(); + RR->sw->send(tPtr,outp,true); } } std::vector Network::_allMulticastGroups() const { // Assumes _lock is locked - std::vector mgs; mgs.reserve(_myMulticastGroups.size() + _multicastGroupsBehindMe.size() + 1); mgs.insert(mgs.end(),_myMulticastGroups.begin(),_myMulticastGroups.end()); @@ -476,8 +1619,13 @@ std::vector Network::_allMulticastGroups() const mgs.push_back(Network::BROADCAST); std::sort(mgs.begin(),mgs.end()); mgs.erase(std::unique(mgs.begin(),mgs.end()),mgs.end()); - return mgs; } +Membership &Network::_membership(const Address &a) +{ + // assumes _lock is locked + return _memberships[a]; +} + } // namespace ZeroTier diff --git a/node/Network.hpp b/node/Network.hpp index 17eed4b..faef0fe 100644 --- a/node/Network.hpp +++ b/node/Network.hpp @@ -40,14 +40,17 @@ #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; -class _MulticastAnnounceAll; /** * A virtual LAN @@ -55,7 +58,6 @@ class _MulticastAnnounceAll; class Network : NonCopyable { friend class SharedPtr; - friend class _MulticastAnnounceAll; // internal function object public: /** @@ -63,6 +65,11 @@ public: */ 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 * @@ -70,50 +77,90 @@ public: * 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,uint64_t nwid,void *uptr); + Network(const RuntimeEnvironment *renv,void *tPtr,uint64_t nwid,void *uptr); ~Network(); - /** - * @return Network ID - */ - inline uint64_t id() const throw() { return _id; } + 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; } /** - * @return Address of network's controller (most significant 40 bits of ID) + * 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 */ - inline Address controller() const throw() { return Address(_id >> 24); } + 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); /** - * @param nwid Network ID - * @return Address of network's controller + * 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 */ - static inline Address controllerFor(uint64_t nwid) throw() { return Address(nwid >> 24); } - - /** - * @return Multicast group memberships for this network's port (local, not learned via bridging) - */ - inline std::vector multicastGroups() const - { - Mutex::Lock _l(_lock); - return _myMulticastGroups; - } - - /** - * @return All multicast groups including learned groups that are behind any bridges we're attached to - */ - inline std::vector allMulticastGroups() const - { - Mutex::Lock _l(_lock); - return _allMulticastGroups(); - } + 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 include any groups we've learned via bridging + * @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; @@ -121,9 +168,10 @@ public: /** * 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(const MulticastGroup &mg); + void multicastSubscribe(void *tPtr,const MulticastGroup &mg); /** * Unsubscribe from a multicast group @@ -133,29 +181,30 @@ public: void multicastUnsubscribe(const MulticastGroup &mg); /** - * Announce multicast groups to a peer if that peer is authorized on this network + * Handle an inbound network config chunk * - * @param peer Peer to try to announce multicast groups to - * @return True if peer was authorized and groups were announced + * 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 */ - bool tryAnnounceMulticastGroupsTo(const SharedPtr &peer); + uint64_t handleConfigChunk(void *tPtr,const uint64_t packetId,const Address &source,const Buffer &chunk,unsigned int ptr); /** - * Apply a NetworkConfig to this network - * - * @param conf Configuration in NetworkConfig form - * @return True if configuration was accepted - */ - bool applyConfiguration(const NetworkConfig &conf); - - /** - * Set or update this network's configuration + * 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 IF true (default), write config to disk - * @return 0 -- rejected, 1 -- accepted but not new, 2 -- accepted new config + * @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(const NetworkConfig &nconf,bool saveToDisk); + int setConfiguration(void *tPtr,const NetworkConfig &nconf,bool saveToDisk); /** * Set netconf failure to 'access denied' -- called in IncomingPacket when controller reports this @@ -167,7 +216,7 @@ public: } /** - * Set netconf failure to 'not found' -- called by PacketDecider when controller reports this + * Set netconf failure to 'not found' -- called by IncomingPacket when controller reports this */ inline void setNotFound() { @@ -177,77 +226,35 @@ public: /** * 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 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 - * @return True if peer is allowed to communicate on this network */ - inline bool isAllowed(const SharedPtr &peer) const - { - Mutex::Lock _l(_lock); - return _isAllowed(peer); - } + bool gate(void *tPtr,const SharedPtr &peer); /** - * Perform cleanup and possibly save state + * Do periodic cleanup and housekeeping tasks */ void clean(); /** - * @return Time of last updated configuration or 0 if none - */ - inline uint64_t lastConfigUpdate() const throw() { return _lastConfigUpdate; } - - /** - * @return Status of this network - */ - inline ZT_VirtualNetworkStatus status() const - { - Mutex::Lock _l(_lock); - return _status(); - } - - /** - * @param ec Buffer to fill with externally-visible network configuration - */ - inline void externalConfig(ZT_VirtualNetworkConfig *ec) const - { - Mutex::Lock _l(_lock); - _externalConfig(ec); - } - - /** - * Get current network config + * Push state to members such as multicast group memberships and latest COM (if needed) * - * This returns a const reference to the network config in place, which is safe - * to concurrently access but *may* change during access. Normally this isn't a - * problem, but if it is use configCopy(). - * - * @return Network configuration (may be a null config if we don't have one yet) + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call */ - inline const NetworkConfig &config() const { return _config; } - - /** - * @return A thread-safe copy of our NetworkConfig instead of a const reference - */ - inline NetworkConfig configCopy() const + inline void sendUpdatesToMembers(void *tPtr) { Mutex::Lock _l(_lock); - return _config; + _sendUpdatesToMembers(tPtr,(const MulticastGroup *)0); } - /** - * @return True if this network has a valid config - */ - inline bool hasConfig() const { return (_config); } - - /** - * @return Ethernet MAC address for this network's local interface - */ - inline const MAC &mac() const throw() { return _mac; } - /** * Find the node on this network that has this MAC behind it (if any) * @@ -258,9 +265,7 @@ public: { Mutex::Lock _l(_lock); const Address *const br = _remoteBridgeRoutes.get(mac); - if (br) - return *br; - return Address(); + return ((br) ? *br : Address()); } /** @@ -274,54 +279,128 @@ public: /** * 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(const MulticastGroup &mg,uint64_t now); + 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. + * This sets the network to completely remove itself on delete. This also prevents the + * call of the normal port shutdown event on delete. */ void destroy(); /** - * @return Pointer to user PTR (modifiable user ptr used in API) + * Get this network's config for export via the ZT core API + * + * @param ec Buffer to fill with externally-visible network configuration */ - inline void **userPtr() throw() { return &_uPtr; } + inline void externalConfig(ZT_VirtualNetworkConfig *ec) const + { + Mutex::Lock _l(_lock); + _externalConfig(ec); + } - inline bool operator==(const Network &n) const throw() { return (_id == n._id); } - inline bool operator!=(const Network &n) const throw() { return (_id != n._id); } - inline bool operator<(const Network &n) const throw() { return (_id < n._id); } - inline bool operator>(const Network &n) const throw() { return (_id > n._id); } - inline bool operator<=(const Network &n) const throw() { return (_id <= n._id); } - inline bool operator>=(const Network &n) const throw() { return (_id >= n._id); } + /** + * @return Externally usable pointer-to-pointer exported via the core API + */ + inline void **userPtr() { return &_uPtr; } private: ZT_VirtualNetworkStatus _status() const; void _externalConfig(ZT_VirtualNetworkConfig *ec) const; // assumes _lock is locked - bool _isAllowed(const SharedPtr &peer) const; - void _announceMulticastGroups(); - void _announceMulticastGroupsTo(const SharedPtr &peer,const std::vector &allMulticastGroups) const; + 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 *RR; + const RuntimeEnvironment *const RR; void *_uPtr; - uint64_t _id; + const uint64_t _id; + uint64_t _lastAnnouncedMulticastGroupsUpstream; MAC _mac; // local MAC address - volatile bool _portInitialized; + 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; - volatile uint64_t _lastConfigUpdate; + uint64_t _lastConfigUpdate; - volatile bool _destroyed; + 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, @@ -329,7 +408,9 @@ private: NETCONF_FAILURE_NOT_FOUND, NETCONF_FAILURE_INIT_FAILED } _netconfFailure; - volatile int _portError; // return value from port config callback + int _portError; // return value from port config callback + + Hashtable _memberships; Mutex _lock; diff --git a/node/NetworkConfig.cpp b/node/NetworkConfig.cpp index 9d5c5f1..fe7393e 100644 --- a/node/NetworkConfig.cpp +++ b/node/NetworkConfig.cpp @@ -18,248 +18,168 @@ #include +#include + #include "NetworkConfig.hpp" -#include "Utils.hpp" namespace ZeroTier { bool NetworkConfig::toDictionary(Dictionary &d,bool includeLegacy) const { - Buffer tmp; + Buffer *tmp = new Buffer(); - d.clear(); + try { + d.clear(); - // Try to put the more human-readable fields first + // 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_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; + 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; + 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 tmp[16]; - Utils::snprintf(tmp,sizeof(tmp),"%x",et); - ets.append(tmp); + 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()); } - 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 (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; + } - if (this->com) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATE_OF_MEMBERSHIP_OLD,this->com.toString().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; + } - 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 (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; } } - if (ab.length() > 0) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ACTIVE_BRIDGES_OLD,ab.c_str())) return false; - } - - std::vector rvec(this->relays()); - std::string rl; - for(std::vector::const_iterator i(rvec.begin());i!=rvec.end();++i) { - if (rl.length() > 0) - rl.push_back(','); - rl.append(i->address.toString()); - if (i->phy4) { - rl.push_back(';'); - rl.append(i->phy4.toString()); - } else if (i->phy6) { - rl.push_back(';'); - rl.append(i->phy6.toString()); - } - } - if (rl.length() > 0) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_RELAYS_OLD,rl.c_str())) return false; - } - } #endif // ZT_SUPPORT_OLD_STYLE_NETCONF - // Then add binary blobs + // 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;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; - } - - tmp.clear(); - for(unsigned int i=0;ipinnedCount;++i) { - this->pinned[i].zt.appendTo(tmp); - this->pinned[i].phy.serialize(tmp); - } - if (tmp.size()) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_PINNED,tmp)) return false; - } - - tmp.clear(); - for(unsigned int i=0;iruleCount;++i) { - tmp.append((uint8_t)rules[i].t); - switch((ZT_VirtualNetworkRuleType)(rules[i].t & 0x7f)) { - //case ZT_NETWORK_RULE_ACTION_DROP: - //case ZT_NETWORK_RULE_ACTION_ACCEPT: - default: - tmp.append((uint8_t)0); - break; - case ZT_NETWORK_RULE_ACTION_TEE: - case ZT_NETWORK_RULE_ACTION_REDIRECT: - case ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS: - case ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS: - tmp.append((uint8_t)5); - Address(rules[i].v.zt).appendTo(tmp); - break; - case ZT_NETWORK_RULE_MATCH_VLAN_ID: - tmp.append((uint8_t)2); - tmp.append((uint16_t)rules[i].v.vlanId); - break; - case ZT_NETWORK_RULE_MATCH_VLAN_PCP: - tmp.append((uint8_t)1); - tmp.append((uint8_t)rules[i].v.vlanPcp); - break; - case ZT_NETWORK_RULE_MATCH_VLAN_DEI: - tmp.append((uint8_t)1); - tmp.append((uint8_t)rules[i].v.vlanDei); - break; - case ZT_NETWORK_RULE_MATCH_ETHERTYPE: - tmp.append((uint8_t)2); - tmp.append((uint16_t)rules[i].v.etherType); - break; - case ZT_NETWORK_RULE_MATCH_MAC_SOURCE: - case ZT_NETWORK_RULE_MATCH_MAC_DEST: - tmp.append((uint8_t)6); - tmp.append(rules[i].v.mac,6); - break; - case ZT_NETWORK_RULE_MATCH_IPV4_SOURCE: - case ZT_NETWORK_RULE_MATCH_IPV4_DEST: - tmp.append((uint8_t)5); - tmp.append(&(rules[i].v.ipv4.ip),4); - tmp.append((uint8_t)rules[i].v.ipv4.mask); - break; - case ZT_NETWORK_RULE_MATCH_IPV6_SOURCE: - case ZT_NETWORK_RULE_MATCH_IPV6_DEST: - tmp.append((uint8_t)17); - tmp.append(rules[i].v.ipv6.ip,16); - tmp.append((uint8_t)rules[i].v.ipv6.mask); - break; - case ZT_NETWORK_RULE_MATCH_IP_TOS: - tmp.append((uint8_t)1); - tmp.append((uint8_t)rules[i].v.ipTos); - break; - case ZT_NETWORK_RULE_MATCH_IP_PROTOCOL: - tmp.append((uint8_t)1); - tmp.append((uint8_t)rules[i].v.ipProtocol); - break; - case ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE: - case ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE: - tmp.append((uint8_t)4); - tmp.append((uint16_t)rules[i].v.port[0]); - tmp.append((uint16_t)rules[i].v.port[1]); - break; - case ZT_NETWORK_RULE_MATCH_CHARACTERISTICS: - tmp.append((uint8_t)8); - tmp.append((uint64_t)rules[i].v.characteristics); - break; - case ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE: - tmp.append((uint8_t)4); - tmp.append((uint16_t)rules[i].v.frameSize[0]); - tmp.append((uint16_t)rules[i].v.frameSize[1]); - break; - case ZT_NETWORK_RULE_MATCH_TCP_RELATIVE_SEQUENCE_NUMBER_RANGE: - tmp.append((uint8_t)8); - tmp.append((uint32_t)rules[i].v.tcpseq[0]); - tmp.append((uint32_t)rules[i].v.tcpseq[1]); - break; - case ZT_NETWORK_RULE_MATCH_COM_FIELD_GE: - case ZT_NETWORK_RULE_MATCH_COM_FIELD_LE: - tmp.append((uint8_t)16); - tmp.append((uint64_t)rules[i].v.comIV[0]); - tmp.append((uint64_t)rules[i].v.comIV[1]); - break; + if (this->com) { + tmp->clear(); + this->com.serialize(*tmp); + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_COM,*tmp)) return false; } - } - if (tmp.size()) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_RULES,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; @@ -267,26 +187,32 @@ bool NetworkConfig::toDictionary(Dictionary &d,b bool NetworkConfig::fromDictionary(const Dictionary &d) { - try { - Buffer tmp; - char tmp2[ZT_NETWORKCONFIG_DICT_CAPACITY]; + 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) + 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) + 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; @@ -338,36 +264,11 @@ bool NetworkConfig::fromDictionary(const Dictionary 0) { char *saveptr = (char *)0; for(char *f=Utils::stok(tmp2,",",&saveptr);(f);f=Utils::stok((char *)0,",",&saveptr)) { - this->addSpecialist(Address(f),ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE); - } - } - - if (d.get(ZT_NETWORKCONFIG_DICT_KEY_RELAYS_OLD,tmp2,sizeof(tmp2)) > 0) { - char *saveptr = (char *)0; - for(char *f=Utils::stok(tmp2,",",&saveptr);(f);f=Utils::stok((char *)0,",",&saveptr)) { - char tmp3[256]; - Utils::scopy(tmp3,sizeof(tmp3),f); - - InetAddress phy; - char *semi = tmp3; - while (*semi) { - if (*semi == ';') { - *semi = (char)0; - ++semi; - phy = InetAddress(semi); - } else ++semi; - } - Address zt(tmp3); - - this->addSpecialist(zt,ZT_NETWORKCONFIG_SPECIALIST_TYPE_NETWORK_PREFERRED_RELAY); - if ((phy)&&(this->pinnedCount < ZT_MAX_NETWORK_PINNED)) { - this->pinned[this->pinnedCount].zt = zt; - this->pinned[this->pinnedCount].phy = phy; - ++this->pinnedCount; - } + this->addSpecialist(Address(Utils::hexStrToU64(f)),ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE); } } #else + delete tmp; return false; #endif // ZT_SUPPORT_OLD_STYLE_NETCONF } else { @@ -375,116 +276,76 @@ bool NetworkConfig::fromDictionary(const Dictionaryflags = 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_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_SPECIALISTS,tmp)) { + 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 + 8) <= tmp.size())&&(specialistCount < ZT_MAX_NETWORK_SPECIALISTS)) { - this->specialists[this->specialistCount++] = tmp.at(p); + 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)) { + 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; + 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)) { + 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); + 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_PINNED,tmp)) { + if (d.get(ZT_NETWORKCONFIG_DICT_KEY_RULES,*tmp)) { + this->ruleCount = 0; unsigned int p = 0; - while ((p < tmp.size())&&(pinnedCount < ZT_MAX_NETWORK_PINNED)) { - this->pinned[this->pinnedCount].zt.setTo(tmp.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; - p += this->pinned[this->pinnedCount].phy.deserialize(tmp,p); - ++this->pinnedCount; - } - } - - if (d.get(ZT_NETWORKCONFIG_DICT_KEY_RULES,tmp)) { - unsigned int p = 0; - while ((p < tmp.size())&&(ruleCount < ZT_MAX_NETWORK_RULES)) { - rules[ruleCount].t = (uint8_t)tmp[p++]; - unsigned int fieldLen = (unsigned int)tmp[p++]; - switch((ZT_VirtualNetworkRuleType)(rules[ruleCount].t & 0x7f)) { - default: - break; - case ZT_NETWORK_RULE_ACTION_TEE: - case ZT_NETWORK_RULE_ACTION_REDIRECT: - case ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS: - case ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS: - rules[ruleCount].v.zt = Address(tmp.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH).toInt(); - break; - case ZT_NETWORK_RULE_MATCH_VLAN_ID: - rules[ruleCount].v.vlanId = tmp.at(p); - break; - case ZT_NETWORK_RULE_MATCH_VLAN_PCP: - rules[ruleCount].v.vlanPcp = (uint8_t)tmp[p]; - break; - case ZT_NETWORK_RULE_MATCH_VLAN_DEI: - rules[ruleCount].v.vlanDei = (uint8_t)tmp[p]; - break; - case ZT_NETWORK_RULE_MATCH_ETHERTYPE: - rules[ruleCount].v.etherType = tmp.at(p); - break; - case ZT_NETWORK_RULE_MATCH_MAC_SOURCE: - case ZT_NETWORK_RULE_MATCH_MAC_DEST: - memcpy(rules[ruleCount].v.mac,tmp.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),tmp.field(p,4),4); - rules[ruleCount].v.ipv4.mask = (uint8_t)tmp[p + 4]; - break; - case ZT_NETWORK_RULE_MATCH_IPV6_SOURCE: - case ZT_NETWORK_RULE_MATCH_IPV6_DEST: - memcpy(rules[ruleCount].v.ipv6.ip,tmp.field(p,16),16); - rules[ruleCount].v.ipv6.mask = (uint8_t)tmp[p + 16]; - break; - case ZT_NETWORK_RULE_MATCH_IP_TOS: - rules[ruleCount].v.ipTos = (uint8_t)tmp[p]; - break; - case ZT_NETWORK_RULE_MATCH_IP_PROTOCOL: - rules[ruleCount].v.ipProtocol = (uint8_t)tmp[p]; - break; - case ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE: - case ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE: - rules[ruleCount].v.port[0] = tmp.at(p); - rules[ruleCount].v.port[1] = tmp.at(p + 2); - break; - case ZT_NETWORK_RULE_MATCH_CHARACTERISTICS: - rules[ruleCount].v.characteristics = tmp.at(p); - break; - case ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE: - rules[ruleCount].v.frameSize[0] = tmp.at(p); - rules[ruleCount].v.frameSize[0] = tmp.at(p + 2); - break; - case ZT_NETWORK_RULE_MATCH_TCP_RELATIVE_SEQUENCE_NUMBER_RANGE: - rules[ruleCount].v.tcpseq[0] = tmp.at(p); - rules[ruleCount].v.tcpseq[1] = tmp.at(p + 4); - break; - case ZT_NETWORK_RULE_MATCH_COM_FIELD_GE: - case ZT_NETWORK_RULE_MATCH_COM_FIELD_LE: - rules[ruleCount].v.comIV[0] = tmp.at(p); - rules[ruleCount].v.comIV[1] = tmp.at(p + 8); - break; - } - p += fieldLen; - ++ruleCount; - } + Capability::deserializeRules(*tmp,p,this->rules,this->ruleCount,ZT_MAX_NETWORK_RULES); } } @@ -492,8 +353,10 @@ bool NetworkConfig::fromDictionary(const Dictionary &d); @@ -267,6 +262,11 @@ public: */ 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) */ @@ -304,40 +304,16 @@ public: } /** - * Get pinned physical address for a given ZeroTier address, if any - * - * @param zt ZeroTier address - * @param af Address family (e.g. AF_INET) or 0 for the first we find of any type - * @return Physical address, if any + * @param a Address to check + * @return True if address is an anchor */ - inline InetAddress findPinnedAddress(const Address &zt,unsigned int af) const + inline bool isAnchor(const Address &a) const { - for(unsigned int i=0;i relays() 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(&(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 &metaData, - NetworkConfig &nc) = 0; + const InetAddress &fromAddr, + uint64_t requestPacketId, + const Identity &identity, + const Dictionary &metaData) = 0; }; } // namespace ZeroTier diff --git a/node/Node.cpp b/node/Node.cpp index 1308502..2b3f799 100644 --- a/node/Node.cpp +++ b/node/Node.cpp @@ -37,7 +37,6 @@ #include "Identity.hpp" #include "SelfAwareness.hpp" #include "Cluster.hpp" -#include "DeferredPackets.hpp" const struct sockaddr_storage ZT_SOCKADDR_NULL = {0}; @@ -47,70 +46,48 @@ namespace ZeroTier { /* Public Node interface (C++, exposed via CAPI bindings) */ /****************************************************************************/ -Node::Node( - uint64_t now, - void *uptr, - ZT_DataStoreGetFunction dataStoreGetFunction, - ZT_DataStorePutFunction dataStorePutFunction, - ZT_WirePacketSendFunction wirePacketSendFunction, - ZT_VirtualNetworkFrameFunction virtualNetworkFrameFunction, - ZT_VirtualNetworkConfigFunction virtualNetworkConfigFunction, - ZT_PathCheckFunction pathCheckFunction, - ZT_EventCallback eventCallback) : +Node::Node(void *uptr,void *tptr,const struct ZT_Node_Callbacks *callbacks,uint64_t now) : _RR(this), RR(&_RR), _uPtr(uptr), - _dataStoreGetFunction(dataStoreGetFunction), - _dataStorePutFunction(dataStorePutFunction), - _wirePacketSendFunction(wirePacketSendFunction), - _virtualNetworkFrameFunction(virtualNetworkFrameFunction), - _virtualNetworkConfigFunction(virtualNetworkConfigFunction), - _pathCheckFunction(pathCheckFunction), - _eventCallback(eventCallback), - _networks(), - _networks_m(), - _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)); + + Utils::getSecureRandom((void *)_prngState,sizeof(_prngState)); + _online = false; - // 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.encrypt12(_prngStream,_prngStream,sizeof(_prngStream)); - } + memset(_expectingRepliesToBucketPtr,0,sizeof(_expectingRepliesToBucketPtr)); + memset(_expectingRepliesTo,0,sizeof(_expectingRepliesTo)); + memset(_lastIdentityVerification,0,sizeof(_lastIdentityVerification)); - { - std::string idtmp(dataStoreGet("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("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("identity.public"); - if (idtmp != RR->publicIdentityStr) { - if (!dataStorePut("identity.public",RR->publicIdentityStr,false)) - throw std::runtime_error("unable to write identity.public"); - } + 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); + RR->topology = new Topology(RR,tptr); RR->sa = new SelfAwareness(RR); - RR->dp = new DeferredPackets(RR); } catch ( ... ) { - delete RR->dp; delete RR->sa; delete RR->topology; delete RR->mc; @@ -118,7 +95,7 @@ Node::Node( throw; } - postEvent(ZT_EVENT_UP); + postEvent(tptr,ZT_EVENT_UP); } Node::~Node() @@ -127,18 +104,18 @@ Node::~Node() _networks.clear(); // ensure that networks are destroyed before shutdow - RR->dpEnabled = 0; - delete RR->dp; 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, @@ -147,11 +124,12 @@ ZT_ResultCode Node::processWirePacket( volatile uint64_t *nextBackgroundTaskDeadline) { _now = now; - RR->sw->onRemotePacket(*(reinterpret_cast(localAddress)),*(reinterpret_cast(remoteAddress)),packetData,packetLength); + 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, @@ -165,20 +143,22 @@ ZT_ResultCode Node::processVirtualNetworkFrame( _now = now; SharedPtr nw(this->network(nwid)); if (nw) { - RR->sw->onLocalEthernet(nw,MAC(sourceMac),MAC(destMac),etherType,vlanId,frameData,frameLength); + 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,uint64_t now,const std::vector &relays) : + _PingPeersThatNeedPing(const RuntimeEnvironment *renv,void *tPtr,Hashtable< Address,std::vector > &upstreamsToContact,uint64_t now) : lastReceiveFromUpstream(0), RR(renv), + _tPtr(tPtr), + _upstreamsToContact(upstreamsToContact), _now(now), - _relays(relays), - _world(RR->topology->world()) + _bestCurrentUpstream(RR->topology->getUpstreamPeer()) { } @@ -186,92 +166,56 @@ public: inline void operator()(Topology &t,const SharedPtr &p) { - bool upstream = false; - InetAddress stableEndpoint4,stableEndpoint6; + const std::vector *const upstreamStableEndpoints = _upstreamsToContact.get(p->address()); + if (upstreamStableEndpoints) { + bool contacted = false; - // If this is a world root, pick (if possible) both an IPv4 and an IPv6 stable endpoint to use if link isn't currently alive. - for(std::vector::const_iterator r(_world.roots().begin());r!=_world.roots().end();++r) { - if (r->identity == p->identity()) { - upstream = true; - for(unsigned long k=0,ptr=(unsigned long)RR->node->prng();k<(unsigned long)r->stableEndpoints.size();++k) { - const InetAddress &addr = r->stableEndpoints[ptr++ % r->stableEndpoints.size()]; - if (!stableEndpoint4) { - if (addr.ss_family == AF_INET) - stableEndpoint4 = addr; - } - if (!stableEndpoint6) { - if (addr.ss_family == AF_INET6) - stableEndpoint6 = addr; + // 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; } } - break; - } - } - - if (!upstream) { - // If I am a root server, only ping other root servers -- roots don't ping "down" - // since that would just be a waste of bandwidth and could potentially cause route - // flapping in Cluster mode. - if (RR->topology->amRoot()) - return; - - // Check for network preferred relays, also considered 'upstream' and thus always - // pinged to keep links up. If they have stable addresses we will try them there. - for(std::vector::const_iterator r(_relays.begin());r!=_relays.end();++r) { - if (r->address == p->address()) { - stableEndpoint4 = r->phy4; - stableEndpoint6 = r->phy6; - upstream = 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 (upstream) { - // "Upstream" devices are roots and relays and get special treatment -- they stay alive - // forever and we try to keep (if available) both IPv4 and IPv6 channels open to them. - bool needToContactIndirect = true; - if (p->doPingAndKeepalive(_now,AF_INET)) { - needToContactIndirect = false; - } else { - if (stableEndpoint4) { - needToContactIndirect = false; - p->sendHELLO(InetAddress(),stableEndpoint4,_now); - } - } - if (p->doPingAndKeepalive(_now,AF_INET6)) { - needToContactIndirect = false; - } else { - if (stableEndpoint6) { - needToContactIndirect = false; - p->sendHELLO(InetAddress(),stableEndpoint6,_now); - } - } - - if (needToContactIndirect) { - // If this is an upstream and we have no stable endpoint for either IPv4 or IPv6, - // send a NOP indirectly if possible to see if we can get to this peer in any - // way whatsoever. This will e.g. find network preferred relays that lack - // stable endpoints by using root servers. - Packet outp(p->address(),RR->identity.address(),Packet::VERB_NOP); - RR->sw->send(outp,true,0); + 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); - } else if (p->activelyTransferringFrames(_now)) { - // Normal nodes get their preferred link kept alive if the node has generated frame traffic recently - p->doPingAndKeepalive(_now,0); + _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; - uint64_t _now; - const std::vector &_relays; - World _world; + void *_tPtr; + Hashtable< Address,std::vector > &_upstreamsToContact; + const uint64_t _now; + const SharedPtr _bestCurrentUpstream; }; -ZT_ResultCode Node::processBackgroundTasks(uint64_t now,volatile uint64_t *nextBackgroundTaskDeadline) +ZT_ResultCode Node::processBackgroundTasks(void *tptr,uint64_t now,volatile uint64_t *nextBackgroundTaskDeadline) { _now = now; Mutex::Lock bl(_backgroundTasksLock); @@ -282,35 +226,37 @@ ZT_ResultCode Node::processBackgroundTasks(uint64_t now,volatile uint64_t *nextB try { _lastPingCheck = now; - // Get relays and networks that need config without leaving the mutex locked - std::vector< NetworkConfig::Relay > networkRelays; + // 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())) { + if (((now - n->second->lastConfigUpdate()) >= ZT_NETWORK_AUTOCONF_DELAY)||(!n->second->hasConfig())) needConfig.push_back(n->second); - } - if (n->second->hasConfig()) { - std::vector r(n->second->config().relays()); - networkRelays.insert(networkRelays.end(),r.begin(),r.end()); - } + n->second->sendUpdatesToMembers(tptr); } } - - // Request updated configuration for networks that need it for(std::vector< SharedPtr >::const_iterator n(needConfig.begin());n!=needConfig.end();++n) - (*n)->requestConfiguration(); + (*n)->requestConfiguration(tptr); // Do pings and keepalives - _PingPeersThatNeedPing pfunc(RR,now,networkRelays); + 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(_online ? ZT_EVENT_ONLINE : ZT_EVENT_OFFLINE); + postEvent(tptr,_online ? ZT_EVENT_ONLINE : ZT_EVENT_OFFLINE); } catch ( ... ) { return ZT_RESULT_FATAL_ERROR_INTERNAL; } @@ -333,12 +279,12 @@ ZT_ResultCode Node::processBackgroundTasks(uint64_t now,volatile uint64_t *nextB #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(now); + 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(now)),(unsigned long)ZT_CORE_TIMER_TASK_GRANULARITY); + *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 @@ -349,38 +295,47 @@ ZT_ResultCode Node::processBackgroundTasks(uint64_t now,volatile uint64_t *nextB return ZT_RESULT_OK; } -ZT_ResultCode Node::join(uint64_t nwid,void *uptr) +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,nwid,uptr)))); - std::sort(_networks.begin(),_networks.end()); // will sort by nwid since it's the first in a pair<> + if(!nw) { + const std::pair< uint64_t,SharedPtr > nn(nwid,SharedPtr(new Network(RR,tptr,nwid,uptr))); + _networks.insert(std::upper_bound(_networks.begin(),_networks.end(),nn),nn); + } return ZT_RESULT_OK; } -ZT_ResultCode Node::leave(uint64_t nwid,void **uptr) +ZT_ResultCode Node::leave(uint64_t nwid,void **uptr,void *tptr) { + ZT_VirtualNetworkConfig ctmp; std::vector< std::pair< uint64_t,SharedPtr > > newn; + void **nUserPtr = (void **)0; Mutex::Lock _l(_networks_m); + for(std::vector< std::pair< uint64_t,SharedPtr > >::const_iterator n(_networks.begin());n!=_networks.end();++n) { - if (n->first != nwid) + if (n->first != nwid) { newn.push_back(*n); - else { + } else { if (uptr) - *uptr = n->second->userPtr(); + *uptr = *n->second->userPtr(); n->second->destroy(); + nUserPtr = n->second->userPtr(); } } _networks.swap(newn); + + if (nUserPtr) + RR->node->configureVirtualNetworkPort(tptr,nwid,nUserPtr,ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY,&ctmp); + return ZT_RESULT_OK; } -ZT_ResultCode Node::multicastSubscribe(uint64_t nwid,uint64_t multicastGroup,unsigned long multicastAdi) +ZT_ResultCode Node::multicastSubscribe(void *tptr,uint64_t nwid,uint64_t multicastGroup,unsigned long multicastAdi) { SharedPtr nw(this->network(nwid)); if (nw) { - nw->multicastSubscribe(MulticastGroup(MAC(multicastGroup),(uint32_t)(multicastAdi & 0xffffffff))); + nw->multicastSubscribe(tptr,MulticastGroup(MAC(multicastGroup),(uint32_t)(multicastAdi & 0xffffffff))); return ZT_RESULT_OK; } else return ZT_RESULT_ERROR_NETWORK_NOT_FOUND; } @@ -394,6 +349,18 @@ ZT_ResultCode Node::multicastUnsubscribe(uint64_t nwid,uint64_t multicastGroup,u } 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(); @@ -402,8 +369,6 @@ uint64_t Node::address() const void Node::status(ZT_NodeStatus *status) const { status->address = RR->identity.address().toInt(); - status->worldId = RR->topology->worldId(); - status->worldTimestamp = RR->topology->worldTimestamp(); status->publicIdentity = RR->publicIdentityStr.c_str(); status->secretIdentity = RR->secretIdentityStr.c_str(); status->online = _online ? 1 : 0; @@ -424,8 +389,6 @@ ZT_PeerList *Node::peers() const 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(); - p->lastUnicastFrame = pi->second->lastUnicastFrame(); - p->lastMulticastFrame = pi->second->lastMulticastFrame(); if (pi->second->remoteVersionKnown()) { p->versionMajor = pi->second->remoteVersionMajor(); p->versionMinor = pi->second->remoteVersionMinor(); @@ -436,18 +399,19 @@ ZT_PeerList *Node::peers() const p->versionRev = -1; } p->latency = pi->second->latency(); - p->role = RR->topology->isRoot(pi->second->identity()) ? ZT_PEER_ROLE_ROOT : ZT_PEER_ROLE_LEAF; + p->role = RR->topology->role(pi->second->identity().address()); - std::vector paths(pi->second->paths()); - Path *bestPath = pi->second->getBestPath(_now); + std::vector< SharedPtr > paths(pi->second->paths(_now)); + SharedPtr bestp(pi->second->getBestPath(_now,false)); p->pathCount = 0; - for(std::vector::iterator path(paths.begin());path!=paths.end();++path) { - memcpy(&(p->paths[p->pathCount].address),&(path->address()),sizeof(struct sockaddr_storage)); - p->paths[p->pathCount].lastSend = path->lastSend(); - p->paths[p->pathCount].lastReceive = path->lastReceived(); - p->paths[p->pathCount].active = path->active(_now) ? 1 : 0; - p->paths[p->pathCount].preferred = ((bestPath)&&(*path == *bestPath)) ? 1 : 0; - p->paths[p->pathCount].trustedPathId = RR->topology->getOutboundPathTrust(path->address()); + for(std::vector< SharedPtr >::iterator path(paths.begin());path!=paths.end();++path) { + memcpy(&(p->paths[p->pathCount].address),&((*path)->address()),sizeof(struct sockaddr_storage)); + p->paths[p->pathCount].lastSend = (*path)->lastOut(); + p->paths[p->pathCount].lastReceive = (*path)->lastIn(); + p->paths[p->pathCount].trustedPathId = RR->topology->getOutboundPathTrust((*path)->address()); + p->paths[p->pathCount].linkQuality = (int)(*path)->linkQuality(); + p->paths[p->pathCount].expired = 0; + p->paths[p->pathCount].preferred = ((*path) == bestp) ? 1 : 0; ++p->pathCount; } } @@ -508,12 +472,28 @@ void Node::clearLocalInterfaceAddresses() _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(ZT_CircuitTest *test,void (*reportCallback)(ZT_Node *,ZT_CircuitTest *,const ZT_CircuitTestReport *)) +ZT_ResultCode Node::circuitTestBegin(void *tptr,ZT_CircuitTest *test,void (*reportCallback)(ZT_Node *,ZT_CircuitTest *,const ZT_CircuitTestReport *)) { if (test->hopCount > 0) { try { @@ -543,7 +523,7 @@ ZT_ResultCode Node::circuitTestBegin(ZT_CircuitTest *test,void (*reportCallback) for(unsigned int a=0;ahops[0].breadth;++a) { outp.newInitializationVector(); outp.setDestination(Address(test->hops[0].addresses[a])); - RR->sw->send(outp,true,0); + RR->sw->send(tptr,outp,true); } } catch ( ... ) { return ZT_RESULT_FATAL_ERROR_INTERNAL; // probably indicates FIFO too big for packet @@ -639,29 +619,17 @@ void Node::clusterStatus(ZT_ClusterStatus *cs) memset(cs,0,sizeof(ZT_ClusterStatus)); } -void Node::backgroundThreadMain() -{ - ++RR->dpEnabled; - for(;;) { - try { - if (RR->dp->process() < 0) - break; - } catch ( ... ) {} // sanity check -- should not throw - } - --RR->dpEnabled; -} - /****************************************************************************/ /* Node methods used only within node/ */ /****************************************************************************/ -std::string Node::dataStoreGet(const char *name) +std::string Node::dataStoreGet(void *tPtr,const char *name) { char buf[1024]; std::string r; unsigned long olen = 0; do { - long n = _dataStoreGetFunction(reinterpret_cast(this),_uPtr,name,buf,sizeof(buf),(unsigned long)r.length(),&olen); + 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); @@ -669,11 +637,14 @@ std::string Node::dataStoreGet(const char *name) return r; } -bool Node::shouldUsePathForZeroTierTraffic(const InetAddress &localAddress,const InetAddress &remoteAddress) +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) { @@ -686,9 +657,7 @@ bool Node::shouldUsePathForZeroTierTraffic(const InetAddress &localAddress,const } } - if (_pathCheckFunction) - return (_pathCheckFunction(reinterpret_cast(this),_uPtr,reinterpret_cast(&localAddress),reinterpret_cast(&remoteAddress)) != 0); - else return true; + return ( (_cb.pathCheckFunction) ? (_cb.pathCheckFunction(reinterpret_cast(this),_uPtr,tPtr,ztaddr.toInt(),reinterpret_cast(&localAddress),reinterpret_cast(&remoteAddress)) != 0) : true); } #ifdef ZT_TRACE @@ -720,16 +689,20 @@ void Node::postTrace(const char *module,unsigned int line,const char *fmt,...) tmp2[sizeof(tmp2)-1] = (char)0; Utils::snprintf(tmp1,sizeof(tmp1),"[%s] %s:%u %s",nowstr,module,line,tmp2); - postEvent(ZT_EVENT_TRACE,tmp1); + postEvent((void *)0,ZT_EVENT_TRACE,tmp1); } #endif // ZT_TRACE uint64_t Node::prng() { - unsigned int p = (++_prngStreamPtr % (sizeof(_prngStream) / sizeof(uint64_t))); - if (!p) - _prng.encrypt12(_prngStream,_prngStream,sizeof(_prngStream)); - return _prngStream[p]; + // https://en.wikipedia.org/wiki/Xorshift#xorshift.2B + uint64_t x = _prngState[0]; + const uint64_t y = _prngState[1]; + _prngState[0] = y; + x ^= x << 23; + const uint64_t z = x ^ y ^ (x >> 17) ^ (y >> 26); + _prngState[1] = z; + return z + y; } void Node::postCircuitTestReport(const ZT_CircuitTestReport *report) @@ -751,6 +724,120 @@ void Node::setTrustedPaths(const struct sockaddr_storage *networks,const uint64_ 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 /****************************************************************************/ @@ -759,21 +846,11 @@ void Node::setTrustedPaths(const struct sockaddr_storage *networks,const uint64_ extern "C" { -enum ZT_ResultCode ZT_Node_new( - ZT_Node **node, - void *uptr, - uint64_t now, - ZT_DataStoreGetFunction dataStoreGetFunction, - ZT_DataStorePutFunction dataStorePutFunction, - ZT_WirePacketSendFunction wirePacketSendFunction, - ZT_VirtualNetworkFrameFunction virtualNetworkFrameFunction, - ZT_VirtualNetworkConfigFunction virtualNetworkConfigFunction, - ZT_PathCheckFunction pathCheckFunction, - ZT_EventCallback eventCallback) +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(now,uptr,dataStoreGetFunction,dataStorePutFunction,wirePacketSendFunction,virtualNetworkFrameFunction,virtualNetworkConfigFunction,pathCheckFunction,eventCallback)); + *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; @@ -793,6 +870,7 @@ void ZT_Node_delete(ZT_Node *node) enum ZT_ResultCode ZT_Node_processWirePacket( ZT_Node *node, + void *tptr, uint64_t now, const struct sockaddr_storage *localAddress, const struct sockaddr_storage *remoteAddress, @@ -801,7 +879,7 @@ enum ZT_ResultCode ZT_Node_processWirePacket( volatile uint64_t *nextBackgroundTaskDeadline) { try { - return reinterpret_cast(node)->processWirePacket(now,localAddress,remoteAddress,packetData,packetLength,nextBackgroundTaskDeadline); + 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 ( ... ) { @@ -811,6 +889,7 @@ enum ZT_ResultCode ZT_Node_processWirePacket( enum ZT_ResultCode ZT_Node_processVirtualNetworkFrame( ZT_Node *node, + void *tptr, uint64_t now, uint64_t nwid, uint64_t sourceMac, @@ -822,7 +901,7 @@ enum ZT_ResultCode ZT_Node_processVirtualNetworkFrame( volatile uint64_t *nextBackgroundTaskDeadline) { try { - return reinterpret_cast(node)->processVirtualNetworkFrame(now,nwid,sourceMac,destMac,etherType,vlanId,frameData,frameLength,nextBackgroundTaskDeadline); + 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 ( ... ) { @@ -830,10 +909,10 @@ enum ZT_ResultCode ZT_Node_processVirtualNetworkFrame( } } -enum ZT_ResultCode ZT_Node_processBackgroundTasks(ZT_Node *node,uint64_t now,volatile uint64_t *nextBackgroundTaskDeadline) +enum ZT_ResultCode ZT_Node_processBackgroundTasks(ZT_Node *node,void *tptr,uint64_t now,volatile uint64_t *nextBackgroundTaskDeadline) { try { - return reinterpret_cast(node)->processBackgroundTasks(now,nextBackgroundTaskDeadline); + return reinterpret_cast(node)->processBackgroundTasks(tptr,now,nextBackgroundTaskDeadline); } catch (std::bad_alloc &exc) { return ZT_RESULT_FATAL_ERROR_OUT_OF_MEMORY; } catch ( ... ) { @@ -841,10 +920,10 @@ enum ZT_ResultCode ZT_Node_processBackgroundTasks(ZT_Node *node,uint64_t now,vol } } -enum ZT_ResultCode ZT_Node_join(ZT_Node *node,uint64_t nwid,void *uptr) +enum ZT_ResultCode ZT_Node_join(ZT_Node *node,uint64_t nwid,void *uptr,void *tptr) { try { - return reinterpret_cast(node)->join(nwid,uptr); + return reinterpret_cast(node)->join(nwid,uptr,tptr); } catch (std::bad_alloc &exc) { return ZT_RESULT_FATAL_ERROR_OUT_OF_MEMORY; } catch ( ... ) { @@ -852,10 +931,10 @@ enum ZT_ResultCode ZT_Node_join(ZT_Node *node,uint64_t nwid,void *uptr) } } -enum ZT_ResultCode ZT_Node_leave(ZT_Node *node,uint64_t nwid,void **uptr) +enum ZT_ResultCode ZT_Node_leave(ZT_Node *node,uint64_t nwid,void **uptr,void *tptr) { try { - return reinterpret_cast(node)->leave(nwid,uptr); + return reinterpret_cast(node)->leave(nwid,uptr,tptr); } catch (std::bad_alloc &exc) { return ZT_RESULT_FATAL_ERROR_OUT_OF_MEMORY; } catch ( ... ) { @@ -863,10 +942,10 @@ enum ZT_ResultCode ZT_Node_leave(ZT_Node *node,uint64_t nwid,void **uptr) } } -enum ZT_ResultCode ZT_Node_multicastSubscribe(ZT_Node *node,uint64_t nwid,uint64_t multicastGroup,unsigned long multicastAdi) +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(nwid,multicastGroup,multicastAdi); + return reinterpret_cast(node)->multicastSubscribe(tptr,nwid,multicastGroup,multicastAdi); } catch (std::bad_alloc &exc) { return ZT_RESULT_FATAL_ERROR_OUT_OF_MEMORY; } catch ( ... ) { @@ -885,6 +964,24 @@ enum ZT_ResultCode ZT_Node_multicastUnsubscribe(ZT_Node *node,uint64_t nwid,uint } } +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(); @@ -947,6 +1044,15 @@ void ZT_Node_clearLocalInterfaceAddresses(ZT_Node *node) } 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 { @@ -954,10 +1060,10 @@ void ZT_Node_setNetconfMaster(ZT_Node *node,void *networkControllerInstance) } catch ( ... ) {} } -enum ZT_ResultCode ZT_Node_circuitTestBegin(ZT_Node *node,ZT_CircuitTest *test,void (*reportCallback)(ZT_Node *,ZT_CircuitTest *,const ZT_CircuitTestReport *)) +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(test,reportCallback); + return reinterpret_cast(node)->circuitTestBegin(tptr,test,reportCallback); } catch ( ... ) { return ZT_RESULT_FATAL_ERROR_INTERNAL; } @@ -1027,13 +1133,6 @@ void ZT_Node_setTrustedPaths(ZT_Node *node,const struct sockaddr_storage *networ } catch ( ... ) {} } -void ZT_Node_backgroundThreadMain(ZT_Node *node) -{ - try { - reinterpret_cast(node)->backgroundThreadMain(); - } catch ( ... ) {} -} - void ZT_version(int *major,int *minor,int *revision) { if (major) *major = ZEROTIER_ONE_VERSION_MAJOR; diff --git a/node/Node.hpp b/node/Node.hpp index 0a39d1e..d25a619 100644 --- a/node/Node.hpp +++ b/node/Node.hpp @@ -24,6 +24,7 @@ #include #include +#include #include "Constants.hpp" @@ -36,6 +37,7 @@ #include "Network.hpp" #include "Path.hpp" #include "Salsa20.hpp" +#include "NetworkController.hpp" #undef TRACE #ifdef ZT_TRACE @@ -44,32 +46,35 @@ #define TRACE(f,...) {} #endif +// Bit mask for "expecting reply" hash +#define ZT_EXPECTING_REPLIES_BUCKET_MASK1 255 +#define ZT_EXPECTING_REPLIES_BUCKET_MASK2 31 + namespace ZeroTier { +class World; + /** * Implementation of Node object as defined in CAPI * * The pointer returned by ZT_Node_new() is an instance of this class. */ -class Node +class Node : public NetworkController::Sender { public: - Node( - uint64_t now, - void *uptr, - ZT_DataStoreGetFunction dataStoreGetFunction, - ZT_DataStorePutFunction dataStorePutFunction, - ZT_WirePacketSendFunction wirePacketSendFunction, - ZT_VirtualNetworkFrameFunction virtualNetworkFrameFunction, - ZT_VirtualNetworkConfigFunction virtualNetworkConfigFunction, - ZT_PathCheckFunction pathCheckFunction, - ZT_EventCallback eventCallback); + Node(void *uptr,void *tptr,const struct ZT_Node_Callbacks *callbacks,uint64_t now); + virtual ~Node(); - ~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, @@ -77,6 +82,7 @@ public: unsigned int packetLength, volatile uint64_t *nextBackgroundTaskDeadline); ZT_ResultCode processVirtualNetworkFrame( + void *tptr, uint64_t now, uint64_t nwid, uint64_t sourceMac, @@ -86,11 +92,13 @@ public: const void *frameData, unsigned int frameLength, volatile uint64_t *nextBackgroundTaskDeadline); - ZT_ResultCode processBackgroundTasks(uint64_t now,volatile uint64_t *nextBackgroundTaskDeadline); - ZT_ResultCode join(uint64_t nwid,void *uptr); - ZT_ResultCode leave(uint64_t nwid,void **uptr); - ZT_ResultCode multicastSubscribe(uint64_t nwid,uint64_t multicastGroup,unsigned long multicastAdi); + 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; @@ -99,8 +107,9 @@ public: 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(ZT_CircuitTest *test,void (*reportCallback)(ZT_Node *,ZT_CircuitTest *,const ZT_CircuitTestReport *)); + 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, @@ -117,38 +126,17 @@ public: void clusterRemoveMember(unsigned int memberId); void clusterHandleIncomingMessage(const void *msg,unsigned int len); void clusterStatus(ZT_ClusterStatus *cs); - void backgroundThreadMain(); // Internal functions ------------------------------------------------------ - /** - * Convenience threadMain() for easy background thread launch - * - * This allows background threads to be launched with Thread::start - * that will run against this node. - */ - inline void threadMain() throw() { this->backgroundThreadMain(); } - - /** - * @return Time as of last call to run() - */ inline uint64_t now() const throw() { return _now; } - /** - * Enqueue a ZeroTier message to be sent - * - * @param localAddress Local address - * @param addr Destination address - * @param data Packet data - * @param len Packet length - * @param ttl Desired TTL (default: 0 for unchanged/default TTL) - * @return True if packet appears to have been sent - */ - inline bool putPacket(const InetAddress &localAddress,const InetAddress &addr,const void *data,unsigned int len,unsigned int ttl = 0) + inline bool putPacket(void *tPtr,const InetAddress &localAddress,const InetAddress &addr,const void *data,unsigned int len,unsigned int ttl = 0) { - return (_wirePacketSendFunction( + return (_cb.wirePacketSendFunction( reinterpret_cast(this), _uPtr, + tPtr, reinterpret_cast(&localAddress), reinterpret_cast(&addr), data, @@ -156,23 +144,12 @@ public: ttl) == 0); } - /** - * Enqueue a frame to be injected into a tap device (port) - * - * @param nwid Network ID - * @param nuptr Network user ptr - * @param source Source MAC - * @param dest Destination MAC - * @param etherType 16-bit ethernet type - * @param vlanId VLAN ID or 0 if none - * @param data Frame data - * @param len Frame length - */ - inline void putFrame(uint64_t nwid,void **nuptr,const MAC &source,const MAC &dest,unsigned int etherType,unsigned int vlanId,const void *data,unsigned int len) + 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) { - _virtualNetworkFrameFunction( + _cb.virtualNetworkFrameFunction( reinterpret_cast(this), _uPtr, + tPtr, nwid, nuptr, source.toInt(), @@ -183,13 +160,6 @@ public: len); } - /** - * @param localAddress Local address - * @param remoteAddress Remote address - * @return True if path should be used - */ - bool shouldUsePathForZeroTierTraffic(const InetAddress &localAddress,const InetAddress &remoteAddress); - inline SharedPtr network(uint64_t nwid) const { Mutex::Lock _l(_networks_m); @@ -216,37 +186,20 @@ public: return nw; } - /** - * @return Potential direct paths to me a.k.a. local interface addresses - */ inline std::vector directPaths() const { Mutex::Lock _l(_directPaths_m); return _directPaths; } - inline bool dataStorePut(const char *name,const void *data,unsigned int len,bool secure) { return (_dataStorePutFunction(reinterpret_cast(this),_uPtr,name,data,len,(int)secure) == 0); } - inline bool dataStorePut(const char *name,const std::string &data,bool secure) { return dataStorePut(name,(const void *)data.data(),(unsigned int)data.length(),secure); } - inline void dataStoreDelete(const char *name) { _dataStorePutFunction(reinterpret_cast(this),_uPtr,name,(const void *)0,0,0); } - std::string dataStoreGet(const char *name); + 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); - /** - * Post an event to the external user - * - * @param ev Event type - * @param md Meta-data (default: NULL/none) - */ - inline void postEvent(ZT_Event ev,const void *md = (const void *)0) { _eventCallback(reinterpret_cast(this),_uPtr,ev,md); } + inline void postEvent(void *tPtr,ZT_Event ev,const void *md = (const void *)0) { _cb.eventCallback(reinterpret_cast(this),_uPtr,tPtr,ev,md); } - /** - * Update virtual network port configuration - * - * @param nwid Network ID - * @param nuptr Network user ptr - * @param op Configuration operation - * @param nc Network configuration - */ - inline int configureVirtualNetworkPort(uint64_t nwid,void **nuptr,ZT_VirtualNetworkConfigOperation op,const ZT_VirtualNetworkConfig *nc) { return _virtualNetworkConfigFunction(reinterpret_cast(this),_uPtr,nwid,nuptr,op,nc); } + 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; } @@ -254,10 +207,74 @@ public: 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 { @@ -271,16 +288,15 @@ private: RuntimeEnvironment _RR; RuntimeEnvironment *RR; - void *_uPtr; // _uptr (lower case) is reserved in Visual Studio :P + ZT_Node_Callbacks _cb; - ZT_DataStoreGetFunction _dataStoreGetFunction; - ZT_DataStorePutFunction _dataStorePutFunction; - ZT_WirePacketSendFunction _wirePacketSendFunction; - ZT_VirtualNetworkFrameFunction _virtualNetworkFrameFunction; - ZT_VirtualNetworkConfigFunction _virtualNetworkConfigFunction; - ZT_PathCheckFunction _pathCheckFunction; - ZT_EventCallback _eventCallback; + // 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; @@ -293,13 +309,10 @@ private: Mutex _backgroundTasksLock; - unsigned int _prngStreamPtr; - Salsa20 _prng; - uint64_t _prngStream[16]; // repeatedly encrypted with _prng to yield a high-quality non-crypto PRNG stream - uint64_t _now; uint64_t _lastPingCheck; uint64_t _lastHousekeepingRun; + volatile uint64_t _prngState[2]; bool _online; }; diff --git a/node/OutboundMulticast.cpp b/node/OutboundMulticast.cpp index eea1132..285bfa5 100644 --- a/node/OutboundMulticast.cpp +++ b/node/OutboundMulticast.cpp @@ -21,8 +21,9 @@ #include "OutboundMulticast.hpp" #include "Switch.hpp" #include "Network.hpp" -#include "CertificateOfMembership.hpp" #include "Node.hpp" +#include "Peer.hpp" +#include "Topology.hpp" namespace ZeroTier { @@ -30,7 +31,7 @@ void OutboundMulticast::init( const RuntimeEnvironment *RR, uint64_t timestamp, uint64_t nwid, - const CertificateOfMembership *com, + bool disableCompression, unsigned int limit, unsigned int gatherLimit, const MAC &src, @@ -39,16 +40,25 @@ void OutboundMulticast::init( 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; - uint8_t flags = 0; if (gatherLimit) flags |= 0x02; - if (src) flags |= 0x04; /* - TRACE(">>MC %.16llx INIT %.16llx/%s limit %u gatherLimit %u from %s to %s length %u com==%d", + 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(), @@ -56,58 +66,38 @@ void OutboundMulticast::init( gatherLimit, (src) ? src.toString().c_str() : MAC(RR->identity.address(),nwid).toString().c_str(), dest.toString().c_str(), - len, - (com) ? 1 : 0); + len); */ - _packetNoCom.setSource(RR->identity.address()); - _packetNoCom.setVerb(Packet::VERB_MULTICAST_FRAME); - _packetNoCom.append((uint64_t)nwid); - _packetNoCom.append(flags); - if (gatherLimit) _packetNoCom.append((uint32_t)gatherLimit); - if (src) src.appendTo(_packetNoCom); - dest.mac().appendTo(_packetNoCom); - _packetNoCom.append((uint32_t)dest.adi()); - _packetNoCom.append((uint16_t)etherType); - _packetNoCom.append(payload,len); - _packetNoCom.compress(); + _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(); - if (com) { - _haveCom = true; - flags |= 0x01; - - _packetWithCom.setSource(RR->identity.address()); - _packetWithCom.setVerb(Packet::VERB_MULTICAST_FRAME); - _packetWithCom.append((uint64_t)nwid); - _packetWithCom.append(flags); - com->serialize(_packetWithCom); - if (gatherLimit) _packetWithCom.append((uint32_t)gatherLimit); - if (src) src.appendTo(_packetWithCom); - dest.mac().appendTo(_packetWithCom); - _packetWithCom.append((uint32_t)dest.adi()); - _packetWithCom.append((uint16_t)etherType); - _packetWithCom.append(payload,len); - _packetWithCom.compress(); - } else _haveCom = false; + memcpy(_frameData,payload,_frameLen); } -void OutboundMulticast::sendOnly(const RuntimeEnvironment *RR,const Address &toAddr) +void OutboundMulticast::sendOnly(const RuntimeEnvironment *RR,void *tPtr,const Address &toAddr) { - if (_haveCom) { - SharedPtr peer(RR->topology->getPeer(toAddr)); - if ( (!peer) || (peer->needsOurNetworkMembershipCertificate(_nwid,RR->node->now(),true)) ) { - //TRACE(">>MC %.16llx -> %s (with COM)",(unsigned long long)this,toAddr.toString().c_str()); - _packetWithCom.newInitializationVector(); - _packetWithCom.setDestination(toAddr); - RR->sw->send(_packetWithCom,true,_nwid); - return; - } - } + 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()); - //TRACE(">>MC %.16llx -> %s (without COM)",(unsigned long long)this,toAddr.toString().c_str()); - _packetNoCom.newInitializationVector(); - _packetNoCom.setDestination(toAddr); - RR->sw->send(_packetNoCom,true,_nwid); + Packet tmp(_packet); // make a copy of packet so as not to garble the original -- GitHub issue #461 + RR->sw->send(tPtr,tmp,true); + } } } // namespace ZeroTier diff --git a/node/OutboundMulticast.hpp b/node/OutboundMulticast.hpp index 3818172..0ecf113 100644 --- a/node/OutboundMulticast.hpp +++ b/node/OutboundMulticast.hpp @@ -56,7 +56,7 @@ public: * @param RR Runtime environment * @param timestamp Creation time * @param nwid Network ID - * @param com Certificate of membership or NULL if none available + * @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 @@ -70,7 +70,7 @@ public: const RuntimeEnvironment *RR, uint64_t timestamp, uint64_t nwid, - const CertificateOfMembership *com, + bool disableCompression, unsigned int limit, unsigned int gatherLimit, const MAC &src, @@ -99,45 +99,53 @@ public: * 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,const Address &toAddr); + 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,const Address &toAddr) + inline void sendAndLog(const RuntimeEnvironment *RR,void *tPtr,const Address &toAddr) { _alreadySentTo.push_back(toAddr); - sendOnly(RR,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,const Address &toAddr) + inline bool sendIfNew(const RuntimeEnvironment *RR,void *tPtr,const Address &toAddr) { if (std::find(_alreadySentTo.begin(),_alreadySentTo.end(),toAddr) == _alreadySentTo.end()) { - sendAndLog(RR,toAddr); + sendAndLog(RR,tPtr,toAddr); return true; - } else return false; + } else { + return false; + } } private: uint64_t _timestamp; uint64_t _nwid; + MAC _macSrc; + MAC _macDest; unsigned int _limit; - Packet _packetNoCom; - Packet _packetWithCom; + unsigned int _frameLen; + unsigned int _etherType; + Packet _packet; std::vector
_alreadySentTo; - bool _haveCom; + uint8_t _frameData[ZT_MAX_MTU]; }; } // namespace ZeroTier diff --git a/node/Packet.cpp b/node/Packet.cpp index 3330a92..8a57dd5 100644 --- a/node/Packet.cpp +++ b/node/Packet.cpp @@ -16,16 +16,1044 @@ * along with this program. If not, see . */ +#include +#include +#include +#include +#include + #include "Packet.hpp" +#ifdef ZT_USE_X64_ASM_SALSA2012 +#include "../ext/x64-salsa2012-asm/salsa2012.h" +#endif +#ifdef ZT_USE_ARM32_NEON_ASM_SALSA2012 +#include "../ext/arm32-neon-salsa2012-asm/salsa2012.h" +#endif + +#ifdef _MSC_VER +#define FORCE_INLINE static __forceinline +#include +#pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ +#pragma warning(disable : 4293) /* disable: C4293: too large shift (32-bits) */ +#else +#define FORCE_INLINE static inline +#endif + namespace ZeroTier { +/************************************************************************** */ + +/* Set up macros for fast single-pass ASM Salsa20/12 crypto, if we have it */ + +// x64 SSE crypto +#ifdef ZT_USE_X64_ASM_SALSA2012 +#define ZT_HAS_FAST_CRYPTO() (true) +#define ZT_FAST_SINGLE_PASS_SALSA2012(b,l,n,k) zt_salsa2012_amd64_xmm6(reinterpret_cast(b),(l),reinterpret_cast(n),reinterpret_cast(k)) +#endif + +// ARM (32-bit) NEON crypto (must be detected) +#ifdef ZT_USE_ARM32_NEON_ASM_SALSA2012 +class _FastCryptoChecker +{ +public: + _FastCryptoChecker() : canHas(zt_arm_has_neon()) {} + bool canHas; +}; +static const _FastCryptoChecker _ZT_FAST_CRYPTO_CHECK; +#define ZT_HAS_FAST_CRYPTO() (_ZT_FAST_CRYPTO_CHECK.canHas) +#define ZT_FAST_SINGLE_PASS_SALSA2012(b,l,n,k) zt_salsa2012_armneon3_xor(reinterpret_cast(b),(const unsigned char *)0,(l),reinterpret_cast(n),reinterpret_cast(k)) +#endif + +// No fast crypto available +#ifndef ZT_HAS_FAST_CRYPTO +#define ZT_HAS_FAST_CRYPTO() (false) +#define ZT_FAST_SINGLE_PASS_SALSA2012(b,l,n,k) {} +#endif + +/************************************************************************** */ + +/* LZ4 is shipped encapsulated into Packet in an anonymous namespace. + * + * We're doing this as a deliberate workaround for various Linux distribution + * policies that forbid static linking of support libraries. + * + * The reason is that relying on distribution versions of LZ4 has been too + * big a source of bugs and compatibility issues. The LZ4 API is not stable + * enough across versions, and dependency hell ensues. So fark it. */ + +/* Needless to say the code in this anonymous namespace should be considered + * BSD 2-clause licensed. */ + +namespace { + +/* lz4.h ------------------------------------------------------------------ */ + +/* + * LZ4 - Fast LZ compression algorithm + * Header File + * Copyright (C) 2011-2016, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 homepage : http://www.lz4.org + - LZ4 source repository : https://github.com/lz4/lz4 +*/ + +/** + Introduction + + LZ4 is lossless compression algorithm, providing compression speed at 400 MB/s per core, + scalable with multi-cores CPU. It features an extremely fast decoder, with speed in + multiple GB/s per core, typically reaching RAM speed limits on multi-core systems. + + The LZ4 compression library provides in-memory compression and decompression functions. + Compression can be done in: + - a single step (described as Simple Functions) + - a single step, reusing a context (described in Advanced Functions) + - unbounded multiple steps (described as Streaming compression) + + lz4.h provides block compression functions. It gives full buffer control to user. + Decompressing an lz4-compressed block also requires metadata (such as compressed size). + Each application is free to encode such metadata in whichever way it wants. + + An additional format, called LZ4 frame specification (doc/lz4_Frame_format.md), + take care of encoding standard metadata alongside LZ4-compressed blocks. + If your application requires interoperability, it's recommended to use it. + A library is provided to take care of it, see lz4frame.h. +*/ + +#define LZ4LIB_API + +/*========== Version =========== */ +#define LZ4_VERSION_MAJOR 1 /* for breaking interface changes */ +#define LZ4_VERSION_MINOR 7 /* for new (non-breaking) interface capabilities */ +#define LZ4_VERSION_RELEASE 5 /* for tweaks, bug-fixes, or development */ + +#define LZ4_VERSION_NUMBER (LZ4_VERSION_MAJOR *100*100 + LZ4_VERSION_MINOR *100 + LZ4_VERSION_RELEASE) + +#define LZ4_LIB_VERSION LZ4_VERSION_MAJOR.LZ4_VERSION_MINOR.LZ4_VERSION_RELEASE +#define LZ4_QUOTE(str) #str +#define LZ4_EXPAND_AND_QUOTE(str) LZ4_QUOTE(str) +#define LZ4_VERSION_STRING LZ4_EXPAND_AND_QUOTE(LZ4_LIB_VERSION) + +/*! + * LZ4_MEMORY_USAGE : + * Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB; etc.) + * Increasing memory usage improves compression ratio + * Reduced memory usage can improve speed, due to cache effect + * Default value is 14, for 16KB, which nicely fits into Intel x86 L1 cache + */ +#define LZ4_MEMORY_USAGE 14 + +#define LZ4_MAX_INPUT_SIZE 0x7E000000 /* 2 113 929 216 bytes */ +#define LZ4_COMPRESSBOUND(isize) ((unsigned)(isize) > (unsigned)LZ4_MAX_INPUT_SIZE ? 0 : (isize) + ((isize)/255) + 16) + +/*-********************************************* +* Streaming Compression Functions +***********************************************/ +typedef union LZ4_stream_u LZ4_stream_t; /* incomplete type (defined later) */ + +/*! LZ4_resetStream() : + * An LZ4_stream_t structure can be allocated once and re-used multiple times. + * Use this function to init an allocated `LZ4_stream_t` structure and start a new compression. + */ +LZ4LIB_API void LZ4_resetStream (LZ4_stream_t* streamPtr); + +/*^********************************************** + * !!!!!! STATIC LINKING ONLY !!!!!! + ***********************************************/ +/*-************************************ + * Private definitions + ************************************** + * Do not use these definitions. + * They are exposed to allow static allocation of `LZ4_stream_t` and `LZ4_streamDecode_t`. + * Using these definitions will expose code to API and/or ABI break in future versions of the library. + **************************************/ +#define LZ4_HASHLOG (LZ4_MEMORY_USAGE-2) +#define LZ4_HASHTABLESIZE (1 << LZ4_MEMORY_USAGE) +#define LZ4_HASH_SIZE_U32 (1 << LZ4_HASHLOG) /* required as macro for static allocation */ + +typedef struct { + uint32_t hashTable[LZ4_HASH_SIZE_U32]; + uint32_t currentOffset; + uint32_t initCheck; + const uint8_t* dictionary; + uint8_t* bufferStart; /* obsolete, used for slideInputBuffer */ + uint32_t dictSize; +} LZ4_stream_t_internal; + +typedef struct { + const uint8_t* externalDict; + size_t extDictSize; + const uint8_t* prefixEnd; + size_t prefixSize; +} LZ4_streamDecode_t_internal; + +/*! + * LZ4_stream_t : + * information structure to track an LZ4 stream. + * init this structure before first use. + * note : only use in association with static linking ! + * this definition is not API/ABI safe, + * and may change in a future version ! + */ +#define LZ4_STREAMSIZE_U64 ((1 << (LZ4_MEMORY_USAGE-3)) + 4) +#define LZ4_STREAMSIZE (LZ4_STREAMSIZE_U64 * sizeof(unsigned long long)) +union LZ4_stream_u { + unsigned long long table[LZ4_STREAMSIZE_U64]; + LZ4_stream_t_internal internal_donotuse; +} ; /* previously typedef'd to LZ4_stream_t */ + +/*! + * LZ4_streamDecode_t : + * information structure to track an LZ4 stream during decompression. + * init this structure using LZ4_setStreamDecode (or memset()) before first use + * note : only use in association with static linking ! + * this definition is not API/ABI safe, + * and may change in a future version ! + */ +#define LZ4_STREAMDECODESIZE_U64 4 +#define LZ4_STREAMDECODESIZE (LZ4_STREAMDECODESIZE_U64 * sizeof(unsigned long long)) +union LZ4_streamDecode_u { + unsigned long long table[LZ4_STREAMDECODESIZE_U64]; + LZ4_streamDecode_t_internal internal_donotuse; +} ; /* previously typedef'd to LZ4_streamDecode_t */ + +#ifndef HEAPMODE +# define HEAPMODE 0 +#endif + +//#define ACCELERATION_DEFAULT 1 + +/* LZ4_FORCE_MEMORY_ACCESS + * By default, access to unaligned memory is controlled by `memcpy()`, which is safe and portable. + * Unfortunately, on some target/compiler combinations, the generated assembly is sub-optimal. + * The below switch allow to select different access method for improved performance. + * Method 0 (default) : use `memcpy()`. Safe and portable. + * Method 1 : `__packed` statement. It depends on compiler extension (ie, not portable). + * This method is safe if your compiler supports it, and *generally* as fast or faster than `memcpy`. + * Method 2 : direct access. This method is portable but violate C standard. + * It can generate buggy code on targets which generate assembly depending on alignment. + * But in some circumstances, it's the only known way to get the most performance (ie GCC + ARMv6) + * See https://fastcompression.blogspot.fr/2015/08/accessing-unaligned-memory.html for details. + * Prefer these methods in priority order (0 > 1 > 2) + */ +#if 0 +#ifndef LZ4_FORCE_MEMORY_ACCESS /* can be defined externally, on command line for example */ +# if defined(__GNUC__) && ( defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) ) +# define LZ4_FORCE_MEMORY_ACCESS 2 +# elif defined(__INTEL_COMPILER) || \ + (defined(__GNUC__) && ( defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7S__) )) +# define LZ4_FORCE_MEMORY_ACCESS 1 +# endif +#endif +#endif + +#ifdef ZT_NO_TYPE_PUNNING +#define LZ4_FORCE_MEMORY_ACCESS 0 +#else +#define LZ4_FORCE_MEMORY_ACCESS 2 +#endif + +/* + * LZ4_FORCE_SW_BITCOUNT + * Define this parameter if your target system or compiler does not support hardware bit count + */ +#if defined(_MSC_VER) && defined(_WIN32_WCE) /* Visual Studio for Windows CE does not support Hardware bit count */ +# define LZ4_FORCE_SW_BITCOUNT +#endif + +/*-************************************ +* Compiler Options +**************************************/ +#if 0 +#ifdef _MSC_VER /* Visual Studio */ +# define FORCE_INLINE static __forceinline +# include +# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ +# pragma warning(disable : 4293) /* disable: C4293: too large shift (32-bits) */ +#else +# if defined(__GNUC__) || defined(__clang__) +# define FORCE_INLINE static inline __attribute__((always_inline)) +# elif defined(__cplusplus) || (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) +# define FORCE_INLINE static inline +# else +# define FORCE_INLINE static +# endif +#endif /* _MSC_VER */ +#endif + +#ifndef FORCE_INLINE +#define FORCE_INLINE static inline +#endif + +#if (defined(__GNUC__) && (__GNUC__ >= 3)) || (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 800)) || defined(__clang__) +# define expect(expr,value) (__builtin_expect ((expr),(value)) ) +#else +# define expect(expr,value) (expr) +#endif + +#define likely(expr) expect((expr) != 0, 1) +#define unlikely(expr) expect((expr) != 0, 0) + +/*-************************************ +* Memory routines +**************************************/ +//#include /* malloc, calloc, free */ +#define ALLOCATOR(n,s) calloc(n,s) +#define FREEMEM free +//#include /* memset, memcpy */ +#define MEM_INIT memset + +/*-************************************ +* Basic Types +**************************************/ +typedef uint8_t BYTE; +typedef uint16_t U16; +typedef uint32_t U32; +typedef int32_t S32; +typedef uint64_t U64; +typedef uintptr_t uptrval; +typedef uintptr_t reg_t; + +/*-************************************ +* Reading and writing into memory +**************************************/ +static unsigned LZ4_isLittleEndian(void) +{ + const union { U32 u; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */ + return one.c[0]; +} + +#if defined(LZ4_FORCE_MEMORY_ACCESS) && (LZ4_FORCE_MEMORY_ACCESS==2) +/* lie to the compiler about data alignment; use with caution */ + +static U16 LZ4_read16(const void* memPtr) { return *(const U16*) memPtr; } +static U32 LZ4_read32(const void* memPtr) { return *(const U32*) memPtr; } +static reg_t LZ4_read_ARCH(const void* memPtr) { return *(const reg_t*) memPtr; } + +static void LZ4_write16(void* memPtr, U16 value) { *(U16*)memPtr = value; } +static void LZ4_write32(void* memPtr, U32 value) { *(U32*)memPtr = value; } + +#elif defined(LZ4_FORCE_MEMORY_ACCESS) && (LZ4_FORCE_MEMORY_ACCESS==1) + +/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */ +/* currently only defined for gcc and icc */ +typedef union { U16 u16; U32 u32; reg_t uArch; } __attribute__((packed)) unalign; + +static U16 LZ4_read16(const void* ptr) { return ((const unalign*)ptr)->u16; } +static U32 LZ4_read32(const void* ptr) { return ((const unalign*)ptr)->u32; } +static reg_t LZ4_read_ARCH(const void* ptr) { return ((const unalign*)ptr)->uArch; } + +static void LZ4_write16(void* memPtr, U16 value) { ((unalign*)memPtr)->u16 = value; } +static void LZ4_write32(void* memPtr, U32 value) { ((unalign*)memPtr)->u32 = value; } + +#else /* safe and portable access through memcpy() */ + +static inline U16 LZ4_read16(const void* memPtr) +{ + U16 val; memcpy(&val, memPtr, sizeof(val)); return val; +} + +static inline U32 LZ4_read32(const void* memPtr) +{ + U32 val; memcpy(&val, memPtr, sizeof(val)); return val; +} + +static inline reg_t LZ4_read_ARCH(const void* memPtr) +{ + reg_t val; memcpy(&val, memPtr, sizeof(val)); return val; +} + +static inline void LZ4_write16(void* memPtr, U16 value) +{ + memcpy(memPtr, &value, sizeof(value)); +} + +static inline void LZ4_write32(void* memPtr, U32 value) +{ + memcpy(memPtr, &value, sizeof(value)); +} + +#endif /* LZ4_FORCE_MEMORY_ACCESS */ + +static inline U16 LZ4_readLE16(const void* memPtr) +{ + if (LZ4_isLittleEndian()) { + return LZ4_read16(memPtr); + } else { + const BYTE* p = (const BYTE*)memPtr; + return (U16)((U16)p[0] + (p[1]<<8)); + } +} + +static inline void LZ4_writeLE16(void* memPtr, U16 value) +{ + if (LZ4_isLittleEndian()) { + LZ4_write16(memPtr, value); + } else { + BYTE* p = (BYTE*)memPtr; + p[0] = (BYTE) value; + p[1] = (BYTE)(value>>8); + } +} + +static inline void LZ4_copy8(void* dst, const void* src) +{ + memcpy(dst,src,8); +} + +/* customized variant of memcpy, which can overwrite up to 8 bytes beyond dstEnd */ +static inline void LZ4_wildCopy(void* dstPtr, const void* srcPtr, void* dstEnd) +{ + BYTE* d = (BYTE*)dstPtr; + const BYTE* s = (const BYTE*)srcPtr; + BYTE* const e = (BYTE*)dstEnd; + + do { LZ4_copy8(d,s); d+=8; s+=8; } while (d>3); +# elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (__builtin_ctzll((U64)val) >> 3); +# else + static const int DeBruijnBytePos[64] = { 0, 0, 0, 0, 0, 1, 1, 2, 0, 3, 1, 3, 1, 4, 2, 7, 0, 2, 3, 6, 1, 5, 3, 5, 1, 3, 4, 4, 2, 5, 6, 7, 7, 0, 1, 2, 3, 3, 4, 6, 2, 6, 5, 5, 3, 4, 5, 6, 7, 1, 2, 4, 6, 4, 4, 5, 7, 2, 6, 5, 7, 6, 7, 7 }; + return DeBruijnBytePos[((U64)((val & -(long long)val) * 0x0218A392CDABBD3FULL)) >> 58]; +# endif + } else /* 32 bits */ { +# if defined(_MSC_VER) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r; + _BitScanForward( &r, (U32)val ); + return (int)(r>>3); +# elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (__builtin_ctz((U32)val) >> 3); +# else + static const int DeBruijnBytePos[32] = { 0, 0, 3, 0, 3, 1, 3, 0, 3, 2, 2, 1, 3, 2, 0, 1, 3, 3, 1, 2, 2, 2, 2, 0, 3, 1, 2, 0, 1, 0, 1, 1 }; + return DeBruijnBytePos[((U32)((val & -(S32)val) * 0x077CB531U)) >> 27]; +# endif + } + } else /* Big Endian CPU */ { + if (sizeof(val)==8) { +# if defined(_MSC_VER) && defined(_WIN64) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r = 0; + _BitScanReverse64( &r, val ); + return (unsigned)(r>>3); +# elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (__builtin_clzll((U64)val) >> 3); +# else + unsigned r; + if (!(val>>32)) { r=4; } else { r=0; val>>=32; } + if (!(val>>16)) { r+=2; val>>=8; } else { val>>=24; } + r += (!val); + return r; +# endif + } else /* 32 bits */ { +# if defined(_MSC_VER) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r = 0; + _BitScanReverse( &r, (unsigned long)val ); + return (unsigned)(r>>3); +# elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (__builtin_clz((U32)val) >> 3); +# else + unsigned r; + if (!(val>>16)) { r=2; val>>=8; } else { r=0; val>>=24; } + r += (!val); + return r; +# endif + } + } +} + +#define STEPSIZE sizeof(reg_t) +static inline unsigned LZ4_count(const BYTE* pIn, const BYTE* pMatch, const BYTE* pInLimit) +{ + const BYTE* const pStart = pIn; + + while (likely(pIn compression run slower on incompressible data */ + +/*-************************************ +* Local Structures and types +**************************************/ +typedef enum { notLimited = 0, limitedOutput = 1 } limitedOutput_directive; +typedef enum { byPtr, byU32, byU16 } tableType_t; + +typedef enum { noDict = 0, withPrefix64k, usingExtDict } dict_directive; +typedef enum { noDictIssue = 0, dictSmall } dictIssue_directive; + +typedef enum { endOnOutputSize = 0, endOnInputSize = 1 } endCondition_directive; +typedef enum { full = 0, partial = 1 } earlyEnd_directive; + +/*-************************************ +* Local Utils +**************************************/ +//int LZ4_versionNumber (void) { return LZ4_VERSION_NUMBER; } +//const char* LZ4_versionString(void) { return LZ4_VERSION_STRING; } +int LZ4_compressBound(int isize) { return LZ4_COMPRESSBOUND(isize); } +//int LZ4_sizeofState() { return LZ4_STREAMSIZE; } + +/*-****************************** +* Compression functions +********************************/ +static inline U32 LZ4_hash4(U32 sequence, tableType_t const tableType) +{ + if (tableType == byU16) + return ((sequence * 2654435761U) >> ((MINMATCH*8)-(LZ4_HASHLOG+1))); + else + return ((sequence * 2654435761U) >> ((MINMATCH*8)-LZ4_HASHLOG)); +} + +static inline U32 LZ4_hash5(U64 sequence, tableType_t const tableType) +{ + static const U64 prime5bytes = 889523592379ULL; + static const U64 prime8bytes = 11400714785074694791ULL; + const U32 hashLog = (tableType == byU16) ? LZ4_HASHLOG+1 : LZ4_HASHLOG; + if (LZ4_isLittleEndian()) + return (U32)(((sequence << 24) * prime5bytes) >> (64 - hashLog)); + else + return (U32)(((sequence >> 24) * prime8bytes) >> (64 - hashLog)); +} + +FORCE_INLINE U32 LZ4_hashPosition(const void* const p, tableType_t const tableType) +{ + if ((sizeof(reg_t)==8) && (tableType != byU16)) return LZ4_hash5(LZ4_read_ARCH(p), tableType); + return LZ4_hash4(LZ4_read32(p), tableType); +} + +static inline void LZ4_putPositionOnHash(const BYTE* p, U32 h, void* tableBase, tableType_t const tableType, const BYTE* srcBase) +{ + switch (tableType) + { + case byPtr: { const BYTE** hashTable = (const BYTE**)tableBase; hashTable[h] = p; return; } + case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = (U32)(p-srcBase); return; } + case byU16: { U16* hashTable = (U16*) tableBase; hashTable[h] = (U16)(p-srcBase); return; } + } +} + +FORCE_INLINE void LZ4_putPosition(const BYTE* p, void* tableBase, tableType_t tableType, const BYTE* srcBase) +{ + U32 const h = LZ4_hashPosition(p, tableType); + LZ4_putPositionOnHash(p, h, tableBase, tableType, srcBase); +} + +static inline const BYTE* LZ4_getPositionOnHash(U32 h, void* tableBase, tableType_t tableType, const BYTE* srcBase) +{ + if (tableType == byPtr) { const BYTE** hashTable = (const BYTE**) tableBase; return hashTable[h]; } + if (tableType == byU32) { const U32* const hashTable = (U32*) tableBase; return hashTable[h] + srcBase; } + { const U16* const hashTable = (U16*) tableBase; return hashTable[h] + srcBase; } /* default, to ensure a return */ +} + +FORCE_INLINE const BYTE* LZ4_getPosition(const BYTE* p, void* tableBase, tableType_t tableType, const BYTE* srcBase) +{ + U32 const h = LZ4_hashPosition(p, tableType); + return LZ4_getPositionOnHash(h, tableBase, tableType, srcBase); +} + +/** LZ4_compress_generic() : + inlined, to ensure branches are decided at compilation time */ +FORCE_INLINE int LZ4_compress_generic( + LZ4_stream_t_internal* const cctx, + const char* const source, + char* const dest, + const int inputSize, + const int maxOutputSize, + const limitedOutput_directive outputLimited, + const tableType_t tableType, + const dict_directive dict, + const dictIssue_directive dictIssue, + const U32 acceleration) +{ + const BYTE* ip = (const BYTE*) source; + const BYTE* base; + const BYTE* lowLimit; + const BYTE* const lowRefLimit = ip - cctx->dictSize; + const BYTE* const dictionary = cctx->dictionary; + const BYTE* const dictEnd = dictionary + cctx->dictSize; + const ptrdiff_t dictDelta = dictEnd - (const BYTE*)source; + const BYTE* anchor = (const BYTE*) source; + const BYTE* const iend = ip + inputSize; + const BYTE* const mflimit = iend - MFLIMIT; + const BYTE* const matchlimit = iend - LASTLITERALS; + + BYTE* op = (BYTE*) dest; + BYTE* const olimit = op + maxOutputSize; + + U32 forwardH; + + /* Init conditions */ + if ((U32)inputSize > (U32)LZ4_MAX_INPUT_SIZE) return 0; /* Unsupported inputSize, too large (or negative) */ + switch(dict) + { + case noDict: + default: + base = (const BYTE*)source; + lowLimit = (const BYTE*)source; + break; + case withPrefix64k: + base = (const BYTE*)source - cctx->currentOffset; + lowLimit = (const BYTE*)source - cctx->dictSize; + break; + case usingExtDict: + base = (const BYTE*)source - cctx->currentOffset; + lowLimit = (const BYTE*)source; + break; + } + if ((tableType == byU16) && (inputSize>=LZ4_64Klimit)) return 0; /* Size too large (not within 64K limit) */ + if (inputSizehashTable, tableType, base); + ip++; forwardH = LZ4_hashPosition(ip, tableType); + + /* Main Loop */ + for ( ; ; ) { + ptrdiff_t refDelta = 0; + const BYTE* match; + BYTE* token; + + /* Find a match */ + { const BYTE* forwardIp = ip; + unsigned step = 1; + unsigned searchMatchNb = acceleration << LZ4_skipTrigger; + do { + U32 const h = forwardH; + ip = forwardIp; + forwardIp += step; + step = (searchMatchNb++ >> LZ4_skipTrigger); + + if (unlikely(forwardIp > mflimit)) goto _last_literals; + + match = LZ4_getPositionOnHash(h, cctx->hashTable, tableType, base); + if (dict==usingExtDict) { + if (match < (const BYTE*)source) { + refDelta = dictDelta; + lowLimit = dictionary; + } else { + refDelta = 0; + lowLimit = (const BYTE*)source; + } } + forwardH = LZ4_hashPosition(forwardIp, tableType); + LZ4_putPositionOnHash(ip, h, cctx->hashTable, tableType, base); + + } while ( ((dictIssue==dictSmall) ? (match < lowRefLimit) : 0) + || ((tableType==byU16) ? 0 : (match + MAX_DISTANCE < ip)) + || (LZ4_read32(match+refDelta) != LZ4_read32(ip)) ); + } + + /* Catch up */ + while (((ip>anchor) & (match+refDelta > lowLimit)) && (unlikely(ip[-1]==match[refDelta-1]))) { ip--; match--; } + + /* Encode Literals */ + { unsigned const litLength = (unsigned)(ip - anchor); + token = op++; + if ((outputLimited) && /* Check output buffer overflow */ + (unlikely(op + litLength + (2 + 1 + LASTLITERALS) + (litLength/255) > olimit))) + return 0; + if (litLength >= RUN_MASK) { + int len = (int)litLength-RUN_MASK; + *token = (RUN_MASK<= 255 ; len-=255) *op++ = 255; + *op++ = (BYTE)len; + } + else *token = (BYTE)(litLength< matchlimit) limit = matchlimit; + matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, limit); + ip += MINMATCH + matchCode; + if (ip==limit) { + unsigned const more = LZ4_count(ip, (const BYTE*)source, matchlimit); + matchCode += more; + ip += more; + } + } else { + matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, matchlimit); + ip += MINMATCH + matchCode; + } + + if ( outputLimited && /* Check output buffer overflow */ + (unlikely(op + (1 + LASTLITERALS) + (matchCode>>8) > olimit)) ) + return 0; + if (matchCode >= ML_MASK) { + *token += ML_MASK; + matchCode -= ML_MASK; + LZ4_write32(op, 0xFFFFFFFF); + while (matchCode >= 4*255) op+=4, LZ4_write32(op, 0xFFFFFFFF), matchCode -= 4*255; + op += matchCode / 255; + *op++ = (BYTE)(matchCode % 255); + } else + *token += (BYTE)(matchCode); + } + + anchor = ip; + + /* Test end of chunk */ + if (ip > mflimit) break; + + /* Fill table */ + LZ4_putPosition(ip-2, cctx->hashTable, tableType, base); + + /* Test next position */ + match = LZ4_getPosition(ip, cctx->hashTable, tableType, base); + if (dict==usingExtDict) { + if (match < (const BYTE*)source) { + refDelta = dictDelta; + lowLimit = dictionary; + } else { + refDelta = 0; + lowLimit = (const BYTE*)source; + } } + LZ4_putPosition(ip, cctx->hashTable, tableType, base); + if ( ((dictIssue==dictSmall) ? (match>=lowRefLimit) : 1) + && (match+MAX_DISTANCE>=ip) + && (LZ4_read32(match+refDelta)==LZ4_read32(ip)) ) + { token=op++; *token=0; goto _next_match; } + + /* Prepare next loop */ + forwardH = LZ4_hashPosition(++ip, tableType); + } + +_last_literals: + /* Encode Last Literals */ + { size_t const lastRun = (size_t)(iend - anchor); + if ( (outputLimited) && /* Check output buffer overflow */ + ((op - (BYTE*)dest) + lastRun + 1 + ((lastRun+255-RUN_MASK)/255) > (U32)maxOutputSize) ) + return 0; + if (lastRun >= RUN_MASK) { + size_t accumulator = lastRun - RUN_MASK; + *op++ = RUN_MASK << ML_BITS; + for(; accumulator >= 255 ; accumulator-=255) *op++ = 255; + *op++ = (BYTE) accumulator; + } else { + *op++ = (BYTE)(lastRun<internal_donotuse; + LZ4_resetStream((LZ4_stream_t*)state); + //if (acceleration < 1) acceleration = ACCELERATION_DEFAULT; + + if (maxOutputSize >= LZ4_compressBound(inputSize)) { + if (inputSize < LZ4_64Klimit) + return LZ4_compress_generic(ctx, source, dest, inputSize, 0, notLimited, byU16, noDict, noDictIssue, acceleration); + else + return LZ4_compress_generic(ctx, source, dest, inputSize, 0, notLimited, (sizeof(void*)==8) ? byU32 : byPtr, noDict, noDictIssue, acceleration); + } else { + if (inputSize < LZ4_64Klimit) + return LZ4_compress_generic(ctx, source, dest, inputSize, maxOutputSize, limitedOutput, byU16, noDict, noDictIssue, acceleration); + else + return LZ4_compress_generic(ctx, source, dest, inputSize, maxOutputSize, limitedOutput, (sizeof(void*)==8) ? byU32 : byPtr, noDict, noDictIssue, acceleration); + } +} + +static inline int LZ4_compress_fast(const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration) +{ +#if (HEAPMODE) + void* ctxPtr = ALLOCATOR(1, sizeof(LZ4_stream_t)); /* malloc-calloc always properly aligned */ +#else + LZ4_stream_t ctx; + void* const ctxPtr = &ctx; +#endif + + int const result = LZ4_compress_fast_extState(ctxPtr, source, dest, inputSize, maxOutputSize, acceleration); + +#if (HEAPMODE) + FREEMEM(ctxPtr); +#endif + return result; +} + +void LZ4_resetStream (LZ4_stream_t* LZ4_stream) +{ + MEM_INIT(LZ4_stream, 0, sizeof(LZ4_stream_t)); +} + +/*-***************************** +* Decompression functions +*******************************/ +/*! LZ4_decompress_generic() : + * This generic decompression function cover all use cases. + * It shall be instantiated several times, using different sets of directives + * Note that it is important this generic function is really inlined, + * in order to remove useless branches during compilation optimization. + */ +FORCE_INLINE int LZ4_decompress_generic( + const char* const source, + char* const dest, + int inputSize, + int outputSize, /* If endOnInput==endOnInputSize, this value is the max size of Output Buffer. */ + + int endOnInput, /* endOnOutputSize, endOnInputSize */ + int partialDecoding, /* full, partial */ + int targetOutputSize, /* only used if partialDecoding==partial */ + int dict, /* noDict, withPrefix64k, usingExtDict */ + const BYTE* const lowPrefix, /* == dest when no prefix */ + const BYTE* const dictStart, /* only if dict==usingExtDict */ + const size_t dictSize /* note : = 0 if noDict */ + ) +{ + /* Local Variables */ + const BYTE* ip = (const BYTE*) source; + const BYTE* const iend = ip + inputSize; + + BYTE* op = (BYTE*) dest; + BYTE* const oend = op + outputSize; + BYTE* cpy; + BYTE* oexit = op + targetOutputSize; + const BYTE* const lowLimit = lowPrefix - dictSize; + + const BYTE* const dictEnd = (const BYTE*)dictStart + dictSize; + const unsigned dec32table[] = {0, 1, 2, 1, 4, 4, 4, 4}; + const int dec64table[] = {0, 0, 0, -1, 0, 1, 2, 3}; + + const int safeDecode = (endOnInput==endOnInputSize); + const int checkOffset = ((safeDecode) && (dictSize < (int)(64 KB))); + + + /* Special cases */ + if ((partialDecoding) && (oexit > oend-MFLIMIT)) oexit = oend-MFLIMIT; /* targetOutputSize too high => decode everything */ + if ((endOnInput) && (unlikely(outputSize==0))) return ((inputSize==1) && (*ip==0)) ? 0 : -1; /* Empty output buffer */ + if ((!endOnInput) && (unlikely(outputSize==0))) return (*ip==0?1:-1); + + /* Main Loop : decode sequences */ + while (1) { + size_t length; + const BYTE* match; + size_t offset; + + /* get literal length */ + unsigned const token = *ip++; + if ((length=(token>>ML_BITS)) == RUN_MASK) { + unsigned s; + do { + s = *ip++; + length += s; + } while ( likely(endOnInput ? ip(partialDecoding?oexit:oend-MFLIMIT)) || (ip+length>iend-(2+1+LASTLITERALS))) ) + || ((!endOnInput) && (cpy>oend-WILDCOPYLENGTH)) ) + { + if (partialDecoding) { + if (cpy > oend) goto _output_error; /* Error : write attempt beyond end of output buffer */ + if ((endOnInput) && (ip+length > iend)) goto _output_error; /* Error : read attempt beyond end of input buffer */ + } else { + if ((!endOnInput) && (cpy != oend)) goto _output_error; /* Error : block decoding must stop exactly there */ + if ((endOnInput) && ((ip+length != iend) || (cpy > oend))) goto _output_error; /* Error : input must be consumed */ + } + memcpy(op, ip, length); + ip += length; + op += length; + break; /* Necessarily EOF, due to parsing restrictions */ + } + LZ4_wildCopy(op, ip, cpy); + ip += length; op = cpy; + + /* get offset */ + offset = LZ4_readLE16(ip); ip+=2; + match = op - offset; + if ((checkOffset) && (unlikely(match < lowLimit))) goto _output_error; /* Error : offset outside buffers */ + LZ4_write32(op, (U32)offset); /* costs ~1%; silence an msan warning when offset==0 */ + + /* get matchlength */ + length = token & ML_MASK; + if (length == ML_MASK) { + unsigned s; + do { + s = *ip++; + if ((endOnInput) && (ip > iend-LASTLITERALS)) goto _output_error; + length += s; + } while (s==255); + if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)op)) goto _output_error; /* overflow detection */ + } + length += MINMATCH; + + /* check external dictionary */ + if ((dict==usingExtDict) && (match < lowPrefix)) { + if (unlikely(op+length > oend-LASTLITERALS)) goto _output_error; /* doesn't respect parsing restriction */ + + if (length <= (size_t)(lowPrefix-match)) { + /* match can be copied as a single segment from external dictionary */ + memmove(op, dictEnd - (lowPrefix-match), length); + op += length; + } else { + /* match encompass external dictionary and current block */ + size_t const copySize = (size_t)(lowPrefix-match); + size_t const restSize = length - copySize; + memcpy(op, dictEnd - copySize, copySize); + op += copySize; + if (restSize > (size_t)(op-lowPrefix)) { /* overlap copy */ + BYTE* const endOfMatch = op + restSize; + const BYTE* copyFrom = lowPrefix; + while (op < endOfMatch) *op++ = *copyFrom++; + } else { + memcpy(op, lowPrefix, restSize); + op += restSize; + } } + continue; + } + + /* copy match within block */ + cpy = op + length; + if (unlikely(offset<8)) { + const int dec64 = dec64table[offset]; + op[0] = match[0]; + op[1] = match[1]; + op[2] = match[2]; + op[3] = match[3]; + match += dec32table[offset]; + memcpy(op+4, match, 4); + match -= dec64; + } else { LZ4_copy8(op, match); match+=8; } + op += 8; + + if (unlikely(cpy>oend-12)) { + BYTE* const oCopyLimit = oend-(WILDCOPYLENGTH-1); + if (cpy > oend-LASTLITERALS) goto _output_error; /* Error : last LASTLITERALS bytes must be literals (uncompressed) */ + if (op < oCopyLimit) { + LZ4_wildCopy(op, match, oCopyLimit); + match += oCopyLimit - op; + op = oCopyLimit; + } + while (op16) LZ4_wildCopy(op+8, match+8, cpy); + } + op=cpy; /* correction */ + } + + /* end of decoding */ + if (endOnInput) + return (int) (((char*)op)-dest); /* Nb of output bytes decoded */ + else + return (int) (((const char*)ip)-source); /* Nb of input bytes read */ + + /* Overflow error detected */ +_output_error: + return (int) (-(((const char*)ip)-source))-1; +} + +static inline int LZ4_decompress_safe(const char* source, char* dest, int compressedSize, int maxDecompressedSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxDecompressedSize, endOnInputSize, full, 0, noDict, (BYTE*)dest, NULL, 0); +} + +} // anonymous namespace + +/************************************************************************** */ +/************************************************************************** */ + const unsigned char Packet::ZERO_KEY[32] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }; -//#ifdef ZT_TRACE +#ifdef ZT_TRACE const char *Packet::verbString(Verb v) - throw() { switch(v) { case VERB_NOP: return "NOP"; @@ -38,21 +1066,20 @@ const char *Packet::verbString(Verb v) case VERB_EXT_FRAME: return "EXT_FRAME"; case VERB_ECHO: return "ECHO"; case VERB_MULTICAST_LIKE: return "MULTICAST_LIKE"; - case VERB_NETWORK_MEMBERSHIP_CERTIFICATE: return "NETWORK_MEMBERSHIP_CERTIFICATE"; + case VERB_NETWORK_CREDENTIALS: return "NETWORK_CREDENTIALS"; case VERB_NETWORK_CONFIG_REQUEST: return "NETWORK_CONFIG_REQUEST"; - case VERB_NETWORK_CONFIG_REFRESH: return "NETWORK_CONFIG_REFRESH"; + 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_REQUEST_PROOF_OF_WORK: return "REQUEST_PROOF_OF_WORK"; + case VERB_USER_MESSAGE: return "USER_MESSAGE"; } return "(unknown)"; } const char *Packet::errorString(ErrorCode e) - throw() { switch(e) { case ERROR_NONE: return "NONE"; @@ -68,89 +1095,142 @@ const char *Packet::errorString(ErrorCode e) return "(unknown)"; } -//#endif // ZT_TRACE +#endif // ZT_TRACE -void Packet::armor(const void *key,bool encryptPayload) +void Packet::armor(const void *key,bool encryptPayload,unsigned int counter) { - unsigned char mangledKey[32]; - unsigned char macKey[32]; - unsigned char mac[16]; - const unsigned int payloadLen = size() - ZT_PACKET_IDX_VERB; - unsigned char *const payload = field(ZT_PACKET_IDX_VERB,payloadLen); + uint8_t mangledKey[32]; + uint8_t *const data = reinterpret_cast(unsafeData()); + + // Mask least significant 3 bits of packet ID with counter to embed packet send counter for QoS use + data[7] = (data[7] & 0xf8) | (uint8_t)(counter & 0x07); // Set flag now, since it affects key mangle function setCipher(encryptPayload ? ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012 : ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_NONE); _salsa20MangleKey((const unsigned char *)key,mangledKey); - Salsa20 s20(mangledKey,256,field(ZT_PACKET_IDX_IV,8)/*,ZT_PROTO_SALSA20_ROUNDS*/); - - // MAC key is always the first 32 bytes of the Salsa20 key stream - // This is the same construction DJB's NaCl library uses - s20.encrypt12(ZERO_KEY,macKey,sizeof(macKey)); - - if (encryptPayload) - s20.encrypt12(payload,payload,payloadLen); - - Poly1305::compute(mac,payload,payloadLen,macKey); - memcpy(field(ZT_PACKET_IDX_MAC,8),mac,8); + if (ZT_HAS_FAST_CRYPTO()) { + const unsigned int encryptLen = (encryptPayload) ? (size() - ZT_PACKET_IDX_VERB) : 0; + uint64_t keyStream[(ZT_PROTO_MAX_PACKET_LENGTH + 64 + 8) / 8]; + ZT_FAST_SINGLE_PASS_SALSA2012(keyStream,encryptLen + 64,(data + ZT_PACKET_IDX_IV),mangledKey); + Salsa20::memxor(data + ZT_PACKET_IDX_VERB,reinterpret_cast(keyStream + 8),encryptLen); + uint64_t mac[2]; + Poly1305::compute(mac,data + ZT_PACKET_IDX_VERB,size() - ZT_PACKET_IDX_VERB,keyStream); +#ifdef ZT_NO_TYPE_PUNNING + memcpy(data + ZT_PACKET_IDX_MAC,mac,8); +#else + (*reinterpret_cast(data + ZT_PACKET_IDX_MAC)) = mac[0]; +#endif + } else { + Salsa20 s20(mangledKey,data + ZT_PACKET_IDX_IV); + uint64_t macKey[4]; + s20.crypt12(ZERO_KEY,macKey,sizeof(macKey)); + uint8_t *const payload = data + ZT_PACKET_IDX_VERB; + const unsigned int payloadLen = size() - ZT_PACKET_IDX_VERB; + if (encryptPayload) + s20.crypt12(payload,payload,payloadLen); + uint64_t mac[2]; + Poly1305::compute(mac,payload,payloadLen,macKey); + memcpy(data + ZT_PACKET_IDX_MAC,mac,8); + } } bool Packet::dearmor(const void *key) { - unsigned char mangledKey[32]; - unsigned char macKey[32]; - unsigned char mac[16]; + uint8_t mangledKey[32]; + uint8_t *const data = reinterpret_cast(unsafeData()); const unsigned int payloadLen = size() - ZT_PACKET_IDX_VERB; - unsigned char *const payload = field(ZT_PACKET_IDX_VERB,payloadLen); - unsigned int cs = cipher(); + 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,field(ZT_PACKET_IDX_IV,8)/*,ZT_PROTO_SALSA20_ROUNDS*/); - - s20.encrypt12(ZERO_KEY,macKey,sizeof(macKey)); - Poly1305::compute(mac,payload,payloadLen,macKey); - if (!Utils::secureEq(mac,field(ZT_PACKET_IDX_MAC,8),8)) - return false; - - if (cs == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012) - s20.decrypt12(payload,payload,payloadLen); + if (ZT_HAS_FAST_CRYPTO()) { + uint64_t keyStream[(ZT_PROTO_MAX_PACKET_LENGTH + 64 + 8) / 8]; + ZT_FAST_SINGLE_PASS_SALSA2012(keyStream,((cs == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012) ? (payloadLen + 64) : 64),(data + ZT_PACKET_IDX_IV),mangledKey); + uint64_t mac[2]; + Poly1305::compute(mac,payload,payloadLen,keyStream); +#ifdef ZT_NO_TYPE_PUNNING + if (!Utils::secureEq(mac,data + ZT_PACKET_IDX_MAC,8)) + return false; +#else + if ((*reinterpret_cast(data + ZT_PACKET_IDX_MAC)) != mac[0]) // also secure, constant time + return false; +#endif + if (cs == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012) + Salsa20::memxor(data + ZT_PACKET_IDX_VERB,reinterpret_cast(keyStream + 8),payloadLen); + } else { + Salsa20 s20(mangledKey,data + ZT_PACKET_IDX_IV); + uint64_t macKey[4]; + s20.crypt12(ZERO_KEY,macKey,sizeof(macKey)); + uint64_t mac[2]; + Poly1305::compute(mac,payload,payloadLen,macKey); +#ifdef ZT_NO_TYPE_PUNNING + if (!Utils::secureEq(mac,data + ZT_PACKET_IDX_MAC,8)) + return false; +#else + if ((*reinterpret_cast(data + ZT_PACKET_IDX_MAC)) != mac[0]) // also secure, constant time + return false; +#endif + if (cs == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012) + s20.crypt12(payload,payload,payloadLen); + } return true; - } else return false; // unrecognized cipher suite + } else { + return false; // unrecognized cipher suite + } +} + +void Packet::cryptField(const void *key,unsigned int start,unsigned int len) +{ + uint8_t *const data = reinterpret_cast(unsafeData()); + uint8_t iv[8]; + for(int i=0;i<8;++i) iv[i] = data[i]; + iv[7] &= 0xf8; // mask off least significant 3 bits of packet ID / IV since this is unset when this function gets called + Salsa20 s20(key,iv); + s20.crypt12(data + start,data + start,len); } bool Packet::compress() { - unsigned char buf[ZT_PROTO_MAX_PACKET_LENGTH * 2]; - if ((!compressed())&&(size() > (ZT_PACKET_IDX_PAYLOAD + 32))) { + 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((const char *)field(ZT_PACKET_IDX_PAYLOAD,(unsigned int)pl),(char *)buf,pl); + int cl = LZ4_compress_fast(data + ZT_PACKET_IDX_PAYLOAD,buf,pl,ZT_PROTO_MAX_PACKET_LENGTH * 2,2); if ((cl > 0)&&(cl < pl)) { - (*this)[ZT_PACKET_IDX_VERB] |= (char)ZT_PROTO_VERB_FLAG_COMPRESSED; + data[ZT_PACKET_IDX_VERB] |= (char)ZT_PROTO_VERB_FLAG_COMPRESSED; setSize((unsigned int)cl + ZT_PACKET_IDX_PAYLOAD); - memcpy(field(ZT_PACKET_IDX_PAYLOAD,(unsigned int)cl),buf,cl); + memcpy(data + ZT_PACKET_IDX_PAYLOAD,buf,cl); return true; } } - (*this)[ZT_PACKET_IDX_VERB] &= (char)(~ZT_PROTO_VERB_FLAG_COMPRESSED); + data[ZT_PACKET_IDX_VERB] &= (char)(~ZT_PROTO_VERB_FLAG_COMPRESSED); + return false; } bool Packet::uncompress() { - unsigned char buf[ZT_PROTO_MAX_PACKET_LENGTH]; + 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 *)field(ZT_PACKET_IDX_PAYLOAD,compLen),(char *)buf,compLen,sizeof(buf)); + 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(field(ZT_PACKET_IDX_PAYLOAD,(unsigned int)ucl),buf,ucl); - } else return false; + memcpy(data + ZT_PACKET_IDX_PAYLOAD,buf,ucl); + } else { + return false; + } } - (*this)[ZT_PACKET_IDX_VERB] &= (char)(~ZT_PROTO_VERB_FLAG_COMPRESSED); + data[ZT_PACKET_IDX_VERB] &= (char)(~ZT_PROTO_VERB_FLAG_COMPRESSED); } + return true; } diff --git a/node/Packet.hpp b/node/Packet.hpp index 3d95b0b..8ad2c0f 100644 --- a/node/Packet.hpp +++ b/node/Packet.hpp @@ -34,11 +34,11 @@ #include "Utils.hpp" #include "Buffer.hpp" -#ifdef ZT_USE_SYSTEM_LZ4 -#include -#else -#include "../ext/lz4/lz4.h" -#endif +//#ifdef ZT_USE_SYSTEM_LZ4 +//#include +//#else +//#include "../ext/lz4/lz4.h" +//#endif /** * Protocol version -- incremented only for major changes @@ -51,19 +51,25 @@ * + Yet another multicast redesign * + New crypto completely changes key agreement cipher * 4 - 0.6.0 ... 1.0.6 - * + New identity format based on hashcash design + * + 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 - * + Deprecate old dictionary-based network config format - * + Introduce new binary serialized network config and meta-data - * 7 - 1.1.10 -- CURRENT + * + 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 7 +#define ZT_PROTO_VERSION 9 /** * Minimum supported protocol version @@ -303,6 +309,7 @@ #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) @@ -346,7 +353,7 @@ namespace ZeroTier { * ZeroTier packet * * Packet format: - * <[8] 64-bit random packet ID and crypto initialization vector> + * <[8] 64-bit packet ID / crypto IV / packet counter> * <[5] destination ZT address> * <[5] source ZT address> * <[1] flags/cipher/hops> @@ -357,6 +364,14 @@ namespace ZeroTier { * * 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. @@ -523,50 +538,60 @@ public: /** * No operation (ignored, no reply) */ - VERB_NOP = 0, + VERB_NOP = 0x00, /** - * Announcement of a node's existence: + * Announcement of a node's existence and vitals: * <[1] protocol version> * <[1] software major version> * <[1] software minor version> * <[2] software revision> - * <[8] timestamp (ms since epoch)> + * <[8] timestamp for determining latency> * <[...] binary serialized identity (see Identity)> - * <[1] destination address type> - * [<[...] destination address>] - * <[8] 64-bit world ID of current world> - * <[8] 64-bit timestamp of current world> + * <[...] 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 ...] * - * This is the only message that ever must be sent in the clear, since it - * is used to push an identity to a new peer. + * 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. * - * The destination address is the wire address to which this packet is - * being sent, and in OK is *also* the destination address of the OK - * packet. This can be used by the receiver to detect NAT, learn its real - * external address if behind NAT, and detect changes to its external - * address that require re-establishing connectivity. - * - * Destination address types and formats (not all of these are used now): - * 0x00 - None -- no destination address data present - * 0x01 - Ethernet address -- format: <[6] Ethernet MAC> - * 0x04 - 6-byte IPv4 UDP address/port -- format: <[4] IP>, <[2] port> - * 0x06 - 18-byte IPv6 UDP address/port -- format: <[16] IP>, <[2] port> + * Destination address is the actual wire address to which the packet + * was sent. See InetAddress::serialize() for format. * * OK payload: - * <[8] timestamp (echoed from original HELLO)> - * <[1] protocol version (of responder)> - * <[1] software major version (of responder)> - * <[1] software minor version (of responder)> - * <[2] software revision (of responder)> - * <[1] destination address type (for this OK, not copied from HELLO)> - * [<[...] destination address>] - * <[2] 16-bit length of world update or 0 if none> - * [[...] world update] + * <[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 = 1, + VERB_HELLO = 0x01, /** * Error response: @@ -575,7 +600,7 @@ public: * <[1] error code> * <[...] error-dependent payload> */ - VERB_ERROR = 2, + VERB_ERROR = 0x02, /** * Success response: @@ -583,50 +608,43 @@ public: * <[8] in-re packet ID> * <[...] request-specific payload> */ - VERB_OK = 3, + 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 should be discarded. + * 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. WHOIS requests - * will time out much like ARP requests and similar do in L2. + * 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 = 4, + VERB_WHOIS = 0x04, /** - * Meet another node at a given protocol address: + * 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)> * - * This is sent by a relaying node to initiate NAT traversal between two - * peers that are communicating by way of indirect relay. The relay will - * send this to both peers at the same time on a periodic basis, telling - * each where it might find the other on the network. + * 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. * - * Nodes should implement rate control, limiting the rate at which they - * respond to these packets to prevent their use in DDOS attacks. Nodes - * may also ignore these messages if a peer is not known or is not being - * actively communicated with. - * - * Unfortunately the physical address format in this message pre-dates - * InetAddress's serialization format. :( ZeroTier is four years old and - * yes we've accumulated a tiny bit of cruft here and there. - * * No OK or ERROR is generated. */ - VERB_RENDEZVOUS = 5, + VERB_RENDEZVOUS = 0x05, /** * ZT-to-ZT unicast ethernet frame (shortened EXT_FRAME): @@ -642,31 +660,44 @@ public: * ERROR may be generated if a membership certificate is needed for a * closed network. Payload will be network ID. */ - VERB_FRAME = 6, + VERB_FRAME = 0x06, /** * Full Ethernet frame with MAC addressing and optional fields: * <[8] 64-bit network ID> * <[1] flags> - * [<[...] certificate of network membership>] * <[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 is attached + * 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) * - * An extended frame carries full MAC addressing, making them a - * superset of VERB_FRAME. They're used for bridging or when we - * want to attach a certificate since FRAME does not support that. + * 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. * - * Multicast frames may not be sent as EXT_FRAME. - * - * ERROR may be generated if a membership certificate is needed for a - * closed network. Payload will be network ID. + * OK payload (if ACK flag is set): + * <[8] 64-bit network ID> */ - VERB_EXT_FRAME = 7, + VERB_EXT_FRAME = 0x07, /** * ECHO request (a.k.a. ping): @@ -676,7 +707,7 @@ public: * is generated. Response to ECHO requests is optional and ECHO may be * ignored if a node detects a possible flood. */ - VERB_ECHO = 8, + VERB_ECHO = 0x08, /** * Announce interest in multicast group(s): @@ -690,77 +721,117 @@ public: * controllers and root servers. In the current network, root servers * will provide the service of final multicast cache. * - * It is recommended that NETWORK_MEMBERSHIP_CERTIFICATE pushes be sent - * along with MULTICAST_LIKE when pushing LIKEs to peers that do not - * share a network membership (such as root servers), since this can be - * used to authenticate GATHER requests and limit responses to peers - * authorized to talk on a network. (Should be an optional field here, - * but saving one or two packets every five minutes is not worth an - * ugly hack or protocol rev.) + * 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 = 9, + VERB_MULTICAST_LIKE = 0x09, /** - * Network member certificate replication/push: - * <[...] serialized certificate of membership> - * [ ... additional certificates may follow ...] + * 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 is sent in response to ERROR_NEED_MEMBERSHIP_CERTIFICATE and may - * be pushed at any other time to keep exchanged certificates up to date. + * 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_MEMBERSHIP_CERTIFICATE = 10, + 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 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. If the optional revision is included, a response is - * only generated if there is a newer network configuration available. + * 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> - * <[...] network configuration dictionary> + * <[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> * - * OK returns a Dictionary (string serialized) containing the network's - * configuration and IP address assignment information for the querying - * node. It also contains a membership certificate that the querying - * node can push to other peers to demonstrate its right to speak on - * a given network. + * The chunk signature signs the entire payload of the OK response. + * Currently only one signature type is supported: ed25519 (1). * - * When a new network configuration is received, another config request - * should be sent with the new netconf's revision. This confirms receipt - * and also causes any subsequent changes to rapidly propagate as this - * cycle will repeat until there are no changes. This is optional but - * recommended behavior. + * 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> - * - * UNSUPPORTED_OPERATION is returned if this service is not supported, - * and OBJ_NOT_FOUND if the queried network ID was not found. */ - VERB_NETWORK_CONFIG_REQUEST = 11, + VERB_NETWORK_CONFIG_REQUEST = 0x0b, /** - * Network configuration refresh request: - * <[...] array of 64-bit network IDs> + * 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 can be sent by the network controller to inform a node that it - * should now make a NETWORK_CONFIG_REQUEST. + * 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. * - * It does not generate an OK or ERROR message, and is treated only as - * a hint to refresh now. + * 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_REFRESH = 12, + VERB_NETWORK_CONFIG = 0x0c, /** * Request endpoints for multicast distribution: @@ -769,10 +840,10 @@ public: * <[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>] + * [<[...] network certificate of membership>] * * Flags: - * 0x01 - Network certificate of membership is attached + * 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 @@ -782,6 +853,9 @@ public: * 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> @@ -793,13 +867,12 @@ public: * * ERROR is not generated; queries that return no response are dropped. */ - VERB_MULTICAST_GATHER = 13, + VERB_MULTICAST_GATHER = 0x0d, /** * Multicast frame: * <[8] 64-bit network ID> * <[1] flags> - * [<[...] network certificate of membership>] * [<[4] 32-bit implicit gather limit>] * [<[6] source MAC>] * <[6] destination MAC (multicast address)> @@ -808,7 +881,7 @@ public: * <[...] ethernet payload> * * Flags: - * 0x01 - Network certificate of membership is attached + * 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 * @@ -823,11 +896,11 @@ public: * <[6] MAC address of multicast group> * <[4] 32-bit ADI for multicast group> * <[1] flags> - * [<[...] network certficate of membership>] + * [<[...] 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 + * 0x01 - OK includes certificate of network membership (DEPRECATED) * 0x02 - OK includes implicit gather results * * ERROR response payload: @@ -835,7 +908,7 @@ public: * <[6] multicast group MAC> * <[4] 32-bit multicast group ADI> */ - VERB_MULTICAST_FRAME = 14, + VERB_MULTICAST_FRAME = 0x0e, /** * Push of potential endpoints for direct communication: @@ -871,7 +944,7 @@ public: * * OK and ERROR are not generated. */ - VERB_PUSH_DIRECT_PATHS = 16, + VERB_PUSH_DIRECT_PATHS = 0x10, /** * Source-routed circuit test message: @@ -887,21 +960,17 @@ public: * [ ... end of signed portion of request ... ] * <[2] 16-bit length of signature of request> * <[...] signature of request by originator> - * <[2] 16-bit previous hop credential length (including type)> - * [[1] previous hop credential type] - * [[...] previous hop credential] + * <[2] 16-bit length of additional fields> + * [[...] additional fields] * <[...] next hop(s) in path> * * Flags: - * 0x01 - Report back to originator at middle hops + * 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 * - * Previous hop credential types: - * 0x01 - Certificate of network membership - * * Path record format: * <[1] 8-bit flags (unused, must be zero)> * <[1] 8-bit breadth (number of next hops)> @@ -950,29 +1019,28 @@ public: * <[8] 64-bit timestamp (echoed from original> * <[8] 64-bit test ID (echoed from original)> */ - VERB_CIRCUIT_TEST = 17, + VERB_CIRCUIT_TEST = 0x11, /** * Circuit test hop report: - * <[8] 64-bit timestamp (from original test)> - * <[8] 64-bit test ID (from original test)> + * <[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 major version> - * <[1] 8-bit reporter minor version> - * <[2] 16-bit reporter revision> - * <[2] 16-bit reporter OS/platform> - * <[2] 16-bit reporter architecture> + * <[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 (set to 0, currently unused)> - * <[8] 64-bit source packet ID> - * <[5] upstream ZeroTier address from which test was received> - * <[1] 8-bit source packet hop count (ZeroTier hop count)> + * <[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 length of additional fields> - * <[...] additional fields> + * <[2] 16-bit path link quality of path over which packet was received> * <[1] 8-bit number of next hops (breadth)> * <[...] next hop information> * @@ -980,6 +1048,9 @@ public: * <[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. @@ -987,50 +1058,22 @@ public: * 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 = 18, + VERB_CIRCUIT_TEST_REPORT = 0x12, /** - * Request proof of work: - * <[1] 8-bit proof of work type> - * <[1] 8-bit proof of work difficulty> - * <[2] 16-bit length of proof of work challenge> - * <[...] proof of work challenge> + * A message with arbitrary user-definable content: + * <[8] 64-bit arbitrary message type ID> + * [<[...] message payload>] * - * This requests that a peer perform a proof of work calucation. It can be - * sent by highly trusted peers (e.g. root servers, network controllers) - * under suspected denial of service conditions in an attempt to filter - * out "non-serious" peers and remain responsive to those proving their - * intent to actually communicate. + * 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. * - * If the peer obliges to perform the work, it does so and responds with - * an OK containing the result. Otherwise it may ignore the message or - * response with an ERROR_INVALID_REQUEST or ERROR_UNSUPPORTED_OPERATION. - * - * Proof of work type IDs: - * 0x01 - Salsa20/12+SHA512 hashcash function - * - * Salsa20/12+SHA512 is based on the following composite hash function: - * - * (1) Compute SHA512(candidate) - * (2) Use the first 256 bits of the result of #1 as a key to encrypt - * 131072 zero bytes with Salsa20/12 (with a zero IV). - * (3) Compute SHA512(the result of step #2) - * (4) Accept this candiate if the first [difficulty] bits of the result - * from step #3 are zero. Otherwise generate a new candidate and try - * again. - * - * This is performed repeatedly on candidates generated by appending the - * supplied challenge to an arbitrary nonce until a valid candidate - * is found. This chosen prepended nonce is then returned as the result - * in OK. - * - * OK payload: - * <[2] 16-bit length of result> - * <[...] computed proof of work> - * - * ERROR has no payload. + * 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_REQUEST_PROOF_OF_WORK = 19 + VERB_USER_MESSAGE = 0x14 }; /** @@ -1039,39 +1082,37 @@ public: enum ErrorCode { /* No error, not actually used in transit */ - ERROR_NONE = 0, + ERROR_NONE = 0x00, /* Invalid request */ - ERROR_INVALID_REQUEST = 1, + ERROR_INVALID_REQUEST = 0x01, /* Bad/unsupported protocol version */ - ERROR_BAD_PROTOCOL_VERSION = 2, + ERROR_BAD_PROTOCOL_VERSION = 0x02, /* Unknown object queried */ - ERROR_OBJ_NOT_FOUND = 3, + ERROR_OBJ_NOT_FOUND = 0x03, /* HELLO pushed an identity whose address is already claimed */ - ERROR_IDENTITY_COLLISION = 4, + ERROR_IDENTITY_COLLISION = 0x04, /* Verb or use case not supported/enabled by this node */ - ERROR_UNSUPPORTED_OPERATION = 5, + ERROR_UNSUPPORTED_OPERATION = 0x05, - /* Message to private network rejected -- no unexpired certificate on file */ - ERROR_NEED_MEMBERSHIP_CERTIFICATE = 6, + /* Network membership certificate update needed */ + ERROR_NEED_MEMBERSHIP_CERTIFICATE = 0x06, /* Tried to join network, but you're not a member */ - ERROR_NETWORK_ACCESS_DENIED_ = 7, /* extra _ to avoid Windows name conflict */ + ERROR_NETWORK_ACCESS_DENIED_ = 0x07, /* extra _ at end to avoid Windows name conflict */ /* Multicasts to this group are not wanted */ - ERROR_UNWANTED_MULTICAST = 8 + ERROR_UNWANTED_MULTICAST = 0x08 }; -//#ifdef ZT_TRACE - static const char *verbString(Verb v) - throw(); - static const char *errorString(ErrorCode e) - throw(); -//#endif +#ifdef ZT_TRACE + static const char *verbString(Verb v); + static const char *errorString(ErrorCode e); +#endif template Packet(const Buffer &b) : @@ -1268,10 +1309,21 @@ public: /** * 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 * @@ -1302,8 +1354,9 @@ public: * * @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); + void armor(const void *key,bool encryptPayload,unsigned int counter); /** * Verify and (if encrypted) decrypt packet @@ -1317,6 +1370,27 @@ public: */ 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) * diff --git a/node/Path.cpp b/node/Path.cpp index 5692af6..7366b56 100644 --- a/node/Path.cpp +++ b/node/Path.cpp @@ -22,10 +22,10 @@ namespace ZeroTier { -bool Path::send(const RuntimeEnvironment *RR,const void *data,unsigned int len,uint64_t now) +bool Path::send(const RuntimeEnvironment *RR,void *tPtr,const void *data,unsigned int len,uint64_t now) { - if (RR->node->putPacket(_localAddress,address(),data,len)) { - sent(now); + if (RR->node->putPacket(tPtr,_localAddress,address(),data,len)) { + _lastOut = now; return true; } return false; diff --git a/node/Path.hpp b/node/Path.hpp index ecf4be2..aef628d 100644 --- a/node/Path.hpp +++ b/node/Path.hpp @@ -21,33 +21,17 @@ #include #include +#include #include #include #include "Constants.hpp" #include "InetAddress.hpp" - -// Note: if you change these flags check the logic below. Some of it depends -// on these bits being what they are. - -/** - * Flag indicating that this path is suboptimal - * - * Clusters set this flag on remote paths if GeoIP or other routing decisions - * indicate that a peer should be handed off to another cluster member. - */ -#define ZT_PATH_FLAG_CLUSTER_SUBOPTIMAL 0x0001 - -/** - * Flag indicating that this path is optimal - * - * Peers set this flag on paths that are pushed by a cluster and indicated as - * optimal. A second flag is needed since we want to prioritize cluster optimal - * paths and de-prioritize sub-optimal paths and for new paths we don't know - * which one they are. So we want a trinary state: optimal, suboptimal, unknown. - */ -#define ZT_PATH_FLAG_CLUSTER_OPTIMAL 0x0002 +#include "SharedPtr.hpp" +#include "AtomicCounter.hpp" +#include "NonCopyable.hpp" +#include "Utils.hpp" /** * Maximum return value of preferenceRank() @@ -59,209 +43,194 @@ namespace ZeroTier { class RuntimeEnvironment; /** - * Base class for paths - * - * The base Path class is an immutable value. + * A path across the physical network */ -class Path +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() : - _lastSend(0), - _lastPing(0), - _lastKeepalive(0), - _lastReceived(0), + _lastOut(0), + _lastIn(0), + _lastTrustEstablishedPacketReceived(0), + _incomingLinkQualityFastLog(0xffffffffffffffffULL), + _incomingLinkQualitySlowLogPtr(0), + _incomingLinkQualitySlowLogCounter(-64), // discard first fast log + _incomingLinkQualityPreviousPacketCounter(0), + _outgoingPacketCounter(0), _addr(), _localAddress(), - _flags(0), _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) : - _lastSend(0), - _lastPing(0), - _lastKeepalive(0), - _lastReceived(0), + _lastOut(0), + _lastIn(0), + _lastTrustEstablishedPacketReceived(0), + _incomingLinkQualityFastLog(0xffffffffffffffffULL), + _incomingLinkQualitySlowLogPtr(0), + _incomingLinkQualitySlowLogCounter(-64), // discard first fast log + _incomingLinkQualityPreviousPacketCounter(0), + _outgoingPacketCounter(0), _addr(addr), _localAddress(localAddress), - _flags(0), _ipScope(addr.ipScope()) { - } - - inline Path &operator=(const Path &p) - { - if (this != &p) - memcpy(this,&p,sizeof(Path)); - return *this; + for(int i=0;i<(int)sizeof(_incomingLinkQualitySlowLog);++i) + _incomingLinkQualitySlowLog[i] = ZT_PATH_LINK_QUALITY_MAX; } /** - * Called when a packet is sent to this remote path - * - * This is called automatically by Path::send(). - * - * @param t Time of send - */ - inline void sent(uint64_t t) { _lastSend = t; } - - /** - * Called when we've sent a ping or echo - * - * @param t Time of send - */ - inline void pinged(uint64_t t) { _lastPing = t; } - - /** - * Called when we send a NAT keepalive - * - * @param t Time of send - */ - inline void sentKeepalive(uint64_t t) { _lastKeepalive = t; } - - /** - * Called when a packet is received from this remote path + * Called when a packet is received from this remote path, regardless of content * * @param t Time of receive */ - inline void received(uint64_t t) - { - _lastReceived = t; - _probation = 0; - } + inline void received(const uint64_t t) { _lastIn = t; } /** - * @param now Current time - * @return True if this path appears active + * 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 bool active(uint64_t now) const + inline void updateLinkQuality(const unsigned int counter) { - return ( ((now - _lastReceived) < ZT_PATH_ACTIVITY_TIMEOUT) && (_probation < ZT_PEER_DEAD_PATH_DETECTION_MAX_PROBATION) ); + 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); + } } /** - * Send a packet via this path + * @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,const void *data,unsigned int len,uint64_t now); + 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 throw() { return _localAddress; } - - /** - * @return Time of last send to this path - */ - inline uint64_t lastSend() const throw() { return _lastSend; } - - /** - * @return Time we last pinged or dead path checked this link - */ - inline uint64_t lastPing() const throw() { return _lastPing; } - - /** - * @return Time of last keepalive - */ - inline uint64_t lastKeepalive() const throw() { return _lastKeepalive; } - - /** - * @return Time of last receive from this path - */ - inline uint64_t lastReceived() const throw() { return _lastReceived; } + inline const InetAddress &localAddress() const { return _localAddress; } /** * @return Physical address */ - inline const InetAddress &address() const throw() { return _addr; } + inline const InetAddress &address() const { return _addr; } /** * @return IP scope -- faster shortcut for address().ipScope() */ - inline InetAddress::IpScope ipScope() const throw() { return _ipScope; } + inline InetAddress::IpScope ipScope() const { return _ipScope; } /** - * @param f Valuve of ZT_PATH_FLAG_CLUSTER_SUBOPTIMAL and inverse of ZT_PATH_FLAG_CLUSTER_OPTIMAL (both are changed) + * @return True if path has received a trust established packet (e.g. common network membership) in the past ZT_TRUST_EXPIRATION ms */ - inline void setClusterSuboptimal(bool f) + inline bool trustEstablished(const uint64_t now) const { return ((now - _lastTrustEstablishedPacketReceived) < ZT_TRUST_EXPIRATION); } + + /** + * @return Preference rank, higher == better + */ + inline unsigned int preferenceRank() const { - if (f) { - _flags = (_flags | ZT_PATH_FLAG_CLUSTER_SUBOPTIMAL) & ~ZT_PATH_FLAG_CLUSTER_OPTIMAL; - } else { - _flags = (_flags | ZT_PATH_FLAG_CLUSTER_OPTIMAL) & ~ZT_PATH_FLAG_CLUSTER_SUBOPTIMAL; - } - } - - /** - * @return True if ZT_PATH_FLAG_CLUSTER_SUBOPTIMAL is set - */ - inline bool isClusterSuboptimal() const { return ((_flags & ZT_PATH_FLAG_CLUSTER_SUBOPTIMAL) != 0); } - - /** - * @return True if ZT_PATH_FLAG_CLUSTER_OPTIMAL is set - */ - inline bool isClusterOptimal() const { return ((_flags & ZT_PATH_FLAG_CLUSTER_OPTIMAL) != 0); } - - /** - * @return Preference rank, higher == better (will be less than 255) - */ - inline unsigned int preferenceRank() const throw() - { - /* First, since the scope enum values in InetAddress.hpp are in order of - * use preference rank, we take that. Then we multiple by two, yielding - * a sequence like 0, 2, 4, 6, etc. Then if it's IPv6 we add one. This - * makes IPv6 addresses of a given scope outrank IPv4 addresses of the - * same scope -- e.g. 1 outranks 0. This makes us prefer IPv6, but not - * if the address scope/class is of a fundamentally lower rank. */ + // 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) ); } - /** - * @return This path's overall quality score (higher is better) - */ - inline uint64_t score() const throw() - { - // This is a little bit convoluted because we try to be branch-free, using multiplication instead of branches for boolean flags - - // Start with the last time this path was active, and add a fudge factor to prevent integer underflow if _lastReceived is 0 - uint64_t score = _lastReceived + (ZT_PEER_DIRECT_PING_DELAY * (ZT_PEER_DEAD_PATH_DETECTION_MAX_PROBATION + 1)); - - // Increase score based on path preference rank, which is based on IP scope and address family - score += preferenceRank() * (ZT_PEER_DIRECT_PING_DELAY / ZT_PATH_MAX_PREFERENCE_RANK); - - // Increase score if this is known to be an optimal path to a cluster - score += (uint64_t)(_flags & ZT_PATH_FLAG_CLUSTER_OPTIMAL) * (ZT_PEER_DIRECT_PING_DELAY / 2); // /2 because CLUSTER_OPTIMAL is flag 0x0002 - - // Decrease score if this is known to be a sub-optimal path to a cluster - score -= (uint64_t)(_flags & ZT_PATH_FLAG_CLUSTER_SUBOPTIMAL) * ZT_PEER_DIRECT_PING_DELAY; - - // Penalize for missed ECHO tests in dead path detection - score -= (uint64_t)((ZT_PEER_DIRECT_PING_DELAY / 2) * _probation); - - return score; - } - - /** - * @return True if path is considered reliable (no NAT keepalives etc. are needed) - */ - inline bool reliable() const throw() - { - if ((_addr.ss_family == AF_INET)||(_addr.ss_family == AF_INET6)) - return ((_ipScope != InetAddress::IP_SCOPE_GLOBAL)&&(_ipScope != InetAddress::IP_SCOPE_PSEUDOPRIVATE)); - return true; - } - - /** - * @return True if address is non-NULL - */ - inline operator bool() const throw() { return (_addr); } - /** * Check whether this address is valid for a ZeroTier path * @@ -272,7 +241,6 @@ public: * @return True if address is good for ZeroTier path use */ static inline bool isAddressValidForPath(const InetAddress &a) - throw() { if ((a.ss_family == AF_INET)||(a.ss_family == AF_INET6)) { switch(a.ipScope()) { @@ -304,60 +272,46 @@ public: } /** - * @return Current path probation count (for dead path detect) + * @return True if path appears alive */ - inline unsigned int probation() const { return _probation; } + inline bool alive(const uint64_t now) const { return ((now - _lastIn) <= ZT_PATH_ALIVE_TIMEOUT); } /** - * Increase this path's probation violation count (for dead path detect) + * @return True if this path needs a heartbeat */ - inline void increaseProbation() { ++_probation; } + inline bool needsHeartbeat(const uint64_t now) const { return ((now - _lastOut) >= ZT_PATH_HEARTBEAT_PERIOD); } - template - inline void serialize(Buffer &b) const - { - b.append((uint8_t)2); // version - b.append((uint64_t)_lastSend); - b.append((uint64_t)_lastPing); - b.append((uint64_t)_lastKeepalive); - b.append((uint64_t)_lastReceived); - _addr.serialize(b); - _localAddress.serialize(b); - b.append((uint16_t)_flags); - b.append((uint16_t)_probation); - } + /** + * @return Last time we sent something + */ + inline uint64_t lastOut() const { return _lastOut; } - template - inline unsigned int deserialize(const Buffer &b,unsigned int startAt = 0) - { - unsigned int p = startAt; - if (b[p++] != 2) - throw std::invalid_argument("invalid serialized Path"); - _lastSend = b.template at(p); p += 8; - _lastPing = b.template at(p); p += 8; - _lastKeepalive = b.template at(p); p += 8; - _lastReceived = b.template at(p); p += 8; - p += _addr.deserialize(b,p); - p += _localAddress.deserialize(b,p); - _flags = b.template at(p); p += 2; - _probation = b.template at(p); p += 2; - _ipScope = _addr.ipScope(); - return (p - startAt); - } + /** + * @return Last time we received anything + */ + inline uint64_t lastIn() const { return _lastIn; } - inline bool operator==(const Path &p) const { return ((p._addr == _addr)&&(p._localAddress == _localAddress)); } - inline bool operator!=(const Path &p) const { return ((p._addr != _addr)||(p._localAddress != _localAddress)); } + /** + * 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: - uint64_t _lastSend; - uint64_t _lastPing; - uint64_t _lastKeepalive; - uint64_t _lastReceived; + 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; - unsigned int _flags; - unsigned int _probation; InetAddress::IpScope _ipScope; // memoize this since it's a computed value checked often + volatile uint8_t _incomingLinkQualitySlowLog[32]; + AtomicCounter __refCount; }; } // namespace ZeroTier diff --git a/node/Peer.cpp b/node/Peer.cpp index cc58100..2e9f6a2 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -27,56 +27,54 @@ #include "Cluster.hpp" #include "Packet.hpp" -#include - -#define ZT_PEER_PATH_SORT_INTERVAL 5000 - namespace ZeroTier { -// Used to send varying values for NAT keepalive -static uint32_t _natKeepaliveBuf = 0; - Peer::Peer(const RuntimeEnvironment *renv,const Identity &myIdentity,const Identity &peerIdentity) : RR(renv), - _lastUsed(0), _lastReceive(0), - _lastUnicastFrame(0), - _lastMulticastFrame(0), - _lastAnnouncedTo(0), + _lastNontrivialReceive(0), + _lastTriedMemorizedPath(0), _lastDirectPathPushSent(0), _lastDirectPathPushReceive(0), - _lastPathSort(0), + _lastCredentialRequestSent(0), + _lastWhoisRequestReceived(0), + _lastEchoRequestReceived(0), + _lastComRequestReceived(0), + _lastComRequestSent(0), + _lastCredentialsReceived(0), + _lastTrustEstablishedPacketReceived(0), _vProto(0), _vMajor(0), _vMinor(0), _vRevision(0), _id(peerIdentity), - _numPaths(0), _latency(0), _directPathPushCutoffCount(0), - _networkComs(4), - _lastPushedComs(4) + _credentialsCutoffCount(0) { if (!myIdentity.agree(peerIdentity,_key,ZT_PEER_SECRET_KEY_LENGTH)) throw std::runtime_error("new peer identity key agreement failed"); } void Peer::received( - const InetAddress &localAddr, - const InetAddress &remoteAddr, - unsigned int hops, - uint64_t packetId, - Packet::Verb verb, - uint64_t inRePacketId, - Packet::Verb inReVerb) + 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; + bool isClusterSuboptimalPath = false; if ((RR->cluster)&&(hops == 0)) { // Note: findBetterEndpoint() is first since we still want to check // for a better endpoint even if we don't actually send a redirect. InetAddress redirectTo; - if ( (verb != Packet::VERB_OK) && (verb != Packet::VERB_ERROR) && (verb != Packet::VERB_RENDEZVOUS) && (verb != Packet::VERB_PUSH_DIRECT_PATHS) && (RR->cluster->findBetterEndpoint(redirectTo,_id.address(),remoteAddr,false)) ) { + 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); @@ -93,8 +91,8 @@ void Peer::received( outp.append(redirectTo.rawIpData(),16); } outp.append((uint16_t)redirectTo.port()); - outp.armor(_key,true); - RR->node->putPacket(localAddr,remoteAddr,outp.data(),outp.size()); + 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); @@ -108,97 +106,222 @@ void Peer::received( outp.append((uint8_t)16); outp.append(redirectTo.rawIpData(),16); } - outp.armor(_key,true); - RR->node->putPacket(localAddr,remoteAddr,outp.data(),outp.size()); + outp.armor(_key,true,path->nextOutgoingCounter()); + path->send(RR,tPtr,outp.data(),outp.size(),now); } - suboptimalPath = true; + isClusterSuboptimalPath = true; } } #endif - const uint64_t now = RR->node->now(); _lastReceive = now; - if ((verb == Packet::VERB_FRAME)||(verb == Packet::VERB_EXT_FRAME)) - _lastUnicastFrame = now; - else if (verb == Packet::VERB_MULTICAST_FRAME) - _lastMulticastFrame = 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; - unsigned int np = _numPaths; - for(unsigned int p=0;paddress().ss_family == AF_INET)&&(_v4Path.p)) { + const struct sockaddr_in *const r = reinterpret_cast(&(path->address())); + const struct sockaddr_in *const l = reinterpret_cast(&(_v4Path.p->address())); + const struct sockaddr_in *const rl = reinterpret_cast(&(path->localAddress())); + const struct sockaddr_in *const ll = reinterpret_cast(&(_v4Path.p->localAddress())); + if ((r->sin_addr.s_addr == l->sin_addr.s_addr)&&(r->sin_port == l->sin_port)&&(rl->sin_addr.s_addr == ll->sin_addr.s_addr)&&(rl->sin_port == ll->sin_port)) { + _v4Path.lr = now; #ifdef ZT_ENABLE_CLUSTER - _paths[p].setClusterSuboptimal(suboptimalPath); + _v4Path.localClusterSuboptimal = isClusterSuboptimalPath; #endif - pathIsConfirmed = true; - break; + pathAlreadyKnown = true; + } + } else if ((path->address().ss_family == AF_INET6)&&(_v6Path.p)) { + const struct sockaddr_in6 *const r = reinterpret_cast(&(path->address())); + const struct sockaddr_in6 *const l = reinterpret_cast(&(_v6Path.p->address())); + const struct sockaddr_in6 *const rl = reinterpret_cast(&(path->localAddress())); + const struct sockaddr_in6 *const ll = reinterpret_cast(&(_v6Path.p->localAddress())); + if ((!memcmp(r->sin6_addr.s6_addr,l->sin6_addr.s6_addr,16))&&(r->sin6_port == l->sin6_port)&&(!memcmp(rl->sin6_addr.s6_addr,ll->sin6_addr.s6_addr,16))&&(rl->sin6_port == ll->sin6_port)) { + _v6Path.lr = now; +#ifdef ZT_ENABLE_CLUSTER + _v6Path.localClusterSuboptimal = isClusterSuboptimalPath; +#endif + pathAlreadyKnown = true; + } } } - if ((!pathIsConfirmed)&&(RR->node->shouldUsePathForZeroTierTraffic(localAddr,remoteAddr))) { - if (verb == Packet::VERB_OK) { - - Path *slot = (Path *)0; - if (np < ZT_MAX_PEER_NETWORK_PATHS) { - slot = &(_paths[np++]); + if ( (!pathAlreadyKnown) && (RR->node->shouldUsePathForZeroTierTraffic(tPtr,_id.address(),path->localAddress(),path->address())) ) { + Mutex::Lock _l(_paths_m); + _PeerPath *potentialNewPeerPath = (_PeerPath *)0; + if (path->address().ss_family == AF_INET) { + if ( (!_v4Path.p) || (!_v4Path.p->alive(now)) || ((_v4Path.p->address() != _v4ClusterPreferred)&&(path->preferenceRank() >= _v4Path.p->preferenceRank())) ) { + potentialNewPeerPath = &_v4Path; + } + } else if (path->address().ss_family == AF_INET6) { + if ( (!_v6Path.p) || (!_v6Path.p->alive(now)) || ((_v6Path.p->address() != _v6ClusterPreferred)&&(path->preferenceRank() >= _v6Path.p->preferenceRank())) ) { + potentialNewPeerPath = &_v6Path; + } + } + if (potentialNewPeerPath) { + if (verb == Packet::VERB_OK) { + potentialNewPeerPath->lr = now; + potentialNewPeerPath->p = path; +#ifdef ZT_ENABLE_CLUSTER + potentialNewPeerPath->localClusterSuboptimal = isClusterSuboptimalPath; + if (RR->cluster) + RR->cluster->broadcastHavePeer(_id); +#endif } else { - uint64_t slotWorstScore = 0xffffffffffffffffULL; - for(unsigned int p=0;paddress().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); } } - if (slot) { - *slot = Path(localAddr,remoteAddr); - slot->received(now); -#ifdef ZT_ENABLE_CLUSTER - slot->setClusterSuboptimal(suboptimalPath); -#endif - _numPaths = np; - } - -#ifdef ZT_ENABLE_CLUSTER - 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(),remoteAddr.toString().c_str()); - - if ( (_vProto >= 5) && ( !((_vMajor == 1)&&(_vMinor == 1)&&(_vRevision == 0)) ) ) { - Packet outp(_id.address(),RR->identity.address(),Packet::VERB_ECHO); - outp.armor(_key,true); - RR->node->putPacket(localAddr,remoteAddr,outp.data(),outp.size()); - } else { - sendHELLO(localAddr,remoteAddr,now); - } - } } } - - if ((now - _lastAnnouncedTo) >= ((ZT_MULTICAST_LIKE_EXPIRE / 2) - 1000)) { - _lastAnnouncedTo = now; - const std::vector< SharedPtr > networks(RR->node->allNetworks()); - for(std::vector< SharedPtr >::const_iterator n(networks.begin());n!=networks.end();++n) - (*n)->tryAnnounceMulticastGroupsTo(SharedPtr(this)); - } } -void Peer::sendHELLO(const InetAddress &localAddr,const InetAddress &atAddress,uint64_t now,unsigned int ttl) +bool Peer::sendDirect(void *tPtr,const void *data,unsigned int len,uint64_t now,bool force) +{ + Mutex::Lock _l(_paths_m); + + uint64_t v6lr = 0; + if ( ((now - _v6Path.lr) < ZT_PEER_PATH_EXPIRATION) && (_v6Path.p) ) + v6lr = _v6Path.p->lastIn(); + uint64_t v4lr = 0; + if ( ((now - _v4Path.lr) < ZT_PEER_PATH_EXPIRATION) && (_v4Path.p) ) + v4lr = _v4Path.p->lastIn(); + + if ( (v6lr > v4lr) && ((now - v6lr) < ZT_PATH_ALIVE_TIMEOUT) ) { + return _v6Path.p->send(RR,tPtr,data,len,now); + } else if ((now - v4lr) < ZT_PATH_ALIVE_TIMEOUT) { + return _v4Path.p->send(RR,tPtr,data,len,now); + } else if (force) { + if (v6lr > v4lr) { + return _v6Path.p->send(RR,tPtr,data,len,now); + } else if (v4lr) { + return _v4Path.p->send(RR,tPtr,data,len,now); + } + } + + return false; +} + +SharedPtr Peer::getBestPath(uint64_t now,bool includeExpired) +{ + Mutex::Lock _l(_paths_m); + + uint64_t v6lr = 0; + if ( ( includeExpired || ((now - _v6Path.lr) < ZT_PEER_PATH_EXPIRATION) ) && (_v6Path.p) ) + v6lr = _v6Path.p->lastIn(); + uint64_t v4lr = 0; + if ( ( includeExpired || ((now - _v4Path.lr) < ZT_PEER_PATH_EXPIRATION) ) && (_v4Path.p) ) + v4lr = _v4Path.p->lastIn(); + + if (v6lr > v4lr) { + return _v6Path.p; + } else if (v4lr) { + return _v4Path.p; + } + + return SharedPtr(); +} + +void Peer::sendHELLO(void *tPtr,const InetAddress &localAddr,const InetAddress &atAddress,uint64_t now,unsigned int counter) { Packet outp(_id.address(),RR->identity.address(),Packet::VERB_HELLO); + outp.append((unsigned char)ZT_PROTO_VERSION); outp.append((unsigned char)ZEROTIER_ONE_VERSION_MAJOR); outp.append((unsigned char)ZEROTIER_ONE_VERSION_MINOR); @@ -206,353 +329,107 @@ void Peer::sendHELLO(const InetAddress &localAddr,const InetAddress &atAddress,u outp.append(now); RR->identity.serialize(outp,false); atAddress.serialize(outp); - outp.append((uint64_t)RR->topology->worldId()); - outp.append((uint64_t)RR->topology->worldTimestamp()); - outp.armor(_key,false); // HELLO is sent in the clear - RR->node->putPacket(localAddr,atAddress,outp.data(),outp.size(),ttl); -} + outp.append((uint64_t)RR->topology->planetWorldId()); + outp.append((uint64_t)RR->topology->planetWorldTimestamp()); -bool Peer::doPingAndKeepalive(uint64_t now,int inetAddressFamily) -{ - Path *p = (Path *)0; + const unsigned int startCryptedPortionAt = outp.size(); - if (inetAddressFamily != 0) { - p = _getBestPath(now,inetAddressFamily); - } else { - p = _getBestPath(now); + 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); } - if (p) { - if ((now - p->lastReceived()) >= ZT_PEER_DIRECT_PING_DELAY) { - //TRACE("PING %s(%s) after %llums/%llums send/receive inactivity",_id.address().toString().c_str(),p->address().toString().c_str(),now - p->lastSend(),now - p->lastReceived()); - sendHELLO(p->localAddress(),p->address(),now); - p->sent(now); - p->pinged(now); - } else if ( ((now - std::max(p->lastSend(),p->lastKeepalive())) >= ZT_NAT_KEEPALIVE_DELAY) && (!p->reliable()) ) { - //TRACE("NAT keepalive %s(%s) after %llums/%llums send/receive inactivity",_id.address().toString().c_str(),p->address().toString().c_str(),now - p->lastSend(),now - p->lastReceived()); - _natKeepaliveBuf += (uint32_t)((now * 0x9e3779b1) >> 1); // tumble this around to send constantly varying (meaningless) payloads - RR->node->putPacket(p->localAddress(),p->address(),&_natKeepaliveBuf,sizeof(_natKeepaliveBuf)); - p->sentKeepalive(now); - } else { - //TRACE("no PING or NAT keepalive: addr==%s reliable==%d %llums/%llums send/receive inactivity",p->address().toString().c_str(),(int)p->reliable(),now - p->lastSend(),now - p->lastReceived()); + const unsigned int corSizeAt = outp.size(); + outp.addSize(2); + RR->topology->appendCertificateOfRepresentation(outp); + outp.setAt(corSizeAt,(uint16_t)(outp.size() - (corSizeAt + 2))); + + outp.cryptField(_key,startCryptedPortionAt,outp.size() - startCryptedPortionAt); + + RR->node->expectReplyTo(outp.packetId()); + + if (atAddress) { + outp.armor(_key,false,counter); // false == don't encrypt full payload, but add MAC + RR->node->putPacket(tPtr,localAddr,atAddress,outp.data(),outp.size()); + } else { + RR->sw->send(tPtr,outp,false); // false == don't encrypt full payload, but add MAC + } +} + +void Peer::attemptToContactAt(void *tPtr,const InetAddress &localAddr,const InetAddress &atAddress,uint64_t now,bool sendFullHello,unsigned int counter) +{ + if ( (!sendFullHello) && (_vProto >= 5) && (!((_vMajor == 1)&&(_vMinor == 1)&&(_vRevision == 0))) ) { + Packet outp(_id.address(),RR->identity.address(),Packet::VERB_ECHO); + RR->node->expectReplyTo(outp.packetId()); + outp.armor(_key,true,counter); + RR->node->putPacket(tPtr,localAddr,atAddress,outp.data(),outp.size()); + } else { + sendHELLO(tPtr,localAddr,atAddress,now,counter); + } +} + +void Peer::tryMemorizedPath(void *tPtr,uint64_t now) +{ + if ((now - _lastTriedMemorizedPath) >= ZT_TRY_MEMORIZED_PATH_INTERVAL) { + _lastTriedMemorizedPath = now; + InetAddress mp; + if (RR->node->externalPathLookup(tPtr,_id.address(),-1,mp)) + attemptToContactAt(tPtr,InetAddress(),mp,now,true,0); + } +} + +bool Peer::doPingAndKeepalive(void *tPtr,uint64_t now,int inetAddressFamily) +{ + Mutex::Lock _l(_paths_m); + + if (inetAddressFamily < 0) { + uint64_t v6lr = 0; + if ( ((now - _v6Path.lr) < ZT_PEER_PATH_EXPIRATION) && (_v6Path.p) ) + v6lr = _v6Path.p->lastIn(); + uint64_t v4lr = 0; + if ( ((now - _v4Path.lr) < ZT_PEER_PATH_EXPIRATION) && (_v4Path.p) ) + v4lr = _v4Path.p->lastIn(); + + if (v6lr > v4lr) { + if ( ((now - _v6Path.lr) >= ZT_PEER_PING_PERIOD) || (_v6Path.p->needsHeartbeat(now)) ) { + attemptToContactAt(tPtr,_v6Path.p->localAddress(),_v6Path.p->address(),now,false,_v6Path.p->nextOutgoingCounter()); + _v6Path.p->sent(now); + return true; + } + } else if (v4lr) { + if ( ((now - _v4Path.lr) >= ZT_PEER_PING_PERIOD) || (_v4Path.p->needsHeartbeat(now)) ) { + attemptToContactAt(tPtr,_v4Path.p->localAddress(),_v4Path.p->address(),now,false,_v4Path.p->nextOutgoingCounter()); + _v4Path.p->sent(now); + return true; + } + } + } else { + if ( (inetAddressFamily == AF_INET) && ((now - _v4Path.lr) < ZT_PEER_PATH_EXPIRATION) ) { + if ( ((now - _v4Path.lr) >= ZT_PEER_PING_PERIOD) || (_v4Path.p->needsHeartbeat(now)) ) { + attemptToContactAt(tPtr,_v4Path.p->localAddress(),_v4Path.p->address(),now,false,_v4Path.p->nextOutgoingCounter()); + _v4Path.p->sent(now); + return true; + } + } else if ( (inetAddressFamily == AF_INET6) && ((now - _v6Path.lr) < ZT_PEER_PATH_EXPIRATION) ) { + if ( ((now - _v6Path.lr) >= ZT_PEER_PING_PERIOD) || (_v6Path.p->needsHeartbeat(now)) ) { + attemptToContactAt(tPtr,_v6Path.p->localAddress(),_v6Path.p->address(),now,false,_v6Path.p->nextOutgoingCounter()); + _v6Path.p->sent(now); + return true; + } } - return true; } return false; } -bool Peer::pushDirectPaths(const InetAddress &localAddr,const InetAddress &toAddress,uint64_t now,bool force,bool includePrivatePaths) -{ -#ifdef ZT_ENABLE_CLUSTER - // Cluster mode disables normal PUSH_DIRECT_PATHS in favor of cluster-based peer redirection - if (RR->cluster) - return false; -#endif - - if (!force) { - if ((now - _lastDirectPathPushSent) < ZT_DIRECT_PATH_PUSH_INTERVAL) - return false; - else _lastDirectPathPushSent = now; - } - - std::vector pathsToPush; - - std::vector dps(RR->node->directPaths()); - for(std::vector::const_iterator i(dps.begin());i!=dps.end();++i) { - if ((includePrivatePaths)||(i->ipScope() == InetAddress::IP_SCOPE_GLOBAL)) - 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.empty()) - return false; - -#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); - RR->node->putPacket(localAddr,toAddress,outp.data(),outp.size(),0); - } - } - - return true; -} - -bool Peer::resetWithinScope(InetAddress::IpScope scope,uint64_t now) -{ - unsigned int np = _numPaths; - unsigned int x = 0; - unsigned int y = 0; - while (x < np) { - if (_paths[x].address().ipScope() == scope) { - // Resetting a path means sending a HELLO and then forgetting it. If we - // get OK(HELLO) then it will be re-learned. - sendHELLO(_paths[x].localAddress(),_paths[x].address(),now); - } else { - _paths[y++] = _paths[x]; - } - ++x; - } - _numPaths = y; - return (y < np); -} - -void Peer::getBestActiveAddresses(uint64_t now,InetAddress &v4,InetAddress &v6) const -{ - uint64_t bestV4 = 0,bestV6 = 0; - for(unsigned int p=0,np=_numPaths;p= bestV4) { - bestV4 = lr; - v4 = _paths[p].address(); - } - } else if (_paths[p].address().isV6()) { - if (lr >= bestV6) { - bestV6 = lr; - v6 = _paths[p].address(); - } - } - } - } - } -} - -bool Peer::networkMembershipCertificatesAgree(uint64_t nwid,const CertificateOfMembership &com) const -{ - Mutex::Lock _l(_networkComs_m); - const _NetworkCom *ourCom = _networkComs.get(nwid); - if (ourCom) - return ourCom->com.agreesWith(com); - return false; -} - -bool Peer::validateAndSetNetworkMembershipCertificate(uint64_t nwid,const CertificateOfMembership &com) -{ - // Sanity checks - if ((!com)||(com.issuedTo() != _id.address())) - return false; - - // Return true if we already have this *exact* COM - { - Mutex::Lock _l(_networkComs_m); - _NetworkCom *ourCom = _networkComs.get(nwid); - if ((ourCom)&&(ourCom->com == com)) - return true; - } - - // Check signature, log and return if cert is invalid - if (com.signedBy() != Network::controllerFor(nwid)) { - TRACE("rejected network membership certificate for %.16llx signed by %s: signer not a controller of this network",(unsigned long long)nwid,com.signedBy().toString().c_str()); - return false; // invalid signer - } - - if (com.signedBy() == RR->identity.address()) { - - // We are the controller: RR->identity.address() == controller() == cert.signedBy() - // So, verify that we signed th cert ourself - if (!com.verify(RR->identity)) { - TRACE("rejected network membership certificate for %.16llx self signed by %s: signature check failed",(unsigned long long)nwid,com.signedBy().toString().c_str()); - return false; // invalid signature - } - - } else { - - SharedPtr signer(RR->topology->getPeer(com.signedBy())); - - if (!signer) { - // This would be rather odd, since this is our controller... could happen - // if we get packets before we've gotten config. - RR->sw->requestWhois(com.signedBy()); - return false; // signer unknown - } - - if (!com.verify(signer->identity())) { - TRACE("rejected network membership certificate for %.16llx signed by %s: signature check failed",(unsigned long long)nwid,com.signedBy().toString().c_str()); - return false; // invalid signature - } - } - - // If we made it past all those checks, add or update cert in our cert info store - { - Mutex::Lock _l(_networkComs_m); - _networkComs.set(nwid,_NetworkCom(RR->node->now(),com)); - } - - return true; -} - -bool Peer::needsOurNetworkMembershipCertificate(uint64_t nwid,uint64_t now,bool updateLastPushedTime) -{ - Mutex::Lock _l(_networkComs_m); - uint64_t &lastPushed = _lastPushedComs[nwid]; - const uint64_t tmp = lastPushed; - if (updateLastPushedTime) - lastPushed = now; - return ((now - tmp) >= (ZT_NETWORK_AUTOCONF_DELAY / 3)); -} - -void Peer::clean(uint64_t now) -{ - { - unsigned int np = _numPaths; - unsigned int x = 0; - unsigned int y = 0; - while (x < np) { - if (_paths[x].active(now)) - _paths[y++] = _paths[x]; - ++x; - } - _numPaths = y; - } - - { - Mutex::Lock _l(_networkComs_m); - { - uint64_t *k = (uint64_t *)0; - _NetworkCom *v = (_NetworkCom *)0; - Hashtable< uint64_t,_NetworkCom >::Iterator i(_networkComs); - while (i.next(k,v)) { - if ( (!RR->node->belongsToNetwork(*k)) && ((now - v->ts) >= ZT_PEER_NETWORK_COM_EXPIRATION) ) - _networkComs.erase(*k); - } - } - { - uint64_t *k = (uint64_t *)0; - uint64_t *v = (uint64_t *)0; - Hashtable< uint64_t,uint64_t >::Iterator i(_lastPushedComs); - while (i.next(k,v)) { - if ((now - *v) > (ZT_NETWORK_AUTOCONF_DELAY * 2)) - _lastPushedComs.erase(*k); - } - } - } -} - -void Peer::_doDeadPathDetection(Path &p,const uint64_t now) -{ - /* Dead path detection: if we have sent something to this peer and have not - * yet received a reply, double check this path. The majority of outbound - * packets including Ethernet frames do generate some kind of reply either - * immediately or at some point in the near future. This will occasionally - * (every NO_ANSWER_TIMEOUT ms) check paths unnecessarily if traffic that - * does not generate a response is being sent such as multicast announcements - * or frames belonging to unidirectional UDP protocols, but the cost is very - * tiny and the benefit in reliability is very large. This takes care of many - * failure modes including crap NATs that forget links and spurious changes - * to physical network topology that cannot be otherwise detected. - * - * Each time we do this we increment a probation counter in the path. This - * counter is reset on any packet receive over this path. If it reaches the - * MAX_PROBATION threshold the path is considred dead. */ - - if ( - (p.lastSend() > p.lastReceived()) && - ((p.lastSend() - p.lastReceived()) >= ZT_PEER_DEAD_PATH_DETECTION_NO_ANSWER_TIMEOUT) && - ((now - p.lastPing()) >= ZT_PEER_DEAD_PATH_DETECTION_NO_ANSWER_TIMEOUT) && - (!p.isClusterSuboptimal()) && - (!RR->topology->amRoot()) - ) { - TRACE("%s(%s) does not seem to be answering in a timely manner, checking if dead (probation == %u)",_id.address().toString().c_str(),p.address().toString().c_str(),p.probation()); - - if ( (_vProto >= 5) && ( !((_vMajor == 1)&&(_vMinor == 1)&&(_vRevision == 0)) ) ) { - Packet outp(_id.address(),RR->identity.address(),Packet::VERB_ECHO); - outp.armor(_key,true); - p.send(RR,outp.data(),outp.size(),now); - p.pinged(now); - } else { - sendHELLO(p.localAddress(),p.address(),now); - p.sent(now); - p.pinged(now); - } - - p.increaseProbation(); - } -} - -Path *Peer::_getBestPath(const uint64_t now) -{ - Path *bestPath = (Path *)0; - uint64_t bestPathScore = 0; - for(unsigned int i=0;i<_numPaths;++i) { - const uint64_t score = _paths[i].score(); - if ((score >= bestPathScore)&&(_paths[i].active(now))) { - bestPathScore = score; - bestPath = &(_paths[i]); - } - } - if (bestPath) - _doDeadPathDetection(*bestPath,now); - return bestPath; -} - -Path *Peer::_getBestPath(const uint64_t now,int inetAddressFamily) -{ - Path *bestPath = (Path *)0; - uint64_t bestPathScore = 0; - for(unsigned int i=0;i<_numPaths;++i) { - const uint64_t score = _paths[i].score(); - if (((int)_paths[i].address().ss_family == inetAddressFamily)&&(score >= bestPathScore)&&(_paths[i].active(now))) { - bestPathScore = score; - bestPath = &(_paths[i]); - } - } - if (bestPath) - _doDeadPathDetection(*bestPath,now); - return bestPath; -} - } // namespace ZeroTier diff --git a/node/Peer.hpp b/node/Peer.hpp index 445535c..b9d8540 100644 --- a/node/Peer.hpp +++ b/node/Peer.hpp @@ -31,7 +31,6 @@ #include "../include/ZeroTierOne.h" #include "RuntimeEnvironment.hpp" -#include "CertificateOfMembership.hpp" #include "Path.hpp" #include "Address.hpp" #include "Utils.hpp" @@ -44,10 +43,6 @@ #include "Mutex.hpp" #include "NonCopyable.hpp" -// Very rough computed estimate: (8 + 256 + 80 + (16 * 64) + (128 * 256) + (128 * 16)) -// 1048576 provides tons of headroom -- overflow would just cause peer not to be persisted -#define ZT_PEER_SUGGESTED_SERIALIZATION_BUFFER_SIZE 1048576 - namespace ZeroTier { /** @@ -73,18 +68,6 @@ public: */ Peer(const RuntimeEnvironment *renv,const Identity &myIdentity,const Identity &peerIdentity); - /** - * @return Time peer record was last used in any way - */ - inline uint64_t lastUsed() const throw() { return _lastUsed; } - - /** - * Log a use of this peer record (done by Topology when peers are looked up) - * - * @param now New time of last use - */ - inline void use(uint64_t now) throw() { _lastUsed = now; } - /** * @return This peer's ZT address (short for identity().address()) */ @@ -101,31 +84,24 @@ public: * This is called by the decode pipe when a packet is proven to be authentic * and appears to be valid. * - * @param RR Runtime environment - * @param localAddr Local address - * @param remoteAddr Internet address of sender + * @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( - const InetAddress &localAddr, - const InetAddress &remoteAddr, - unsigned int hops, - uint64_t packetId, - Packet::Verb verb, - uint64_t inRePacketId = 0, - Packet::Verb inReVerb = Packet::VERB_NOP); - - /** - * Get the current best direct path to this peer - * - * @param now Current time - * @return Best path or NULL if there are no active direct paths - */ - inline Path *getBestPath(uint64_t now) { return _getBestPath(now); } + 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 @@ -134,116 +110,164 @@ public: */ inline bool hasActivePathTo(uint64_t now,const InetAddress &addr) const { - for(unsigned int p=0;p<_numPaths;++p) { - if ((_paths[p].active(now))&&(_paths[p].address() == addr)) - return true; - } - return false; + Mutex::Lock _l(_paths_m); + return ( ((addr.ss_family == AF_INET)&&(_v4Path.p)&&(_v4Path.p->address() == addr)&&(_v4Path.p->alive(now))) || ((addr.ss_family == AF_INET6)&&(_v6Path.p)&&(_v6Path.p->address() == addr)&&(_v6Path.p->alive(now))) ); } /** - * Set all paths in the same ss_family that are not this one to cluster suboptimal - * - * Addresses in other families are not affected. - * - * @param addr Address to make exclusive - */ - inline void setClusterOptimalPathForAddressFamily(const InetAddress &addr) - { - for(unsigned int p=0;p<_numPaths;++p) { - if (_paths[p].address().ss_family == addr.ss_family) { - _paths[p].setClusterSuboptimal(_paths[p].address() != addr); - } - } - } - - /** - * Send via best path + * 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 - * @return Path used on success or NULL on failure + * @param force If true, send even if path is not alive + * @return True if we actually sent something */ - inline Path *send(const void *data,unsigned int len,uint64_t now) - { - Path *const bestPath = getBestPath(now); - if (bestPath) { - if (bestPath->send(RR,data,len,now)) - return bestPath; - } - return (Path *)0; - } + bool sendDirect(void *tPtr,const void *data,unsigned int len,uint64_t now,bool force); + + /** + * Get the best current direct path + * + * This does not check Path::alive(), but does return the most recently + * active path and does check expiration (which is a longer timeout). + * + * @param now Current time + * @param includeExpired If true, include even expired paths + * @return Best current path or NULL if none + */ + SharedPtr getBestPath(uint64_t now,bool includeExpired); /** * Send a HELLO to this peer at a specified physical address * - * This does not update any statistics. It's used to send initial HELLOs - * for NAT traversal and path verification. + * 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 ttl Desired IP TTL (default: 0 to leave alone) + * @param counter Outgoing packet counter */ - void sendHELLO(const InetAddress &localAddr,const InetAddress &atAddress,uint64_t now,unsigned int ttl = 0); + 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 0 to simply pick current best ignoring family - * @return True if at least one direct path seems alive + * @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(uint64_t now,int inetAddressFamily); + bool doPingAndKeepalive(void *tPtr,uint64_t now,int inetAddressFamily); /** - * Push direct paths back to self if we haven't done so in the configured timeout + * Reset paths within a given IP scope and address family * - * @param localAddr Local address - * @param toAddress Remote address to send push to (usually from path) + * 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 - * @param force If true, push regardless of rate limit - * @param includePrivatePaths If true, include local interface address paths (should only be done to peers with a trust relationship) - * @return True if something was actually sent */ - bool pushDirectPaths(const InetAddress &localAddr,const InetAddress &toAddress,uint64_t now,bool force,bool includePrivatePaths); + inline void resetWithinScope(void *tPtr,InetAddress::IpScope scope,int inetAddressFamily,uint64_t now) + { + Mutex::Lock _l(_paths_m); + if ((inetAddressFamily == AF_INET)&&(_v4Path.lr)&&(_v4Path.p->address().ipScope() == scope)) { + attemptToContactAt(tPtr,_v4Path.p->localAddress(),_v4Path.p->address(),now,false,_v4Path.p->nextOutgoingCounter()); + _v4Path.p->sent(now); + _v4Path.lr = 0; // path will not be used unless it speaks again + } else if ((inetAddressFamily == AF_INET6)&&(_v6Path.lr)&&(_v6Path.p->address().ipScope() == scope)) { + attemptToContactAt(tPtr,_v6Path.p->localAddress(),_v6Path.p->address(),now,false,_v6Path.p->nextOutgoingCounter()); + _v6Path.p->sent(now); + _v6Path.lr = 0; // path will not be used unless it speaks again + } + } /** - * @return All known direct paths to this peer (active or inactive) + * Indicate that the given address was provided by a cluster as a preferred destination + * + * @param addr Address cluster prefers that we use */ - inline std::vector paths() const + inline void setClusterPreferred(const InetAddress &addr) { - std::vector pp; - for(unsigned int p=0,np=_numPaths;palive(now))) + v4 = _v4Path.p->address(); + if (((now - _v6Path.lr) < ZT_PEER_PATH_EXPIRATION)&&(_v6Path.p->alive(now))) + v6 = _v6Path.p->address(); + } + + /** + * @param now Current time + * @return All known paths to this peer + */ + inline std::vector< SharedPtr > paths(const uint64_t now) const + { + std::vector< SharedPtr > pp; + Mutex::Lock _l(_paths_m); + if (((now - _v4Path.lr) < ZT_PEER_PATH_EXPIRATION)&&(_v4Path.p->alive(now))) + pp.push_back(_v4Path.p); + if (((now - _v6Path.lr) < ZT_PEER_PATH_EXPIRATION)&&(_v6Path.p->alive(now))) + pp.push_back(_v6Path.p); return pp; } /** * @return Time of last receive of anything, whether direct or relayed */ - inline uint64_t lastReceive() const throw() { return _lastReceive; } + inline uint64_t lastReceive() const { return _lastReceive; } /** - * @return Time of most recent unicast frame received + * @return True if we've heard from this peer in less than ZT_PEER_ACTIVITY_TIMEOUT */ - inline uint64_t lastUnicastFrame() const throw() { return _lastUnicastFrame; } - - /** - * @return Time of most recent multicast frame received - */ - inline uint64_t lastMulticastFrame() const throw() { return _lastMulticastFrame; } - - /** - * @return Time of most recent frame of any kind (unicast or multicast) - */ - inline uint64_t lastFrame() const throw() { return std::max(_lastUnicastFrame,_lastMulticastFrame); } + 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 activelyTransferringFrames(uint64_t now) const throw() { return ((now - lastFrame()) < ZT_PEER_ACTIVITY_TIMEOUT); } + inline uint64_t isActive(uint64_t now) const { return ((now - _lastNontrivialReceive) < ZT_PEER_ACTIVITY_TIMEOUT); } /** * @return Latency in milliseconds or 0 if unknown @@ -269,7 +293,7 @@ public: unsigned int l = _latency; if (!l) l = 0xffff; - return (l * (((unsigned int)tsr / (ZT_PEER_DIRECT_PING_DELAY + 1000)) + 1)); + return (l * (((unsigned int)tsr / (ZT_PEER_PING_PERIOD + 1000)) + 1)); } /** @@ -285,47 +309,22 @@ public: else _latency = std::min(l,(unsigned int)65535); } - /** - * @param now Current time - * @return True if this peer has at least one active direct path - */ - inline bool hasActiveDirectPath(uint64_t now) const - { - for(unsigned int p=0;p<_numPaths;++p) { - if (_paths[p].active(now)) - return true; - } - return false; - } - #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 hasClusterOptimalPath(uint64_t now) const + inline bool hasLocalClusterOptimalPath(uint64_t now) const { - for(unsigned int p=0,np=_numPaths;palive(now))&&(!_v4Path.localClusterSuboptimal)) || ((_v6Path.p)&&(_v6Path.p->alive(now))&&(!_v6Path.localClusterSuboptimal)) ); } #endif - /** - * Reset paths within a given scope - * - * @param scope IP scope of paths to reset - * @param now Current time - * @return True if at least one path was forgotten - */ - bool resetWithinScope(InetAddress::IpScope scope,uint64_t now); - /** * @return 256-bit secret symmetric encryption key */ - inline const unsigned char *key() const throw() { return _key; } + inline const unsigned char *key() const { return _key; } /** * Set the currently known remote version of this peer's client @@ -343,69 +342,22 @@ public: _vRevision = (uint16_t)vrev; } - inline unsigned int remoteVersionProtocol() const throw() { return _vProto; } - inline unsigned int remoteVersionMajor() const throw() { return _vMajor; } - inline unsigned int remoteVersionMinor() const throw() { return _vMinor; } - inline unsigned int remoteVersionRevision() const throw() { return _vRevision; } + 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 throw() { return ((_vMajor > 0)||(_vMinor > 0)||(_vRevision > 0)); } + inline bool remoteVersionKnown() const { return ((_vMajor > 0)||(_vMinor > 0)||(_vRevision > 0)); } /** - * 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 + * @return True if peer has received a trust established packet (e.g. common network membership) in the past ZT_TRUST_EXPIRATION ms */ - void getBestActiveAddresses(uint64_t now,InetAddress &v4,InetAddress &v6) const; + inline bool trustEstablished(const uint64_t now) const { return ((now - _lastTrustEstablishedPacketReceived) < ZT_TRUST_EXPIRATION); } /** - * Check network COM agreement with this peer - * - * @param nwid Network ID - * @param com Another certificate of membership - * @return True if supplied COM agrees with ours, false if not or if we don't have one + * Rate limit gate for VERB_PUSH_DIRECT_PATHS */ - bool networkMembershipCertificatesAgree(uint64_t nwid,const CertificateOfMembership &com) const; - - /** - * Check the validity of the COM and add/update if valid and new - * - * @param nwid Network ID - * @param com Externally supplied COM - */ - bool validateAndSetNetworkMembershipCertificate(uint64_t nwid,const CertificateOfMembership &com); - - /** - * @param nwid Network ID - * @param now Current time - * @param updateLastPushedTime If true, go ahead and update the last pushed time regardless of return value - * @return Whether or not this peer needs another COM push from us - */ - bool needsOurNetworkMembershipCertificate(uint64_t nwid,uint64_t now,bool updateLastPushedTime); - - /** - * Perform periodic cleaning operations - * - * @param now Current time - */ - void clean(uint64_t now); - - /** - * Update direct path push stats and return true if we should respond - * - * This is a circuit breaker to make VERB_PUSH_DIRECT_PATHS not particularly - * useful as a DDOS amplification attack vector. Otherwise a malicious peer - * could send loads of these and cause others to bombard arbitrary IPs with - * traffic. - * - * @param now Current time - * @return True if we should respond - */ - inline bool shouldRespondToDirectPathPush(const uint64_t now) + inline bool rateGatePushDirectPaths(const uint64_t now) { if ((now - _lastDirectPathPushReceive) <= ZT_PUSH_DIRECT_PATHS_CUTOFF_TIME) ++_directPathPushCutoffCount; @@ -415,187 +367,126 @@ public: } /** - * Find a common set of addresses by which two peers can link, if any - * - * @param a Peer A - * @param b Peer B - * @param now Current time - * @return Pair: B's address (to send to A), A's address (to send to B) + * Rate limit gate for VERB_NETWORK_CREDENTIALS */ - static inline std::pair findCommonGround(const Peer &a,const Peer &b,uint64_t now) + inline bool rateGateCredentialsReceived(const uint64_t now) { - std::pair v4,v6; - b.getBestActiveAddresses(now,v4.first,v6.first); - a.getBestActiveAddresses(now,v4.second,v6.second); - if ((v6.first)&&(v6.second)) // prefer IPv6 if both have it since NAT-t is (almost) unnecessary - return v6; - else if ((v4.first)&&(v4.second)) - return v4; - else return std::pair(); - } - - template - inline void serialize(Buffer &b) const - { - Mutex::Lock _l(_networkComs_m); - - const unsigned int recSizePos = b.size(); - b.addSize(4); // space for uint32_t field length - - b.append((uint16_t)1); // version of serialized Peer data - - _id.serialize(b,false); - - b.append((uint64_t)_lastUsed); - b.append((uint64_t)_lastReceive); - b.append((uint64_t)_lastUnicastFrame); - b.append((uint64_t)_lastMulticastFrame); - b.append((uint64_t)_lastAnnouncedTo); - b.append((uint64_t)_lastDirectPathPushSent); - b.append((uint64_t)_lastDirectPathPushReceive); - b.append((uint64_t)_lastPathSort); - b.append((uint16_t)_vProto); - b.append((uint16_t)_vMajor); - b.append((uint16_t)_vMinor); - b.append((uint16_t)_vRevision); - b.append((uint32_t)_latency); - b.append((uint16_t)_directPathPushCutoffCount); - - b.append((uint16_t)_numPaths); - for(unsigned int i=0;i<_numPaths;++i) - _paths[i].serialize(b); - - b.append((uint32_t)_networkComs.size()); - { - uint64_t *k = (uint64_t *)0; - _NetworkCom *v = (_NetworkCom *)0; - Hashtable::Iterator i(const_cast(this)->_networkComs); - while (i.next(k,v)) { - b.append((uint64_t)*k); - b.append((uint64_t)v->ts); - v->com.serialize(b); - } - } - - b.append((uint32_t)_lastPushedComs.size()); - { - uint64_t *k = (uint64_t *)0; - uint64_t *v = (uint64_t *)0; - Hashtable::Iterator i(const_cast(this)->_lastPushedComs); - while (i.next(k,v)) { - b.append((uint64_t)*k); - b.append((uint64_t)*v); - } - } - - b.template setAt(recSizePos,(uint32_t)(b.size() - (recSizePos + 4))); // set size + if ((now - _lastCredentialsReceived) <= ZT_PEER_CREDENTIALS_CUTOFF_TIME) + ++_credentialsCutoffCount; + else _credentialsCutoffCount = 0; + _lastCredentialsReceived = now; + return (_directPathPushCutoffCount < ZT_PEER_CREDEITIALS_CUTOFF_LIMIT); } /** - * Create a new Peer from a serialized instance - * - * @param renv Runtime environment - * @param myIdentity This node's identity - * @param b Buffer containing serialized Peer data - * @param p Pointer to current position in buffer, will be updated in place as buffer is read (value/result) - * @return New instance of Peer or NULL if serialized data was corrupt or otherwise invalid (may also throw an exception via Buffer) + * Rate limit gate for sending of ERROR_NEED_MEMBERSHIP_CERTIFICATE */ - template - static inline SharedPtr deserializeNew(const RuntimeEnvironment *renv,const Identity &myIdentity,const Buffer &b,unsigned int &p) + inline bool rateGateRequestCredentials(const uint64_t now) { - const unsigned int recSize = b.template at(p); p += 4; - if ((p + recSize) > b.size()) - return SharedPtr(); // size invalid - if (b.template at(p) != 1) - return SharedPtr(); // version mismatch - p += 2; - - Identity npid; - p += npid.deserialize(b,p); - if (!npid) - return SharedPtr(); - - SharedPtr np(new Peer(renv,myIdentity,npid)); - - np->_lastUsed = b.template at(p); p += 8; - np->_lastReceive = b.template at(p); p += 8; - np->_lastUnicastFrame = b.template at(p); p += 8; - np->_lastMulticastFrame = b.template at(p); p += 8; - np->_lastAnnouncedTo = b.template at(p); p += 8; - np->_lastDirectPathPushSent = b.template at(p); p += 8; - np->_lastDirectPathPushReceive = b.template at(p); p += 8; - np->_lastPathSort = b.template at(p); p += 8; - np->_vProto = b.template at(p); p += 2; - np->_vMajor = b.template at(p); p += 2; - np->_vMinor = b.template at(p); p += 2; - np->_vRevision = b.template at(p); p += 2; - np->_latency = b.template at(p); p += 4; - np->_directPathPushCutoffCount = b.template at(p); p += 2; - - const unsigned int numPaths = b.template at(p); p += 2; - for(unsigned int i=0;i_paths[np->_numPaths++].deserialize(b,p); - } else { - // Skip any paths beyond max, but still read stream - Path foo; - p += foo.deserialize(b,p); - } + if ((now - _lastCredentialRequestSent) >= ZT_PEER_GENERAL_RATE_LIMIT) { + _lastCredentialRequestSent = now; + return true; } + return false; + } - const unsigned int numNetworkComs = b.template at(p); p += 4; - for(unsigned int i=0;i_networkComs[b.template at(p)]; p += 8; - c.ts = b.template at(p); p += 8; - p += c.com.deserialize(b,p); + /** + * 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; + } - const unsigned int numLastPushed = b.template at(p); p += 4; - for(unsigned int i=0;i(p); p += 8; - const uint64_t ts = b.template at(p); p += 8; - np->_lastPushedComs.set(nwid,ts); + /** + * 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; + } - return np; + /** + * 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: - void _doDeadPathDetection(Path &p,const uint64_t now); - Path *_getBestPath(const uint64_t now); - Path *_getBestPath(const uint64_t now,int inetAddressFamily); + struct _PeerPath + { +#ifdef ZT_ENABLE_CLUSTER + _PeerPath() : lr(0),p(),localClusterSuboptimal(false) {} +#else + _PeerPath() : lr(0),p() {} +#endif + uint64_t lr; // time of last valid ZeroTier packet + SharedPtr p; +#ifdef ZT_ENABLE_CLUSTER + bool localClusterSuboptimal; // true if our cluster has determined that we should not be serving this peer +#endif + }; - unsigned char _key[ZT_PEER_SECRET_KEY_LENGTH]; // computed with key agreement, not serialized + uint8_t _key[ZT_PEER_SECRET_KEY_LENGTH]; const RuntimeEnvironment *RR; - uint64_t _lastUsed; + uint64_t _lastReceive; // direct or indirect - uint64_t _lastUnicastFrame; - uint64_t _lastMulticastFrame; - uint64_t _lastAnnouncedTo; + uint64_t _lastNontrivialReceive; // frames, things like netconf, etc. + uint64_t _lastTriedMemorizedPath; uint64_t _lastDirectPathPushSent; uint64_t _lastDirectPathPushReceive; - uint64_t _lastPathSort; + uint64_t _lastCredentialRequestSent; + uint64_t _lastWhoisRequestReceived; + uint64_t _lastEchoRequestReceived; + uint64_t _lastComRequestReceived; + uint64_t _lastComRequestSent; + uint64_t _lastCredentialsReceived; + uint64_t _lastTrustEstablishedPacketReceived; + uint16_t _vProto; uint16_t _vMajor; uint16_t _vMinor; uint16_t _vRevision; + + InetAddress _v4ClusterPreferred; + InetAddress _v6ClusterPreferred; + + _PeerPath _v4Path; // IPv4 direct path + _PeerPath _v6Path; // IPv6 direct path + Mutex _paths_m; + Identity _id; - Path _paths[ZT_MAX_PEER_NETWORK_PATHS]; - unsigned int _numPaths; + unsigned int _latency; unsigned int _directPathPushCutoffCount; - - struct _NetworkCom - { - _NetworkCom() {} - _NetworkCom(uint64_t t,const CertificateOfMembership &c) : ts(t),com(c) {} - uint64_t ts; - CertificateOfMembership com; - }; - Hashtable _networkComs; - Hashtable _lastPushedComs; - Mutex _networkComs_m; + unsigned int _credentialsCutoffCount; AtomicCounter __refCount; }; diff --git a/node/Poly1305.cpp b/node/Poly1305.cpp index b78071f..13d4712 100644 --- a/node/Poly1305.cpp +++ b/node/Poly1305.cpp @@ -135,11 +135,12 @@ typedef struct poly1305_context { 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__)) +#if (defined(_MSC_VER) || defined(__GNUC__)) && (defined(__amd64) || defined(__amd64__) || defined(__x86_64) || defined(__x86_64__) || defined(__AMD64) || defined(__AMD64__) || defined(_M_X64)) ////////////////////////////////////////////////////////////////////////////// // 128-bit implementation for MSC and GCC from Poly1305-donna + #if defined(_MSC_VER) #include @@ -183,9 +184,9 @@ typedef struct poly1305_state_internal_t { 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) { +#if defined(ZT_NO_TYPE_PUNNING) || (__BYTE_ORDER != __LITTLE_ENDIAN) +static inline unsigned long long U8TO64(const unsigned char *p) +{ return (((unsigned long long)(p[0] & 0xff) ) | ((unsigned long long)(p[1] & 0xff) << 8) | @@ -196,10 +197,13 @@ U8TO64(const unsigned char *p) { ((unsigned long long)(p[6] & 0xff) << 48) | ((unsigned long long)(p[7] & 0xff) << 56)); } +#else +#define U8TO64(p) (*reinterpret_cast(p)) +#endif -/* 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) { +#if defined(ZT_NO_TYPE_PUNNING) || (__BYTE_ORDER != __LITTLE_ENDIAN) +static inline void U64TO8(unsigned char *p, unsigned long long v) +{ p[0] = (v ) & 0xff; p[1] = (v >> 8) & 0xff; p[2] = (v >> 16) & 0xff; @@ -209,6 +213,9 @@ U64TO8(unsigned char *p, unsigned long long v) { p[6] = (v >> 48) & 0xff; p[7] = (v >> 56) & 0xff; } +#else +#define U64TO8(p,v) ((*reinterpret_cast(p)) = (v)) +#endif static inline void poly1305_init(poly1305_context *ctx, const unsigned char key[32]) { diff --git a/node/README.md b/node/README.md index 01378c7..1728400 100644 --- a/node/README.md +++ b/node/README.md @@ -1,4 +1,4 @@ -ZeroTier Virtual Switch Core +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. diff --git a/node/Revocation.cpp b/node/Revocation.cpp new file mode 100644 index 0000000..bab5653 --- /dev/null +++ b/node/Revocation.cpp @@ -0,0 +1,46 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "Revocation.hpp" +#include "RuntimeEnvironment.hpp" +#include "Identity.hpp" +#include "Topology.hpp" +#include "Switch.hpp" +#include "Network.hpp" + +namespace ZeroTier { + +int Revocation::verify(const RuntimeEnvironment *RR,void *tPtr) const +{ + if ((!_signedBy)||(_signedBy != Network::controllerFor(_networkId))) + return -1; + const Identity id(RR->topology->getIdentity(tPtr,_signedBy)); + if (!id) { + RR->sw->requestWhois(tPtr,_signedBy); + return 1; + } + try { + Buffer tmp; + this->serialize(tmp,true); + return (id.verify(tmp.data(),tmp.size(),_signature) ? 0 : -1); + } catch ( ... ) { + return -1; + } +} + +} // namespace ZeroTier diff --git a/node/Revocation.hpp b/node/Revocation.hpp new file mode 100644 index 0000000..e5e013b --- /dev/null +++ b/node/Revocation.hpp @@ -0,0 +1,189 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ZT_REVOCATION_HPP +#define ZT_REVOCATION_HPP + +#include +#include +#include +#include + +#include "Constants.hpp" +#include "../include/ZeroTierOne.h" +#include "Credential.hpp" +#include "Address.hpp" +#include "C25519.hpp" +#include "Utils.hpp" +#include "Buffer.hpp" +#include "Identity.hpp" + +/** + * Flag: fast propagation via rumor mill algorithm + */ +#define ZT_REVOCATION_FLAG_FAST_PROPAGATE 0x1ULL + +namespace ZeroTier { + +class RuntimeEnvironment; + +/** + * Revocation certificate to instantaneously revoke a COM, capability, or tag + */ +class Revocation : public Credential +{ +public: + static inline Credential::Type credentialType() { return Credential::CREDENTIAL_TYPE_REVOCATION; } + + Revocation() + { + memset(this,0,sizeof(Revocation)); + } + + /** + * @param i ID (arbitrary for revocations, currently random) + * @param nwid Network ID + * @param cid Credential ID being revoked (0 for all or for COMs, which lack IDs) + * @param thr Revocation time threshold before which credentials will be revoked + * @param fl Flags + * @param tgt Target node whose credential(s) are being revoked + * @param ct Credential type being revoked + */ + Revocation(const uint32_t i,const uint64_t nwid,const uint32_t cid,const uint64_t thr,const uint64_t fl,const Address &tgt,const Credential::Type ct) : + _id(i), + _credentialId(cid), + _networkId(nwid), + _threshold(thr), + _flags(fl), + _target(tgt), + _signedBy(), + _type(ct) {} + + inline uint32_t id() const { return _id; } + inline uint32_t credentialId() const { return _credentialId; } + inline uint64_t networkId() const { return _networkId; } + inline uint64_t threshold() const { return _threshold; } + inline const Address &target() const { return _target; } + inline const Address &signer() const { return _signedBy; } + inline Credential::Type type() const { return _type; } + + inline bool fastPropagate() const { return ((_flags & ZT_REVOCATION_FLAG_FAST_PROPAGATE) != 0); } + + /** + * @param signer Signing identity, must have private key + * @return True if signature was successful + */ + inline bool sign(const Identity &signer) + { + if (signer.hasPrivate()) { + Buffer tmp; + _signedBy = signer.address(); + this->serialize(tmp,true); + _signature = signer.sign(tmp.data(),tmp.size()); + return true; + } + return false; + } + + /** + * Verify this revocation's signature + * + * @param RR Runtime environment to provide for peer lookup, etc. + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or chain + */ + int verify(const RuntimeEnvironment *RR,void *tPtr) const; + + template + inline void serialize(Buffer &b,const bool forSign = false) const + { + if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + + b.append((uint32_t)0); // 4 unused bytes, currently set to 0 + b.append(_id); + b.append(_networkId); + b.append((uint32_t)0); // 4 unused bytes, currently set to 0 + b.append(_credentialId); + b.append(_threshold); + b.append(_flags); + _target.appendTo(b); + _signedBy.appendTo(b); + b.append((uint8_t)_type); + + if (!forSign) { + b.append((uint8_t)1); // 1 == Ed25519 signature + b.append((uint16_t)ZT_C25519_SIGNATURE_LEN); + b.append(_signature.data,ZT_C25519_SIGNATURE_LEN); + } + + // This is the size of any additional fields, currently 0. + b.append((uint16_t)0); + + if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + } + + template + inline unsigned int deserialize(const Buffer &b,unsigned int startAt = 0) + { + memset(this,0,sizeof(Revocation)); + + unsigned int p = startAt; + + p += 4; // 4 bytes, currently unused + _id = b.template at(p); p += 4; + _networkId = b.template at(p); p += 8; + p += 4; // 4 bytes, currently unused + _credentialId = b.template at(p); p += 4; + _threshold = b.template at(p); p += 8; + _flags = b.template at(p); p += 8; + _target.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; + _signedBy.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; + _type = (Credential::Type)b[p++]; + + if (b[p++] == 1) { + if (b.template at(p) == ZT_C25519_SIGNATURE_LEN) { + p += 2; + memcpy(_signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); + p += ZT_C25519_SIGNATURE_LEN; + } else throw std::runtime_error("invalid signature"); + } else { + p += 2 + b.template at(p); + } + + p += 2 + b.template at(p); + if (p > b.size()) + throw std::runtime_error("extended field overflow"); + + return (p - startAt); + } + +private: + uint32_t _id; + uint32_t _credentialId; + uint64_t _networkId; + uint64_t _threshold; + uint64_t _flags; + Address _target; + Address _signedBy; + Credential::Type _type; + C25519::Signature _signature; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/RuntimeEnvironment.hpp b/node/RuntimeEnvironment.hpp index 1f52773..7ba1c98 100644 --- a/node/RuntimeEnvironment.hpp +++ b/node/RuntimeEnvironment.hpp @@ -35,7 +35,6 @@ class Multicaster; class NetworkController; class SelfAwareness; class Cluster; -class DeferredPackets; /** * Holds global state for an instance of ZeroTier::Node @@ -51,11 +50,9 @@ public: ,mc((Multicaster *)0) ,topology((Topology *)0) ,sa((SelfAwareness *)0) - ,dp((DeferredPackets *)0) #ifdef ZT_ENABLE_CLUSTER ,cluster((Cluster *)0) #endif - ,dpEnabled(0) { } @@ -82,15 +79,9 @@ public: Multicaster *mc; Topology *topology; SelfAwareness *sa; - DeferredPackets *dp; - #ifdef ZT_ENABLE_CLUSTER Cluster *cluster; #endif - - // This is set to >0 if background threads are waiting on deferred - // packets, otherwise 'dp' should not be used. - volatile int dpEnabled; }; } // namespace ZeroTier diff --git a/node/Salsa20.cpp b/node/Salsa20.cpp index 3aa19ac..1d4117e 100644 --- a/node/Salsa20.cpp +++ b/node/Salsa20.cpp @@ -66,65 +66,49 @@ static const _s20sseconsts _S20SSECONSTANTS; namespace ZeroTier { -void Salsa20::init(const void *key,unsigned int kbits,const void *iv) - throw() +void Salsa20::init(const void *key,const void *iv) { #ifdef ZT_SALSA20_SSE - const uint32_t *k = (const uint32_t *)key; - + const uint32_t *const k = (const uint32_t *)key; _state.i[0] = 0x61707865; + _state.i[1] = 0x3320646e; + _state.i[2] = 0x79622d32; _state.i[3] = 0x6b206574; - _state.i[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[6] = k[7]; + _state.i[7] = k[2]; _state.i[8] = 0; + _state.i[9] = k[6]; + _state.i[10] = k[1]; + _state.i[11] = ((const uint32_t *)iv)[1]; + _state.i[12] = k[5]; + _state.i[13] = k[0]; + _state.i[14] = ((const uint32_t *)iv)[0]; + _state.i[15] = k[4]; #else - const char *constants; - const uint8_t *k = (const uint8_t *)key; - + const char *const constants = "expand 32-byte k"; + const uint8_t *const k = (const uint8_t *)key; + _state.i[0] = U8TO32_LITTLE(constants + 0); _state.i[1] = U8TO32_LITTLE(k + 0); _state.i[2] = U8TO32_LITTLE(k + 4); _state.i[3] = U8TO32_LITTLE(k + 8); _state.i[4] = U8TO32_LITTLE(k + 12); - 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[11] = U8TO32_LITTLE(k + 16); + _state.i[12] = U8TO32_LITTLE(k + 20); + _state.i[13] = U8TO32_LITTLE(k + 24); + _state.i[14] = U8TO32_LITTLE(k + 28); _state.i[15] = U8TO32_LITTLE(constants + 12); - _state.i[0] = U8TO32_LITTLE(constants + 0); #endif } -void Salsa20::encrypt12(const void *in,void *out,unsigned int bytes) - throw() +void Salsa20::crypt12(const void *in,void *out,unsigned int bytes) { uint8_t tmp[64]; const uint8_t *m = (const uint8_t *)in; @@ -623,8 +607,7 @@ void Salsa20::encrypt12(const void *in,void *out,unsigned int bytes) } } -void Salsa20::encrypt20(const void *in,void *out,unsigned int bytes) - throw() +void Salsa20::crypt20(const void *in,void *out,unsigned int bytes) { uint8_t tmp[64]; const uint8_t *m = (const uint8_t *)in; diff --git a/node/Salsa20.hpp b/node/Salsa20.hpp index 7e4c1e5..5259260 100644 --- a/node/Salsa20.hpp +++ b/node/Salsa20.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "Constants.hpp" #include "Utils.hpp" @@ -30,76 +31,80 @@ namespace ZeroTier { class Salsa20 { public: - Salsa20() throw() {} - + Salsa20() {} ~Salsa20() { Utils::burn(&_state,sizeof(_state)); } /** - * @param key Key bits - * @param kbits Number of key bits: 128 or 256 (recommended) + * XOR d with s + * + * This is done efficiently using e.g. SSE if available. It's used when + * alternative Salsa20 implementations are used in Packet and is here + * since this is where all the SSE stuff is already included. + * + * @param d Destination to XOR + * @param s Source bytes to XOR with destination + * @param len Length of s and d + */ + static inline void memxor(uint8_t *d,const uint8_t *s,unsigned int len) + { +#ifdef ZT_SALSA20_SSE + while (len >= 16) { + _mm_storeu_si128(reinterpret_cast<__m128i *>(d),_mm_xor_si128(_mm_loadu_si128(reinterpret_cast<__m128i *>(d)),_mm_loadu_si128(reinterpret_cast(s)))); + s += 16; + d += 16; + len -= 16; + } +#else +#ifndef ZT_NO_TYPE_PUNNING + while (len >= 16) { + (*reinterpret_cast(d)) ^= (*reinterpret_cast(s)); + s += 8; + d += 8; + (*reinterpret_cast(d)) ^= (*reinterpret_cast(s)); + s += 8; + d += 8; + len -= 16; + } +#endif +#endif + while (len--) + *(d++) ^= *(s++); + } + + /** + * @param key 256-bit (32 byte) key * @param iv 64-bit initialization vector */ - Salsa20(const void *key,unsigned int kbits,const void *iv) - throw() + Salsa20(const void *key,const void *iv) { - init(key,kbits,iv); + init(key,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(); + void init(const void *key,const void *iv); /** - * Encrypt data using Salsa20/12 + * Encrypt/decrypt data using Salsa20/12 * * @param in Input data * @param out Output buffer * @param bytes Length of data */ - void encrypt12(const void *in,void *out,unsigned int bytes) - throw(); + void crypt12(const void *in,void *out,unsigned int bytes); /** - * Encrypt data using Salsa20/20 + * Encrypt/decrypt data using Salsa20/20 * * @param in Input data * @param out Output buffer * @param bytes Length of data */ - void encrypt20(const void *in,void *out,unsigned int bytes) - throw(); - - /** - * Decrypt data - * - * @param in Input data - * @param out Output buffer - * @param bytes Length of data - */ - inline void decrypt12(const void *in,void *out,unsigned int bytes) - throw() - { - encrypt12(in,out,bytes); - } - - /** - * Decrypt data - * - * @param in Input data - * @param out Output buffer - * @param bytes Length of data - */ - inline void decrypt20(const void *in,void *out,unsigned int bytes) - throw() - { - encrypt20(in,out,bytes); - } + void crypt20(const void *in,void *out,unsigned int bytes); private: union { diff --git a/node/SelfAwareness.cpp b/node/SelfAwareness.cpp index 8bed0c5..cba84cd 100644 --- a/node/SelfAwareness.cpp +++ b/node/SelfAwareness.cpp @@ -33,41 +33,35 @@ #include "Switch.hpp" // Entry timeout -- make it fairly long since this is just to prevent stale buildup -#define ZT_SELFAWARENESS_ENTRY_TIMEOUT 3600000 +#define ZT_SELFAWARENESS_ENTRY_TIMEOUT 600000 namespace ZeroTier { class _ResetWithinScope { public: - _ResetWithinScope(uint64_t now,InetAddress::IpScope scope) : + _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) - { - if (p->resetWithinScope(_scope,_now)) - peersReset.push_back(p); - } - - std::vector< SharedPtr > peersReset; + 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(32) + _phy(128) { } -SelfAwareness::~SelfAwareness() -{ -} - -void SelfAwareness::iam(const Address &reporter,const InetAddress &receivedOnLocalAddress,const InetAddress &reporterPhysicalAddress,const InetAddress &myPhysicalAddress,bool trusted,uint64_t now) +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(); @@ -79,9 +73,11 @@ void SelfAwareness::iam(const Address &reporter,const InetAddress &receivedOnLoc 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; - 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.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. @@ -96,23 +92,14 @@ void SelfAwareness::iam(const Address &reporter,const InetAddress &receivedOnLoc } } - // Reset all paths within this scope - _ResetWithinScope rset(now,(InetAddress::IpScope)scope); + // Reset all paths within this scope and address family + _ResetWithinScope rset(tPtr,now,myPhysicalAddress.ss_family,(InetAddress::IpScope)scope); RR->topology->eachPeer<_ResetWithinScope &>(rset); - - // Send a NOP to all peers for whom we forgot a path. This will cause direct - // links to be re-established if possible, possibly using a root server or some - // other relay. - for(std::vector< SharedPtr >::const_iterator p(rset.peersReset.begin());p!=rset.peersReset.end();++p) { - if ((*p)->activelyTransferringFrames(now)) { - Packet outp((*p)->address(),RR->identity.address(),Packet::VERB_NOP); - RR->sw->send(outp,true,0); - } - } } else { // Otherwise just update DB to use to determine external surface info entry.mySurface = myPhysicalAddress; entry.ts = now; + entry.trusted = trusted; } } @@ -133,49 +120,70 @@ std::vector SelfAwareness::getSymmetricNatPredictions() /* This is based on ideas and strategies found here: * https://tools.ietf.org/html/draft-takeda-symmetric-nat-traversal-00 * - * In short: a great many symmetric NATs allocate ports sequentially. - * This is common on enterprise and carrier grade NATs as well as consumer - * devices. This code generates a list of "you might try this" addresses by - * extrapolating likely port assignments from currently known external - * global IPv4 surfaces. These can then be included in a PUSH_DIRECT_PATHS - * message to another peer, causing it to possibly try these addresses and - * bust our local symmetric NAT. It works often enough to be worth the - * extra bit of code and does no harm in cases where it fails. */ + * 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. */ - // Gather unique surfaces indexed by local received-on address and flag - // us as behind a symmetric NAT if there is more than one. - std::map< InetAddress,std::set > surfaces; + std::map< uint32_t,std::pair > maxPortByIp; + InetAddress theOneTrueSurface; bool symmetric = false; { 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 ((e->mySurface.ss_family == AF_INET)&&(e->mySurface.ipScope() == InetAddress::IP_SCOPE_GLOBAL)) { - std::set &s = surfaces[k->receivedOnLocalAddress]; - s.insert(e->mySurface); - symmetric = symmetric||(s.size() > 1); + + { // 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 we appear to be symmetrically NATed, generate and return extrapolations - // of those surfaces. Since PUSH_DIRECT_PATHS is sent multiple times, we - // probabilistically generate extrapolations of anywhere from +1 to +5 to - // increase the odds that it will work "eventually". if (symmetric) { std::vector r; - for(std::map< InetAddress,std::set >::iterator si(surfaces.begin());si!=surfaces.end();++si) { - for(std::set::iterator i(si->second.begin());i!=si->second.end();++i) { - InetAddress ipp(*i); - unsigned int p = ipp.port() + 1 + ((unsigned int)RR->node->prng() & 3); - if (p >= 65535) - p -= 64510; // NATs seldom use ports <=1024 so wrap to 1025 - ipp.setPort(p); - if ((si->second.count(ipp) == 0)&&(std::find(r.begin(),r.end(),ipp) == r.end())) { - r.push_back(ipp); - } + 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; diff --git a/node/SelfAwareness.hpp b/node/SelfAwareness.hpp index 06c264a..c1db0c8 100644 --- a/node/SelfAwareness.hpp +++ b/node/SelfAwareness.hpp @@ -36,7 +36,6 @@ class SelfAwareness { public: SelfAwareness(const RuntimeEnvironment *renv); - ~SelfAwareness(); /** * Called when a trusted remote peer informs us of our external network address @@ -48,7 +47,7 @@ public: * @param trusted True if this peer is trusted as an authority to inform us of external address changes * @param now Current time */ - void iam(const Address &reporter,const InetAddress &receivedOnLocalAddress,const InetAddress &reporterPhysicalAddress,const InetAddress &myPhysicalAddress,bool trusted,uint64_t now); + 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 @@ -82,9 +81,10 @@ private: { InetAddress mySurface; uint64_t ts; + bool trusted; - PhySurfaceEntry() : mySurface(),ts(0) {} - PhySurfaceEntry(const InetAddress &a,const uint64_t t) : mySurface(a),ts(t) {} + PhySurfaceEntry() : mySurface(),ts(0),trusted(false) {} + PhySurfaceEntry(const InetAddress &a,const uint64_t t) : mySurface(a),ts(t),trusted(false) {} }; const RuntimeEnvironment *RR; diff --git a/node/SharedPtr.hpp b/node/SharedPtr.hpp index 3ff5ed1..1dd3b43 100644 --- a/node/SharedPtr.hpp +++ b/node/SharedPtr.hpp @@ -119,15 +119,39 @@ public: inline T *ptr() const throw() { return _ptr; } /** - * Set this pointer to null + * 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; } - _ptr = (T *)0; } inline bool operator==(const SharedPtr &sp) const throw() { return (_ptr == sp._ptr); } diff --git a/node/Switch.cpp b/node/Switch.cpp index bf3afe3..56299a9 100644 --- a/node/Switch.cpp +++ b/node/Switch.cpp @@ -64,37 +64,36 @@ Switch::Switch(const RuntimeEnvironment *renv) : { } -Switch::~Switch() -{ -} - -void Switch::onRemotePacket(const InetAddress &localAddr,const InetAddress &fromAddr,const void *data,unsigned int len) +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. */ - Address beaconAddr(reinterpret_cast(data) + 8,5); + const Address beaconAddr(reinterpret_cast(data) + 8,5); if (beaconAddr == RR->identity.address()) return; - if (!RR->node->shouldUsePathForZeroTierTraffic(localAddr,fromAddr)) + if (!RR->node->shouldUsePathForZeroTierTraffic(tPtr,beaconAddr,localAddr,fromAddr)) return; - SharedPtr peer(RR->topology->getPeer(beaconAddr)); + 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); - RR->node->putPacket(localAddr,fromAddr,outp.data(),outp.size()); + outp.armor(peer->key(),true,path->nextOutgoingCounter()); + path->send(RR,tPtr,outp.data(),outp.size(),now); } } - } else if (len > ZT_PROTO_MIN_FRAGMENT_LENGTH) { // min length check is important! + } 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 ---------------------------------------------------- @@ -102,25 +101,33 @@ void Switch::onRemotePacket(const InetAddress &localAddr,const InetAddress &from const Address destination(fragment.destination()); if (destination != RR->identity.address()) { - // Fragment is not for us, so try to relay it +#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(destination); - if ((!relayTo)||(!relayTo->send(fragment.data(),fragment.size(),now))) { + 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) { - RR->cluster->sendViaCluster(Address(),destination,fragment.data(),fragment.size(),false); + 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 root server - relayTo = RR->topology->getBestRoot(); + // Don't know peer or no direct path -- so relay via someone upstream + relayTo = RR->topology->getUpstreamPeer(); if (relayTo) - relayTo->send(fragment.data(),fragment.size(),now); + 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()); @@ -164,7 +171,7 @@ void Switch::onRemotePacket(const InetAddress &localAddr,const InetAddress &from for(unsigned int f=1;ffrag0.append(rq->frags[f - 1].payload(),rq->frags[f - 1].payloadLength()); - if (rq->frag0.tryDecode(RR,false)) { + 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 @@ -178,60 +185,100 @@ void Switch::onRemotePacket(const InetAddress &localAddr,const InetAddress &from } else if (len >= ZT_PROTO_MIN_PACKET_LENGTH) { // min length check is important! // Handle packet head ------------------------------------------------- - // See packet format in Packet.hpp to understand this - 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]) - ); const Address destination(reinterpret_cast(data) + 8,ZT_ADDRESS_LENGTH); const Address source(reinterpret_cast(data) + 13,ZT_ADDRESS_LENGTH); - // Catch this and toss it -- it would never work, but it could happen if we somehow - // mistakenly guessed an address we're bound to as a destination for another peer. - if (source == RR->identity.address()) - return; - //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); - // Packet is not for us, so try to relay it 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(destination); - if ((relayTo)&&((relayTo->send(packet.data(),packet.size(),now)))) { - Mutex::Lock _l(_lastUniteAttempt_m); - uint64_t &luts = _lastUniteAttempt[_LastUniteKey(source,destination)]; - if ((now - luts) >= ZT_MIN_UNITE_INTERVAL) { - luts = now; - unite(source,destination); + 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) { - bool shouldUnite; - { - Mutex::Lock _l(_lastUniteAttempt_m); - uint64_t &luts = _lastUniteAttempt[_LastUniteKey(source,destination)]; - shouldUnite = ((now - luts) >= ZT_MIN_UNITE_INTERVAL); - if (shouldUnite) - luts = now; - } - RR->cluster->sendViaCluster(source,destination,packet.data(),packet.size(),shouldUnite); + 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->getBestRoot(&source,1,true); + relayTo = RR->topology->getUpstreamPeer(&source,1,true); if (relayTo) - relayTo->send(packet.data(),packet.size(),now); + 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()); @@ -239,6 +286,17 @@ void Switch::onRemotePacket(const InetAddress &localAddr,const InetAddress &from } 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); @@ -248,7 +306,7 @@ void Switch::onRemotePacket(const InetAddress &localAddr,const InetAddress &from rq->timestamp = now; rq->packetId = packetId; - rq->frag0.init(data,len,localAddr,fromAddr,now); + rq->frag0.init(data,len,path,now); rq->totalFragments = 0; rq->haveFragments = 1; rq->complete = false; @@ -259,24 +317,24 @@ void Switch::onRemotePacket(const InetAddress &localAddr,const InetAddress &from // We have all fragments -- assemble and process full Packet //TRACE("packet %.16llx is complete, assembling and processing...",pid); - rq->frag0.init(data,len,localAddr,fromAddr,now); + 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,false)) { + 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,localAddr,fromAddr,now); + 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,localAddr,fromAddr,now); - if (!packet.tryDecode(RR,false)) { + 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; @@ -286,7 +344,7 @@ void Switch::onRemotePacket(const InetAddress &localAddr,const InetAddress &from rq = tmp; } rq->timestamp = now; - rq->packetId = packetId; + rq->packetId = packet.packetId(); rq->frag0 = packet; rq->totalFragments = 1; rq->haveFragments = 1; @@ -304,34 +362,22 @@ void Switch::onRemotePacket(const InetAddress &localAddr,const InetAddress &from } } -void Switch::onLocalEthernet(const SharedPtr &network,const MAC &from,const MAC &to,unsigned int etherType,unsigned int vlanId,const void *data,unsigned int len) +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; - // Sanity check -- bridge loop? OS problem? - if (to == network->mac()) - return; - - // Check to make sure this protocol is allowed on this network - if (!network->config().permitsEtherType(etherType)) { - TRACE("%.16llx: ignored tap: %s -> %s: ethertype %s not allowed on network %.16llx",network->id(),from.toString().c_str(),to.toString().c_str(),etherTypeName(etherType),(unsigned long long)network->id()); - return; - } - // Check if this packet is from someone other than the tap -- i.e. bridged in - bool fromBridged = false; - if (from != network->mac()) { + 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; } - fromBridged = true; } if (to.isMulticast()) { - // Destination is a multicast address (including broadcast) - MulticastGroup mg(to,0); + 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)) ) { @@ -344,7 +390,7 @@ void Switch::onLocalEthernet(const SharedPtr &network,const MAC &from,c * 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. */ - mg = MulticastGroup::deriveMulticastGroupForAddressResolution(InetAddress(((const unsigned char *)data) + 24,4,0)); + 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()); @@ -428,74 +474,92 @@ void Switch::onLocalEthernet(const SharedPtr &network,const MAC &from,c adv[42] = (checksum >> 8) & 0xff; adv[43] = checksum & 0xff; - RR->node->putFrame(network->id(),network->userPtr(),peerMac,from,ZT_ETHERTYPE_IPV6,0,adv,72); + 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(mg,RR->node->now()); + network->learnBridgedMulticastGroup(tPtr,multicastGroup,RR->node->now()); - //TRACE("%.16llx: MULTICAST %s -> %s %s %u",network->id(),from.toString().c_str(),mg.toString().c_str(),etherTypeName(etherType),len); + //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( - ((!network->config().isPublic())&&(network->config().com)) ? &(network->config().com) : (const CertificateOfMembership *)0, + tPtr, network->config().multicastLimit, RR->node->now(), network->id(), + network->config().disableCompression(), network->config().activeBridges(), - mg, + multicastGroup, (fromBridged) ? from : MAC(), etherType, data, len); - - return; - } - - if (to[0] == MAC::firstOctetForNetwork(network->id())) { + } 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(toZT)); - const bool includeCom = ( (network->config().isPrivate()) && (network->config().com) && ((!toPeer)||(toPeer->needsOurNetworkMembershipCertificate(network->id(),RR->node->now(),true))) ); - if ((fromBridged)||(includeCom)) { + 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()); - if (includeCom) { - outp.append((unsigned char)0x01); // 0x01 -- COM included - network->config().com.serialize(outp); - } else { - outp.append((unsigned char)0x00); - } + outp.append((unsigned char)0x00); to.appendTo(outp); from.appendTo(outp); outp.append((uint16_t)etherType); outp.append(data,len); - outp.compress(); - send(outp,true,network->id()); + 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); - outp.compress(); - send(outp,true,network->id()); + 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); - - return; - } - - { + } 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; @@ -529,122 +593,46 @@ void Switch::onLocalEthernet(const SharedPtr &network,const MAC &from,c } for(unsigned int b=0;b bridgePeer(RR->topology->getPeer(bridges[b])); - Packet outp(bridges[b],RR->identity.address(),Packet::VERB_EXT_FRAME); - outp.append(network->id()); - if ( (network->config().isPrivate()) && (network->config().com) && ((!bridgePeer)||(bridgePeer->needsOurNetworkMembershipCertificate(network->id(),RR->node->now(),true))) ) { - outp.append((unsigned char)0x01); // 0x01 -- COM included - network->config().com.serialize(outp); + if (network->filterOutgoingPacket(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 { - outp.append((unsigned char)0); + TRACE("%.16llx: %s -> %s %s packet not sent: filterOutgoingPacket() returned false",network->id(),from.toString().c_str(),to.toString().c_str(),etherTypeName(etherType)); } - to.appendTo(outp); - from.appendTo(outp); - outp.append((uint16_t)etherType); - outp.append(data,len); - outp.compress(); - send(outp,true,network->id()); } } } -void Switch::send(const Packet &packet,bool encrypt,uint64_t nwid) +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; } - //TRACE(">> %s to %s (%u bytes, encrypt==%d, nwid==%.16llx)",Packet::verbString(packet.verb()),packet.destination().toString().c_str(),packet.size(),(int)encrypt,nwid); - - if (!_trySend(packet,encrypt,nwid)) { + if (!_trySend(tPtr,packet,encrypt)) { Mutex::Lock _l(_txQueue_m); - _txQueue.push_back(TXQueueEntry(packet.destination(),RR->node->now(),packet,encrypt,nwid)); + _txQueue.push_back(TXQueueEntry(packet.destination(),RR->node->now(),packet,encrypt)); } } -bool Switch::unite(const Address &p1,const Address &p2) +void Switch::requestWhois(void *tPtr,const Address &addr) { - if ((p1 == RR->identity.address())||(p2 == RR->identity.address())) - return false; - SharedPtr p1p = RR->topology->getPeer(p1); - if (!p1p) - return false; - SharedPtr p2p = RR->topology->getPeer(p2); - if (!p2p) - return false; - - const uint64_t now = RR->node->now(); - - std::pair cg(Peer::findCommonGround(*p1p,*p2p,now)); - if ((!(cg.first))||(cg.first.ipScope() != cg.second.ipScope())) - return false; - - TRACE("unite: %s(%s) <> %s(%s)",p1.toString().c_str(),cg.second.toString().c_str(),p2.toString().c_str(),cg.first.toString().c_str()); - - /* Tell P1 where to find P2 and vice versa, sending the packets to P1 and - * P2 in randomized order in terms of which gets sent first. This is done - * since in a few cases NAT-t can be sensitive to slight timing differences - * in terms of when the two peers initiate. Normally this is accounted for - * by the nearly-simultaneous RENDEZVOUS kickoff from the relay, but - * given that relay are hosted on cloud providers this can in some - * cases have a few ms of latency between packet departures. By randomizing - * the order we make each attempted NAT-t favor one or the other going - * first, meaning if it doesn't succeed the first time it might the second - * and so forth. */ - unsigned int alt = (unsigned int)RR->node->prng() & 1; - unsigned int completed = alt + 2; - while (alt != completed) { - if ((alt & 1) == 0) { - // Tell p1 where to find p2. - Packet outp(p1,RR->identity.address(),Packet::VERB_RENDEZVOUS); - outp.append((unsigned char)0); - p2.appendTo(outp); - outp.append((uint16_t)cg.first.port()); - if (cg.first.isV6()) { - outp.append((unsigned char)16); - outp.append(cg.first.rawIpData(),16); - } else { - outp.append((unsigned char)4); - outp.append(cg.first.rawIpData(),4); - } - outp.armor(p1p->key(),true); - p1p->send(outp.data(),outp.size(),now); - } else { - // Tell p2 where to find p1. - Packet outp(p2,RR->identity.address(),Packet::VERB_RENDEZVOUS); - outp.append((unsigned char)0); - p1.appendTo(outp); - outp.append((uint16_t)cg.second.port()); - if (cg.second.isV6()) { - outp.append((unsigned char)16); - outp.append(cg.second.rawIpData(),16); - } else { - outp.append((unsigned char)4); - outp.append(cg.second.rawIpData(),4); - } - outp.armor(p2p->key(),true); - p2p->send(outp.data(),outp.size(),now); - } - ++alt; // counts up and also flips LSB +#ifdef ZT_TRACE + if (addr == RR->identity.address()) { + fprintf(stderr,"FATAL BUG: Switch::requestWhois() caught attempt to WHOIS self" ZT_EOL_S); + abort(); } +#endif - return true; -} - -void Switch::rendezvous(const SharedPtr &peer,const InetAddress &localAddr,const InetAddress &atAddr) -{ - TRACE("sending NAT-t message to %s(%s)",peer->address().toString().c_str(),atAddr.toString().c_str()); - const uint64_t now = RR->node->now(); - peer->sendHELLO(localAddr,atAddr,now,2); // first attempt: send low-TTL packet to 'open' local NAT - { - Mutex::Lock _l(_contactQueue_m); - _contactQueue.push_back(ContactQueueEntry(peer,now + ZT_NAT_T_TACTICAL_ESCALATION_DELAY,localAddr,atAddr)); - } -} - -void Switch::requestWhois(const Address &addr) -{ bool inserted = false; { Mutex::Lock _l(_outstandingWhoisRequests_m); @@ -657,10 +645,10 @@ void Switch::requestWhois(const Address &addr) } } if (inserted) - _sendWhoisRequest(addr,(const Address *)0,0); + _sendWhoisRequest(tPtr,addr,(const Address *)0,0); } -void Switch::doAnythingWaitingForPeer(const SharedPtr &peer) +void Switch::doAnythingWaitingForPeer(void *tPtr,const SharedPtr &peer) { { // cancel pending WHOIS since we now know this peer Mutex::Lock _l(_outstandingWhoisRequests_m); @@ -673,7 +661,7 @@ void Switch::doAnythingWaitingForPeer(const SharedPtr &peer) while (i) { RXQueueEntry *rq = &(_rxQueue[--i]); if ((rq->timestamp)&&(rq->complete)) { - if (rq->frag0.tryDecode(RR,false)) + if (rq->frag0.tryDecode(RR,tPtr)) rq->timestamp = 0; } } @@ -683,7 +671,7 @@ void Switch::doAnythingWaitingForPeer(const SharedPtr &peer) Mutex::Lock _l(_txQueue_m); for(std::list< TXQueueEntry >::iterator txi(_txQueue.begin());txi!=_txQueue.end();) { if (txi->dest == peer->address()) { - if (_trySend(txi->packet,txi->encrypt,txi->nwid)) + if (_trySend(tPtr,txi->packet,txi->encrypt)) _txQueue.erase(txi++); else ++txi; } else ++txi; @@ -691,46 +679,10 @@ void Switch::doAnythingWaitingForPeer(const SharedPtr &peer) } } -unsigned long Switch::doTimerTasks(uint64_t now) +unsigned long Switch::doTimerTasks(void *tPtr,uint64_t now) { unsigned long nextDelay = 0xffffffff; // ceiling delay, caller will cap to minimum - { // Iterate through NAT traversal strategies for entries in contact queue - Mutex::Lock _l(_contactQueue_m); - for(std::list::iterator qi(_contactQueue.begin());qi!=_contactQueue.end();) { - if (now >= qi->fireAtTime) { - if (!qi->peer->pushDirectPaths(qi->localAddr,qi->inaddr,now,true,false)) - qi->peer->sendHELLO(qi->localAddr,qi->inaddr,now); - _contactQueue.erase(qi++); - continue; - /* Old symmetric NAT buster code, obsoleted by port prediction alg in SelfAwareness but left around for now in case we revert - if (qi->strategyIteration == 0) { - // First strategy: send packet directly to destination - qi->peer->sendHELLO(qi->localAddr,qi->inaddr,now); - } else if (qi->strategyIteration <= 3) { - // Strategies 1-3: try escalating ports for symmetric NATs that remap sequentially - InetAddress tmpaddr(qi->inaddr); - int p = (int)qi->inaddr.port() + qi->strategyIteration; - if (p > 65535) - p -= 64511; - tmpaddr.setPort((unsigned int)p); - qi->peer->sendHELLO(qi->localAddr,tmpaddr,now); - } else { - // All strategies tried, expire entry - _contactQueue.erase(qi++); - continue; - } - ++qi->strategyIteration; - qi->fireAtTime = now + ZT_NAT_T_TACTICAL_ESCALATION_DELAY; - nextDelay = std::min(nextDelay,(unsigned long)ZT_NAT_T_TACTICAL_ESCALATION_DELAY); - */ - } else { - nextDelay = std::min(nextDelay,(unsigned long)(qi->fireAtTime - now)); - } - ++qi; // if qi was erased, loop will have continued before here - } - } - { // Retry outstanding WHOIS requests Mutex::Lock _l(_outstandingWhoisRequests_m); Hashtable< Address,WhoisRequest >::Iterator i(_outstandingWhoisRequests); @@ -744,9 +696,9 @@ unsigned long Switch::doTimerTasks(uint64_t now) _outstandingWhoisRequests.erase(*a); } else { r->lastSent = now; - r->peersConsulted[r->retries] = _sendWhoisRequest(*a,r->peersConsulted,r->retries); - ++r->retries; + 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 { @@ -758,7 +710,7 @@ unsigned long Switch::doTimerTasks(uint64_t now) { // 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(txi->packet,txi->encrypt,txi->nwid)) + 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()); @@ -781,106 +733,149 @@ unsigned long Switch::doTimerTasks(uint64_t now) return nextDelay; } -Address Switch::_sendWhoisRequest(const Address &addr,const Address *peersAlreadyConsulted,unsigned int numPeersAlreadyConsulted) +bool Switch::_shouldUnite(const uint64_t now,const Address &source,const Address &destination) { - SharedPtr root(RR->topology->getBestRoot(peersAlreadyConsulted,numPeersAlreadyConsulted,false)); - if (root) { - Packet outp(root->address(),RR->identity.address(),Packet::VERB_WHOIS); - addr.appendTo(outp); - outp.armor(root->key(),true); - if (root->send(outp.data(),outp.size(),RR->node->now())) - return root->address(); - } - return Address(); -} - -bool Switch::_trySend(const Packet &packet,bool encrypt,uint64_t nwid) -{ - SharedPtr peer(RR->topology->getPeer(packet.destination())); - - if (peer) { - const uint64_t now = RR->node->now(); - - SharedPtr network; - if (nwid) { - network = RR->node->network(nwid); - if ((!network)||(!network->hasConfig())) - return false; // we probably just left this network, let its packets die - } - - Path *viaPath = peer->getBestPath(now); - SharedPtr relay; - - if (!viaPath) { - if (network) { - unsigned int bestq = ~((unsigned int)0); // max unsigned int since quality is lower==better - unsigned int ptr = 0; - for(;;) { - const Address raddr(network->config().nextRelay(ptr)); - if (raddr) { - SharedPtr rp(RR->topology->getPeer(raddr)); - if (rp) { - const unsigned int q = rp->relayQuality(now); - if (q < bestq) { - bestq = q; - rp.swap(relay); - } - } - } else break; - } - } - - if (!relay) - relay = RR->topology->getBestRoot(); - - if ( (!relay) || (!(viaPath = relay->getBestPath(now))) ) - return false; - } - // viaPath will not be null if we make it here - - // Push possible direct paths to us if we are relaying - if (relay) { - peer->pushDirectPaths(viaPath->localAddress(),viaPath->address(),now,false,( (network)&&(network->isAllowed(peer)) )); - viaPath->sent(now); - } - - Packet tmp(packet); - - unsigned int chunkSize = std::min(tmp.size(),(unsigned int)ZT_UDP_DEFAULT_PAYLOAD_MTU); - tmp.setFragmented(chunkSize < tmp.size()); - - const uint64_t trustedPathId = RR->topology->getOutboundPathTrust(viaPath->address()); - if (trustedPathId) { - tmp.setTrusted(trustedPathId); - } else { - tmp.armor(peer->key(),encrypt); - } - - if (viaPath->send(RR,tmp.data(),chunkSize,now)) { - if (chunkSize < tmp.size()) { - // Too big for one packet, fragment the rest - unsigned int fragStart = chunkSize; - unsigned int remaining = tmp.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; - unsigned int totalFragments = fragsRemaining + 1; - - for(unsigned int fno=1;fnosend(RR,frag.data(),frag.size(),now); - fragStart += chunkSize; - remaining -= chunkSize; - } - } - - return true; - } - } else { - requestWhois(packet.destination()); + Mutex::Lock _l(_lastUniteAttempt_m); + uint64_t &ts = _lastUniteAttempt[_LastUniteKey(source,destination)]; + if ((now - ts) >= ZT_MIN_UNITE_INTERVAL) { + ts = now; + return true; } return false; } +Address Switch::_sendWhoisRequest(void *tPtr,const Address &addr,const Address *peersAlreadyConsulted,unsigned int numPeersAlreadyConsulted) +{ + SharedPtr upstream(RR->topology->getUpstreamPeer(peersAlreadyConsulted,numPeersAlreadyConsulted,false)); + if (upstream) { + Packet outp(upstream->address(),RR->identity.address(),Packet::VERB_WHOIS); + addr.appendTo(outp); + RR->node->expectReplyTo(outp.packetId()); + send(tPtr,outp,true); + } + return Address(); +} + +bool Switch::_trySend(void *tPtr,Packet &packet,bool encrypt) +{ + SharedPtr viaPath; + const uint64_t now = RR->node->now(); + const Address destination(packet.destination()); + +#ifdef ZT_ENABLE_CLUSTER + uint64_t clusterMostRecentTs = 0; + int clusterMostRecentMemberId = -1; + uint8_t clusterPeerSecret[ZT_PEER_SECRET_KEY_LENGTH]; + if (RR->cluster) + clusterMostRecentMemberId = RR->cluster->checkSendViaCluster(destination,clusterMostRecentTs,clusterPeerSecret); +#endif + + const SharedPtr peer(RR->topology->getPeer(tPtr,destination)); + if (peer) { + /* First get the best path, and if it's dead (and this is not a root) + * we attempt to re-activate that path but this packet will flow + * upstream. If the path comes back alive, it will be used in the future. + * For roots we don't do the alive check since roots are not required + * to send heartbeats "down" and because we have to at least try to + * go somewhere. */ + + viaPath = peer->getBestPath(now,false); + if ( (viaPath) && (!viaPath->alive(now)) && (!RR->topology->isUpstream(peer->identity())) ) { +#ifdef ZT_ENABLE_CLUSTER + if ((clusterMostRecentMemberId < 0)||(viaPath->lastIn() > clusterMostRecentTs)) { +#endif + if ((now - viaPath->lastOut()) > std::max((now - viaPath->lastIn()) * 4,(uint64_t)ZT_PATH_MIN_REACTIVATE_INTERVAL)) { + peer->attemptToContactAt(tPtr,viaPath->localAddress(),viaPath->address(),now,false,viaPath->nextOutgoingCounter()); + viaPath->sent(now); + } +#ifdef ZT_ENABLE_CLUSTER + } +#endif + viaPath.zero(); + } + +#ifdef ZT_ENABLE_CLUSTER + if (clusterMostRecentMemberId >= 0) { + if ((viaPath)&&(viaPath->lastIn() < clusterMostRecentTs)) + viaPath.zero(); + } else if (!viaPath) { +#else + if (!viaPath) { +#endif + peer->tryMemorizedPath(tPtr,now); // periodically attempt memorized or statically defined paths, if any are known + const SharedPtr relay(RR->topology->getUpstreamPeer()); + if ( (!relay) || (!(viaPath = relay->getBestPath(now,false))) ) { + if (!(viaPath = peer->getBestPath(now,true))) + return false; + } +#ifdef ZT_ENABLE_CLUSTER + } +#else + } +#endif + } else { +#ifdef ZT_ENABLE_CLUSTER + if (clusterMostRecentMemberId < 0) { +#else + requestWhois(tPtr,destination); + return false; // if we are not in cluster mode, there is no way we can send without knowing the peer directly +#endif +#ifdef ZT_ENABLE_CLUSTER + } +#endif + } + + unsigned int chunkSize = std::min(packet.size(),(unsigned int)ZT_UDP_DEFAULT_PAYLOAD_MTU); + packet.setFragmented(chunkSize < packet.size()); + +#ifdef ZT_ENABLE_CLUSTER + const uint64_t trustedPathId = (viaPath) ? RR->topology->getOutboundPathTrust(viaPath->address()) : 0; + if (trustedPathId) { + packet.setTrusted(trustedPathId); + } else { + packet.armor((clusterMostRecentMemberId >= 0) ? clusterPeerSecret : peer->key(),encrypt,(viaPath) ? viaPath->nextOutgoingCounter() : 0); + } +#else + const uint64_t trustedPathId = RR->topology->getOutboundPathTrust(viaPath->address()); + if (trustedPathId) { + packet.setTrusted(trustedPathId); + } else { + packet.armor(peer->key(),encrypt,viaPath->nextOutgoingCounter()); + } +#endif + +#ifdef ZT_ENABLE_CLUSTER + if ( ((viaPath)&&(viaPath->send(RR,tPtr,packet.data(),chunkSize,now))) || ((clusterMostRecentMemberId >= 0)&&(RR->cluster->sendViaCluster(clusterMostRecentMemberId,destination,packet.data(),chunkSize))) ) { +#else + if (viaPath->send(RR,tPtr,packet.data(),chunkSize,now)) { +#endif + if (chunkSize < packet.size()) { + // Too big for one packet, fragment the rest + unsigned int fragStart = chunkSize; + unsigned int remaining = packet.size() - chunkSize; + unsigned int fragsRemaining = (remaining / (ZT_UDP_DEFAULT_PAYLOAD_MTU - ZT_PROTO_MIN_FRAGMENT_LENGTH)); + if ((fragsRemaining * (ZT_UDP_DEFAULT_PAYLOAD_MTU - ZT_PROTO_MIN_FRAGMENT_LENGTH)) < remaining) + ++fragsRemaining; + const unsigned int totalFragments = fragsRemaining + 1; + + for(unsigned int fno=1;fnosend(RR,tPtr,frag.data(),frag.size(),now); + else if (clusterMostRecentMemberId >= 0) + RR->cluster->sendViaCluster(clusterMostRecentMemberId,destination,frag.data(),frag.size()); +#else + viaPath->send(RR,tPtr,frag.data(),frag.size(),now); +#endif + fragStart += chunkSize; + remaining -= chunkSize; + } + } + } + + return true; +} + } // namespace ZeroTier diff --git a/node/Switch.hpp b/node/Switch.hpp index ce4f00a..ff35093 100644 --- a/node/Switch.hpp +++ b/node/Switch.hpp @@ -55,21 +55,22 @@ class Switch : NonCopyable { public: Switch(const RuntimeEnvironment *renv); - ~Switch(); /** * 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(const InetAddress &localAddr,const InetAddress &fromAddr,const void *data,unsigned int len); + 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 @@ -78,7 +79,7 @@ public: * @param data Ethernet payload * @param len Frame length */ - void onLocalEthernet(const SharedPtr &network,const MAC &from,const MAC &to,unsigned int etherType,unsigned int vlanId,const void *data,unsigned int len); + 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) @@ -92,51 +93,29 @@ public: * Needless to say, the packet's source must be this node. Otherwise it * won't be encrypted right. (This is not used for relaying.) * - * The network ID should only be specified for frames and other actual - * network traffic. Other traffic such as controller requests and regular - * protocol messages should specify zero. - * - * @param packet Packet to send + * @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) - * @param nwid Related network ID or 0 if message is not in-network traffic */ - void send(const Packet &packet,bool encrypt,uint64_t nwid); - - /** - * Send RENDEZVOUS to two peers to permit them to directly connect - * - * This only works if both peers are known, with known working direct - * links to this peer. The best link for each peer is sent to the other. - * - * @param p1 One of two peers (order doesn't matter) - * @param p2 Second of pair - */ - bool unite(const Address &p1,const Address &p2); - - /** - * Attempt NAT traversal to peer at a given physical address - * - * @param peer Peer to contact - * @param localAddr Local interface address - * @param atAddr Address of peer - */ - void rendezvous(const SharedPtr &peer,const InetAddress &localAddr,const InetAddress &atAddr); + 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(const Address &addr); + 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(const SharedPtr &peer); + void doAnythingWaitingForPeer(void *tPtr,const SharedPtr &peer); /** * Perform retries and other periodic timer tasks @@ -144,14 +123,16 @@ public: * 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(uint64_t now); + unsigned long doTimerTasks(void *tPtr,uint64_t now); private: - Address _sendWhoisRequest(const Address &addr,const Address *peersAlreadyConsulted,unsigned int numPeersAlreadyConsulted); - bool _trySend(const Packet &packet,bool encrypt,uint64_t nwid); + 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; @@ -205,16 +186,14 @@ private: struct TXQueueEntry { TXQueueEntry() {} - TXQueueEntry(Address d,uint64_t ct,const Packet &p,bool enc,uint64_t nw) : + TXQueueEntry(Address d,uint64_t ct,const Packet &p,bool enc) : dest(d), creationTime(ct), - nwid(nw), packet(p), encrypt(enc) {} Address dest; uint64_t creationTime; - uint64_t nwid; Packet packet; // unencrypted/unMAC'd packet -- this is done at send time bool encrypt; }; @@ -241,26 +220,6 @@ private: }; Hashtable< _LastUniteKey,uint64_t > _lastUniteAttempt; // key is always sorted in ascending order, for set-like behavior Mutex _lastUniteAttempt_m; - - // Active attempts to contact remote peers, including state of multi-phase NAT traversal - struct ContactQueueEntry - { - ContactQueueEntry() {} - ContactQueueEntry(const SharedPtr &p,uint64_t ft,const InetAddress &laddr,const InetAddress &a) : - peer(p), - fireAtTime(ft), - inaddr(a), - localAddr(laddr), - strategyIteration(0) {} - - SharedPtr peer; - uint64_t fireAtTime; - InetAddress inaddr; - InetAddress localAddr; - unsigned int strategyIteration; - }; - std::list _contactQueue; - Mutex _contactQueue_m; }; } // namespace ZeroTier diff --git a/node/Tag.cpp b/node/Tag.cpp new file mode 100644 index 0000000..3f924da --- /dev/null +++ b/node/Tag.cpp @@ -0,0 +1,46 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "Tag.hpp" +#include "RuntimeEnvironment.hpp" +#include "Identity.hpp" +#include "Topology.hpp" +#include "Switch.hpp" +#include "Network.hpp" + +namespace ZeroTier { + +int Tag::verify(const RuntimeEnvironment *RR,void *tPtr) const +{ + if ((!_signedBy)||(_signedBy != Network::controllerFor(_networkId))) + return -1; + const Identity id(RR->topology->getIdentity(tPtr,_signedBy)); + if (!id) { + RR->sw->requestWhois(tPtr,_signedBy); + return 1; + } + try { + Buffer<(sizeof(Tag) * 2)> tmp; + this->serialize(tmp,true); + return (id.verify(tmp.data(),tmp.size(),_signature) ? 0 : -1); + } catch ( ... ) { + return -1; + } +} + +} // namespace ZeroTier diff --git a/node/Tag.hpp b/node/Tag.hpp new file mode 100644 index 0000000..1f7f683 --- /dev/null +++ b/node/Tag.hpp @@ -0,0 +1,202 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ZT_TAG_HPP +#define ZT_TAG_HPP + +#include +#include +#include +#include + +#include "Constants.hpp" +#include "Credential.hpp" +#include "C25519.hpp" +#include "Address.hpp" +#include "Identity.hpp" +#include "Buffer.hpp" + +namespace ZeroTier { + +class RuntimeEnvironment; + +/** + * A tag that can be associated with members and matched in rules + * + * Capabilities group rules, while tags group members subject to those + * rules. Tag values can be matched in rules, and tags relevant to a + * capability are presented along with it. + * + * E.g. a capability might be "can speak Samba/CIFS within your + * department." This cap might have a rule to allow TCP/137 but + * only if a given tag ID's value matches between two peers. The + * capability is what members can do, while the tag is who they are. + * Different departments might have tags with the same ID but different + * values. + * + * Unlike capabilities tags are signed only by the issuer and are never + * transferrable. + */ +class Tag : public Credential +{ +public: + static inline Credential::Type credentialType() { return Credential::CREDENTIAL_TYPE_TAG; } + + Tag() + { + memset(this,0,sizeof(Tag)); + } + + /** + * @param nwid Network ID + * @param ts Timestamp + * @param issuedTo Address to which this tag was issued + * @param id Tag ID + * @param value Tag value + */ + Tag(const uint64_t nwid,const uint64_t ts,const Address &issuedTo,const uint32_t id,const uint32_t value) : + _id(id), + _value(value), + _networkId(nwid), + _ts(ts), + _issuedTo(issuedTo), + _signedBy() + { + } + + inline uint32_t id() const { return _id; } + inline const uint32_t &value() const { return _value; } + inline uint64_t networkId() const { return _networkId; } + inline uint64_t timestamp() const { return _ts; } + inline const Address &issuedTo() const { return _issuedTo; } + inline const Address &signedBy() const { return _signedBy; } + + /** + * Sign this tag + * + * @param signer Signing identity, must have private key + * @return True if signature was successful + */ + inline bool sign(const Identity &signer) + { + if (signer.hasPrivate()) { + Buffer tmp; + _signedBy = signer.address(); + this->serialize(tmp,true); + _signature = signer.sign(tmp.data(),tmp.size()); + return true; + } + return false; + } + + /** + * Check this tag's signature + * + * @param RR Runtime environment to allow identity lookup for signedBy + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or tag + */ + int verify(const RuntimeEnvironment *RR,void *tPtr) const; + + template + inline void serialize(Buffer &b,const bool forSign = false) const + { + if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + + b.append(_networkId); + b.append(_ts); + b.append(_id); + b.append(_value); + + _issuedTo.appendTo(b); + _signedBy.appendTo(b); + if (!forSign) { + b.append((uint8_t)1); // 1 == Ed25519 + b.append((uint16_t)ZT_C25519_SIGNATURE_LEN); // length of signature + b.append(_signature.data,ZT_C25519_SIGNATURE_LEN); + } + + b.append((uint16_t)0); // length of additional fields, currently 0 + + if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + } + + template + inline unsigned int deserialize(const Buffer &b,unsigned int startAt = 0) + { + unsigned int p = startAt; + + memset(this,0,sizeof(Tag)); + + _networkId = b.template at(p); p += 8; + _ts = b.template at(p); p += 8; + _id = b.template at(p); p += 4; + + _value = b.template at(p); p += 4; + + _issuedTo.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; + _signedBy.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; + if (b[p++] == 1) { + if (b.template at(p) != ZT_C25519_SIGNATURE_LEN) + throw std::runtime_error("invalid signature length"); + p += 2; + memcpy(_signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); p += ZT_C25519_SIGNATURE_LEN; + } else { + p += 2 + b.template at(p); + } + + p += 2 + b.template at(p); + if (p > b.size()) + throw std::runtime_error("extended field overflow"); + + return (p - startAt); + } + + // Provides natural sort order by ID + inline bool operator<(const Tag &t) const { return (_id < t._id); } + + inline bool operator==(const Tag &t) const { return (memcmp(this,&t,sizeof(Tag)) == 0); } + inline bool operator!=(const Tag &t) const { return (memcmp(this,&t,sizeof(Tag)) != 0); } + + // For searching sorted arrays or lists of Tags by ID + struct IdComparePredicate + { + inline bool operator()(const Tag &a,const Tag &b) const { return (a.id() < b.id()); } + inline bool operator()(const uint32_t a,const Tag &b) const { return (a < b.id()); } + inline bool operator()(const Tag &a,const uint32_t b) const { return (a.id() < b); } + inline bool operator()(const Tag *a,const Tag *b) const { return (a->id() < b->id()); } + inline bool operator()(const Tag *a,const Tag &b) const { return (a->id() < b.id()); } + inline bool operator()(const Tag &a,const Tag *b) const { return (a.id() < b->id()); } + inline bool operator()(const uint32_t a,const Tag *b) const { return (a < b->id()); } + inline bool operator()(const Tag *a,const uint32_t b) const { return (a->id() < b); } + inline bool operator()(const uint32_t a,const uint32_t b) const { return (a < b); } + }; + +private: + uint32_t _id; + uint32_t _value; + uint64_t _networkId; + uint64_t _ts; + Address _issuedTo; + Address _signedBy; + C25519::Signature _signature; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Topology.cpp b/node/Topology.cpp index 6e96f2e..a1d3733 100644 --- a/node/Topology.cpp +++ b/node/Topology.cpp @@ -23,115 +23,62 @@ #include "Network.hpp" #include "NetworkConfig.hpp" #include "Buffer.hpp" +#include "Switch.hpp" namespace ZeroTier { -// 2015-11-16 -- The Fabulous Four (should have named them after Beatles!) -//#define ZT_DEFAULT_WORLD_LENGTH 494 -//static const unsigned char ZT_DEFAULT_WORLD[ZT_DEFAULT_WORLD_LENGTH] = {0x01,0x00,0x00,0x00,0x00,0x08,0xea,0xc9,0x0a,0x00,0x00,0x01,0x51,0x11,0x70,0xb2,0xfb,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,0x80,0x31,0xa4,0x65,0x95,0x45,0x06,0x1c,0xfb,0xc2,0x4e,0x5d,0xe7,0x0a,0x40,0x7a,0x97,0xce,0x36,0xa2,0x3d,0x05,0xca,0x87,0xc7,0x59,0x27,0x5c,0x8b,0x0d,0x4c,0xb4,0xbb,0x26,0x2f,0x77,0x17,0x5e,0xb7,0x4d,0xb8,0xd3,0xb4,0xe9,0x23,0x5d,0xcc,0xa2,0x71,0xa8,0xdf,0xf1,0x23,0xa3,0xb2,0x66,0x74,0xea,0xe5,0xdc,0x8d,0xef,0xd3,0x0a,0xa9,0xac,0xcb,0xda,0x93,0xbd,0x6c,0xcd,0x43,0x1d,0xa7,0x98,0x6a,0xde,0x70,0xc0,0xc6,0x1c,0xaf,0xf0,0xfd,0x7f,0x8a,0xb9,0x76,0x13,0xe1,0xde,0x4f,0xf3,0xd6,0x13,0x04,0x7e,0x19,0x87,0x6a,0xba,0x00,0x2a,0x6e,0x2b,0x23,0x18,0x93,0x0f,0x60,0xeb,0x09,0x7f,0x70,0xd0,0xf4,0xb0,0x28,0xb2,0xcd,0x6d,0x3d,0x0c,0x63,0xc0,0x14,0xb9,0x03,0x9f,0xf3,0x53,0x90,0xe4,0x11,0x81,0xf2,0x16,0xfb,0x2e,0x6f,0xa8,0xd9,0x5c,0x1e,0xe9,0x66,0x71,0x56,0x41,0x19,0x05,0xc3,0xdc,0xcf,0xea,0x78,0xd8,0xc6,0xdf,0xaf,0xba,0x68,0x81,0x70,0xb3,0xfa,0x00,0x01,0x04,0xc6,0xc7,0x61,0xdc,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,0x01,0x04,0x6b,0xbf,0x2e,0xd2,0x27,0x09,0x8a,0xcf,0x05,0x9f,0xe3,0x00,0x48,0x2f,0x6e,0xe5,0xdf,0xe9,0x02,0x31,0x9b,0x41,0x9d,0xe5,0xbd,0xc7,0x65,0x20,0x9c,0x0e,0xcd,0xa3,0x8c,0x4d,0x6e,0x4f,0xcf,0x0d,0x33,0x65,0x83,0x98,0xb4,0x52,0x7d,0xcd,0x22,0xf9,0x31,0x12,0xfb,0x9b,0xef,0xd0,0x2f,0xd7,0x8b,0xf7,0x26,0x1b,0x33,0x3f,0xc1,0x05,0xd1,0x92,0xa6,0x23,0xca,0x9e,0x50,0xfc,0x60,0xb3,0x74,0xa5,0x00,0x01,0x04,0xa2,0xf3,0x4d,0x6f,0x27,0x09,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,0x01,0x04,0x80,0xc7,0xc5,0xd9,0x27,0x09}; - -// 2015-11-20 -- Alice and Bob are live, and we're now IPv6 dual-stack! -//#define ZT_DEFAULT_WORLD_LENGTH 792 -//static const unsigned char ZT_DEFAULT_WORLD[ZT_DEFAULT_WORLD_LENGTH] = {0x01,0x00,0x00,0x00,0x00,0x08,0xea,0xc9,0x0a,0x00,0x00,0x01,0x51,0x26,0x6f,0x7c,0x8a,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,0xe8,0x0a,0xf5,0xbc,0xf8,0x3d,0x97,0xcd,0xc3,0xf8,0xe2,0x41,0x16,0x42,0x0f,0xc7,0x76,0x8e,0x07,0xf3,0x7e,0x9e,0x7d,0x1b,0xb3,0x23,0x21,0x79,0xce,0xb9,0xd0,0xcb,0xb5,0x94,0x7b,0x89,0x21,0x57,0x72,0xf6,0x70,0xa1,0xdd,0x67,0x38,0xcf,0x45,0x45,0xc2,0x8d,0x46,0xec,0x00,0x2c,0xe0,0x2a,0x63,0x3f,0x63,0x8d,0x33,0x08,0x51,0x07,0x77,0x81,0x5b,0x32,0x49,0xae,0x87,0x89,0xcf,0x31,0xaa,0x41,0xf1,0x52,0x97,0xdc,0xa2,0x55,0xe1,0x4a,0x6e,0x3c,0x04,0xf0,0x4f,0x8a,0x0e,0xe9,0xca,0xec,0x24,0x30,0x04,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,0x7e,0x19,0x87,0x6a,0xba,0x00,0x2a,0x6e,0x2b,0x23,0x18,0x93,0x0f,0x60,0xeb,0x09,0x7f,0x70,0xd0,0xf4,0xb0,0x28,0xb2,0xcd,0x6d,0x3d,0x0c,0x63,0xc0,0x14,0xb9,0x03,0x9f,0xf3,0x53,0x90,0xe4,0x11,0x81,0xf2,0x16,0xfb,0x2e,0x6f,0xa8,0xd9,0x5c,0x1e,0xe9,0x66,0x71,0x56,0x41,0x19,0x05,0xc3,0xdc,0xcf,0xea,0x78,0xd8,0xc6,0xdf,0xaf,0xba,0x68,0x81,0x70,0xb3,0xfa,0x00,0x01,0x04,0xc6,0xc7,0x61,0xdc,0x27,0x09,0x8a,0xcf,0x05,0x9f,0xe3,0x00,0x48,0x2f,0x6e,0xe5,0xdf,0xe9,0x02,0x31,0x9b,0x41,0x9d,0xe5,0xbd,0xc7,0x65,0x20,0x9c,0x0e,0xcd,0xa3,0x8c,0x4d,0x6e,0x4f,0xcf,0x0d,0x33,0x65,0x83,0x98,0xb4,0x52,0x7d,0xcd,0x22,0xf9,0x31,0x12,0xfb,0x9b,0xef,0xd0,0x2f,0xd7,0x8b,0xf7,0x26,0x1b,0x33,0x3f,0xc1,0x05,0xd1,0x92,0xa6,0x23,0xca,0x9e,0x50,0xfc,0x60,0xb3,0x74,0xa5,0x00,0x01,0x04,0xa2,0xf3,0x4d,0x6f,0x27,0x09}; - -// 2015-12-17 -- Old New York root is dead, old SF still alive -//#define ZT_DEFAULT_WORLD_LENGTH 732 -//static const unsigned char ZT_DEFAULT_WORLD[ZT_DEFAULT_WORLD_LENGTH] = {0x01,0x00,0x00,0x00,0x00,0x08,0xea,0xc9,0x0a,0x00,0x00,0x01,0x51,0xb1,0x7e,0x39,0x9d,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,0x8a,0xca,0xf2,0x3d,0x71,0x2e,0xc2,0x39,0x45,0x66,0xb3,0xe9,0x39,0x79,0xb1,0x55,0xc4,0xa9,0xfc,0xbc,0xfc,0x55,0xaf,0x8a,0x2f,0x38,0xc8,0xcd,0xe9,0x02,0x5b,0x86,0xa9,0x72,0xf7,0x16,0x00,0x35,0xb7,0x84,0xc9,0xfc,0xe4,0xfa,0x96,0x8b,0xf4,0x1e,0xba,0x60,0x9f,0x85,0x14,0xc2,0x07,0x4b,0xfd,0xd1,0x6c,0x19,0x69,0xd3,0xf9,0x09,0x9c,0x9d,0xe3,0xb9,0x8f,0x11,0x78,0x71,0xa7,0x4a,0x05,0xd8,0xcc,0x60,0xa2,0x06,0x66,0x9f,0x47,0xc2,0x71,0xb8,0x54,0x80,0x9c,0x45,0x16,0x10,0xa9,0xd0,0xbd,0xf7,0x03,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,0x7e,0x19,0x87,0x6a,0xba,0x00,0x2a,0x6e,0x2b,0x23,0x18,0x93,0x0f,0x60,0xeb,0x09,0x7f,0x70,0xd0,0xf4,0xb0,0x28,0xb2,0xcd,0x6d,0x3d,0x0c,0x63,0xc0,0x14,0xb9,0x03,0x9f,0xf3,0x53,0x90,0xe4,0x11,0x81,0xf2,0x16,0xfb,0x2e,0x6f,0xa8,0xd9,0x5c,0x1e,0xe9,0x66,0x71,0x56,0x41,0x19,0x05,0xc3,0xdc,0xcf,0xea,0x78,0xd8,0xc6,0xdf,0xaf,0xba,0x68,0x81,0x70,0xb3,0xfa,0x00,0x02,0x04,0xc6,0xc7,0x61,0xdc,0x27,0x09,0x06,0x26,0x04,0xa8,0x80,0x00,0x01,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0xc5,0xf0,0x01,0x27,0x09}; - -// 2016-01-13 -- Old San Francisco 1.0.1 root is dead, now we're just on Alice and Bob! +/* + * 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) : +Topology::Topology(const RuntimeEnvironment *renv,void *tPtr) : RR(renv), _trustedPathCount(0), _amRoot(false) { - std::string alls(RR->node->dataStoreGet("peers.save")); - const uint8_t *all = reinterpret_cast(alls.data()); - RR->node->dataStoreDelete("peers.save"); - - Buffer *deserializeBuf = new Buffer(); - unsigned int ptr = 0; - while ((ptr + 4) < alls.size()) { - try { - const unsigned int reclen = ( // each Peer serialized record is prefixed by a record length - ((((unsigned int)all[ptr]) & 0xff) << 24) | - ((((unsigned int)all[ptr + 1]) & 0xff) << 16) | - ((((unsigned int)all[ptr + 2]) & 0xff) << 8) | - (((unsigned int)all[ptr + 3]) & 0xff) - ); - unsigned int pos = 0; - deserializeBuf->copyFrom(all + ptr,reclen + 4); - SharedPtr p(Peer::deserializeNew(RR,RR->identity,*deserializeBuf,pos)); - ptr += pos; - if (!p) - break; // stop if invalid records - if (p->address() != RR->identity.address()) - _peers.set(p->address(),p); - } catch ( ... ) { - break; // stop if invalid records + 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); } - } - delete deserializeBuf; + addWorld(tPtr,cachedPlanet,false); + } catch ( ... ) {} - clean(RR->node->now()); - - std::string dsWorld(RR->node->dataStoreGet("world")); - World cachedWorld; - if (dsWorld.length() > 0) { - try { - Buffer dswtmp(dsWorld.data(),(unsigned int)dsWorld.length()); - cachedWorld.deserialize(dswtmp,0); - } catch ( ... ) { - cachedWorld = World(); // clear if cached world is invalid - } - } - World defaultWorld; + World defaultPlanet; { Buffer wtmp(ZT_DEFAULT_WORLD,ZT_DEFAULT_WORLD_LENGTH); - defaultWorld.deserialize(wtmp,0); // throws on error, which would indicate a bad static variable up top + defaultPlanet.deserialize(wtmp,0); // throws on error, which would indicate a bad static variable up top } - if (cachedWorld.shouldBeReplacedBy(defaultWorld,false)) { - _setWorld(defaultWorld); - if (dsWorld.length() > 0) - RR->node->dataStoreDelete("world"); - } else _setWorld(cachedWorld); + addWorld(tPtr,defaultPlanet,false); } -Topology::~Topology() -{ - Buffer *pbuf = 0; - try { - pbuf = new Buffer(); - std::string all; - - Address *a = (Address *)0; - SharedPtr *p = (SharedPtr *)0; - Hashtable< Address,SharedPtr >::Iterator i(_peers); - while (i.next(a,p)) { - if (std::find(_rootAddresses.begin(),_rootAddresses.end(),*a) == _rootAddresses.end()) { - pbuf->clear(); - try { - (*p)->serialize(*pbuf); - try { - all.append((const char *)pbuf->data(),pbuf->size()); - } catch ( ... ) { - return; // out of memory? just skip - } - } catch ( ... ) {} // peer too big? shouldn't happen, but it so skip - } - } - - RR->node->dataStorePut("peers.save",all,true); - - delete pbuf; - } catch ( ... ) { - delete pbuf; - } -} - -SharedPtr Topology::addPeer(const SharedPtr &peer) +SharedPtr Topology::addPeer(void *tPtr,const SharedPtr &peer) { #ifdef ZT_TRACE if ((!peer)||(peer->address() == RR->identity.address())) { @@ -144,20 +91,19 @@ SharedPtr Topology::addPeer(const SharedPtr &peer) SharedPtr np; { - Mutex::Lock _l(_lock); + Mutex::Lock _l(_peers_m); SharedPtr &hp = _peers[peer->address()]; if (!hp) hp = peer; np = hp; } - np->use(RR->node->now()); - saveIdentity(np->identity()); + saveIdentity(tPtr,np->identity()); return np; } -SharedPtr Topology::getPeer(const Address &zta) +SharedPtr Topology::getPeer(void *tPtr,const Address &zta) { if (zta == RR->identity.address()) { TRACE("BUG: ignored attempt to getPeer() for self, returned NULL"); @@ -165,115 +111,88 @@ SharedPtr Topology::getPeer(const Address &zta) } { - Mutex::Lock _l(_lock); + Mutex::Lock _l(_peers_m); const SharedPtr *const ap = _peers.get(zta); - if (ap) { - (*ap)->use(RR->node->now()); + if (ap) return *ap; - } } try { - Identity id(_getIdentity(zta)); + Identity id(_getIdentity(tPtr,zta)); if (id) { SharedPtr np(new Peer(RR,RR->identity,id)); { - Mutex::Lock _l(_lock); + Mutex::Lock _l(_peers_m); SharedPtr &ap = _peers[zta]; if (!ap) ap.swap(np); - ap->use(RR->node->now()); return ap; } } - } catch ( ... ) { - fprintf(stderr,"EXCEPTION in getPeer() part 2\n"); - abort(); - } // invalid identity on disk? + } catch ( ... ) {} // invalid identity on disk? return SharedPtr(); } -Identity Topology::getIdentity(const Address &zta) +Identity Topology::getIdentity(void *tPtr,const Address &zta) { - { - Mutex::Lock _l(_lock); + 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(zta); + return _getIdentity(tPtr,zta); } -void Topology::saveIdentity(const Identity &id) +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(p,id.toString(false),false); + RR->node->dataStorePut(tPtr,p,id.toString(false),false); } } -SharedPtr Topology::getBestRoot(const Address *avoid,unsigned int avoidCount,bool strictAvoid) +SharedPtr Topology::getUpstreamPeer(const Address *avoid,unsigned int avoidCount,bool strictAvoid) { const uint64_t now = RR->node->now(); - Mutex::Lock _l(_lock); + unsigned int bestQualityOverall = ~((unsigned int)0); + unsigned int bestQualityNotAvoid = ~((unsigned int)0); + const SharedPtr *bestOverall = (const SharedPtr *)0; + const SharedPtr *bestNotAvoid = (const SharedPtr *)0; - if (_amRoot) { - /* If I am a root server, the "best" root server is the one whose address - * is numerically greater than mine (with wrap at top of list). This - * causes packets searching for a route to pretty much literally - * circumnavigate the globe rather than bouncing between just two. */ + Mutex::Lock _l1(_peers_m); + Mutex::Lock _l2(_upstreams_m); - for(unsigned long p=0;p<_rootAddresses.size();++p) { - if (_rootAddresses[p] == RR->identity.address()) { - for(unsigned long q=1;q<_rootAddresses.size();++q) { - const SharedPtr *const nextsn = _peers.get(_rootAddresses[(p + q) % _rootAddresses.size()]); - if ((nextsn)&&((*nextsn)->hasActiveDirectPath(now))) { - (*nextsn)->use(now); - return *nextsn; - } - } - break; - } - } - - } else { - /* If I am not a root server, the best root server is the active one with - * the lowest quality score. (lower == better) */ - - unsigned int bestQualityOverall = ~((unsigned int)0); - unsigned int bestQualityNotAvoid = ~((unsigned int)0); - const SharedPtr *bestOverall = (const SharedPtr *)0; - const SharedPtr *bestNotAvoid = (const SharedPtr *)0; - - for(std::vector< SharedPtr >::const_iterator r(_rootPeers.begin());r!=_rootPeers.end();++r) { + 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()) { + if (avoid[i] == (*p)->address()) { avoiding = true; break; } } - const unsigned int q = (*r)->relayQuality(now); + const unsigned int q = (*p)->relayQuality(now); if (q <= bestQualityOverall) { bestQualityOverall = q; - bestOverall = &(*r); + bestOverall = &(*p); } if ((!avoiding)&&(q <= bestQualityNotAvoid)) { bestQualityNotAvoid = q; - bestNotAvoid = &(*r); + bestNotAvoid = &(*p); } } + } - if (bestNotAvoid) { - (*bestNotAvoid)->use(now); - return *bestNotAvoid; - } else if ((!strictAvoid)&&(bestOverall)) { - (*bestOverall)->use(now); - return *bestOverall; - } - + if (bestNotAvoid) { + return *bestNotAvoid; + } else if ((!strictAvoid)&&(bestOverall)) { + return *bestOverall; } return SharedPtr(); @@ -281,54 +200,226 @@ SharedPtr Topology::getBestRoot(const Address *avoid,unsigned int avoidCou bool Topology::isUpstream(const Identity &id) const { - if (isRoot(id)) + 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; - std::vector< SharedPtr > nws(RR->node->allNetworks()); - for(std::vector< SharedPtr >::const_iterator nw(nws.begin());nw!=nws.end();++nw) { - if ((*nw)->config().isRelay(id.address())) { + 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; } -bool Topology::worldUpdateIfValid(const World &newWorld) +ZT_PeerRole Topology::role(const Address &ztaddr) const { - Mutex::Lock _l(_lock); - if (_world.shouldBeReplacedBy(newWorld,true)) { - _setWorld(newWorld); - try { - Buffer dswtmp; - newWorld.serialize(dswtmp,false); - RR->node->dataStorePut("world",dswtmp.data(),dswtmp.size(),false); - } catch ( ... ) { - RR->node->dataStoreDelete("world"); + 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 _l(_lock); - Hashtable< Address,SharedPtr >::Iterator i(_peers); - Address *a = (Address *)0; - SharedPtr *p = (SharedPtr *)0; - while (i.next(a,p)) { - if (((now - (*p)->lastUsed()) >= ZT_PEER_IN_MEMORY_EXPIRATION)&&(std::find(_rootAddresses.begin(),_rootAddresses.end(),*a) == _rootAddresses.end())) { - _peers.erase(*a); - } else { - (*p)->clean(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(const Address &zta) +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(p)); + std::string ids(RR->node->dataStoreGet(tPtr,p)); if (ids.length() > 0) { try { return Identity(ids); @@ -337,28 +428,48 @@ Identity Topology::_getIdentity(const Address &zta) return Identity(); } -void Topology::_setWorld(const World &newWorld) +void Topology::_memoizeUpstreams(void *tPtr) { - // assumed _lock is locked (or in constructor) - _world = newWorld; + // assumes _upstreams_m and _peers_m are locked + _upstreamAddresses.clear(); _amRoot = false; - _rootAddresses.clear(); - _rootPeers.clear(); - for(std::vector::const_iterator r(_world.roots().begin());r!=_world.roots().end();++r) { - _rootAddresses.push_back(r->identity.address()); - if (r->identity.address() == RR->identity.address()) { + + for(std::vector::const_iterator i(_planet.roots().begin());i!=_planet.roots().end();++i) { + if (i->identity == RR->identity) { _amRoot = true; - } else { - SharedPtr *rp = _peers.get(r->identity.address()); - if (rp) { - _rootPeers.push_back(*rp); - } else { - SharedPtr newrp(new Peer(RR,RR->identity,r->identity)); - _peers.set(r->identity.address(),newrp); - _rootPeers.push_back(newrp); + } else if (std::find(_upstreamAddresses.begin(),_upstreamAddresses.end(),i->identity.address()) == _upstreamAddresses.end()) { + _upstreamAddresses.push_back(i->identity.address()); + SharedPtr &hp = _peers[i->identity.address()]; + if (!hp) { + hp = new Peer(RR,RR->identity,i->identity); + saveIdentity(tPtr,i->identity); } } } + + for(std::vector::const_iterator m(_moons.begin());m!=_moons.end();++m) { + for(std::vector::const_iterator i(m->roots().begin());i!=m->roots().end();++i) { + if (i->identity == RR->identity) { + _amRoot = true; + } else if (std::find(_upstreamAddresses.begin(),_upstreamAddresses.end(),i->identity.address()) == _upstreamAddresses.end()) { + _upstreamAddresses.push_back(i->identity.address()); + SharedPtr &hp = _peers[i->identity.address()]; + if (!hp) { + hp = new Peer(RR,RR->identity,i->identity); + saveIdentity(tPtr,i->identity); + } + } + } + } + + std::sort(_upstreamAddresses.begin(),_upstreamAddresses.end()); + + _cor.clear(); + for(std::vector
::const_iterator a(_upstreamAddresses.begin());a!=_upstreamAddresses.end();++a) { + if (!_cor.addRepresentative(*a)) + break; + } + _cor.sign(RR->identity,RR->node->now()); } } // namespace ZeroTier diff --git a/node/Topology.hpp b/node/Topology.hpp index 03c491e..d29c424 100644 --- a/node/Topology.hpp +++ b/node/Topology.hpp @@ -33,10 +33,12 @@ #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 { @@ -48,8 +50,7 @@ class RuntimeEnvironment; class Topology { public: - Topology(const RuntimeEnvironment *renv); - ~Topology(); + Topology(const RuntimeEnvironment *renv,void *tPtr); /** * Add a peer to database @@ -57,18 +58,20 @@ public: * 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(const SharedPtr &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(const Address &zta); + SharedPtr getPeer(void *tPtr,const Address &zta); /** * Get a peer only if it is presently in memory (no disk cache) @@ -82,20 +85,37 @@ public: */ inline SharedPtr getPeerNoCache(const Address &zta) { - Mutex::Lock _l(_lock); + 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(const Address &zta); + Identity getIdentity(void *tPtr,const Address &zta); /** * Cache an identity @@ -103,40 +123,27 @@ public: * 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(const Identity &id); + void saveIdentity(void *tPtr,const Identity &id); /** - * Get the current favorite root server + * Get the current best upstream peer * * @return Root server with lowest latency or NULL if none */ - inline SharedPtr getBestRoot() { return getBestRoot((const Address *)0,0,false); } + inline SharedPtr getUpstreamPeer() { return getUpstreamPeer((const Address *)0,0,false); } /** - * Get the best root server, avoiding root servers listed in an array - * - * This will get the best root server (lowest latency, etc.) but will - * try to avoid the listed root servers, only using them if no others - * are available. + * 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 getBestRoot(const Address *avoid,unsigned int avoidCount,bool strictAvoid); - - /** - * @param id Identity to check - * @return True if this is a designated root server in this world - */ - inline bool isRoot(const Identity &id) const - { - Mutex::Lock _l(_lock); - return (std::find(_rootAddresses.begin(),_rootAddresses.end(),id.address()) != _rootAddresses.end()); - } + SharedPtr getUpstreamPeer(const Address *avoid,unsigned int avoidCount,bool strictAvoid); /** * @param id Identity to check @@ -145,46 +152,150 @@ public: bool isUpstream(const Identity &id) const; /** - * @return Vector of root server addresses + * @param addr Address to check + * @return True if we should accept a world update from this address */ - inline std::vector
rootAddresses() const + 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(_lock); - return _rootAddresses; + 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 Current World (copy) + * @return Vector of active upstream addresses (including roots) */ - inline World world() const + inline std::vector
upstreamAddresses() const { - Mutex::Lock _l(_lock); - return _world; + Mutex::Lock _l(_upstreams_m); + return _upstreamAddresses; } /** - * @return Current world ID + * @return Current moons */ - inline uint64_t worldId() const + inline std::vector moons() const { - return _world.id(); // safe to read without lock, and used from within eachPeer() so don't lock + Mutex::Lock _l(_upstreams_m); + return _moons; } /** - * @return Current world timestamp + * @return Moon IDs we are waiting for from seeds */ - inline uint64_t worldTimestamp() const + inline std::vector moonsWanted() const { - return _world.timestamp(); // safe to read without lock, and used from within eachPeer() so don't lock + 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 newWorld Potential new world definition revision - * @return True if an update actually occurred + * @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 worldUpdateIfValid(const World &newWorld); + 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 @@ -198,12 +309,14 @@ public: inline unsigned long countActive(uint64_t now) const { unsigned long cnt = 0; - Mutex::Lock _l(_lock); + 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)); + const SharedPtr pp((*p)->getBestPath(now,false)); + if ((pp)&&(pp->alive(now))) + ++cnt; } return cnt; } @@ -211,20 +324,13 @@ public: /** * Apply a function or function object to all peers * - * Note: explicitly template this by reference if you want the object - * passed by reference instead of copied. - * - * Warning: be careful not to use features in these that call any other - * methods of Topology that may lock _lock, otherwise a recursive lock - * and deadlock or lock corruption may occur. - * * @param f Function to apply * @tparam F Function or function object type */ template inline void eachPeer(F f) { - Mutex::Lock _l(_lock); + Mutex::Lock _l(_peers_m); Hashtable< Address,SharedPtr >::Iterator i(_peers); Address *a = (Address *)0; SharedPtr *p = (SharedPtr *)0; @@ -244,14 +350,14 @@ public: */ inline std::vector< std::pair< Address,SharedPtr > > allPeers() const { - Mutex::Lock _l(_lock); + Mutex::Lock _l(_peers_m); return _peers.entries(); } /** - * @return True if I am a root server in the current World + * @return True if I am a root server in a planet or moon */ - inline bool amRoot() const throw() { return _amRoot; } + inline bool amRoot() const { return _amRoot; } /** * Get the outbound trusted path ID for a physical address, or 0 if none @@ -294,7 +400,7 @@ public: { if (count > ZT_MAX_TRUSTED_PATHS) count = ZT_MAX_TRUSTED_PATHS; - Mutex::Lock _l(_lock); + 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(const Address &zta); - void _setWorld(const World &newWorld); + 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; - World _world; - Hashtable< Address,SharedPtr > _peers; - std::vector< Address > _rootAddresses; - std::vector< SharedPtr > _rootPeers; - bool _amRoot; + Mutex _trustedPaths_m; - Mutex _lock; + 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 diff --git a/node/Utils.cpp b/node/Utils.cpp index 2d9515e..9ce1bf0 100644 --- a/node/Utils.cpp +++ b/node/Utils.cpp @@ -47,21 +47,14 @@ namespace ZeroTier { const char Utils::HEXCHARS[16] = { '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' }; -static void _Utils_doBurn(char *ptr,unsigned int len) +// 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) { - for(unsigned int i=0;i= sizeof(randomBuf)) { + if (cryptProvider == NULL) { + if (!CryptAcquireContextA(&cryptProvider,NULL,NULL,PROV_RSA_FULL,CRYPT_VERIFYCONTEXT|CRYPT_SILENT)) { + fprintf(stderr,"FATAL ERROR: Utils::getSecureRandom() unable to obtain WinCrypt context!\r\n"); + exit(1); + } + } + if (!CryptGenRandom(cryptProvider,(DWORD)sizeof(randomBuf),(BYTE *)randomBuf)) { + fprintf(stderr,"FATAL ERROR: Utils::getSecureRandom() CryptGenRandom failed!\r\n"); + exit(1); + } + randomPtr = 0; + s20.crypt12(randomBuf,randomBuf,sizeof(randomBuf)); + s20.init(randomBuf,randomBuf); } - } - if (!CryptGenRandom(cryptProvider,(DWORD)bytes,(BYTE *)buf)) { - fprintf(stderr,"FATAL ERROR: Utils::getSecureRandom() CryptGenRandom failed!\r\n"); - exit(1); + ((uint8_t *)buf)[i] = randomBuf[randomPtr++]; } #else // not __WINDOWS__ - static char randomBuf[131072]; - static unsigned int randomPtr = sizeof(randomBuf); static int devURandomFd = -1; - if (devURandomFd <= 0) { + if (devURandomFd < 0) { devURandomFd = ::open("/dev/urandom",O_RDONLY); - if (devURandomFd <= 0) { + if (devURandomFd < 0) { fprintf(stderr,"FATAL ERROR: Utils::getSecureRandom() unable to open /dev/urandom\n"); exit(1); return; @@ -201,7 +201,7 @@ void Utils::getSecureRandom(void *buf,unsigned int bytes) if ((int)::read(devURandomFd,randomBuf,sizeof(randomBuf)) != (int)sizeof(randomBuf)) { ::close(devURandomFd); devURandomFd = ::open("/dev/urandom",O_RDONLY); - if (devURandomFd <= 0) { + if (devURandomFd < 0) { fprintf(stderr,"FATAL ERROR: Utils::getSecureRandom() unable to open /dev/urandom\n"); exit(1); return; @@ -209,57 +209,13 @@ void Utils::getSecureRandom(void *buf,unsigned int bytes) } else break; } randomPtr = 0; + s20.crypt12(randomBuf,randomBuf,sizeof(randomBuf)); + s20.init(randomBuf,randomBuf); } - ((char *)buf)[i] = randomBuf[randomPtr++]; + ((uint8_t *)buf)[i] = randomBuf[randomPtr++]; } #endif // __WINDOWS__ or not - - s20.encrypt12(buf,buf,bytes); -} - -std::vector Utils::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; } bool Utils::scopy(char *dest,unsigned int len,const char *src) diff --git a/node/Utils.hpp b/node/Utils.hpp index cfe5650..ceb29d7 100644 --- a/node/Utils.hpp +++ b/node/Utils.hpp @@ -59,8 +59,7 @@ public: /** * Securely zero memory, avoiding compiler optimizations and such */ - static void burn(void *ptr,unsigned int len) - throw(); + static void burn(void *ptr,unsigned int len); /** * Convert binary data to hexadecimal @@ -111,17 +110,6 @@ public: */ static void getSecureRandom(void *buf,unsigned int bytes); - /** - * 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); - /** * Tokenize a string (alias for strtok_r or strtok_s depending on platform) * @@ -264,6 +252,20 @@ public: 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 * @@ -346,8 +348,7 @@ public: * * @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 maj2,unsigned int min2,unsigned int rev2) - throw() + 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; @@ -363,7 +364,13 @@ public: return 1; else if (rev1 < rev2) return -1; - else return 0; + else { + if (b1 > b2) + return 1; + else if (b1 < b2) + return -1; + else return 0; + } } } } diff --git a/node/World.hpp b/node/World.hpp index fdada2a..6e835be 100644 --- a/node/World.hpp +++ b/node/World.hpp @@ -48,16 +48,6 @@ */ #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 indicating null / empty World object - */ -#define ZT_WORLD_ID_NULL 0 - -/** - * World ID for a test network with ephemeral or temporary roots - */ -#define ZT_WORLD_ID_TESTNET 1 - /** * World ID for Earth * @@ -90,15 +80,23 @@ namespace ZeroTier { * 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. - * - * If you absolutely must run your own "unofficial" ZeroTier network, please - * define your world IDs above 0xffffffff (4294967295). Code to make a World - * is in mkworld.cpp in the parent directory and must be edited to change - * settings. */ 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; @@ -113,45 +111,54 @@ public: * Construct an empty / null World */ World() : - _id(ZT_WORLD_ID_NULL), - _ts(0) {} + _id(0), + _ts(0), + _type(TYPE_NULL) {} /** * @return Root servers for this world and their stable endpoints */ - inline const std::vector &roots() const throw() { return _roots; } + 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 throw() { return _id; } + inline uint64_t id() const { return _id; } /** * @return World definition timestamp */ - inline uint64_t timestamp() const throw() { return _ts; } + 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 * - * A new world update is valid if it is for the same world ID, is newer, - * and is signed by the current world's signing key. If this world object - * is null, it can always be updated. - * * @param update Candidate update - * @param fullSignatureCheck Perform full cryptographic signature check (true == yes, false == skip) - * @return True if update is newer than current and is properly signed + * @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,bool fullSignatureCheck) + inline bool shouldBeReplacedBy(const World &update) { - if (_id == ZT_WORLD_ID_NULL) + if ((_id == 0)||(_type == TYPE_NULL)) return true; - if ((_id == update._id)&&(_ts < update._ts)) { - if (fullSignatureCheck) { - Buffer tmp; - update.serialize(tmp,true); - return C25519::verify(_updateSigningKey,tmp.data(),tmp.size(),update._signature); - } else 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; } @@ -159,17 +166,17 @@ public: /** * @return True if this World is non-empty */ - inline operator bool() const throw() { return (_id != ZT_WORLD_ID_NULL); } + 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)0x01); // version -- only one valid value for now + if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + + b.append((uint8_t)_type); b.append((uint64_t)_id); b.append((uint64_t)_ts); - b.append(_updateSigningKey.data,ZT_C25519_PUBLIC_KEY_LEN); + 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()); @@ -179,8 +186,10 @@ public: for(std::vector::const_iterator ep(r->stableEndpoints.begin());ep!=r->stableEndpoints.end();++ep) ep->serialize(b); } - if (forSign) - b.append((uint64_t)0xf7f7f7f7f7f7f7f7ULL); + if (_type == TYPE_MOON) + b.append((uint16_t)0); // no attached dictionary (for future use) + + if (forSign) b.append((uint64_t)0xf7f7f7f7f7f7f7f7ULL); } template @@ -190,14 +199,19 @@ public: _roots.clear(); - if (b[p++] != 0x01) - throw std::invalid_argument("invalid World serialized version"); + 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(_updateSigningKey.data,b.field(p,ZT_C25519_PUBLIC_KEY_LEN),ZT_C25519_PUBLIC_KEY_LEN); p += ZT_C25519_PUBLIC_KEY_LEN; + 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; - unsigned int numRoots = b[p++]; + 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(p) + 2; return (p - startAt); } - inline bool operator==(const World &w) const throw() { return ((_id == w._id)&&(_ts == w._ts)&&(_updateSigningKey == w._updateSigningKey)&&(_signature == w._signature)&&(_roots == w._roots)); } - inline bool operator!=(const World &w) const throw() { return (!(*this == w)); } + 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; - C25519::Public _updateSigningKey; + Type _type; + C25519::Public _updatesMustBeSignedBy; C25519::Signature _signature; std::vector _roots; }; diff --git a/objects.mk b/objects.mk index 4a7a36a..74efc33 100644 --- a/objects.mk +++ b/objects.mk @@ -1,11 +1,15 @@ OBJS=\ + controller/EmbeddedNetworkController.o \ + controller/JSONDB.o \ node/C25519.o \ + node/Capability.o \ node/CertificateOfMembership.o \ + node/CertificateOfOwnership.o \ node/Cluster.o \ - node/DeferredPackets.o \ node/Identity.o \ node/IncomingPacket.o \ node/InetAddress.o \ + node/Membership.o \ node/Multicaster.o \ node/Network.o \ node/NetworkConfig.o \ @@ -15,15 +19,16 @@ OBJS=\ 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/BackgroundResolver.o \ osdep/ManagedRoute.o \ osdep/Http.o \ osdep/OSUtils.o \ service/ClusterGeoIpService.o \ - service/ControlPlane.o + service/SoftwareUpdater.o diff --git a/one.cpp b/one.cpp index 9f7a0a2..b40e28f 100644 --- a/one.cpp +++ b/one.cpp @@ -43,31 +43,41 @@ #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" -#ifdef ZT_USE_SYSTEM_JSON_PARSER -#include -#else -#include "ext/json-parser/json.h" -#endif - #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; @@ -75,7 +85,7 @@ using namespace ZeroTier; static OneService *volatile zt1Service = (OneService *)0; #define PROGRAM_NAME "ZeroTier One" -#define COPYRIGHT_NOTICE "Copyright © 2011–2016 ZeroTier, Inc." +#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 \ @@ -91,9 +101,10 @@ static OneService *volatile zt1Service = (OneService *)0; static void cliPrintHelp(const char *pn,FILE *out) { fprintf(out, - "%s version %d.%d.%d" ZT_EOL_S, + "%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_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); @@ -112,6 +123,9 @@ static void cliPrintHelp(const char *pn,FILE *out) 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) @@ -283,221 +297,146 @@ static int cli(int argc,char **argv) return 1; } } else if ((command == "info")||(command == "status")) { - unsigned int scode = Http::GET( - 1024 * 1024 * 16, - 60000, - (const struct sockaddr *)&addr, - "/status", - requestHeaders, - responseHeaders, - responseBody); + 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",cliFixJsonCRs(responseBody).c_str()); - return 0; + printf("%s" ZT_EOL_S,OSUtils::jsonDump(j).c_str()); } else { - json_value *j = json_parse(responseBody.c_str(),responseBody.length()); - bool good = false; - if (j) { - if (j->type == json_object) { - const char *address = (const char *)0; - bool online = false; - const char *version = (const char *)0; - for(unsigned int k=0;ku.object.length;++k) { - if ((!strcmp(j->u.object.values[k].name,"address"))&&(j->u.object.values[k].value->type == json_string)) - address = j->u.object.values[k].value->u.string.ptr; - else if ((!strcmp(j->u.object.values[k].name,"version"))&&(j->u.object.values[k].value->type == json_string)) - version = j->u.object.values[k].value->u.string.ptr; - else if ((!strcmp(j->u.object.values[k].name,"online"))&&(j->u.object.values[k].value->type == json_boolean)) - online = (j->u.object.values[k].value->u.boolean != 0); - } - if ((address)&&(version)) { - printf("200 info %s %s %s" ZT_EOL_S,address,(online ? "ONLINE" : "OFFLINE"),version); - good = true; - } - } - json_value_free(j); - } - if (good) { - return 0; - } else { - printf("%u %s invalid JSON response" ZT_EOL_S,scode,command.c_str()); - return 1; + 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") { - unsigned int scode = Http::GET( - 1024 * 1024 * 16, - 60000, - (const struct sockaddr *)&addr, - "/peer", - requestHeaders, - responseHeaders, - responseBody); + 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",cliFixJsonCRs(responseBody).c_str()); - return 0; + printf("%s" ZT_EOL_S,OSUtils::jsonDump(j).c_str()); } else { - printf("200 listpeers " ZT_EOL_S); - json_value *j = json_parse(responseBody.c_str(),responseBody.length()); - if (j) { - if (j->type == json_array) { - for(unsigned int p=0;pu.array.length;++p) { - json_value *jp = j->u.array.values[p]; - if (jp->type == json_object) { - const char *address = (const char *)0; - std::string paths; - int64_t latency = 0; - int64_t versionMajor = -1,versionMinor = -1,versionRev = -1; - const char *role = (const char *)0; - for(unsigned int k=0;ku.object.length;++k) { - if ((!strcmp(jp->u.object.values[k].name,"address"))&&(jp->u.object.values[k].value->type == json_string)) - address = jp->u.object.values[k].value->u.string.ptr; - else if ((!strcmp(jp->u.object.values[k].name,"versionMajor"))&&(jp->u.object.values[k].value->type == json_integer)) - versionMajor = jp->u.object.values[k].value->u.integer; - else if ((!strcmp(jp->u.object.values[k].name,"versionMinor"))&&(jp->u.object.values[k].value->type == json_integer)) - versionMinor = jp->u.object.values[k].value->u.integer; - else if ((!strcmp(jp->u.object.values[k].name,"versionRev"))&&(jp->u.object.values[k].value->type == json_integer)) - versionRev = jp->u.object.values[k].value->u.integer; - else if ((!strcmp(jp->u.object.values[k].name,"role"))&&(jp->u.object.values[k].value->type == json_string)) - role = jp->u.object.values[k].value->u.string.ptr; - else if ((!strcmp(jp->u.object.values[k].name,"latency"))&&(jp->u.object.values[k].value->type == json_integer)) - latency = jp->u.object.values[k].value->u.integer; - else if ((!strcmp(jp->u.object.values[k].name,"paths"))&&(jp->u.object.values[k].value->type == json_array)) { - for(unsigned int pp=0;ppu.object.values[k].value->u.array.length;++pp) { - json_value *jpath = jp->u.object.values[k].value->u.array.values[pp]; - if (jpath->type == json_object) { - const char *paddr = (const char *)0; - int64_t lastSend = 0; - int64_t lastReceive = 0; - bool preferred = false; - bool active = false; - for(unsigned int kk=0;kku.object.length;++kk) { - if ((!strcmp(jpath->u.object.values[kk].name,"address"))&&(jpath->u.object.values[kk].value->type == json_string)) - paddr = jpath->u.object.values[kk].value->u.string.ptr; - else if ((!strcmp(jpath->u.object.values[kk].name,"lastSend"))&&(jpath->u.object.values[kk].value->type == json_integer)) - lastSend = jpath->u.object.values[kk].value->u.integer; - else if ((!strcmp(jpath->u.object.values[kk].name,"lastReceive"))&&(jpath->u.object.values[kk].value->type == json_integer)) - lastReceive = jpath->u.object.values[kk].value->u.integer; - else if ((!strcmp(jpath->u.object.values[kk].name,"preferred"))&&(jpath->u.object.values[kk].value->type == json_boolean)) - preferred = (jpath->u.object.values[kk].value->u.boolean != 0); - else if ((!strcmp(jpath->u.object.values[kk].name,"active"))&&(jpath->u.object.values[kk].value->type == json_boolean)) - active = (jpath->u.object.values[kk].value->u.boolean != 0); - } - if ((paddr)&&(active)) { - int64_t now = (int64_t)OSUtils::now(); - if (lastSend > 0) - lastSend = now - lastSend; - if (lastReceive > 0) - lastReceive = now - lastReceive; - char pathtmp[256]; - Utils::snprintf(pathtmp,sizeof(pathtmp),"%s;%lld;%lld;%s", - paddr, - lastSend, - lastReceive, - (preferred ? "preferred" : "active")); - if (paths.length()) - paths.push_back(','); - paths.append(pathtmp); - } - } - } - } - } - if ((address)&&(role)) { - char verstr[64]; - if ((versionMajor >= 0)&&(versionMinor >= 0)&&(versionRev >= 0)) - Utils::snprintf(verstr,sizeof(verstr),"%lld.%lld.%lld",versionMajor,versionMinor,versionRev); - else { - verstr[0] = '-'; - verstr[1] = (char)0; - } - printf("200 listpeers %s %s %lld %s %s" ZT_EOL_S,address,(paths.length()) ? paths.c_str() : "-",(long long)latency,verstr,role); + 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()); } - json_value_free(j); } - return 0; } + return 0; } else { printf("%u %s %s" ZT_EOL_S,scode,command.c_str(),responseBody.c_str()); return 1; } } else if (command == "listnetworks") { - unsigned int scode = Http::GET( - 1024 * 1024 * 16, - 60000, - (const struct sockaddr *)&addr, - "/network", - requestHeaders, - responseHeaders, - responseBody); + 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",cliFixJsonCRs(responseBody).c_str()); - return 0; + printf("%s" ZT_EOL_S,OSUtils::jsonDump(j).c_str()); } else { printf("200 listnetworks " ZT_EOL_S); - json_value *j = json_parse(responseBody.c_str(),responseBody.length()); - if (j) { - if (j->type == json_array) { - for(unsigned int p=0;pu.array.length;++p) { - json_value *jn = j->u.array.values[p]; - if (jn->type == json_object) { - const char *nwid = (const char *)0; - const char *name = ""; - const char *mac = (const char *)0; - const char *status = (const char *)0; - const char *type = (const char *)0; - const char *portDeviceName = ""; - std::string ips; - for(unsigned int k=0;ku.object.length;++k) { - if ((!strcmp(jn->u.object.values[k].name,"nwid"))&&(jn->u.object.values[k].value->type == json_string)) - nwid = jn->u.object.values[k].value->u.string.ptr; - else if ((!strcmp(jn->u.object.values[k].name,"name"))&&(jn->u.object.values[k].value->type == json_string)) - name = jn->u.object.values[k].value->u.string.ptr; - else if ((!strcmp(jn->u.object.values[k].name,"mac"))&&(jn->u.object.values[k].value->type == json_string)) - mac = jn->u.object.values[k].value->u.string.ptr; - else if ((!strcmp(jn->u.object.values[k].name,"status"))&&(jn->u.object.values[k].value->type == json_string)) - status = jn->u.object.values[k].value->u.string.ptr; - else if ((!strcmp(jn->u.object.values[k].name,"type"))&&(jn->u.object.values[k].value->type == json_string)) - type = jn->u.object.values[k].value->u.string.ptr; - else if ((!strcmp(jn->u.object.values[k].name,"portDeviceName"))&&(jn->u.object.values[k].value->type == json_string)) - portDeviceName = jn->u.object.values[k].value->u.string.ptr; - else if ((!strcmp(jn->u.object.values[k].name,"assignedAddresses"))&&(jn->u.object.values[k].value->type == json_array)) { - for(unsigned int a=0;au.object.values[k].value->u.array.length;++a) { - json_value *aa = jn->u.object.values[k].value->u.array.values[a]; - if (aa->type == json_string) { - if (ips.length()) - ips.push_back(','); - ips.append(aa->u.string.ptr); - } - } + if (j.is_array()) { + for(unsigned long i=0;i 0) aa.push_back(','); + aa.append(addr.get()); } } - if ((nwid)&&(mac)&&(status)&&(type)) { - printf("200 listnetworks %s %s %s %s %s %s %s" ZT_EOL_S, - nwid, - (((name)&&(name[0])) ? name : "-"), - mac, - status, - type, - (((portDeviceName)&&(portDeviceName[0])) ? portDeviceName : "-"), - ((ips.length() > 0) ? ips.c_str() : "-")); - } } + 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()); } } - json_value_free(j); } } + return 0; } else { printf("%u %s %s" ZT_EOL_S,scode,command.c_str(),responseBody.c_str()); return 1; @@ -554,6 +493,75 @@ static int cli(int argc,char **argv) 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); @@ -577,7 +585,7 @@ static int cli(int argc,char **argv) (std::string("/network/") + arg1).c_str(), requestHeaders, jsons, - strlen(jsons), + (unsigned long)strlen(jsons), responseHeaders, responseBody); if (scode == 200) { @@ -619,7 +627,8 @@ static void idtoolPrintHelp(FILE *out,const char *pn) fprintf(out," getpublic " ZT_EOL_S); fprintf(out," sign " ZT_EOL_S); fprintf(out," verify " ZT_EOL_S); - fprintf(out," mkcom [ ...] (hexadecimal integers)" ZT_EOL_S); + fprintf(out," initmoon " ZT_EOL_S); + fprintf(out," genmoon " ZT_EOL_S); } static Identity getIdFromArg(char *arg) @@ -654,7 +663,7 @@ static int idtool(int argc,char **argv) int vanityBits = 0; if (argc >= 5) { vanity = Utils::hexStrToU64(argv[4]) & 0xffffffffffULL; - vanityBits = 4 * strlen(argv[4]); + vanityBits = 4 * (int)strlen(argv[4]); if (vanityBits > 40) vanityBits = 40; } @@ -764,34 +773,93 @@ static int idtool(int argc,char **argv) fprintf(stderr,"%s signature check FAILED" ZT_EOL_S,argv[3]); return 1; } - } else if (!strcmp(argv[1],"mkcom")) { + } else if (!strcmp(argv[1],"initmoon")) { if (argc < 3) { idtoolPrintHelp(stdout,argv[0]); - return 1; - } - - Identity id = getIdFromArg(argv[2]); - if ((!id)||(!id.hasPrivate())) { - fprintf(stderr,"Identity argument invalid, does not include private key, or file unreadable: %s" ZT_EOL_S,argv[2]); - return 1; - } - - CertificateOfMembership com; - for(int a=3;a params(Utils::split(argv[a],",","","")); - if (params.size() == 3) { - uint64_t qId = Utils::hexStrToU64(params[0].c_str()); - uint64_t qValue = Utils::hexStrToU64(params[1].c_str()); - uint64_t qMaxDelta = Utils::hexStrToU64(params[2].c_str()); - com.setQualifier(qId,qValue,qMaxDelta); + } else { + const Identity id = getIdFromArg(argv[2]); + if (!id) { + fprintf(stderr,"%s is not a valid identity" ZT_EOL_S,argv[2]); + return 1; } - } - if (!com.sign(id)) { - fprintf(stderr,"Signature of certificate of membership failed." ZT_EOL_S); - return 1; - } - printf("%s",com.toString().c_str()); + 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; @@ -817,6 +885,147 @@ static void _sighandlerQuit(int sig) } #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 */ /****************************************************************************/ @@ -979,14 +1188,11 @@ static void printHelp(const char *cn,FILE *out) fprintf(out, COPYRIGHT_NOTICE ZT_EOL_S LICENSE_GRANT ZT_EOL_S); - std::string updateUrl(OneService::autoUpdateUrl()); - if (updateUrl.length()) - fprintf(out,"Automatic updates enabled:" ZT_EOL_S" %s" ZT_EOL_S" (all updates are securely authenticated by 256-bit ECDSA signature)" ZT_EOL_S"" ZT_EOL_S,updateUrl.c_str()); 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 - Run as unprivileged user (skip privilege check)" 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__ @@ -1004,8 +1210,54 @@ static void printHelp(const char *cn,FILE *out) 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[]) +int __cdecl _tmain(int argc, _TCHAR* argv[]) #else int main(int argc,char **argv) #endif @@ -1157,7 +1409,7 @@ int main(int argc,char **argv) fprintf(stderr,"%s: no home path specified and no platform default available" ZT_EOL_S,argv[0]); return 1; } else { - std::vector hpsp(Utils::split(homeDir.c_str(),ZT_PATH_SEPARATOR_S,"","")); + 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); @@ -1172,6 +1424,12 @@ int main(int argc,char **argv) } } + // 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)) { @@ -1210,8 +1468,8 @@ int main(int argc,char **argv) } else { // Running from service manager _winPokeAHole(); - ZeroTierOneService zt1Service; - if (CServiceBase::Run(zt1Service) == TRUE) { + 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]); @@ -1221,6 +1479,10 @@ int main(int argc,char **argv) #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 @@ -1232,39 +1494,13 @@ int main(int argc,char **argv) } #endif // __UNIX_LIKE__ - unsigned int returnValue = 0; - - 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,argv[0],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; + _OneServiceRunner thr(argv[0],homeDir,port); + thr.threadMain(); + //Thread::join(Thread::start(&thr)); #ifdef __UNIX_LIKE__ OSUtils::rm(pidPath.c_str()); #endif - return returnValue; + return thr.returnValue; } diff --git a/osdep/BSDEthernetTap.cpp b/osdep/BSDEthernetTap.cpp index e8d36c9..62fabc4 100644 --- a/osdep/BSDEthernetTap.cpp +++ b/osdep/BSDEthernetTap.cpp @@ -71,7 +71,7 @@ BSDEthernetTap::BSDEthernetTap( unsigned int metric, uint64_t nwid, const char *friendlyName, - void (*handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int), + void (*handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int), void *arg) : _handler(handler), _arg(arg), @@ -83,10 +83,15 @@ BSDEthernetTap::BSDEthernetTap( { static Mutex globalTapCreateLock; char devpath[64],ethaddr[64],mtustr[32],metstr[32],tmpdevname[32]; - struct stat stattmp; - // On FreeBSD at least we can rename, so use nwid to generate a deterministic unique zt#### name using base32 - // As a result we don't use desiredDevice + 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)]); @@ -102,13 +107,6 @@ BSDEthernetTap::BSDEthernetTap( _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 5) & 0x1f)]); _dev.push_back(ZT_BASE32_CHARS[(unsigned long)(nwid & 0x1f)]); - Mutex::Lock _gl(globalTapCreateLock); - - if (mtu > 2800) - throw std::runtime_error("max tap MTU is 2800"); - - // On BSD we create taps and they can have high numbers, so use ones starting - // at 9993 to not conflict with other stuff. Then we rename it to zt std::vector devFiles(OSUtils::listDirectory("/dev")); for(int i=9993;i<(9993+128);++i) { Utils::snprintf(tmpdevname,sizeof(tmpdevname),"tap%d",i); @@ -123,6 +121,7 @@ BSDEthernetTap::BSDEthernetTap( ::waitpid(cpid,&exitcode,0); } else throw std::runtime_error("fork() failed"); + struct stat stattmp; if (!stat(devpath,&stattmp)) { cpid = (long)vfork(); if (cpid == 0) { @@ -144,6 +143,19 @@ BSDEthernetTap::BSDEthernetTap( } } } +#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"); @@ -325,6 +337,7 @@ void BSDEthernetTap::scanMulticastGroups(std::vector &added,std: { std::vector newGroups; +#ifndef __OpenBSD__ struct ifmaddrs *ifmap = (struct ifmaddrs *)0; if (!getifmaddrs(&ifmap)) { struct ifmaddrs *p = ifmap; @@ -339,6 +352,7 @@ void BSDEthernetTap::scanMulticastGroups(std::vector &added,std: } freeifmaddrs(ifmap); } +#endif // __OpenBSD__ std::vector allIps(ips()); for(std::vector::iterator ip(allIps.begin());ip!=allIps.end();++ip) @@ -446,8 +460,7 @@ void BSDEthernetTap::threadMain() to.setTo(getBuf,6); from.setTo(getBuf + 6,6); unsigned int etherType = ntohs(((const uint16_t *)getBuf)[6]); - // TODO: VLAN support - _handler(_arg,_nwid,from,to,etherType,0,(const void *)(getBuf + 14),r - 14); + _handler(_arg,(void *)0,_nwid,from,to,etherType,0,(const void *)(getBuf + 14),r - 14); } r = 0; diff --git a/osdep/BSDEthernetTap.hpp b/osdep/BSDEthernetTap.hpp index 1bb48d3..8c6314d 100644 --- a/osdep/BSDEthernetTap.hpp +++ b/osdep/BSDEthernetTap.hpp @@ -43,7 +43,7 @@ public: unsigned int metric, uint64_t nwid, const char *friendlyName, - void (*handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int), + void (*handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int), void *arg); ~BSDEthernetTap(); @@ -62,7 +62,7 @@ public: throw(); private: - void (*_handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int); + 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; diff --git a/osdep/BackgroundResolver.cpp b/osdep/BackgroundResolver.cpp deleted file mode 100644 index ffcfdba..0000000 --- a/osdep/BackgroundResolver.cpp +++ /dev/null @@ -1,121 +0,0 @@ -/* - * 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 "OSUtils.hpp" -#include "Thread.hpp" -#include "BackgroundResolver.hpp" - -namespace ZeroTier { - -/* - * We can't actually abort a job. This is a legacy characteristic of the - * ancient synchronous resolver APIs. So to abort jobs, we just abandon - * them by setting their parent to null. - */ -class BackgroundResolverJob -{ -public: - std::string name; - BackgroundResolver *volatile parent; - Mutex lock; - - void threadMain() - throw() - { - std::vector ips; - try { - ips = OSUtils::resolve(name.c_str()); - } catch ( ... ) {} - { - Mutex::Lock _l(lock); - BackgroundResolver *p = parent; - if (p) - p->_postResult(ips); - } - delete this; - } -}; - -BackgroundResolver::BackgroundResolver(const char *name) : - _name(name), - _job((BackgroundResolverJob *)0), - _callback(0), - _arg((void *)0), - _ips(), - _lock() -{ -} - -BackgroundResolver::~BackgroundResolver() -{ - abort(); -} - -std::vector BackgroundResolver::get() const -{ - Mutex::Lock _l(_lock); - return _ips; -} - -void BackgroundResolver::resolveNow(void (*callback)(BackgroundResolver *,void *),void *arg) -{ - Mutex::Lock _l(_lock); - - if (_job) { - Mutex::Lock _l2(_job->lock); - _job->parent = (BackgroundResolver *)0; - _job = (BackgroundResolverJob *)0; - } - - BackgroundResolverJob *j = new BackgroundResolverJob(); - j->name = _name; - j->parent = this; - - _job = j; - _callback = callback; - _arg = arg; - - _jobThread = Thread::start(j); -} - -void BackgroundResolver::abort() -{ - Mutex::Lock _l(_lock); - if (_job) { - Mutex::Lock _l2(_job->lock); - _job->parent = (BackgroundResolver *)0; - _job = (BackgroundResolverJob *)0; - } -} - -void BackgroundResolver::_postResult(const std::vector &ips) -{ - void (*cb)(BackgroundResolver *,void *); - void *a; - { - Mutex::Lock _l(_lock); - _job = (BackgroundResolverJob *)0; - cb = _callback; - a = _arg; - _ips = ips; - } - if (cb) - cb(this,a); -} - -} // namespace ZeroTier diff --git a/osdep/BackgroundResolver.hpp b/osdep/BackgroundResolver.hpp deleted file mode 100644 index ba89548..0000000 --- a/osdep/BackgroundResolver.hpp +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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_BACKGROUNDRESOLVER_HPP -#define ZT_BACKGROUNDRESOLVER_HPP - -#include -#include - -#include "../node/Constants.hpp" -#include "../node/Mutex.hpp" -#include "../node/InetAddress.hpp" -#include "../node/NonCopyable.hpp" -#include "Thread.hpp" - -namespace ZeroTier { - -class BackgroundResolverJob; - -/** - * A simple background resolver - */ -class BackgroundResolver : NonCopyable -{ - friend class BackgroundResolverJob; - -public: - /** - * Construct a new resolver - * - * resolveNow() must be called to actually initiate background resolution. - * - * @param name Name to resolve - */ - BackgroundResolver(const char *name); - - ~BackgroundResolver(); - - /** - * @return Most recent resolver results or empty vector if none - */ - std::vector get() const; - - /** - * Launch a background resolve job now - * - * If a resolve job is currently in progress, it is aborted and another - * job is started. - * - * Note that jobs can't actually be aborted due to the limitations of the - * ancient synchronous OS resolver APIs. As a result, in progress jobs - * that are aborted are simply abandoned. Don't call this too frequently - * or background threads might pile up. - * - * @param callback Callback function to receive notification or NULL if none - * @praam arg Second argument to callback function - */ - void resolveNow(void (*callback)(BackgroundResolver *,void *) = 0,void *arg = 0); - - /** - * Abort (abandon) any current resolve jobs - */ - void abort(); - - /** - * @return True if a background job is in progress - */ - inline bool running() const - { - Mutex::Lock _l(_lock); - return (_job != (BackgroundResolverJob *)0); - } - - /** - * Wait for pending job to complete (if any) - */ - inline void wait() const - { - Thread t; - { - Mutex::Lock _l(_lock); - if (!_job) - return; - t = _jobThread; - } - Thread::join(t); - } - -private: - void _postResult(const std::vector &ips); - - std::string _name; - BackgroundResolverJob *_job; - Thread _jobThread; - void (*_callback)(BackgroundResolver *,void *); - void *_arg; - std::vector _ips; - Mutex _lock; -}; - -} // namespace ZeroTier - -#endif diff --git a/osdep/Binder.hpp b/osdep/Binder.hpp index 72456d3..9829f17 100644 --- a/osdep/Binder.hpp +++ b/osdep/Binder.hpp @@ -53,8 +53,10 @@ #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 @@ -164,14 +166,57 @@ public: #else // not __WINDOWS__ - 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)) { + /* 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: @@ -179,14 +224,90 @@ public: case InetAddress::IP_SCOPE_SHARED: case InetAddress::IP_SCOPE_PRIVATE: ip.setPort(port); - localIfAddrs.insert(std::pair(ip,std::string(ifa->ifa_name))); + localIfAddrs.insert(std::pair(ip,std::string(devname))); break; } } } - ifa = ifa->ifa_next; } - freeifaddrs(ifatbl); + 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 diff --git a/osdep/BlockingQueue.hpp b/osdep/BlockingQueue.hpp new file mode 100644 index 0000000..6172f4d --- /dev/null +++ b/osdep/BlockingQueue.hpp @@ -0,0 +1,64 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ZT_BLOCKINGQUEUE_HPP +#define ZT_BLOCKINGQUEUE_HPP + +#include +#include +#include + +namespace ZeroTier { + +/** + * Simple C++11 thread-safe queue + * + * Do not use in node/ since we have not gone C++11 there yet. + */ +template +class BlockingQueue +{ +public: + BlockingQueue(void) {} + + inline void post(T t) + { + std::lock_guard lock(m); + q.push(t); + c.notify_one(); + } + + inline T get(void) + { + std::unique_lock lock(m); + while(q.empty()) + c.wait(lock); + T val = q.front(); + q.pop(); + return val; + } + +private: + std::queue q; + mutable std::mutex m; + std::condition_variable c; +}; + +} // namespace ZeroTier + +#endif diff --git a/osdep/Http.hpp b/osdep/Http.hpp index 1ecf4ee..e7d4d03 100644 --- a/osdep/Http.hpp +++ b/osdep/Http.hpp @@ -135,6 +135,39 @@ public: responseBody); } + /** + * Make HTTP PUT request + * + * It is the responsibility of the caller to set all headers. With PUT, the + * Content-Length and Content-Type headers must be set or the PUT will not + * work. + * + * @return HTTP status code or 0 on error (responseBody will contain error message) + */ + static inline unsigned int PUT( + unsigned long maxResponseSize, + unsigned long timeout, + const struct sockaddr *remoteAddress, + const char *path, + const std::map &requestHeaders, + const void *postData, + unsigned long postDataLength, + std::map &responseHeaders, + std::string &responseBody) + { + return _do( + "PUT", + maxResponseSize, + timeout, + remoteAddress, + path, + requestHeaders, + postData, + postDataLength, + responseHeaders, + responseBody); + } + private: static unsigned int _do( const char *method, diff --git a/osdep/LinuxEthernetTap.cpp b/osdep/LinuxEthernetTap.cpp index e336bb6..f74efc0 100644 --- a/osdep/LinuxEthernetTap.cpp +++ b/osdep/LinuxEthernetTap.cpp @@ -62,7 +62,7 @@ LinuxEthernetTap::LinuxEthernetTap( unsigned int metric, uint64_t nwid, const char *friendlyName, - void (*handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int), + void (*handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int), void *arg) : _handler(handler), _arg(arg), @@ -93,23 +93,40 @@ LinuxEthernetTap::LinuxEthernetTap( 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); + std::map globalDeviceMap; + FILE *devmapf = fopen((_homePath + ZT_PATH_SEPARATOR_S + "devicemap").c_str(),"r"); + if (devmapf) { + char buf[256]; + while (fgets(buf,sizeof(buf),devmapf)) { + char *x = (char *)0; + char *y = (char *)0; + char *saveptr = (char *)0; + for(char *f=Utils::stok(buf,"\r\n=",&saveptr);(f);f=Utils::stok((char *)0,"\r\n=",&saveptr)) { + if (!x) x = f; + else if (!y) y = f; + else break; + } + if ((x)&&(y)&&(x[0])&&(y[0])) + globalDeviceMap[x] = y; } + fclose(devmapf); + } + bool recalledDevice = false; + std::map::const_iterator gdmEntry = globalDeviceMap.find(nwids); + if (gdmEntry != globalDeviceMap.end()) { + Utils::scopy(ifr.ifr_name,sizeof(ifr.ifr_name),gdmEntry->second.c_str()); + Utils::snprintf(procpath,sizeof(procpath),"/proc/sys/net/ipv4/conf/%s",ifr.ifr_name); + recalledDevice = (stat(procpath,&sbuf) != 0); } - if (!recalledDevice) { int devno = 0; do { +#ifdef __SYNOLOGY__ + devno+=50; // Arbitrary number to prevent interface name conflicts + Utils::snprintf(ifr.ifr_name,sizeof(ifr.ifr_name),"eth%d",devno++); +#else Utils::snprintf(ifr.ifr_name,sizeof(ifr.ifr_name),"zt%d",devno++); +#endif Utils::snprintf(procpath,sizeof(procpath),"/proc/sys/net/ipv4/conf/%s",ifr.ifr_name); } while (stat(procpath,&sbuf) == 0); // try zt#++ until we find one that does not exist } @@ -174,9 +191,16 @@ LinuxEthernetTap::LinuxEthernetTap( (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()); + globalDeviceMap[nwids] = _dev; + devmapf = fopen((_homePath + ZT_PATH_SEPARATOR_S + "devicemap").c_str(),"w"); + if (devmapf) { + gdmEntry = globalDeviceMap.begin(); + while (gdmEntry != globalDeviceMap.end()) { + fprintf(devmapf,"%s=%s\n",gdmEntry->first.c_str(),gdmEntry->second.c_str()); + ++gdmEntry; + } + fclose(devmapf); + } _thread = Thread::start(this); } @@ -215,6 +239,59 @@ static bool ___removeIp(const std::string &_dev,const InetAddress &ip) } } +#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) @@ -412,7 +489,7 @@ void LinuxEthernetTap::threadMain() from.setTo(getBuf + 6,6); unsigned int etherType = ntohs(((const uint16_t *)getBuf)[6]); // TODO: VLAN support - _handler(_arg,_nwid,from,to,etherType,0,(const void *)(getBuf + 14),r - 14); + _handler(_arg,(void *)0,_nwid,from,to,etherType,0,(const void *)(getBuf + 14),r - 14); } r = 0; diff --git a/osdep/LinuxEthernetTap.hpp b/osdep/LinuxEthernetTap.hpp index cbb58ef..a2a00a7 100644 --- a/osdep/LinuxEthernetTap.hpp +++ b/osdep/LinuxEthernetTap.hpp @@ -44,7 +44,7 @@ public: unsigned int metric, uint64_t nwid, const char *friendlyName, - void (*handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int), + void (*handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int), void *arg); ~LinuxEthernetTap(); @@ -52,6 +52,9 @@ public: 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); @@ -63,7 +66,7 @@ public: throw(); private: - void (*_handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int); + 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; diff --git a/osdep/ManagedRoute.cpp b/osdep/ManagedRoute.cpp index 0bb74c1..3a020d6 100644 --- a/osdep/ManagedRoute.cpp +++ b/osdep/ManagedRoute.cpp @@ -57,8 +57,6 @@ #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 { @@ -69,23 +67,28 @@ static void _forkTarget(const InetAddress &t,InetAddress &left,InetAddress &righ { const unsigned int bits = t.netmaskBits() + 1; left = t; - if ((t.ss_family == AF_INET)&&(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 if ((t.ss_family == AF_INET6)&&(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); + 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(); + } } } -#ifdef __BSD__ // ------------------------------------------------------------ -#define ZT_ROUTING_SUPPORT_FOUND 1 - struct _RTE { InetAddress target; @@ -95,6 +98,9 @@ struct _RTE bool ifscope; }; +#ifdef __BSD__ // ------------------------------------------------------------ +#define ZT_ROUTING_SUPPORT_FOUND 1 + static std::vector<_RTE> _getRTEs(const InetAddress &target,bool contains) { std::vector<_RTE> rtes; @@ -232,6 +238,7 @@ static std::vector<_RTE> _getRTEs(const InetAddress &target,bool contains) 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; @@ -339,10 +346,42 @@ static bool _winRoute(bool del,const NET_LUID &interfaceLuid,const NET_IFINDEX & } } +static bool _winHasRoute(const NET_LUID &interfaceLuid, const NET_IFINDEX &interfaceIndex, const InetAddress &target, const InetAddress &via) +{ + MIB_IPFORWARD_ROW2 rtrow; + InitializeIpForwardEntry(&rtrow); + rtrow.InterfaceLuid.Value = interfaceLuid.Value; + rtrow.InterfaceIndex = interfaceIndex; + if (target.ss_family == AF_INET) { + rtrow.DestinationPrefix.Prefix.si_family = AF_INET; + rtrow.DestinationPrefix.Prefix.Ipv4.sin_family = AF_INET; + rtrow.DestinationPrefix.Prefix.Ipv4.sin_addr.S_un.S_addr = reinterpret_cast(&target)->sin_addr.S_un.S_addr; + if (via.ss_family == AF_INET) { + rtrow.NextHop.si_family = AF_INET; + rtrow.NextHop.Ipv4.sin_family = AF_INET; + rtrow.NextHop.Ipv4.sin_addr.S_un.S_addr = reinterpret_cast(&via)->sin_addr.S_un.S_addr; + } + } else if (target.ss_family == AF_INET6) { + rtrow.DestinationPrefix.Prefix.si_family = AF_INET6; + rtrow.DestinationPrefix.Prefix.Ipv6.sin6_family = AF_INET6; + memcpy(rtrow.DestinationPrefix.Prefix.Ipv6.sin6_addr.u.Byte, reinterpret_cast(&target)->sin6_addr.u.Byte, 16); + if (via.ss_family == AF_INET6) { + rtrow.NextHop.si_family = AF_INET6; + rtrow.NextHop.Ipv6.sin6_family = AF_INET6; + memcpy(rtrow.NextHop.Ipv6.sin6_addr.u.Byte, reinterpret_cast(&via)->sin6_addr.u.Byte, 16); + } + } else { + return false; + } + rtrow.DestinationPrefix.PrefixLength = target.netmaskBits(); + rtrow.SitePrefixLength = rtrow.DestinationPrefix.PrefixLength; + return (GetIpForwardEntry2(&rtrow) == NO_ERROR); +} + #endif // __WINDOWS__ -------------------------------------------------------- #ifndef ZT_ROUTING_SUPPORT_FOUND -#error "ManagedRoute.cpp has no support for managing routes on this platform! You'll need to check and see if one of the existing ones will work and make sure proper defines are set, or write one. Please do a Github pull request if you do this for a new OS." +#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 @@ -369,145 +408,104 @@ bool ManagedRoute::sync() return false; #endif - if ((_target.isDefaultRoute())||((_target.ss_family == AF_INET)&&(_target.netmaskBits() < 32))) { - /* In ZeroTier we create two more specific routes for every one route. We - * do this for default routes and IPv4 routes other than /32s. If there - * is a pre-existing system route that this route will override, we create - * two more specific interface-bound shadow routes for it. - * - * This means that ZeroTier can *itself* continue communicating over - * whatever physical routes might be present while simultaneously - * overriding them for general system traffic. This is mostly for - * "full tunnel" VPN modes of operation, but might be useful for - * virtualizing physical networks in a hybrid design as well. */ - - // Generate two more specific routes than target with one extra bit - InetAddress leftt,rightt; + InetAddress leftt,rightt; + if (_target.netmaskBits() == 0) // bifurcate only the default route _forkTarget(_target,leftt,rightt); + else leftt = _target; #ifdef __BSD__ // ------------------------------------------------------------ - // Find lowest metric system route that this route should override (if any) - InetAddress newSystemVia; - char newSystemDevice[128]; - newSystemDevice[0] = (char)0; - int systemMetric = 9999999; - std::vector<_RTE> rtes(_getRTEs(_target,false)); + // 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->via) { - if ((!newSystemVia)||(r->metric < systemMetric)) { - newSystemVia = r->via; - Utils::scopy(newSystemDevice,sizeof(newSystemDevice),r->device); - systemMetric = r->metric; - } - } - } - if ((newSystemVia)&&(!newSystemDevice[0])) { - rtes = _getRTEs(newSystemVia,true); - for(std::vector<_RTE>::iterator r(rtes.begin());r!=rtes.end();++r) { - if (r->device[0]) { - Utils::scopy(newSystemDevice,sizeof(newSystemDevice),r->device); - break; - } + 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))) && (strcmp(_device,newSystemDevice)) ) { - if ((_systemVia)&&(_systemDevice[0])) { - _routeCmd("delete",leftt,_systemVia,_systemDevice,(const char *)0); + // 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); + _systemVia = newSystemVia; + Utils::scopy(_systemDevice,sizeof(_systemDevice),newSystemDevice); - if ((_systemVia)&&(_systemDevice[0])) { - _routeCmd("add",leftt,_systemVia,_systemDevice,(const char *)0); - _routeCmd("change",leftt,_systemVia,_systemDevice,(const char *)0); + 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); } } - - // Apply overriding non-device-scoped routes - if (!_applied) { - if (_via) { - _routeCmd("add",leftt,_via,(const char *)0,(const char *)0); - _routeCmd("change",leftt,_via,(const char *)0,(const char *)0); - _routeCmd("add",rightt,_via,(const char *)0,(const char *)0); - _routeCmd("change",rightt,_via,(const char *)0,(const char *)0); - } else if (_device[0]) { - _routeCmd("add",leftt,_via,(const char *)0,_device); - _routeCmd("change",leftt,_via,(const char *)0,_device); - _routeCmd("add",rightt,_via,(const char *)0,_device); - _routeCmd("change",rightt,_via,(const char *)0,_device); - } - - _applied = true; - } - -#endif // __BSD__ ------------------------------------------------------------ - -#ifdef __LINUX__ // ---------------------------------------------------------- - - if (!_applied) { - _routeCmd("replace",leftt,_via,(_via) ? _device : (const char *)0); - _routeCmd("replace",rightt,_via,(_via) ? _device : (const char *)0); - _applied = true; - } - -#endif // __LINUX__ ---------------------------------------------------------- - -#ifdef __WINDOWS__ // -------------------------------------------------------- - - if (!_applied) { - _winRoute(false,interfaceLuid,interfaceIndex,leftt,_via); - _winRoute(false,interfaceLuid,interfaceIndex,rightt,_via); - _applied = true; - } - -#endif // __WINDOWS__ -------------------------------------------------------- - - } else { - -#ifdef __BSD__ // ------------------------------------------------------------ - - if (!_applied) { - if (_via) { - _routeCmd("add",_target,_via,(const char *)0,(const char *)0); - _routeCmd("change",_target,_via,(const char *)0,(const char *)0); - } else if (_device[0]) { - _routeCmd("add",_target,_via,(const char *)0,_device); - _routeCmd("change",_target,_via,(const char *)0,_device); - } - _applied = true; - } - -#endif // __BSD__ ------------------------------------------------------------ - -#ifdef __LINUX__ // ---------------------------------------------------------- - - if (!_applied) { - _routeCmd("replace",_target,_via,(_via) ? _device : (const char *)0); - _applied = true; - } - -#endif // __LINUX__ ---------------------------------------------------------- - -#ifdef __WINDOWS__ // -------------------------------------------------------- - - if (!_applied) { - _winRoute(false,interfaceLuid,interfaceIndex,_target,_via); - _applied = true; - } - -#endif // __WINDOWS__ -------------------------------------------------------- - } + if (!_applied.count(leftt)) { + _applied[leftt] = false; // not ifscoped + _routeCmd("add",leftt,_via,(const char *)0,(_via) ? (const char *)0 : _device); + _routeCmd("change",leftt,_via,(const char *)0,(_via) ? (const char *)0 : _device); + } + if ((rightt)&&(!_applied.count(rightt))) { + _applied[rightt] = false; // not ifscoped + _routeCmd("add",rightt,_via,(const char *)0,(_via) ? (const char *)0 : _device); + _routeCmd("change",rightt,_via,(const char *)0,(_via) ? (const char *)0 : _device); + } + +#endif // __BSD__ ------------------------------------------------------------ + +#ifdef __LINUX__ // ---------------------------------------------------------- + + if (!_applied.count(leftt)) { + _applied[leftt] = false; // boolean unused + _routeCmd("replace",leftt,_via,(_via) ? (const char *)0 : _device); + } + if ((rightt)&&(!_applied.count(rightt))) { + _applied[rightt] = false; // boolean unused + _routeCmd("replace",rightt,_via,(_via) ? (const char *)0 : _device); + } + +#endif // __LINUX__ ---------------------------------------------------------- + +#ifdef __WINDOWS__ // -------------------------------------------------------- + + if ( (!_applied.count(leftt)) || (!_winHasRoute(interfaceLuid,interfaceIndex,leftt,_via)) ) { + _applied[leftt] = false; // boolean unused + _winRoute(false,interfaceLuid,interfaceIndex,leftt,_via); + } + if ( (rightt) && ( (!_applied.count(rightt)) || (!_winHasRoute(interfaceLuid,interfaceIndex,rightt,_via)) ) ) { + _applied[rightt] = false; // boolean unused + _winRoute(false,interfaceLuid,interfaceIndex,rightt,_via); + } + +#endif // __WINDOWS__ -------------------------------------------------------- + return true; } @@ -521,66 +519,28 @@ void ManagedRoute::remove() return; #endif - if (_applied) { - if ((_target.isDefaultRoute())||((_target.ss_family == AF_INET)&&(_target.netmaskBits() < 32))) { - InetAddress leftt,rightt; - _forkTarget(_target,leftt,rightt); +#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__ // ------------------------------------------------------------ - - if ((_systemVia)&&(_systemDevice[0])) { - _routeCmd("delete",leftt,_systemVia,_systemDevice,(const char *)0); - _routeCmd("delete",rightt,_systemVia,_systemDevice,(const char *)0); - } - if (_via) { - _routeCmd("delete",leftt,_via,(const char *)0,(const char *)0); - _routeCmd("delete",rightt,_via,(const char *)0,(const char *)0); - } else if (_device[0]) { - _routeCmd("delete",leftt,_via,(const char *)0,_device); - _routeCmd("delete",rightt,_via,(const char *)0,_device); - } - + _routeCmd("delete",r->first,_via,r->second ? _device : (const char *)0,(_via) ? (const char *)0 : _device); #endif // __BSD__ ------------------------------------------------------------ #ifdef __LINUX__ // ---------------------------------------------------------- - - _routeCmd("del",leftt,_via,(_via) ? _device : (const char *)0); - _routeCmd("del",rightt,_via,(_via) ? _device : (const char *)0); - + _routeCmd("del",r->first,_via,(_via) ? (const char *)0 : _device); #endif // __LINUX__ ---------------------------------------------------------- #ifdef __WINDOWS__ // -------------------------------------------------------- - - _winRoute(true,interfaceLuid,interfaceIndex,leftt,_via); - _winRoute(true,interfaceLuid,interfaceIndex,rightt,_via); - + _winRoute(true,interfaceLuid,interfaceIndex,r->first,_via); #endif // __WINDOWS__ -------------------------------------------------------- - - } else { - -#ifdef __BSD__ // ------------------------------------------------------------ - - if (_via) { - _routeCmd("delete",_target,_via,(const char *)0,(const char *)0); - } else if (_device[0]) { - _routeCmd("delete",_target,_via,(const char *)0,_device); - } - -#endif // __BSD__ ------------------------------------------------------------ - -#ifdef __LINUX__ // ---------------------------------------------------------- - - _routeCmd("del",_target,_via,(_via) ? _device : (const char *)0); - -#endif // __LINUX__ ---------------------------------------------------------- - -#ifdef __WINDOWS__ // -------------------------------------------------------- - - _winRoute(true,interfaceLuid,interfaceIndex,_target,_via); - -#endif // __WINDOWS__ -------------------------------------------------------- - - } } _target.zero(); @@ -588,7 +548,7 @@ void ManagedRoute::remove() _systemVia.zero(); _device[0] = (char)0; _systemDevice[0] = (char)0; - _applied = false; + _applied.clear(); } } // namespace ZeroTier diff --git a/osdep/ManagedRoute.hpp b/osdep/ManagedRoute.hpp index 63310f2..fd77a79 100644 --- a/osdep/ManagedRoute.hpp +++ b/osdep/ManagedRoute.hpp @@ -6,23 +6,34 @@ #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 +class ManagedRoute : NonCopyable { + friend class SharedPtr; + public: - ManagedRoute() + ManagedRoute(const InetAddress &target,const InetAddress &via,const char *device) { - _device[0] = (char)0; + _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; - _applied = false; } ~ManagedRoute() @@ -30,45 +41,6 @@ public: this->remove(); } - ManagedRoute(const ManagedRoute &r) - { - _applied = false; - *this = r; - } - - inline ManagedRoute &operator=(const ManagedRoute &r) - { - if ((!_applied)&&(!r._applied)) { - memcpy(this,&r,sizeof(ManagedRoute)); // InetAddress is memcpy'able - } else { - fprintf(stderr,"Applied ManagedRoute isn't copyable!\n"); - abort(); - } - return *this; - } - - /** - * Initialize object and set route - * - * Note: on Windows, use the interface NET_LUID in hexadecimal as the - * "device name." - * - * @param target Route target (e.g. 0.0.0.0/0 for default) - * @param via Route next L3 hop or NULL InetAddress if local in which case it will be routed via device - * @param device Name or hex LUID of ZeroTier device (e.g. zt#) - * @return True if route was successfully set - */ - inline bool set(const InetAddress &target,const InetAddress &via,const char *device) - { - if ((!via)&&(!device[0])) - return false; - this->remove(); - _target = target; - _via = via; - Utils::scopy(_device,sizeof(_device),device); - return this->sync(); - } - /** * Set or update currently set route * @@ -93,13 +65,14 @@ public: 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 - bool _applied; + + AtomicCounter __refCount; }; } // namespace ZeroTier diff --git a/osdep/NeighborDiscovery.cpp b/osdep/NeighborDiscovery.cpp new file mode 100644 index 0000000..4f63631 --- /dev/null +++ b/osdep/NeighborDiscovery.cpp @@ -0,0 +1,264 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "NeighborDiscovery.hpp" +#include "OSUtils.hpp" + +#include "../include/ZeroTierOne.h" + +#include + +namespace ZeroTier { + +uint16_t calc_checksum (uint16_t *addr, int len) +{ + int count = len; + uint32_t sum = 0; + uint16_t answer = 0; + + // Sum up 2-byte values until none or only one byte left. + while (count > 1) { + sum += *(addr++); + count -= 2; + } + + // Add left-over byte, if any. + if (count > 0) { + sum += *(uint8_t *) addr; + } + + // Fold 32-bit sum into 16 bits; we lose information by doing this, + // increasing the chances of a collision. + // sum = (lower 16 bits) + (upper 16 bits shifted right 16 bits) + while (sum >> 16) { + sum = (sum & 0xffff) + (sum >> 16); + } + + // Checksum is one's compliment of sum. + answer = ~sum; + + return (answer); +} + +struct _pseudo_header { + uint8_t sourceAddr[16]; + uint8_t targetAddr[16]; + uint32_t length; + uint8_t zeros[3]; + uint8_t next; // 58 +}; + +struct _option { + _option(int optionType) + : type(optionType) + , length(8) + { + memset(mac, 0, sizeof(mac)); + } + + uint8_t type; + uint8_t length; + uint8_t mac[6]; +}; + +struct _neighbor_solicitation { + _neighbor_solicitation() + : type(135) + , code(0) + , checksum(0) + , option(1) + { + memset(&reserved, 0, sizeof(reserved)); + memset(target, 0, sizeof(target)); + } + + void calculateChecksum(const sockaddr_storage &sourceIp, const sockaddr_storage &destIp) { + _pseudo_header ph; + memset(&ph, 0, sizeof(_pseudo_header)); + const sockaddr_in6 *src = (const sockaddr_in6*)&sourceIp; + const sockaddr_in6 *dest = (const sockaddr_in6*)&destIp; + + memcpy(ph.sourceAddr, &src->sin6_addr, sizeof(struct in6_addr)); + memcpy(ph.targetAddr, &dest->sin6_addr, sizeof(struct in6_addr)); + ph.next = 58; + ph.length = htonl(sizeof(_neighbor_solicitation)); + + size_t len = sizeof(_pseudo_header) + sizeof(_neighbor_solicitation); + uint8_t *tmp = (uint8_t*)malloc(len); + memcpy(tmp, &ph, sizeof(_pseudo_header)); + memcpy(tmp+sizeof(_pseudo_header), this, sizeof(_neighbor_solicitation)); + + checksum = calc_checksum((uint16_t*)tmp, (int)len); + + free(tmp); + tmp = NULL; + } + + uint8_t type; // 135 + uint8_t code; // 0 + uint16_t checksum; + uint32_t reserved; + uint8_t target[16]; + _option option; +}; + +struct _neighbor_advertisement { + _neighbor_advertisement() + : type(136) + , code(0) + , checksum(0) + , rso(0x40) + , option(2) + { + memset(padding, 0, sizeof(padding)); + memset(target, 0, sizeof(target)); + } + + void calculateChecksum(const sockaddr_storage &sourceIp, const sockaddr_storage &destIp) { + _pseudo_header ph; + memset(&ph, 0, sizeof(_pseudo_header)); + const sockaddr_in6 *src = (const sockaddr_in6*)&sourceIp; + const sockaddr_in6 *dest = (const sockaddr_in6*)&destIp; + + memcpy(ph.sourceAddr, &src->sin6_addr, sizeof(struct in6_addr)); + memcpy(ph.targetAddr, &dest->sin6_addr, sizeof(struct in6_addr)); + ph.next = 58; + ph.length = htonl(sizeof(_neighbor_advertisement)); + + size_t len = sizeof(_pseudo_header) + sizeof(_neighbor_advertisement); + uint8_t *tmp = (uint8_t*)malloc(len); + memcpy(tmp, &ph, sizeof(_pseudo_header)); + memcpy(tmp+sizeof(_pseudo_header), this, sizeof(_neighbor_advertisement)); + + checksum = calc_checksum((uint16_t*)tmp, (int)len); + + free(tmp); + tmp = NULL; + } + + uint8_t type; // 136 + uint8_t code; // 0 + uint16_t checksum; + uint8_t rso; + uint8_t padding[3]; + uint8_t target[16]; + _option option; +}; + +NeighborDiscovery::NeighborDiscovery() + : _cache(256) + , _lastCleaned(OSUtils::now()) +{} + +void NeighborDiscovery::addLocal(const sockaddr_storage &address, const MAC &mac) +{ + _NDEntry &e = _cache[InetAddress(address)]; + e.lastQuerySent = 0; + e.lastResponseReceived = 0; + e.mac = mac; + e.local = true; +} + +void NeighborDiscovery::remove(const sockaddr_storage &address) +{ + _cache.erase(InetAddress(address)); +} + +sockaddr_storage NeighborDiscovery::processIncomingND(const uint8_t *nd, unsigned int len, const sockaddr_storage &localIp, uint8_t *response, unsigned int &responseLen, MAC &responseDest) +{ + assert(sizeof(_neighbor_solicitation) == 28); + assert(sizeof(_neighbor_advertisement) == 32); + + const uint64_t now = OSUtils::now(); + sockaddr_storage ip = ZT_SOCKADDR_NULL; + + if (len >= sizeof(_neighbor_solicitation) && nd[0] == 0x87) { + // respond to Neighbor Solicitation request for local address + _neighbor_solicitation solicitation; + memcpy(&solicitation, nd, len); + InetAddress targetAddress(solicitation.target, 16, 0); + _NDEntry *targetEntry = _cache.get(targetAddress); + if (targetEntry && targetEntry->local) { + _neighbor_advertisement adv; + targetEntry->mac.copyTo(adv.option.mac, 6); + memcpy(adv.target, solicitation.target, 16); + adv.calculateChecksum(localIp, targetAddress); + memcpy(response, &adv, sizeof(_neighbor_advertisement)); + responseLen = sizeof(_neighbor_advertisement); + responseDest.setTo(solicitation.option.mac, 6); + } + } else if (len >= sizeof(_neighbor_advertisement) && nd[0] == 0x88) { + _neighbor_advertisement adv; + memcpy(&adv, nd, len); + InetAddress responseAddress(adv.target, 16, 0); + _NDEntry *queryEntry = _cache.get(responseAddress); + if(queryEntry && !queryEntry->local && (now - queryEntry->lastQuerySent <= ZT_ND_QUERY_MAX_TTL)) { + queryEntry->lastResponseReceived = now; + queryEntry->mac.setTo(adv.option.mac, 6); + ip = responseAddress; + } + } + + if ((now - _lastCleaned) >= ZT_ND_EXPIRE) { + _lastCleaned = now; + Hashtable::Iterator i(_cache); + InetAddress *k = NULL; + _NDEntry *v = NULL; + while (i.next(k, v)) { + if(!v->local && (now - v->lastResponseReceived) >= ZT_ND_EXPIRE) { + _cache.erase(*k); + } + } + } + + return ip; +} + +MAC NeighborDiscovery::query(const MAC &localMac, const sockaddr_storage &localIp, const sockaddr_storage &targetIp, uint8_t *query, unsigned int &queryLen, MAC &queryDest) +{ + const uint64_t now = OSUtils::now(); + + InetAddress localAddress(localIp); + localAddress.setPort(0); + InetAddress targetAddress(targetIp); + targetAddress.setPort(0); + + _NDEntry &e = _cache[targetAddress]; + + if ( (e.mac && ((now - e.lastResponseReceived) >= (ZT_ND_EXPIRE / 3))) || + (!e.mac && ((now - e.lastQuerySent) >= ZT_ND_QUERY_INTERVAL))) { + e.lastQuerySent = now; + + _neighbor_solicitation ns; + memcpy(ns.target, targetAddress.rawIpData(), 16); + localMac.copyTo(ns.option.mac, 6); + ns.calculateChecksum(localIp, targetIp); + if (e.mac) { + queryDest = e.mac; + } else { + queryDest = (uint64_t)0xffffffffffffULL; + } + } else { + queryLen = 0; + queryDest.zero(); + } + + return e.mac; +} + +} diff --git a/osdep/NeighborDiscovery.hpp b/osdep/NeighborDiscovery.hpp new file mode 100644 index 0000000..47831bd --- /dev/null +++ b/osdep/NeighborDiscovery.hpp @@ -0,0 +1,76 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ZT_NEIGHBORDISCOVERY_HPP +#define ZT_NEIGHBORDISCOVERY_HPP + +#include "../node/Hashtable.hpp" +#include "../node/MAC.hpp" +#include "../node/InetAddress.hpp" + + +#define ZT_ND_QUERY_INTERVAL 2000 + +#define ZT_ND_QUERY_MAX_TTL 5000 + +#define ZT_ND_EXPIRE 600000 + + +namespace ZeroTier { + +class NeighborDiscovery +{ +public: + NeighborDiscovery(); + + /** + * Set a local IP entry that we should respond to Neighbor Requests withPrefix64k + * + * @param mac Our local MAC address + * @param ip Our IPv6 address + */ + void addLocal(const sockaddr_storage &address, const MAC &mac); + + /** + * Delete a local IP entry or cached Neighbor entry + * + * @param address IPv6 address to remove + */ + void remove(const sockaddr_storage &address); + + sockaddr_storage processIncomingND(const uint8_t *nd, unsigned int len, const sockaddr_storage &localIp, uint8_t *response, unsigned int &responseLen, MAC &responseDest); + + MAC query(const MAC &localMac, const sockaddr_storage &localIp, const sockaddr_storage &targetIp, uint8_t *query, unsigned int &queryLen, MAC &queryDest); + +private: + struct _NDEntry + { + _NDEntry() : lastQuerySent(0), lastResponseReceived(0), mac(), local(false) {} + uint64_t lastQuerySent; + uint64_t lastResponseReceived; + MAC mac; + bool local; + }; + + Hashtable _cache; + uint64_t _lastCleaned; +}; + +} // namespace ZeroTier + +#endif diff --git a/osdep/OSUtils.cpp b/osdep/OSUtils.cpp index 3a04308..fd5efed 100644 --- a/osdep/OSUtils.cpp +++ b/osdep/OSUtils.cpp @@ -23,6 +23,7 @@ #include #include "../node/Constants.hpp" +#include "../node/Utils.hpp" #ifdef __UNIX_LIKE__ #include @@ -72,7 +73,7 @@ bool OSUtils::redirectUnixOutputs(const char *stdoutPath,const char *stderrPath) } #endif // __UNIX_LIKE__ -std::vector OSUtils::listDirectory(const char *path) +std::vector OSUtils::listDirectory(const char *path,bool includeDirectories) { std::vector r; @@ -81,7 +82,7 @@ std::vector OSUtils::listDirectory(const char *path) 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)) + if ( (strcmp(ffd.cFileName,".")) && (strcmp(ffd.cFileName,"..")) && (((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)||(((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)&&(includeDirectories))) ) r.push_back(std::string(ffd.cFileName)); } while (FindNextFileA(hFind,&ffd)); FindClose(hFind); @@ -97,7 +98,7 @@ std::vector OSUtils::listDirectory(const char *path) if (readdir_r(d,&de,&dptr)) break; if (dptr) { - if ((strcmp(dptr->d_name,"."))&&(strcmp(dptr->d_name,".."))&&(dptr->d_type != DT_DIR)) + if ((strcmp(dptr->d_name,"."))&&(strcmp(dptr->d_name,".."))&&((dptr->d_type != DT_DIR)||(includeDirectories))) r.push_back(std::string(dptr->d_name)); } else break; } @@ -107,6 +108,111 @@ std::vector OSUtils::listDirectory(const char *path) 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__ @@ -171,34 +277,6 @@ int64_t OSUtils::getFileSize(const char *path) return -1; } -std::vector OSUtils::resolve(const char *name) -{ - std::vector r; - std::vector::iterator i; - InetAddress tmp; - struct addrinfo *ai = (struct addrinfo *)0,*p; - if (!getaddrinfo(name,(const char *)0,(const struct addrinfo *)0,&ai)) { - try { - p = ai; - while (p) { - if ((p->ai_addr)&&((p->ai_addr->sa_family == AF_INET)||(p->ai_addr->sa_family == AF_INET6))) { - tmp = *(p->ai_addr); - for(i=r.begin();i!=r.end();++i) { - if (i->ipsEqual(tmp)) - goto skip_add_inetaddr; - } - r.push_back(tmp); - } -skip_add_inetaddr: - p = p->ai_next; - } - } catch ( ... ) {} - freeaddrinfo(ai); - } - std::sort(r.begin(),r.end()); - return r; -} - bool OSUtils::readFile(const char *path,std::string &buf) { char tmp[1024]; @@ -231,6 +309,50 @@ bool OSUtils::writeFile(const char *path,const void *buf,unsigned int len) 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__ @@ -267,6 +389,81 @@ std::string OSUtils::platformDefaultHomePath() #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 }; diff --git a/osdep/OSUtils.hpp b/osdep/OSUtils.hpp index 25bed9f..b84d5d2 100644 --- a/osdep/OSUtils.hpp +++ b/osdep/OSUtils.hpp @@ -45,6 +45,8 @@ #include #endif +#include "../ext/json/json.hpp" + namespace ZeroTier { /** @@ -102,12 +104,29 @@ public: /** * List a directory's contents * - * This returns only files, not sub-directories. - * * @param path Path to list - * @return Names of files in directory + * @param includeDirectories If true, include directories as well as files + * @return Names of files in directory (without path prepended) */ - static std::vector listDirectory(const char *path); + static std::vector listDirectory(const char *path,bool includeDirectories = false); + + /** + * Clean a directory of files whose last modified time is older than this + * + * This ignores directories, symbolic links, and other special files. + * + * @param olderThan Last modified older than timestamp (ms since epoch) + * @return Number of cleaned files or negative on fatal error + */ + static long cleanDirectory(const char *path,const uint64_t olderThan); + + /** + * Delete a directory and all its files and subdirectories recursively + * + * @param path Path to delete + * @return True on success + */ + static bool rmDashRf(const char *path); /** * Set modes on a file to something secure @@ -220,6 +239,17 @@ public: */ 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 * @@ -240,6 +270,13 @@ public: */ 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]; }; diff --git a/osdep/OSXEthernetTap.cpp b/osdep/OSXEthernetTap.cpp index b358092..f70908b 100644 --- a/osdep/OSXEthernetTap.cpp +++ b/osdep/OSXEthernetTap.cpp @@ -314,7 +314,7 @@ OSXEthernetTap::OSXEthernetTap( unsigned int metric, uint64_t nwid, const char *friendlyName, - void (*handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *data,unsigned int len), + void (*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), @@ -352,20 +352,33 @@ OSXEthernetTap::OSXEthernetTap( } // Try to reopen the last device we had, if we had one and it's still unused. + std::map globalDeviceMap; + FILE *devmapf = fopen((_homePath + ZT_PATH_SEPARATOR_S + "devicemap").c_str(),"r"); + if (devmapf) { + char buf[256]; + while (fgets(buf,sizeof(buf),devmapf)) { + char *x = (char *)0; + char *y = (char *)0; + char *saveptr = (char *)0; + for(char *f=Utils::stok(buf,"\r\n=",&saveptr);(f);f=Utils::stok((char *)0,"\r\n=",&saveptr)) { + if (!x) x = f; + else if (!y) y = f; + else break; + } + if ((x)&&(y)&&(x[0])&&(y[0])) + globalDeviceMap[x] = y; + } + fclose(devmapf); + } bool recalledDevice = false; - std::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; - } + std::map::const_iterator gdmEntry = globalDeviceMap.find(nwids); + if (gdmEntry != globalDeviceMap.end()) { + std::string devpath("/dev/"); devpath.append(gdmEntry->second); + if (stat(devpath.c_str(),&stattmp) == 0) { + _fd = ::open(devpath.c_str(),O_RDWR); + if (_fd > 0) { + _dev = gdmEntry->second; + recalledDevice = true; } } } @@ -420,9 +433,16 @@ OSXEthernetTap::OSXEthernetTap( ++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()); + globalDeviceMap[nwids] = _dev; + devmapf = fopen((_homePath + ZT_PATH_SEPARATOR_S + "devicemap").c_str(),"w"); + if (devmapf) { + gdmEntry = globalDeviceMap.begin(); + while (gdmEntry != globalDeviceMap.end()) { + fprintf(devmapf,"%s=%s\n",gdmEntry->first.c_str(),gdmEntry->second.c_str()); + ++gdmEntry; + } + fclose(devmapf); + } _thread = Thread::start(this); } @@ -646,7 +666,7 @@ void OSXEthernetTap::threadMain() from.setTo(getBuf + 6,6); unsigned int etherType = ntohs(((const uint16_t *)getBuf)[6]); // TODO: VLAN support - _handler(_arg,_nwid,from,to,etherType,0,(const void *)(getBuf + 14),r - 14); + _handler(_arg,(void *)0,_nwid,from,to,etherType,0,(const void *)(getBuf + 14),r - 14); } r = 0; diff --git a/osdep/OSXEthernetTap.hpp b/osdep/OSXEthernetTap.hpp index de48f9a..5a96c21 100644 --- a/osdep/OSXEthernetTap.hpp +++ b/osdep/OSXEthernetTap.hpp @@ -48,7 +48,7 @@ public: unsigned int metric, uint64_t nwid, const char *friendlyName, - void (*handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int), + void (*handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int), void *arg); ~OSXEthernetTap(); @@ -67,7 +67,7 @@ public: throw(); private: - void (*_handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int); + 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; diff --git a/osdep/Thread.hpp b/osdep/Thread.hpp index 7fb38d8..227c2cf 100644 --- a/osdep/Thread.hpp +++ b/osdep/Thread.hpp @@ -28,6 +28,7 @@ #include #include #include + #include "../node/Mutex.hpp" namespace ZeroTier { @@ -125,9 +126,18 @@ public: 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() { @@ -157,7 +167,7 @@ public: { Thread t; t._started = true; - if (pthread_create(&t._tid,(const pthread_attr_t *)0,&___zt_threadMain,instance)) + if (pthread_create(&t._tid,&t._tattr,&___zt_threadMain,instance)) throw std::runtime_error("pthread_create() failed, unable to create thread"); return t; } @@ -184,6 +194,7 @@ public: private: pthread_t _tid; + pthread_attr_t _tattr; volatile bool _started; }; diff --git a/osdep/WindowsEthernetTap.cpp b/osdep/WindowsEthernetTap.cpp index 7e1a5a1..79b9d35 100644 --- a/osdep/WindowsEthernetTap.cpp +++ b/osdep/WindowsEthernetTap.cpp @@ -456,7 +456,7 @@ WindowsEthernetTap::WindowsEthernetTap( unsigned int metric, uint64_t nwid, const char *friendlyName, - void (*handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int), + void (*handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int), void *arg) : _handler(handler), _arg(arg), @@ -599,10 +599,10 @@ WindowsEthernetTap::WindowsEthernetTap( 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); - DWORD tmp = mtu; - RegSetKeyValueA(nwAdapters,mySubkeyName.c_str(),"MTU",REG_DWORD,(LPCVOID)&tmp,sizeof(tmp)); + tmpsl = Utils::snprintf(tmps, sizeof(tmps), "%d", mtu); + RegSetKeyValueA(nwAdapters,mySubkeyName.c_str(),"MTU",REG_SZ,tmps,tmpsl); - tmp = 0; + 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)); @@ -640,7 +640,7 @@ WindowsEthernetTap::WindowsEthernetTap( if (ConvertInterfaceGuidToLuid(&_deviceGuid,&_deviceLuid) != NO_ERROR) throw std::runtime_error("unable to convert device interface GUID to LUID"); - _initialized = true; + //_initialized = true; if (friendlyName) setFriendlyName(friendlyName); @@ -672,6 +672,7 @@ 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; @@ -682,6 +683,9 @@ bool WindowsEthernetTap::addIp(const InetAddress &ip) 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)); @@ -713,18 +717,20 @@ bool WindowsEthernetTap::removeIp(const InetAddress &ip) DeleteUnicastIpAddressEntry(&(ipt->Table[i])); FreeMibTable(ipt); - 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; - } - } + 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; } @@ -1001,6 +1007,10 @@ void WindowsEthernetTap::threadMain() 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) @@ -1048,8 +1058,7 @@ void WindowsEthernetTap::threadMain() MAC from(tapReadBuf + 6,6); unsigned int etherType = ((((unsigned int)tapReadBuf[12]) & 0xff) << 8) | (((unsigned int)tapReadBuf[13]) & 0xff); try { - // TODO: decode vlans - _handler(_arg,_nwid,from,to,etherType,0,tapReadBuf + 14,bytesRead - 14); + _handler(_arg,(void *)0,_nwid,from,to,etherType,0,tapReadBuf + 14,bytesRead - 14); } catch ( ... ) {} // handlers should not throw } } @@ -1195,15 +1204,18 @@ void WindowsEthernetTap::_syncIps() CreateUnicastIpAddressEntry(&ipr); } - std::string ipStr(aip->toString()); - 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); - } + 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); + } + } } } diff --git a/osdep/WindowsEthernetTap.hpp b/osdep/WindowsEthernetTap.hpp index 0bbb17d..f2cf73f 100644 --- a/osdep/WindowsEthernetTap.hpp +++ b/osdep/WindowsEthernetTap.hpp @@ -87,7 +87,7 @@ public: unsigned int metric, uint64_t nwid, const char *friendlyName, - void (*handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int), + void (*handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int), void *arg); ~WindowsEthernetTap(); @@ -110,13 +110,15 @@ public: 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 *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int); + 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; diff --git a/root-watcher/README.md b/root-watcher/README.md new file mode 100644 index 0000000..ded6a63 --- /dev/null +++ b/root-watcher/README.md @@ -0,0 +1,8 @@ +Root Server Watcher +====== + +This is a small daemon written in NodeJS that watches a set of root servers and records peer status information into a Postgres database. + +To use type `npm install` to install modules. Then edit `config.json.example` and rename to `config.json`. For each of your roots you will need to configure a way for this script to reach it. You will also need to use `schema.sql` to initialize a Postgres database to contain your logs and set it up in `config.json` as well. + +This doesn't (yet) include any software for reading the log database and doing anything useful with the information inside, though given that it's a simple SQL database it should not be hard to compose queries to show interesting statistics. diff --git a/root-watcher/config.json.example b/root-watcher/config.json.example new file mode 100644 index 0000000..0ad1bbe --- /dev/null +++ b/root-watcher/config.json.example @@ -0,0 +1,30 @@ +{ + "interval": 30000, + "dbSaveInterval": 60000, + "peerTimeout": 600000, + "db": { + "database": "ztr", + "user": "postgres", + "password": "s00p3rs3kr3t", + "host": "127.0.0.1", + "port": 5432, + "max": 16, + "idleTimeoutMillis": 30000 + }, + "roots": { + "my-root-01": { + "id": 1, + "ip": "10.0.0.1", + "port": 9993, + "authToken": "foobarbaz", + "peers": "/peer" + }, + "my-root-02": { + "id": 2, + "ip": "10.0.0.2", + "port": 9993, + "authToken": "lalafoo", + "peers": "/peer" + } + } +} diff --git a/root-watcher/package.json b/root-watcher/package.json new file mode 100644 index 0000000..d6e86d7 --- /dev/null +++ b/root-watcher/package.json @@ -0,0 +1,16 @@ +{ + "name": "zerotier-root-watcher", + "version": "1.0.0", + "description": "Simple background service to watch a cluster of roots and record peer info into a database", + "main": "zerotier-root-watcher.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "ZeroTier, Inc. ", + "license": "GPL-3.0", + "dependencies": { + "async": "^2.3.0", + "pg": "^6.1.5", + "zlib": "^1.0.5" + } +} diff --git a/root-watcher/schema.sql b/root-watcher/schema.sql new file mode 100644 index 0000000..bdb3a1c --- /dev/null +++ b/root-watcher/schema.sql @@ -0,0 +1,21 @@ +/* Schema for ZeroTier root watcher log database */ + +/* If you cluster this DB using any PG clustering scheme that uses logs, you must remove UNLOGGED here! */ +CREATE UNLOGGED TABLE "Peer" +( + "ztAddress" BIGINT NOT NULL, + "timestamp" BIGINT NOT NULL, + "versionMajor" INTEGER NOT NULL, + "versionMinor" INTEGER NOT NULL, + "versionRev" INTEGER NOT NULL, + "rootId" INTEGER NOT NULL, + "phyPort" INTEGER NOT NULL, + "phyLinkQuality" REAL NOT NULL, + "phyLastReceive" BIGINT NOT NULL, + "phyAddress" INET NOT NULL +); + +CREATE INDEX "Peer_ztAddress" ON "Peer" ("ztAddress"); +CREATE INDEX "Peer_timestamp" ON "Peer" ("timestamp"); +CREATE INDEX "Peer_rootId" ON "Peer" ("rootId"); +CREATE INDEX "Peer_phyAddress" ON "Peer" ("phyAddress"); diff --git a/root-watcher/zerotier-root-watcher.js b/root-watcher/zerotier-root-watcher.js new file mode 100644 index 0000000..d4607fc --- /dev/null +++ b/root-watcher/zerotier-root-watcher.js @@ -0,0 +1,235 @@ +'use strict'; + +const pg = require('pg'); +const zlib = require('zlib'); +const http = require('http'); +const fs = require('fs'); +const async = require('async'); + +const config = JSON.parse(fs.readFileSync('./config.json')); +const roots = config.roots||{}; + +const db = new pg.Pool(config.db); + +process.on('uncaughtException',function(err) { + console.error('ERROR: uncaught exception: '+err); + if (err.stack) + console.error(err.stack); +}); + +function httpRequest(host,port,authToken,method,path,args,callback) +{ + var responseBody = []; + var postData = (args) ? JSON.stringify(args) : null; + + var req = http.request({ + host: host, + port: port, + path: path, + method: method, + headers: { + 'X-ZT1-Auth': (authToken||''), + 'Content-Length': (postData) ? postData.length : 0 + } + },function(res) { + res.on('data',function(chunk) { + if ((chunk)&&(chunk.length > 0)) + responseBody.push(chunk); + }); + res.on('timeout',function() { + try { + if (typeof callback === 'function') { + var cb = callback; + callback = null; + cb(new Error('connection timed out'),null); + } + req.abort(); + } catch (e) {} + }); + res.on('error',function(e) { + try { + if (typeof callback === 'function') { + var cb = callback; + callback = null; + cb(new Error('connection timed out'),null); + } + req.abort(); + } catch (e) {} + }); + res.on('end',function() { + if (typeof callback === 'function') { + var cb = callback; + callback = null; + if (responseBody.length === 0) { + return cb(null,{}); + } else { + responseBody = Buffer.concat(responseBody); + + if (responseBody.length < 2) { + return cb(null,{}); + } + + if ((responseBody.readUInt8(0,true) === 0x1f)&&(responseBody.readUInt8(1,true) === 0x8b)) { + try { + responseBody = zlib.gunzipSync(responseBody); + } catch (e) { + return cb(e,null); + } + } + + try { + return cb(null,JSON.parse(responseBody)); + } catch (e) { + return cb(e,null); + } + } + } + }); + }).on('error',function(e) { + try { + if (typeof callback === 'function') { + var cb = callback; + callback = null; + cb(e,null); + } + req.abort(); + } catch (e) {} + }).on('timeout',function() { + try { + if (typeof callback === 'function') { + var cb = callback; + callback = null; + cb(new Error('connection timed out'),null); + } + req.abort(); + } catch (e) {} + }); + + req.setTimeout(30000); + req.setNoDelay(true); + + if (postData !== null) + req.end(postData); + else req.end(); +}; + +var peerStatus = {}; + +function saveToDb() +{ + db.connect(function(err,client,clientDone) { + if (err) { + console.log('WARNING: database error writing peers: '+err.toString()); + clientDone(); + return setTimeout(saveToDb,config.dbSaveInterval||60000); + } + client.query('BEGIN',function(err) { + if (err) { + console.log('WARNING: database error writing peers: '+err.toString()); + clientDone(); + return setTimeout(saveToDb,config.dbSaveInterval||60000); + } + let timeout = Date.now() - (config.peerTimeout||600000); + let wtotal = 0; + async.eachSeries(Object.keys(peerStatus),function(address,nextAddress) { + let s = peerStatus[address]; + if (s[1] <= timeout) { + delete peerStatus[address]; + return process.nextTick(nextAddress); + } else { + ++wtotal; + client.query('INSERT INTO "Peer" ("ztAddress","timestamp","versionMajor","versionMinor","versionRev","rootId","phyPort","phyLinkQuality","phyLastReceive","phyAddress") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10)',s,nextAddress); + } + },function(err) { + if (err) + console.log('WARNING database error writing peers: '+err.toString()); + console.log(Date.now().toString()+' '+wtotal); + client.query('COMMIT',function(err,result) { + clientDone(); + return setTimeout(saveToDb,config.dbSaveInterval||60000); + }); + }); + }); + }); +}; + +function doRootUpdate(name,id,ip,port,peersPath,authToken,interval) +{ + httpRequest(ip,port,authToken,"GET",peersPath,null,function(err,res) { + if (err) { + console.log('WARNING: cannot reach '+name+peersPath+' (will try again in 1s): '+err.toString()); + return setTimeout(function() { doRootUpdate(name,id,ip,port,peersPath,authToken,interval); },1000); + } + if (!Array.isArray(res)) { + console.log('WARNING: cannot reach '+name+peersPath+' (will try again in 1s): response is not an array of peers'); + return setTimeout(function() { doRootUpdate(name,id,ip,port,peersPath,authToken,interval); },1000); + } + + //console.log(name+': '+res.length+' peer entries.'); + let now = Date.now(); + let count = 0; + for(let pi=0;pi 0)) { + let bestPath = null; + for(let i=0;i 0)&&((!bestPath)||(bestPath.lastReceive < lr))) + bestPath = paths[i]; + } + } + + if (bestPath) { + let a = bestPath.address; + if (typeof a === 'string') { + let a2 = a.split('/'); + if (a2.length === 2) { + let vmaj = peer.versionMajor; + if ((typeof vmaj === 'undefined')||(vmaj === null)) vmaj = -1; + let vmin = peer.versionMinor; + if ((typeof vmin === 'undefined')||(vmin === null)) vmin = -1; + let vrev = peer.versionRev; + if ((typeof vrev === 'undefined')||(vrev === null)) vrev = -1; + let lr = parseInt(bestPath.lastReceive)||0; + + let s = peerStatus[address]; + if ((!s)||(s[8] < lr)) { + peerStatus[address] = [ + ztAddress, + now, + vmaj, + vmin, + vrev, + id, + parseInt(a2[1])||0, + parseFloat(bestPath.linkQuality)||1.0, + lr, + a2[0] + ]; + } + ++count; + } + } + } + } + } + + console.log(name+': '+count+' peers with active direct paths.'); + return setTimeout(function() { doRootUpdate(name,id,ip,port,peersPath,authToken,interval); },interval); + }); +}; + +for(var r in roots) { + var rr = roots[r]; + if (rr.peers) + doRootUpdate(r,rr.id,rr.ip,rr.port,rr.peers,rr.authToken||null,config.interval||60000); +} + +return setTimeout(saveToDb,config.dbSaveInterval||60000); diff --git a/rule-compiler/README.md b/rule-compiler/README.md new file mode 100644 index 0000000..1ceeb71 --- /dev/null +++ b/rule-compiler/README.md @@ -0,0 +1,8 @@ +ZeroTier Rules Compiler +====== + +This script converts ZeroTier rules in human-readable format into rules suitable for import into a ZeroTier network controller. It's the script that is used in the rules editor on [ZeroTier Central](https://my.zerotier.com/). + +A command line interface is included that may be invoked as: `node cli.js `. + +See the [manual](https://www.zerotier.com/manual.shtml) for information about the rules engine and rules script syntax. diff --git a/rule-compiler/cli.js b/rule-compiler/cli.js new file mode 100644 index 0000000..75235ac --- /dev/null +++ b/rule-compiler/cli.js @@ -0,0 +1,47 @@ +'use strict'; + +var fs = require('fs'); +var RuleCompiler = require('./rule-compiler.js'); + +if (process.argv.length < 3) { + console.log('Usage: node cli.js '); + process.exit(1); +} + +var rules = []; +var caps = {}; +var tags = {}; +var err = RuleCompiler.compile(fs.readFileSync(process.argv[2]).toString(),rules,caps,tags); + +if (err) { + console.error('ERROR parsing '+process.argv[2]+' line '+err[0]+' column '+err[1]+': '+err[2]); + process.exit(1); +} else { + let capsArray = []; + let capabilitiesByName = {}; + for(let n in caps) { + capsArray.push(caps[n]); + capabilitiesByName[n] = caps[n].id; + } + let tagsArray = []; + for(let n in tags) { + let t = tags[n]; + let dfl = t['default']; + tagsArray.push({ + 'id': t.id, + 'default': (((dfl)||(dfl === 0)) ? dfl : null) + }); + } + + console.log(JSON.stringify({ + config: { + rules: rules, + capabilities: capsArray, + tags: tagsArray + }, + capabilitiesByName: capabilitiesByName, + tagsByName: tags + },null,1)); + + process.exit(0); +} diff --git a/rule-compiler/examples/capabilities-and-tags.ztrules b/rule-compiler/examples/capabilities-and-tags.ztrules new file mode 100644 index 0000000..9b35f28 --- /dev/null +++ b/rule-compiler/examples/capabilities-and-tags.ztrules @@ -0,0 +1,40 @@ +# This is a default rule set that allows IPv4 and IPv6 traffic. +# You can edit as needed. If your rule set gets large we recommend +# cutting and pasting it somewhere to keep a backup. + +# Drop all Ethernet frame types that are not IPv4 or IPv6 +drop + not ethertype 0x0800 # IPv4 + not ethertype 0x0806 # IPv4 ARP + not ethertype 0x86dd # IPv6 +; + +# Capability: outgoing SSH +cap ssh + id 1000 + accept + ipprotocol tcp + dport 22 + ; +; + +# A tag indicating which department people belong to +tag department + id 1000 + enum 100 sales + enum 200 marketing + enum 300 accounting + enum 400 engineering +; + +# Accept all traffic between members of the same department +accept + tdiff department 0 +; + +# You can insert other drop, tee, etc. rules here. This rule +# set ends with a blanket accept, making it permissive by +# default. + +accept; + diff --git a/rule-compiler/package.json b/rule-compiler/package.json new file mode 100644 index 0000000..1db006b --- /dev/null +++ b/rule-compiler/package.json @@ -0,0 +1,18 @@ +{ + "name": "zerotier-rule-compiler", + "version": "1.2.2-2", + "description": "ZeroTier Rule Script Compiler", + "main": "cli.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com/zerotier/ZeroTierOne/rule-compiler" + }, + "keywords": [ + "ZeroTier" + ], + "author": "ZeroTier, Inc. ", + "license": "GPL-2.0" +} diff --git a/rule-compiler/rule-compiler.js b/rule-compiler/rule-compiler.js new file mode 100644 index 0000000..bd84824 --- /dev/null +++ b/rule-compiler/rule-compiler.js @@ -0,0 +1,904 @@ +'use strict'; + +// Names for bits in characteristics -- 0==LSB, 63==MSB +const CHARACTERISTIC_BITS = { + 'inbound': 63, + 'multicast': 62, + 'broadcast': 61, + 'ipauth': 60, + 'macauth': 59, + 'tcp_fin': 0, + 'tcp_syn': 1, + 'tcp_rst': 2, + 'tcp_psh': 3, + 'tcp_ack': 4, + 'tcp_urg': 5, + 'tcp_ece': 6, + 'tcp_cwr': 7, + 'tcp_ns': 8, + 'tcp_rs2': 9, + 'tcp_rs1': 10, + 'tcp_rs0': 11 +}; + +// Shorthand names for common ethernet types +const ETHERTYPES = { + 'ipv4': 0x0800, + 'arp': 0x0806, + 'wol': 0x0842, + 'rarp': 0x8035, + 'ipv6': 0x86dd, + 'atalk': 0x809b, + 'aarp': 0x80f3, + 'ipx_a': 0x8137, + 'ipx_b': 0x8138 +}; + +// Shorthand names for common IP protocols +const IP_PROTOCOLS = { + 'icmp': 0x01, + 'icmp4': 0x01, + 'icmpv4': 0x01, + 'igmp': 0x02, + 'ipip': 0x04, + 'tcp': 0x06, + 'egp': 0x08, + 'igp': 0x09, + 'udp': 0x11, + 'rdp': 0x1b, + 'esp': 0x32, + 'ah': 0x33, + 'icmp6': 0x3a, + 'icmpv6': 0x3a, + 'l2tp': 0x73, + 'sctp': 0x84, + 'udplite': 0x88 +}; + +// Keywords that open new blocks that must be terminated by a semicolon +const OPEN_BLOCK_KEYWORDS = { + 'macro': true, + 'tag': true, + 'cap': true, + 'drop': true, + 'accept': true, + 'tee': true, + 'watch': true, + 'redirect': true, + 'break': true +}; + +// Reserved words that can't be used as tag, capability, or rule set names +const RESERVED_WORDS = { + 'macro': true, + 'tag': true, + 'cap': true, + 'default': true, + + 'drop': true, + 'accept': true, + 'tee': true, + 'watch': true, + 'redirect': true, + 'break': true, + + 'ztsrc': true, + 'ztdest': true, + 'vlan': true, + 'vlanpcp': true, + 'vlandei': true, + 'ethertype': true, + 'macsrc': true, + 'macdest': true, + 'ipsrc': true, + 'ipdest': true, + 'iptos': true, + 'ipprotocol': true, + 'icmp': true, + 'sport': true, + 'dport': true, + 'chr': true, + 'framesize': true, + 'random': true, + 'tand': true, + 'tor': true, + 'txor': true, + 'tdiff': true, + 'teq': true, + 'tseq': true, + 'treq': true, + + 'type': true, + 'enum': true, + 'class': true, + 'define': true, + 'import': true, + 'include': true, + 'log': true, + 'not': true, + 'xor': true, + 'or': true, + 'and': true, + 'set': true, + 'var': true, + 'let': true +}; + +const KEYWORD_TO_API_MAP = { + 'drop': 'ACTION_DROP', + 'accept': 'ACTION_ACCEPT', + 'tee': 'ACTION_TEE', + 'watch': 'ACTION_WATCH', + 'redirect': 'ACTION_REDIRECT', + 'break': 'ACTION_BREAK', + + 'ztsrc': 'MATCH_SOURCE_ZEROTIER_ADDRESS', + 'ztdest': 'MATCH_DEST_ZEROTIER_ADDRESS', + 'vlan': 'MATCH_VLAN_ID', + 'vlanpcp': 'MATCH_VLAN_PCP', + 'vlandei': 'MATCH_VLAN_DEI', + 'ethertype': 'MATCH_ETHERTYPE', + 'macsrc': 'MATCH_MAC_SOURCE', + 'macdest': 'MATCH_MAC_DEST', + //'ipsrc': '', // special handling since we programmatically differentiate between V4 and V6 + //'ipdest': '', // special handling + 'iptos': 'MATCH_IP_TOS', + 'ipprotocol': 'MATCH_IP_PROTOCOL', + 'icmp': 'MATCH_ICMP', + 'sport': 'MATCH_IP_SOURCE_PORT_RANGE', + 'dport': 'MATCH_IP_DEST_PORT_RANGE', + 'chr': 'MATCH_CHARACTERISTICS', + 'framesize': 'MATCH_FRAME_SIZE_RANGE', + 'random': 'MATCH_RANDOM', + 'tand': 'MATCH_TAGS_BITWISE_AND', + 'tor': 'MATCH_TAGS_BITWISE_OR', + 'txor': 'MATCH_TAGS_BITWISE_XOR', + 'tdiff': 'MATCH_TAGS_DIFFERENCE', + 'teq': 'MATCH_TAGS_EQUAL', + 'tseq': 'MATCH_TAG_SENDER', + 'treq': 'MATCH_TAG_RECEIVER' +}; + +// Number of args for each match +const MATCH_ARG_COUNTS = { + 'ztsrc': 1, + 'ztdest': 1, + 'vlan': 1, + 'vlanpcp': 1, + 'vlandei': 1, + 'ethertype': 1, + 'macsrc': 1, + 'macdest': 1, + 'ipsrc': 1, + 'ipdest': 1, + 'iptos': 2, + 'ipprotocol': 1, + 'icmp': 2, + 'sport': 1, + 'dport': 1, + 'chr': 1, + 'framesize': 1, + 'random': 1, + 'tand': 2, + 'tor': 2, + 'txor': 2, + 'tdiff': 2, + 'teq': 2, + 'tseq': 2, + 'treq': 2 +}; + +// Regex of all alphanumeric characters in Unicode +const INTL_ALPHANUM_REGEX = new RegExp('[0-9A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]'); + +// Checks whether something is a valid capability, tag, or macro name +function _isValidName(n) +{ + if ((typeof n !== 'string')||(n.length === 0)) return false; + if ("0123456789".indexOf(n.charAt(0)) >= 0) return false; + for(let i=0;i 2)&&(n.substr(0,2) === '0x')) + n = parseInt(n.substr(2),16); + else n = parseInt(n,10); + return (((typeof n === 'number')&&(n !== null)&&(!isNaN(n))) ? n : -1); + } catch (e) { + return -1; + } +} + +function _cleanMac(m) +{ + m = m.toLowerCase(); + var m2 = ''; + for(let i=0;((i= 0) { + m2 += c; + if ((m2.length > 0)&&(m2.length !== 17)&&((m2.length & 1) === 0)) + m2 += ':'; + } + } + return m2; +} + +function _cleanHex(m) +{ + m = m.toLowerCase(); + var m2 = ''; + for(let i=0;i= 0) + m2 += c; + } + return m2; +} + +function _renderMatches(mtree,rules,macros,caps,tags,params) +{ + let not = false; + let or = false; + for(let k=0;k= mtree.length) + return [ mtree[k - 1][1],mtree[k - 1][2],'Missing argument(s) to match.' ]; + let arg = mtree[k][0]; + if ((typeof arg !== 'string')||(arg in RESERVED_WORDS)||(arg.length === 0)) + return [ mtree[k - 1][1],mtree[k - 1][2],'Missing argument(s) to match (invalid argument or argument is reserved word).' ]; + if (arg.charAt(0) === '$') { + let tmp = params[arg]; + if (typeof tmp === 'undefined') + return [ mtree[k][1],mtree[k][2],'Undefined variable name.' ]; + args.push([ tmp,mtree[k][1],mtree[k][2] ]); + } else { + args.push(mtree[k]); + } + } + + switch(match) { + case 'ztsrc': + case 'ztdest': { + let zt = _cleanHex(args[0][0]); + if (zt.length !== 10) + return [ args[0][1],args[0][2],'Invalid ZeroTier address.' ]; + rules.push({ + 'type': KEYWORD_TO_API_MAP[match], + 'not': not, + 'or': or, + 'zt': zt + }); + } break; + + case 'vlan': + case 'vlanpcp': + case 'vlandei': + case 'ethertype': + case 'ipprotocol': { + let num = null; + switch (match) { + case 'ethertype': num = ETHERTYPES[args[0][0]]; break; + case 'ipprotocol': num = IP_PROTOCOLS[args[0][0]]; break; + } + if (typeof num !== 'number') + num = _parseNum(args[0][0]); + if ((typeof num !== 'number')||(num < 0)||(num > 0xffffffff)||(num === null)) + return [ args[0][1],args[0][2],'Invalid numeric value.' ]; + let r = { + 'type': KEYWORD_TO_API_MAP[match], + 'not': not, + 'or': or + }; + switch(match) { + case 'vlan': r['vlanId'] = num; break; + case 'vlanpcp': r['vlanPcp'] = num; break; + case 'vlandei': r['vlanDei'] = num; break; + case 'ethertype': r['etherType'] = num; break; + case 'ipprotocol': r['ipProtocol'] = num; break; + } + rules.push(r); + } break; + + case 'random': { + let num = parseFloat(args[0][0])||0.0; + if (num < 0.0) num = 0.0; + if (num > 1.0) num = 1.0; + rules.push({ + 'type': KEYWORD_TO_API_MAP[match], + 'not': not, + 'or': or, + 'probability': Math.floor(4294967295 * num) + }); + } break; + + case 'macsrc': + case 'macdest': { + let mac = _cleanMac(args[0][0]); + if (mac.length !== 17) + return [ args[0][1],args[0][2],'Invalid MAC address.' ]; + rules.push({ + 'type': KEYWORD_TO_API_MAP[match], + 'not': not, + 'or': or, + 'mac': mac + }); + } break; + + case 'ipsrc': + case 'ipdest': { + let ip = args[0][0]; + let slashIdx = ip.indexOf('/'); + if (slashIdx <= 0) + return [ args[0][1],args[0][2],'Missing /bits netmask length designation in IP.' ]; + let ipOnly = ip.substr(0,slashIdx); + if (IPV6_REGEX.test(ipOnly)) { + rules.push({ + 'type': ((match === 'ipsrc') ? 'MATCH_IPV6_SOURCE' : 'MATCH_IPV6_DEST'), + 'not': not, + 'or': or, + 'ip': ip + }); + } else if (IPV4_REGEX.test(ipOnly)) { + rules.push({ + 'type': ((match === 'ipsrc') ? 'MATCH_IPV4_SOURCE' : 'MATCH_IPV4_DEST'), + 'not': not, + 'or': or, + 'ip': ip + }); + } else { + return [ args[0][1],args[0][2],'Invalid IP address (not valid IPv4 or IPv6).' ]; + } + } break; + + case 'icmp': { + let icmpType = _parseNum(args[0][0]); + if ((icmpType < 0)||(icmpType > 0xff)) + return [ args[0][1],args[0][2],'Missing or invalid ICMP type.' ]; + let icmpCode = _parseNum(args[1][0]); // -1 okay, indicates don't match code + if (icmpCode > 0xff) + return [ args[1][1],args[1][2],'Invalid ICMP code (use -1 for none).' ]; + rules.push({ + 'type': 'MATCH_ICMP', + 'not': not, + 'or': or, + 'icmpType': icmpType, + 'icmpCode': ((icmpCode < 0) ? null : icmpCode) + }); + } break; + + case 'sport': + case 'dport': + case 'framesize': { + let arg = args[0][0]; + let fn = null; + let tn = null; + if (arg.indexOf('-') > 0) { + let asplit = arg.split('-'); + if (asplit.length !== 2) { + return [ args[0][1],args[0][2],'Invalid numeric range.' ]; + } else { + fn = _parseNum(asplit[0]); + tn = _parseNum(asplit[1]); + } + } else { + fn = _parseNum(arg); + tn = fn; + } + if ((fn < 0)||(fn > 0xffff)||(tn < 0)||(tn > 0xffff)||(tn < fn)) + return [ args[0][1],args[0][2],'Invalid numeric range.' ]; + rules.push({ + 'type': KEYWORD_TO_API_MAP[match], + 'not': not, + 'or': or, + 'start': fn, + 'end': tn + }); + } break; + + case 'iptos': { + let mask = _parseNum(args[0][0]); + if ((typeof mask !== 'number')||(mask < 0)||(mask > 0xff)||(mask === null)) + return [ args[0][1],args[0][2],'Invalid mask.' ]; + let arg = args[1][0]; + let fn = null; + let tn = null; + if (arg.indexOf('-') > 0) { + let asplit = arg.split('-'); + if (asplit.length !== 2) { + return [ args[1][1],args[1][2],'Invalid value range.' ]; + } else { + fn = _parseNum(asplit[0]); + tn = _parseNum(asplit[1]); + } + } else { + fn = _parseNum(arg); + tn = fn; + } + if ((fn < 0)||(fn > 0xff)||(tn < 0)||(tn > 0xff)||(tn < fn)) + return [ args[1][1],args[1][2],'Invalid value range.' ]; + rules.push({ + 'type': 'MATCH_IP_TOS', + 'not': not, + 'or': or, + 'mask': mask, + 'start': fn, + 'end': tn + }); + } break; + + case 'chr': { + let chrb = args[0][0].split(/[,]+/); + let maskhi = 0; + let masklo = 0; + for(let i=0;i 0) { + let tmp = CHARACTERISTIC_BITS[chrb[i]]; + let bit = (typeof tmp === 'number') ? tmp : _parseNum(chrb[i]); + if ((bit < 0)||(bit > 63)) + return [ args[0][1],args[0][2],'Invalid bit index (range 0-63) or unrecognized name.' ]; + if (bit >= 32) + maskhi |= Math.abs(1 << (bit - 32)); + else masklo |= Math.abs(1 << bit); + } + } + maskhi = Math.abs(maskhi).toString(16); + while (maskhi.length < 8) maskhi = '0' + maskhi; + masklo = Math.abs(masklo).toString(16); + while (masklo.length < 8) masklo = '0' + masklo; + rules.push({ + 'type': 'MATCH_CHARACTERISTICS', + 'not': not, + 'or': or, + 'mask': (maskhi + masklo) + }); + } break; + + case 'tand': + case 'tor': + case 'txor': + case 'tdiff': + case 'teq': + case 'tseq': + case 'treq': { + let tag = tags[args[0][0]]; + let tagId = -1; + let tagValue = -1; + if (tag) { + tagId = tag.id; + tagValue = args[1][0]; + if (tagValue in tag.flags) + tagValue = tag.flags[tagValue]; + else if (tagValue in tag.enums) + tagValue = tag.enums[tagValue]; + else tagValue = _parseNum(tagValue); + } else { + tagId = _parseNum(args[0][0]); + tagValue = _parseNum(args[1][0]); + } + if ((tagId < 0)||(tagId > 0xffffffff)) + return [ args[0][1],args[0][2],'Undefined tag name and invalid tag value.' ]; + if ((tagValue < 0)||(tagValue > 0xffffffff)) + return [ args[1][1],args[1][2],'Invalid tag value or unrecognized flag/enum name.' ]; + rules.push({ + 'type': KEYWORD_TO_API_MAP[match], + 'not': not, + 'or': or, + 'id': tagId, + 'value': tagValue + }); + } break; + } + + not = false; + or = false; + } + } + return null; +} + +function _renderActions(rtree,rules,macros,caps,tags,params) +{ + for(let k=0;k= rtree.length) + return [ rtree[k][1],rtree[k][2],'Include directive is missing a macro name.' ]; + let macroName = rtree[k + 1][0]; + ++k; + + let macroParamArray = []; + let parenIdx = macroName.indexOf('('); + if (parenIdx > 0) { + let pns = macroName.substr(parenIdx + 1).split(/[,)]+/); + for(let k=0;k 0) + macroParamArray.push(pns[k]); + } + macroName = macroName.substr(0,parenIdx); + } + + let macro = macros[macroName]; + if (!macro) + return [ rtree[k][1],rtree[k][2],'Macro name not found.' ]; + let macroParams = {}; + for(let param in macro.params) { + let pidx = macro.params[param]; + if (pidx >= macroParamArray.length) + return [ rtree[k][1],rtree[k][2],'Missing one or more required macro parameter.' ]; + macroParams[param] = macroParamArray[pidx]; + } + + let err = _renderActions(macro.rules,rules,macros,caps,tags,macroParams); + if (err !== null) + return err; + } else if ((action === 'drop')||(action === 'accept')||(action === 'break')) { // actions without arguments + if (((k + 1) < rtree.length)&&(Array.isArray(rtree[k + 1][0]))) { + let mtree = rtree[k + 1]; ++k; + let err = _renderMatches(mtree,rules,macros,caps,tags,params); + if (err !== null) + return err; + } + rules.push({ + 'type': KEYWORD_TO_API_MAP[action] + }); + } else if ((action === 'tee')||(action === 'watch')) { // actions with arguments (ZeroTier address) + if (((k + 1) < rtree.length)&&(Array.isArray(rtree[k + 1][0]))&&(rtree[k + 1][0].length >= 2)) { + let mtree = rtree[k + 1]; ++k; + let maxLength = _parseNum(mtree[0][0]); + if ((maxLength < -1)||(maxLength > 0xffff)) + return [ mtree[0][1],mtree[1][2],'Tee/watch max packet length to forward invalid or out of range.' ]; + let target = mtree[1][0]; + if ((typeof target !== 'string')||(target.length !== 10)) + return [ mtree[1][1],mtree[1][2],'Missing or invalid ZeroTier address target for tee/watch.' ]; + let err = _renderMatches(mtree.slice(2),rules,macros,caps,tags,params); + if (err !== null) + return err; + rules.push({ + 'type': KEYWORD_TO_API_MAP[action], + 'address': target, + 'length': maxLength + }); + } else { + return [ rtree[k][1],rtree[k][2],'The tee and watch actions require two paremters (max length or 0 for all, target).' ]; + } + } else if (action === 'redirect') { + if (((k + 1) < rtree.length)&&(Array.isArray(rtree[k + 1][0]))&&(rtree[k + 1][0].length >= 1)) { + let mtree = rtree[k + 1]; ++k; + let target = mtree[0][0]; + if ((typeof target !== 'string')||(target.length !== 10)) + return [ mtree[0][1],mtree[0][2],'Missing or invalid ZeroTier address target for redirect.' ]; + let err = _renderMatches(mtree.slice(1),rules,macros,caps,tags,params); + if (err !== null) + return err; + rules.push({ + 'type': KEYWORD_TO_API_MAP[action], + 'address': target + }); + } else { + return [ rtree[k][1],rtree[k][2],'The redirect action requires a target parameter.' ]; + } + } else { + return [ rtree[k][1],rtree[k][2],'Unrecognized action or directive in rule set.' ]; + } + } + + return null; +} + +function compile(src,rules,caps,tags) +{ + try { + if (typeof src !== 'string') + return [ 0,0,'"src" parameter must be a string.' ]; + + // Pass 1: parse source into a tree of arrays of elements. Each element is a 3-item + // tuple consisting of string, line number, and character index in line to enable + // informative error messages to be returned. + + var blockStack = [ [] ]; + var curr = [ '',-1,-1 ]; + var skipRestOfLine = false; + for(let idx=0,lineNo=1,lineIdx=0;idx 0) { + let endOfBlock = false; + if (curr[0].charAt(curr[0].length - 1) === ';') { + endOfBlock = true; + curr[0] = curr[0].substr(0,curr[0].length - 1); + } + + if (curr[0].length > 0) { + blockStack[blockStack.length - 1].push(curr); + } + if ((endOfBlock)&&(blockStack.length > 1)&&(blockStack[blockStack.length - 1].length > 0)) { + blockStack[blockStack.length - 2].push(blockStack[blockStack.length - 1]); + blockStack.pop(); + } else if (curr[0] in OPEN_BLOCK_KEYWORDS) { + blockStack.push([]); + } + + curr = [ '',-1,-1 ]; + } + break; + default: + if (curr[0].length === 0) { + if (ch === '#') { + skipRestOfLine = true; + continue; + } else { + curr[1] = lineNo; + curr[2] = lineIdx; + } + } + curr[0] += ch; + break; + } + } + } + + if (curr[0].length > 0) { + if (curr[0].charAt(curr[0].length - 1) === ';') + curr[0] = curr[0].substr(0,curr[0].length - 1); + if (curr[0].length > 0) + blockStack[blockStack.length - 1].push(curr); + } + while ((blockStack.length > 1)&&(blockStack[blockStack.length - 1].length > 0)) { + blockStack[blockStack.length - 2].push(blockStack[blockStack.length - 1]); + blockStack.pop(); + } + var parsed = blockStack[0]; + + // Pass 2: parse tree into capabilities, tags, rule sets, and document-level rules. + + let baseRuleTree = []; + let macros = {}; + for(let i=0;i= parsed.length) || (!Array.isArray(parsed[i + 1])) || (parsed[i + 1].length < 1) || (!Array.isArray(parsed[i + 1][0])) ) + return [ parsed[i][1],parsed[i][2],'Macro definition is missing name.' ]; + let macro = parsed[++i]; + let macroName = macro[0][0].toLowerCase(); + + let params = {}; + let parenIdx = macroName.indexOf('('); + if (parenIdx > 0) { + let pns = macroName.substr(parenIdx + 1).split(/[,)]+/); + for(let k=0;k 0) + params[pns[k]] = k; + } + macroName = macroName.substr(0,parenIdx); + } + + if (!_isValidName(macroName)) + return [ macro[0][1],macro[0][2],'Invalid macro name.' ]; + if (macroName in RESERVED_WORDS) + return [ macro[0][1],macro[0][2],'Macro name is a reserved word.' ]; + + if (macroName in macros) + return [ macro[0][1],macro[0][2],'Multiple definition of macro name.' ]; + + macros[macroName] = { + params: params, + rules: macro.slice(1) + }; + } else if (keyword === 'tag') { + // Define tags + + if ( ((i + 1) >= parsed.length) || (!Array.isArray(parsed[i + 1])) || (parsed[i + 1].length < 1) || (!Array.isArray(parsed[i + 1][0])) ) + return [ parsed[i][1],parsed[i][2],'Tag definition is missing name.' ]; + let tag = parsed[++i]; + let tagName = tag[0][0].toLowerCase(); + + if (!_isValidName(tagName)) + return [ tag[0][1],tag[0][2],'Invalid tag name.' ]; + if (tagName in RESERVED_WORDS) + return [ tag[0][1],tag[0][2],'Tag name is a reserved word.' ]; + + if (tagName in tags) + return [ tag[0][1],tag[0][2],'Multiple definition of tag name.' ]; + + let flags = {}; + let enums = {}; + let id = -1; + let dfl = null; + for(let k=1;k= 0) + return [ tag[k][1],tag[k][2],'Duplicate tag id definition.' ]; + if ((k + 1) >= tag.length) + return [ tag[k][1],tag[k][2],'Missing numeric value for ID.' ]; + id = _parseNum(tag[++k][0]); + if ((id < 0)||(id > 0xffffffff)) + return [ tag[k][1],tag[k][2],'Invalid or out of range tag ID.' ]; + } else if (tkeyword === 'default') { + if (dfl !== null) + return [ tag[k][1],tag[k][2],'Duplicate tag default directive.' ]; + if ((k + 1) >= tag.length) + return [ tag[k][1],tag[k][2],'Missing value for default.' ]; + dfl = tag[++k][0]; + } else if (tkeyword === 'flag') { + if ((k + 2) >= tag.length) + return [ tag[k][1],tag[k][2],'Missing tag flag name or bit index.' ]; + ++k; + let bits = tag[k][0].split(/[,]+/); + let mask = 0; + for(let j=0;j 31)) + return [ tag[k][1],tag[k][2],'Bit index invalid, out of range, or references an undefined flag name.' ]; + mask |= (1 << b); + } + } + let flagName = tag[++k][0].toLowerCase(); + if (!_isValidName(flagName)) + return [ tag[k][1],tag[k][2],'Invalid or reserved flag name.' ]; + if (flagName in flags) + return [ tag[k][1],tag[k][2],'Duplicate flag name in tag definition.' ]; + flags[flagName] = mask; + } else if (tkeyword === 'enum') { + if ((k + 2) >= tag.length) + return [ tag[k][1],tag[k][2],'Missing tag enum name or value.' ]; + ++k; + let value = _parseNum(tag[k][0]); + if ((value < 0)||(value > 0xffffffff)) + return [ tag[k][1],tag[k][2],'Tag enum value invalid or out of range.' ]; + let enumName = tag[++k][0].toLowerCase(); + if (!_isValidName(enumName)) + return [ tag[k][1],tag[k][2],'Invalid or reserved tag enum name.' ]; + if (enumName in enums) + return [ tag[k][1],tag[k][2],'Duplicate enum name in tag definition.' ]; + enums[enumName] = value; + } else { + return [ tag[k][1],tag[k][2],'Unrecognized keyword in tag definition.' ]; + } + } + if (id < 0) + return [ tag[0][1],tag[0][2],'Tag definition is missing a numeric ID.' ]; + + if (typeof dfl === 'string') { + let dfl2 = enums[dfl]; + if (typeof dfl2 === 'number') { + dfl = dfl2; + } else { + dfl2 = flags[dfl]; + if (typeof dfl2 === 'number') { + dfl = dfl2; + } else { + dfl = Math.abs(parseInt(dfl)||0) & 0xffffffff; + } + } + } else if (typeof dfl === 'number') { + dfl = Math.abs(dfl) & 0xffffffff; + } + + tags[tagName] = { + 'id': id, + 'default': dfl, + 'enums': enums, + 'flags': flags + }; + } else if (keyword === 'cap') { + // Define capabilities + + if ( ((i + 1) >= parsed.length) || (!Array.isArray(parsed[i + 1])) || (parsed[i + 1].length < 1) || (!Array.isArray(parsed[i + 1][0])) ) + return [ parsed[i][1],parsed[i][2],'Capability definition is missing name.' ]; + let cap = parsed[++i]; + let capName = cap[0][0].toLowerCase(); + + if (!_isValidName(capName)) + return [ cap[0][1],cap[0][2],'Invalid capability name.' ]; + if (capName in RESERVED_WORDS) + return [ cap[0][1],cap[0][2],'Capability name is a reserved word.' ]; + + if (capName in caps) + return [ cap[0][1],cap[0][2],'Multiple definition of capability name.' ]; + + let capRules = []; + let id = -1; + let dfl = false; + for(let k=1;k= 0) + return [ cap[k][1],cap[k][2],'Duplicate id directive in capability definition.' ]; + if ((k + 1) >= cap.length) + return [ cap[k][1],cap[k][2],'Missing value for ID.' ]; + id = _parseNum(cap[++k][0]); + if ((id < 0)||(id > 0xffffffff)) + return [ cap[k - 1][1],cap[k - 1][2],'Invalid or out of range capability ID.' ]; + for(let cn in caps) { + if (caps[cn].id === id) + return [ cap[k - 1][1],cap[k - 1][2],'Duplicate capability ID.' ]; + } + } else if (dn === 'default') { + dfl = true; + } else { + capRules.push(cap[k]); + } + } + if (id < 0) + return [ cap[0][1],cap[0][2],'Capability definition is missing a numeric ID.' ]; + + caps[capName] = { + 'id': id, + 'default': dfl, + 'rules': capRules + }; + } else { + baseRuleTree.push(parsed[i]); + } + } + + // Pass 3: render low-level ZeroTier rules arrays for capabilities and base. + + for(let capName in caps) { + let r = []; + let err = _renderActions(caps[capName].rules,r,macros,caps,tags,{}); + if (err !== null) + return err; + caps[capName].rules = r; + } + + let err = _renderActions(baseRuleTree,rules,macros,caps,tags,{}); + if (err !== null) + return err; + + return null; + } catch (e) { + console.log(e.stack); + return [ 0,0,'Unexpected exception: '+e.toString() ]; + } +} + +exports.compile = compile; diff --git a/selftest.cpp b/selftest.cpp index f423285..91d304a 100644 --- a/selftest.cpp +++ b/selftest.cpp @@ -49,13 +49,17 @@ #include "osdep/OSUtils.hpp" #include "osdep/Phy.hpp" #include "osdep/Http.hpp" -#include "osdep/BackgroundResolver.hpp" #include "osdep/PortMapper.hpp" #include "osdep/Thread.hpp" -#ifdef ZT_ENABLE_NETWORK_CONTROLLER -#include "controller/SqliteNetworkController.hpp" -#endif // ZT_ENABLE_NETWORK_CONTROLLER +#include "controller/JSONDB.hpp" + +#ifdef ZT_USE_X64_ASM_SALSA2012 +#include "ext/x64-salsa2012-asm/salsa2012.h" +#endif +#ifdef ZT_USE_ARM32_NEON_ASM_SALSA2012 +#include "ext/arm32-neon-salsa2012-asm/salsa2012.h" +#endif #ifdef __WINDOWS__ #include @@ -137,8 +141,6 @@ static const C25519TestVector C25519_TEST_VECTORS[ZT_NUM_C25519_TEST_VECTORS] = ////////////////////////////////////////////////////////////////////////////// -static unsigned char fuzzbuf[1048576]; - static int testCrypto() { unsigned char buf1[16384]; @@ -156,27 +158,27 @@ static int testCrypto() memset(buf2,0,sizeof(buf2)); memset(buf3,0,sizeof(buf3)); Salsa20 s20; - s20.init("12345678123456781234567812345678",256,"12345678"); - s20.encrypt20(buf1,buf2,sizeof(buf1)); - s20.init("12345678123456781234567812345678",256,"12345678"); - s20.decrypt20(buf2,buf3,sizeof(buf2)); + s20.init("12345678123456781234567812345678","12345678"); + s20.crypt20(buf1,buf2,sizeof(buf1)); + s20.init("12345678123456781234567812345678","12345678"); + s20.crypt20(buf2,buf3,sizeof(buf2)); if (memcmp(buf1,buf3,sizeof(buf1))) { std::cout << "FAIL (encrypt/decrypt test)" << std::endl; return -1; } } - Salsa20 s20(s20TV0Key,256,s20TV0Iv); + Salsa20 s20(s20TV0Key,s20TV0Iv); memset(buf1,0,sizeof(buf1)); memset(buf2,0,sizeof(buf2)); - s20.encrypt20(buf1,buf2,64); + s20.crypt20(buf1,buf2,64); if (memcmp(buf2,s20TV0Ks,64)) { std::cout << "FAIL (test vector 0)" << std::endl; return -1; } - s20.init(s2012TV0Key,256,s2012TV0Iv); + s20.init(s2012TV0Key,s2012TV0Iv); memset(buf1,0,sizeof(buf1)); memset(buf2,0,sizeof(buf2)); - s20.encrypt12(buf1,buf2,64); + s20.crypt12(buf1,buf2,64); if (memcmp(buf2,s2012TV0Ks,64)) { std::cout << "FAIL (test vector 1)" << std::endl; return -1; @@ -194,34 +196,68 @@ static int testCrypto() unsigned char *bb = (unsigned char *)::malloc(1234567); for(unsigned int i=0;i<1234567;++i) bb[i] = (unsigned char)i; - Salsa20 s20(s20TV0Key,256,s20TV0Iv); - double bytes = 0.0; + Salsa20 s20(s20TV0Key,s20TV0Iv); + long double bytes = 0.0; uint64_t start = OSUtils::now(); for(unsigned int i=0;i<200;++i) { - s20.encrypt12(bb,bb,1234567); + s20.crypt12(bb,bb,1234567); bytes += 1234567.0; } uint64_t end = OSUtils::now(); SHA512::hash(buf1,bb,1234567); - std::cout << ((bytes / 1048576.0) / ((double)(end - start) / 1000.0)) << " MiB/second (" << Utils::hex(buf1,16) << ')' << std::endl; + std::cout << ((bytes / 1048576.0) / ((long double)(end - start) / 1024.0)) << " MiB/second (" << Utils::hex(buf1,16) << ')' << std::endl; ::free((void *)bb); } +#ifdef ZT_USE_X64_ASM_SALSA2012 + std::cout << "[crypto] Benchmarking Salsa20/12 fast x64 ASM... "; std::cout.flush(); + { + unsigned char *bb = (unsigned char *)::malloc(1234567); + double bytes = 0.0; + uint64_t start = OSUtils::now(); + for(unsigned int i=0;i<200;++i) { + zt_salsa2012_amd64_xmm6(bb,1234567,s20TV0Iv,s20TV0Key); + bytes += 1234567.0; + } + uint64_t end = OSUtils::now(); + std::cout << ((bytes / 1048576.0) / ((double)(end - start) / 1024.0)) << " MiB/second" << std::endl; + ::free((void *)bb); + } +#endif + +#ifdef ZT_USE_ARM32_NEON_ASM_SALSA2012 + if (zt_arm_has_neon()) { + std::cout << "[crypto] Benchmarking Salsa20/12 fast arm32/neon ASM... "; std::cout.flush(); + { + unsigned char *bb = (unsigned char *)::malloc(1234567); + double bytes = 0.0; + uint64_t start = OSUtils::now(); + for(unsigned int i=0;i<200;++i) { + zt_salsa2012_armneon3_xor(bb,(const unsigned char *)0,1234567,s20TV0Iv,s20TV0Key); + bytes += 1234567.0; + } + uint64_t end = OSUtils::now(); + std::cout << ((bytes / 1048576.0) / ((double)(end - start) / 1024.0)) << " MiB/second" << std::endl; + ::free((void *)bb); + } + } +#endif + std::cout << "[crypto] Benchmarking Salsa20/20... "; std::cout.flush(); { unsigned char *bb = (unsigned char *)::malloc(1234567); for(unsigned int i=0;i<1234567;++i) bb[i] = (unsigned char)i; - Salsa20 s20(s20TV0Key,256,s20TV0Iv); - double bytes = 0.0; + Salsa20 s20(s20TV0Key,s20TV0Iv); + long double bytes = 0.0; uint64_t start = OSUtils::now(); for(unsigned int i=0;i<200;++i) { - s20.encrypt20(bb,bb,1234567); + s20.crypt20(bb,bb,1234567); bytes += 1234567.0; } uint64_t end = OSUtils::now(); SHA512::hash(buf1,bb,1234567); - std::cout << ((bytes / 1048576.0) / ((double)(end - start) / 1000.0)) << " MiB/second (" << Utils::hex(buf1,16) << ')' << std::endl; + std::cout << ((bytes / 1048576.0) / ((long double)(end - start) / 1024.0)) << " MiB/second (" << Utils::hex(buf1,16) << ')' << std::endl; ::free((void *)bb); } @@ -251,14 +287,14 @@ static int testCrypto() unsigned char *bb = (unsigned char *)::malloc(1234567); for(unsigned int i=0;i<1234567;++i) bb[i] = (unsigned char)i; - double bytes = 0.0; + long double bytes = 0.0; uint64_t start = OSUtils::now(); for(unsigned int i=0;i<200;++i) { Poly1305::compute(buf1,bb,1234567,poly1305TV0Key); bytes += 1234567.0; } uint64_t end = OSUtils::now(); - std::cout << ((bytes / 1048576.0) / ((double)(end - start) / 1000.0)) << " MiB/second" << std::endl; + std::cout << ((bytes / 1048576.0) / ((long double)(end - start) / 1000.0)) << " MiB/second" << std::endl; ::free((void *)bb); } @@ -329,6 +365,17 @@ static int testCrypto() } 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) { @@ -378,11 +425,15 @@ static int testIdentity() std::cout << "FAIL (1)" << std::endl; return -1; } - if (!id.locallyValidate()) { - std::cout << "FAIL (2)" << 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; + } } - std::cout << "PASS" << std::endl; + 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)) { @@ -505,19 +556,6 @@ static int testCertificate() return -1; } - std::cout << "[certificate] Testing string serialization... "; - CertificateOfMembership copyA(cA.toString()); - CertificateOfMembership copyB(cB.toString()); - if (copyA != cA) { - std::cout << "FAIL" << std::endl; - return -1; - } - if (copyB != cB) { - std::cout << "FAIL" << std::endl; - return -1; - } - std::cout << "PASS" << std::endl; - std::cout << "[certificate] Generating two certificates that should not agree..."; cA = CertificateOfMembership(10000,100,1,idA.address()); cB = CertificateOfMembership(10101,100,1,idB.address()); @@ -573,7 +611,7 @@ static int testPacket() return -1; } - a.armor(salsaKey,true); + a.armor(salsaKey,true,0); if (!a.dearmor(salsaKey)) { std::cout << "FAIL (encrypt-decrypt/verify)" << std::endl; return -1; @@ -583,8 +621,35 @@ static int testPacket() return 0; } +static void _testExcept(int &depth) +{ + if (depth >= 16) { + throw std::runtime_error("LOL!"); + } else { + ++depth; + _testExcept(depth); + } +} + static int testOther() { + std::cout << "[other] Testing C++ exceptions... "; std::cout.flush(); + int depth = 0; + try { + _testExcept(depth); + } catch (std::runtime_error &e) { + if (depth == 16) { + std::cout << "OK" << std::endl; + } else { + std::cout << "ERROR (depth not 16)" << std::endl; + return -1; + } + } catch ( ... ) { + std::cout << "ERROR (exception not std::runtime_error)" << std::endl; + return -1; + } + +#if 0 std::cout << "[other] Testing Hashtable... "; std::cout.flush(); { Hashtable ht; @@ -748,42 +813,29 @@ static int testOther() } } 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; + Dictionary<8194> *test = new Dictionary<8194>(); char key[32][16]; char value[32][128]; + memset(key, 0, sizeof(key)); + memset(value, 0, sizeof(value)); for(unsigned int q=0;q<32;++q) { - Utils::snprintf(key[q],16,"%.8lx",(unsigned long)rand()); + Utils::snprintf(key[q],16,"%.8lx",(unsigned long)(rand() % 1000) + (q * 1000)); int r = rand() % 128; for(int x=0;xadd(key[q],value[q],r); } for(unsigned int q=0;q<1024;++q) { - //int r = rand() % 128; - int r = 31; + int r = rand() % 32; char tmp[128]; - if (test.get(key[r],tmp,sizeof(tmp)) >= 0) { + if (test->get(key[r],tmp,sizeof(tmp)) >= 0) { if (strcmp(value[r],tmp)) { - std::cout << "FAILED (invalid value)!" << std::endl; + std::cout << "FAILED (invalid value '" << value[r] << "' != '" << tmp << "')!" << std::endl; return -1; } } else { @@ -791,36 +843,27 @@ static int testOther() 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; - } - } + delete test; } int foo = 0; volatile int *volatile bar = &foo; // force compiler not to optimize out test.get() below for(int k=0;k<200;++k) { int r = rand() % 8194; - unsigned char tmp[8194]; + unsigned char *tmp = new unsigned char[8194]; for(int q=0;q test((const char *)tmp); + Dictionary<8194> *test = new Dictionary<8194>((const char *)tmp); for(unsigned int q=0;q<100;++q) { char tmp[128]; for(unsigned int x=0;x<128;++x) tmp[x] = (char)(rand() & 0xff); tmp[127] = (char)0; char value[8194]; - *bar += test.get(tmp,value,sizeof(value)); + *bar += test->get(tmp,value,sizeof(value)); } + delete test; + delete[] tmp; } std::cout << "PASS (junk value to prevent optimization-out of test: " << foo << ")" << std::endl; @@ -975,23 +1018,6 @@ static int testPhy() return 0; } -static int testResolver() -{ - std::cout << "[resolver] Testing BackgroundResolver..."; std::cout.flush(); - - BackgroundResolver r("tcp-fallback.zerotier.com"); - r.resolveNow(); - r.wait(); - - std::vector ips(r.get()); - for(std::vector::const_iterator ip(ips.begin());ip!=ips.end();++ip) { - std::cout << ' ' << ip->toString(); - } - std::cout << std::endl; - - return 0; -} - /* static int testHttp() { @@ -1038,7 +1064,7 @@ static int testHttp() */ #ifdef __WINDOWS__ -int _tmain(int argc, _TCHAR* argv[]) +int __cdecl _tmain(int argc, _TCHAR* argv[]) #else int main(int argc,char **argv) #endif @@ -1099,7 +1125,6 @@ int main(int argc,char **argv) r |= testIdentity(); r |= testCertificate(); r |= testPhy(); - r |= testResolver(); //r |= testHttp(); //*/ diff --git a/service/ClusterDefinition.hpp b/service/ClusterDefinition.hpp index 441cc04..dda1a8c 100644 --- a/service/ClusterDefinition.hpp +++ b/service/ClusterDefinition.hpp @@ -66,9 +66,9 @@ public: char myAddressStr[64]; Utils::snprintf(myAddressStr,sizeof(myAddressStr),"%.10llx",myAddress); - std::vector lines(Utils::split(cf.c_str(),"\r\n","","")); + std::vector lines(OSUtils::split(cf.c_str(),"\r\n","","")); for(std::vector::iterator l(lines.begin());l!=lines.end();++l) { - std::vector fields(Utils::split(l->c_str()," \t","","")); + std::vector fields(OSUtils::split(l->c_str()," \t","","")); if ((fields.size() < 5)||(fields[0][0] == '#')||(fields[0] != myAddressStr)) continue; @@ -93,7 +93,7 @@ public: md.id = (unsigned int)id; if (fields.size() >= 6) { - std::vector xyz(Utils::split(fields[5].c_str(),",","","")); + 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; @@ -102,7 +102,7 @@ public: md.clusterEndpoint.fromString(fields[3]); if (!md.clusterEndpoint) continue; - std::vector zips(Utils::split(fields[4].c_str(),",","","")); + 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); diff --git a/service/ClusterGeoIpService.cpp b/service/ClusterGeoIpService.cpp index 3ad6975..89015c5 100644 --- a/service/ClusterGeoIpService.cpp +++ b/service/ClusterGeoIpService.cpp @@ -101,7 +101,7 @@ bool ClusterGeoIpService::locate(const InetAddress &ip,int &x,int &y,int &z) 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(Utils::split(line,",\t","\\","\"'")); + 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()))&& diff --git a/service/ControlPlane.cpp b/service/ControlPlane.cpp deleted file mode 100644 index a10697a..0000000 --- a/service/ControlPlane.cpp +++ /dev/null @@ -1,628 +0,0 @@ -/* - * 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 "ControlPlane.hpp" -#include "OneService.hpp" - -#include "../version.h" -#include "../include/ZeroTierOne.h" - -#ifdef ZT_USE_SYSTEM_HTTP_PARSER -#include -#else -#include "../ext/http-parser/http_parser.h" -#endif - -#ifdef ZT_USE_SYSTEM_JSON_PARSER -#include -#else -#include "../ext/json-parser/json.h" -#endif - -#ifdef ZT_ENABLE_NETWORK_CONTROLLER -#include "../controller/SqliteNetworkController.hpp" -#endif - -#include "../node/InetAddress.hpp" -#include "../node/Node.hpp" -#include "../node/Utils.hpp" -#include "../osdep/OSUtils.hpp" - -namespace ZeroTier { - -static std::string _jsonEscape(const char *s) -{ - std::string buf; - for(const char *p=s;(*p);++p) { - switch(*p) { - case '\t': buf.append("\\t"); break; - case '\b': buf.append("\\b"); break; - case '\r': buf.append("\\r"); break; - case '\n': buf.append("\\n"); break; - case '\f': buf.append("\\f"); break; - case '"': buf.append("\\\""); break; - case '\\': buf.append("\\\\"); break; - case '/': buf.append("\\/"); break; - default: buf.push_back(*p); break; - } - } - return buf; -} -static std::string _jsonEscape(const std::string &s) { return _jsonEscape(s.c_str()); } - -static std::string _jsonEnumerate(const struct sockaddr_storage *ss,unsigned int count) -{ - std::string buf; - buf.push_back('['); - for(unsigned int i=0;i 0) - buf.push_back(','); - buf.push_back('"'); - buf.append(_jsonEscape(reinterpret_cast(&(ss[i]))->toString())); - buf.push_back('"'); - } - buf.push_back(']'); - return buf; -} -static std::string _jsonEnumerate(const ZT_VirtualNetworkRoute *routes,unsigned int count) -{ - std::string buf; - buf.push_back('['); - for(unsigned int i=0;i 0) - buf.push_back(','); - buf.append("{\"target\":\""); - buf.append(_jsonEscape(reinterpret_cast(&(routes[i].target))->toString())); - buf.append("\",\"via\":"); - if (routes[i].via.ss_family == routes[i].target.ss_family) { - buf.push_back('"'); - buf.append(_jsonEscape(reinterpret_cast(&(routes[i].via))->toIpString())); - buf.append("\","); - } else buf.append("null,"); - char tmp[1024]; - Utils::snprintf(tmp,sizeof(tmp),"\"flags\":%u,\"metric\":%u}",(unsigned int)routes[i].flags,(unsigned int)routes[i].metric); - buf.append(tmp); - } - buf.push_back(']'); - return buf; -} - -static void _jsonAppend(unsigned int depth,std::string &buf,const ZT_VirtualNetworkConfig *nc,const std::string &portDeviceName,const OneService::NetworkSettings &localSettings) -{ - char json[4096]; - char prefix[32]; - - if (depth >= sizeof(prefix)) // sanity check -- shouldn't be possible - return; - for(unsigned int i=0;istatus) { - 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(json,sizeof(json), - "%s{\n" - "%s\t\"nwid\": \"%.16llx\",\n" - "%s\t\"mac\": \"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x\",\n" - "%s\t\"name\": \"%s\",\n" - "%s\t\"status\": \"%s\",\n" - "%s\t\"type\": \"%s\",\n" - "%s\t\"mtu\": %u,\n" - "%s\t\"dhcp\": %s,\n" - "%s\t\"bridge\": %s,\n" - "%s\t\"broadcastEnabled\": %s,\n" - "%s\t\"portError\": %d,\n" - "%s\t\"netconfRevision\": %lu,\n" - "%s\t\"assignedAddresses\": %s,\n" - "%s\t\"routes\": %s,\n" - "%s\t\"portDeviceName\": \"%s\",\n" - "%s\t\"allowManaged\": %s,\n" - "%s\t\"allowGlobal\": %s,\n" - "%s\t\"allowDefault\": %s\n" - "%s}", - prefix, - prefix,nc->nwid, - prefix,(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), - prefix,_jsonEscape(nc->name).c_str(), - prefix,nstatus, - prefix,ntype, - prefix,nc->mtu, - prefix,(nc->dhcp == 0) ? "false" : "true", - prefix,(nc->bridge == 0) ? "false" : "true", - prefix,(nc->broadcastEnabled == 0) ? "false" : "true", - prefix,nc->portError, - prefix,nc->netconfRevision, - prefix,_jsonEnumerate(nc->assignedAddresses,nc->assignedAddressCount).c_str(), - prefix,_jsonEnumerate(nc->routes,nc->routeCount).c_str(), - prefix,_jsonEscape(portDeviceName).c_str(), - prefix,(localSettings.allowManaged) ? "true" : "false", - prefix,(localSettings.allowGlobal) ? "true" : "false", - prefix,(localSettings.allowDefault) ? "true" : "false", - prefix); - buf.append(json); -} - -static std::string _jsonEnumerate(unsigned int depth,const ZT_PeerPhysicalPath *pp,unsigned int count) -{ - char json[1024]; - char prefix[32]; - - if (depth >= sizeof(prefix)) // sanity check -- shouldn't be possible - return std::string(); - for(unsigned int i=0;i 0) - buf.push_back(','); - Utils::snprintf(json,sizeof(json), - "{\n" - "%s\t\"address\": \"%s\",\n" - "%s\t\"lastSend\": %llu,\n" - "%s\t\"lastReceive\": %llu,\n" - "%s\t\"active\": %s,\n" - "%s\t\"preferred\": %s,\n" - "%s\t\"trustedPathId\": %llu\n" - "%s}", - prefix,_jsonEscape(reinterpret_cast(&(pp[i].address))->toString()).c_str(), - prefix,pp[i].lastSend, - prefix,pp[i].lastReceive, - prefix,(pp[i].active == 0) ? "false" : "true", - prefix,(pp[i].preferred == 0) ? "false" : "true", - prefix,pp[i].trustedPathId, - prefix); - buf.append(json); - } - return buf; -} - -static void _jsonAppend(unsigned int depth,std::string &buf,const ZT_Peer *peer) -{ - char json[1024]; - char prefix[32]; - - if (depth >= sizeof(prefix)) // sanity check -- shouldn't be possible - return; - for(unsigned int i=0;irole) { - case ZT_PEER_ROLE_LEAF: prole = "LEAF"; break; - case ZT_PEER_ROLE_RELAY: prole = "RELAY"; break; - case ZT_PEER_ROLE_ROOT: prole = "ROOT"; break; - } - - Utils::snprintf(json,sizeof(json), - "%s{\n" - "%s\t\"address\": \"%.10llx\",\n" - "%s\t\"lastUnicastFrame\": %llu,\n" - "%s\t\"lastMulticastFrame\": %llu,\n" - "%s\t\"versionMajor\": %d,\n" - "%s\t\"versionMinor\": %d,\n" - "%s\t\"versionRev\": %d,\n" - "%s\t\"version\": \"%d.%d.%d\",\n" - "%s\t\"latency\": %u,\n" - "%s\t\"role\": \"%s\",\n" - "%s\t\"paths\": [%s]\n" - "%s}", - prefix, - prefix,peer->address, - prefix,peer->lastUnicastFrame, - prefix,peer->lastMulticastFrame, - prefix,peer->versionMajor, - prefix,peer->versionMinor, - prefix,peer->versionRev, - prefix,peer->versionMajor,peer->versionMinor,peer->versionRev, - prefix,peer->latency, - prefix,prole, - prefix,_jsonEnumerate(depth+1,peer->paths,peer->pathCount).c_str(), - prefix); - buf.append(json); -} - -ControlPlane::ControlPlane(OneService *svc,Node *n,const char *uiStaticPath) : - _svc(svc), - _node(n), -#ifdef ZT_ENABLE_NETWORK_CONTROLLER - _controller((SqliteNetworkController *)0), -#endif - _uiStaticPath((uiStaticPath) ? uiStaticPath : "") -{ -} - -ControlPlane::~ControlPlane() -{ -} - -unsigned int ControlPlane::handleRequest( - 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 json[8194]; - unsigned int scode = 404; - std::vector ps(Utils::split(path.c_str(),"/","","")); - std::map urlArgs; - Mutex::Lock _l(_lock); - - if (!((fromAddress.ipsEqual(InetAddress::LO4))||(fromAddress.ipsEqual(InetAddress::LO6)))) - return 403; // Forbidden: we only allow access from localhost right now - - /* 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(Utils::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); - } - } - } else { - ps.push_back(std::string("index.html")); - } - - bool isAuth = false; - { - std::map::const_iterator ah(headers.find("x-zt1-auth")); - if ((ah != headers.end())&&(_authTokens.count(ah->second) > 0)) { - isAuth = true; - } else { - ah = urlArgs.find("auth"); - if ((ah != urlArgs.end())&&(_authTokens.count(ah->second) > 0)) - isAuth = true; - } - } - - if (httpMethod == HTTP_GET) { - - std::string ext; - std::size_t dotIdx = ps[0].find_last_of('.'); - if (dotIdx != std::string::npos) - ext = ps[0].substr(dotIdx); - - if ((ps.size() == 1)&&(ext.length() >= 2)&&(ext[0] == '.')) { - /* Static web pages can be served without authentication to enable a simple web - * UI. This is still only allowed from approved IP addresses. Anything with a - * dot in the first path element (e.g. foo.html) is considered a static page, - * as nothing in the API is so named. */ - - if (_uiStaticPath.length() > 0) { - if (ext == ".html") - responseContentType = "text/html"; - else if (ext == ".js") - responseContentType = "application/javascript"; - else if (ext == ".jsx") - responseContentType = "text/jsx"; - else if (ext == ".json") - responseContentType = "application/json"; - else if (ext == ".css") - responseContentType = "text/css"; - else if (ext == ".png") - responseContentType = "image/png"; - else if (ext == ".jpg") - responseContentType = "image/jpeg"; - else if (ext == ".gif") - responseContentType = "image/gif"; - else if (ext == ".txt") - responseContentType = "text/plain"; - else if (ext == ".xml") - responseContentType = "text/xml"; - else if (ext == ".svg") - responseContentType = "image/svg+xml"; - else responseContentType = "application/octet-stream"; - scode = OSUtils::readFile((_uiStaticPath + ZT_PATH_SEPARATOR_S + ps[0]).c_str(),responseBody) ? 200 : 404; - } else { - scode = 404; - } - - } else if (isAuth) { - /* Things that require authentication -- a.k.a. everything but static web app pages. */ - - if (ps[0] == "status") { - responseContentType = "application/json"; - - ZT_NodeStatus status; - _node->status(&status); - - std::string clusterJson; -#ifdef ZT_ENABLE_CLUSTER - { - ZT_ClusterStatus cs; - _node->clusterStatus(&cs); - - if (cs.clusterSize >= 1) { - char t[1024]; - Utils::snprintf(t,sizeof(t),"{\n\t\t\"myId\": %u,\n\t\t\"clusterSize\": %u,\n\t\t\"members\": [",cs.myId,cs.clusterSize); - clusterJson.append(t); - for(unsigned int i=0;itcpFallbackActive()) ? "true" : "false", - ZEROTIER_ONE_VERSION_MAJOR, - ZEROTIER_ONE_VERSION_MINOR, - ZEROTIER_ONE_VERSION_REVISION, - ZEROTIER_ONE_VERSION_MAJOR,ZEROTIER_ONE_VERSION_MINOR,ZEROTIER_ONE_VERSION_REVISION, - (unsigned long long)OSUtils::now(), - ((clusterJson.length() > 0) ? clusterJson.c_str() : "null")); - responseBody = json; - scode = 200; - } else if (ps[0] == "config") { - responseContentType = "application/json"; - responseBody = "{}"; // TODO - scode = 200; - } else if (ps[0] == "network") { - ZT_VirtualNetworkList *nws = _node->networks(); - if (nws) { - if (ps.size() == 1) { - // Return [array] of all networks - responseContentType = "application/json"; - responseBody = "[\n"; - for(unsigned long i=0;inetworkCount;++i) { - if (i > 0) - responseBody.append(","); - OneService::NetworkSettings localSettings; - _svc->getNetworkSettings(nws->networks[i].nwid,localSettings); - _jsonAppend(1,responseBody,&(nws->networks[i]),_svc->portDeviceName(nws->networks[i].nwid),localSettings); - } - responseBody.append("\n]\n"); - scode = 200; - } else if (ps.size() == 2) { - // Return a single network by ID or 404 if not found - uint64_t wantnw = Utils::hexStrToU64(ps[1].c_str()); - for(unsigned long i=0;inetworkCount;++i) { - if (nws->networks[i].nwid == wantnw) { - responseContentType = "application/json"; - OneService::NetworkSettings localSettings; - _svc->getNetworkSettings(nws->networks[i].nwid,localSettings); - _jsonAppend(0,responseBody,&(nws->networks[i]),_svc->portDeviceName(nws->networks[i].nwid),localSettings); - responseBody.push_back('\n'); - scode = 200; - break; - } - } - } // else 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 - responseContentType = "application/json"; - responseBody = "[\n"; - for(unsigned long i=0;ipeerCount;++i) { - if (i > 0) - responseBody.append(",\n"); - _jsonAppend(1,responseBody,&(pl->peers[i])); - } - responseBody.append("\n]\n"); - 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) { - responseContentType = "application/json"; - _jsonAppend(0,responseBody,&(pl->peers[i])); - responseBody.push_back('\n'); - scode = 200; - break; - } - } - } // else 404 - _node->freeQueryResult((void *)pl); - } else scode = 500; - } else if (ps[0] == "newIdentity") { - // Return a newly generated ZeroTier identity -- this is primarily for debugging - // and testing to make it easy for automated test scripts to generate test IDs. - Identity newid; - newid.generate(); - responseBody = newid.toString(true); - responseContentType = "text/plain"; - scode = 200; - } else { -#ifdef ZT_ENABLE_NETWORK_CONTROLLER - if (_controller) - scode = _controller->handleControlPlaneHttpGET(std::vector(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType); - else scode = 404; -#else - scode = 404; -#endif - } - - } else scode = 401; // isAuth == false - - } else if ((httpMethod == HTTP_POST)||(httpMethod == HTTP_PUT)) { - - if (isAuth) { - - if (ps[0] == "config") { - // TODO - } else if (ps[0] == "network") { - if (ps.size() == 2) { - uint64_t wantnw = Utils::hexStrToU64(ps[1].c_str()); - _node->join(wantnw,(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; - _svc->getNetworkSettings(nws->networks[i].nwid,localSettings); - - json_value *j = json_parse(body.c_str(),body.length()); - if (j) { - if (j->type == json_object) { - for(unsigned int k=0;ku.object.length;++k) { - if (!strcmp(j->u.object.values[k].name,"allowManaged")) { - if (j->u.object.values[k].value->type == json_boolean) - localSettings.allowManaged = (j->u.object.values[k].value->u.boolean != 0); - } else if (!strcmp(j->u.object.values[k].name,"allowGlobal")) { - if (j->u.object.values[k].value->type == json_boolean) - localSettings.allowGlobal = (j->u.object.values[k].value->u.boolean != 0); - } else if (!strcmp(j->u.object.values[k].name,"allowDefault")) { - if (j->u.object.values[k].value->type == json_boolean) - localSettings.allowDefault = (j->u.object.values[k].value->u.boolean != 0); - } - } - } - json_value_free(j); - } - - _svc->setNetworkSettings(nws->networks[i].nwid,localSettings); - - responseContentType = "application/json"; - _jsonAppend(0,responseBody,&(nws->networks[i]),_svc->portDeviceName(nws->networks[i].nwid),localSettings); - responseBody.push_back('\n'); - scode = 200; - break; - } - } - _node->freeQueryResult((void *)nws); - } else scode = 500; - } - } else { -#ifdef ZT_ENABLE_NETWORK_CONTROLLER - if (_controller) - scode = _controller->handleControlPlaneHttpPOST(std::vector(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType); - else scode = 404; -#else - scode = 404; -#endif - } - - } else scode = 401; // isAuth == false - - } else if (httpMethod == HTTP_DELETE) { - - if (isAuth) { - - if (ps[0] == "config") { - // TODO - } 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); - responseBody = "true"; - responseContentType = "application/json"; - scode = 200; - break; - } - } - } // else 404 - _node->freeQueryResult((void *)nws); - } else scode = 500; - } else { -#ifdef ZT_ENABLE_NETWORK_CONTROLLER - if (_controller) - scode = _controller->handleControlPlaneHttpDELETE(std::vector(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType); - else scode = 404; -#else - scode = 404; -#endif - } - - } else { - scode = 401; // isAuth = false - } - - } else { - scode = 400; - responseBody = "Method not supported."; - } - - // 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; -} - -} // namespace ZeroTier diff --git a/service/ControlPlane.hpp b/service/ControlPlane.hpp deleted file mode 100644 index 08a9d6e..0000000 --- a/service/ControlPlane.hpp +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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_ONE_CONTROLPLANE_HPP -#define ZT_ONE_CONTROLPLANE_HPP - -#include -#include -#include - -#include "../include/ZeroTierOne.h" - -#include "../node/Mutex.hpp" - -namespace ZeroTier { - -class OneService; -class Node; -class SqliteNetworkController; -struct InetAddress; - -/** - * HTTP control plane and static web server - */ -class ControlPlane -{ -public: - ControlPlane(OneService *svc,Node *n,const char *uiStaticPath); - ~ControlPlane(); - -#ifdef ZT_ENABLE_NETWORK_CONTROLLER - /** - * Set controller, which will be available under /controller - * - * @param c Network controller instance - */ - inline void setController(SqliteNetworkController *c) - { - Mutex::Lock _l(_lock); - _controller = c; - } -#endif - - /** - * Add an authentication token for API access - */ - inline void addAuthToken(const char *tok) - { - Mutex::Lock _l(_lock); - _authTokens.insert(std::string(tok)); - } - - /** - * Handle HTTP request - * - * @param fromAddress Originating IP address of request - * @param httpMethod HTTP method (as defined in ext/http-parser/http_parser.h) - * @param path Request path - * @param headers Request headers - * @param body Request body - * @param responseBody Result parameter: fill with response data - * @param responseContentType Result parameter: fill with content type - * @return HTTP response code - */ - unsigned int handleRequest( - const InetAddress &fromAddress, - unsigned int httpMethod, - const std::string &path, - const std::map &headers, - const std::string &body, - std::string &responseBody, - std::string &responseContentType); - -private: - OneService *const _svc; - Node *const _node; -#ifdef ZT_ENABLE_NETWORK_CONTROLLER - SqliteNetworkController *_controller; -#endif - std::string _uiStaticPath; - std::set _authTokens; - Mutex _lock; -}; - -} // namespace ZeroTier - -#endif diff --git a/service/OneService.cpp b/service/OneService.cpp index 13820f5..b151d25 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -31,12 +31,6 @@ #include "../version.h" #include "../include/ZeroTierOne.h" -#ifdef ZT_USE_SYSTEM_HTTP_PARSER -#include -#else -#include "../ext/http-parser/http_parser.h" -#endif - #include "../node/Constants.hpp" #include "../node/Mutex.hpp" #include "../node/Node.hpp" @@ -44,36 +38,20 @@ #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/BackgroundResolver.hpp" #include "../osdep/PortMapper.hpp" #include "../osdep/Binder.hpp" #include "../osdep/ManagedRoute.hpp" #include "OneService.hpp" -#include "ControlPlane.hpp" #include "ClusterGeoIpService.hpp" #include "ClusterDefinition.hpp" - -/** - * 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 - -#ifdef ZT_ENABLE_NETWORK_CONTROLLER -#include "../controller/SqliteNetworkController.hpp" -#else -class SqliteNetworkController; -#endif // ZT_ENABLE_NETWORK_CONTROLLER +#include "SoftwareUpdater.hpp" #ifdef __WINDOWS__ #include @@ -89,6 +67,28 @@ class SqliteNetworkController; #include #endif +#ifdef ZT_USE_SYSTEM_HTTP_PARSER +#include +#else +#include "../ext/http-parser/http_parser.h" +#endif + +#include "../ext/json/json.hpp" + +using json = nlohmann::json; + +/** + * Uncomment to enable UDP breakage switch + * + * If this is defined, the presence of a file called /tmp/ZT_BREAK_UDP + * will cause direct UDP TX/RX to stop working. This can be used to + * test TCP tunneling fallback and other robustness features. Deleting + * this file will cause it to start working again. + */ +//#define ZT_BREAK_UDP + +#include "../controller/EmbeddedNetworkController.hpp" + // Include the right tap device driver for this platform -- add new platforms here #ifdef ZT_SERVICE_NETCON @@ -114,6 +114,10 @@ namespace ZeroTier { typedef WindowsEthernetTap EthernetTap; } #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 @@ -129,11 +133,10 @@ namespace ZeroTier { typedef BSDEthernetTap EthernetTap; } #define ZT_TAP_CHECK_MULTICAST_INTERVAL 5000 // Path under ZT1 home for controller database if controller is enabled -#define ZT_CONTROLLER_DB_PATH "controller.db" +#define ZT_CONTROLLER_DB_PATH "controller.d" -// TCP fallback relay host -- geo-distributed using Amazon Route53 geo-aware DNS -#define ZT_TCP_FALLBACK_RELAY "tcp-fallback.zerotier.com" -#define ZT_TCP_FALLBACK_RELAY_PORT 443 +// 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 @@ -144,239 +147,13 @@ namespace ZeroTier { typedef BSDEthernetTap EthernetTap; } // 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 { -#ifdef ZT_AUTO_UPDATE -#define ZT_AUTO_UPDATE_MAX_HTTP_RESPONSE_SIZE (1024 * 1024 * 64) -#define ZT_AUTO_UPDATE_CHECK_PERIOD 21600000 -class BackgroundSoftwareUpdateChecker -{ -public: - bool isValidSigningIdentity(const Identity &id) - { - return ( - /* 0001 - 0004 : obsolete, used in old versions */ - /* 0005 */ (id == Identity("ba57ea350e:0:9d4be6d7f86c5660d5ee1951a3d759aa6e12a84fc0c0b74639500f1dbc1a8c566622e7d1c531967ebceb1e9d1761342f88324a8ba520c93c35f92f35080fa23f")) - /* 0006 */ ||(id == Identity("5067b21b83:0:8af477730f5055c48135b84bed6720a35bca4c0e34be4060a4c636288b1ec22217eb22709d610c66ed464c643130c51411bbb0294eef12fbe8ecc1a1e2c63a7a")) - /* 0007 */ ||(id == Identity("4f5e97a8f1:0:57880d056d7baeb04bbc057d6f16e6cb41388570e87f01492fce882485f65a798648595610a3ad49885604e7fb1db2dd3c2c534b75e42c3c0b110ad07b4bb138")) - /* 0008 */ ||(id == Identity("580bbb8e15:0:ad5ef31155bebc6bc413991992387e083fed26d699997ef76e7c947781edd47d1997161fa56ba337b1a2b44b129fd7c7197ce5185382f06011bc88d1363b4ddd")) - ); - } - - void doUpdateCheck() - { - std::string url(OneService::autoUpdateUrl()); - if ((url.length() <= 7)||(url.substr(0,7) != "http://")) - return; - - std::string httpHost; - std::string httpPath; - { - std::size_t slashIdx = url.substr(7).find_first_of('/'); - if (slashIdx == std::string::npos) { - httpHost = url.substr(7); - httpPath = "/"; - } else { - httpHost = url.substr(7,slashIdx); - httpPath = url.substr(slashIdx + 7); - } - } - if (httpHost.length() == 0) - return; - - std::vector ips(OSUtils::resolve(httpHost.c_str())); - for(std::vector::iterator ip(ips.begin());ip!=ips.end();++ip) { - if (!ip->port()) - ip->setPort(80); - std::string nfoPath = httpPath + "LATEST.nfo"; - std::map requestHeaders,responseHeaders; - std::string body; - requestHeaders["Host"] = httpHost; - unsigned int scode = Http::GET(ZT_AUTO_UPDATE_MAX_HTTP_RESPONSE_SIZE,60000,reinterpret_cast(&(*ip)),nfoPath.c_str(),requestHeaders,responseHeaders,body); - //fprintf(stderr,"UPDATE %s %s %u %lu\n",ip->toString().c_str(),nfoPath.c_str(),scode,body.length()); - if ((scode == 200)&&(body.length() > 0)) { - /* NFO fields: - * - * file= - * signedBy= - * ed25519= - * vMajor= - * vMinor= - * vRevision= */ - Dictionary<4096> nfo(body.c_str()); - char tmp[2048]; - - if (nfo.get("vMajor",tmp,sizeof(tmp)) <= 0) return; - const unsigned int vMajor = Utils::strToUInt(tmp); - if (nfo.get("vMinor",tmp,sizeof(tmp)) <= 0) return; - const unsigned int vMinor = Utils::strToUInt(tmp); - if (nfo.get("vRevision",tmp,sizeof(tmp)) <= 0) return; - const unsigned int vRevision = Utils::strToUInt(tmp); - if (Utils::compareVersion(vMajor,vMinor,vRevision,ZEROTIER_ONE_VERSION_MAJOR,ZEROTIER_ONE_VERSION_MINOR,ZEROTIER_ONE_VERSION_REVISION) <= 0) { - //fprintf(stderr,"UPDATE %u.%u.%u is not newer than our version\n",vMajor,vMinor,vRevision); - return; - } - - if (nfo.get("signedBy",tmp,sizeof(tmp)) <= 0) return; - Identity signedBy; - if ((!signedBy.fromString(tmp))||(!isValidSigningIdentity(signedBy))) { - //fprintf(stderr,"UPDATE invalid signedBy or not authorized signing identity.\n"); - return; - } - - if (nfo.get("file",tmp,sizeof(tmp)) <= 0) return; - std::string filePath(tmp); - if ((!filePath.length())||(filePath.find("..") != std::string::npos)) - return; - filePath = httpPath + filePath; - - std::string fileData; - if (Http::GET(ZT_AUTO_UPDATE_MAX_HTTP_RESPONSE_SIZE,60000,reinterpret_cast(&(*ip)),filePath.c_str(),requestHeaders,responseHeaders,fileData) != 200) { - //fprintf(stderr,"UPDATE GET %s failed\n",filePath.c_str()); - return; - } - - if (nfo.get("ed25519",tmp,sizeof(tmp)) <= 0) return; - std::string ed25519(Utils::unhex(tmp)); - if ((ed25519.length() == 0)||(!signedBy.verify(fileData.data(),(unsigned int)fileData.length(),ed25519.data(),(unsigned int)ed25519.length()))) { - //fprintf(stderr,"UPDATE %s failed signature check!\n",filePath.c_str()); - return; - } - - /* --------------------------------------------------------------- */ - /* We made it! Begin OS-specific installation code. */ - -#ifdef __APPLE__ - /* OSX version is in the form of a MacOSX .pkg file, so we will - * launch installer (normally in /usr/sbin) to install it. It will - * then turn around and shut down the service, update files, and - * relaunch. */ - { - char bashp[128],pkgp[128]; - Utils::snprintf(bashp,sizeof(bashp),"/tmp/ZeroTierOne-update-%u.%u.%u.sh",vMajor,vMinor,vRevision); - Utils::snprintf(pkgp,sizeof(pkgp),"/tmp/ZeroTierOne-update-%u.%u.%u.pkg",vMajor,vMinor,vRevision); - FILE *pkg = fopen(pkgp,"w"); - if ((!pkg)||(fwrite(fileData.data(),fileData.length(),1,pkg) != 1)) { - fclose(pkg); - unlink(bashp); - unlink(pkgp); - fprintf(stderr,"UPDATE error writing %s\n",pkgp); - return; - } - fclose(pkg); - FILE *bash = fopen(bashp,"w"); - if (!bash) { - fclose(pkg); - unlink(bashp); - unlink(pkgp); - fprintf(stderr,"UPDATE error writing %s\n",bashp); - return; - } - fprintf(bash, - "#!/bin/bash\n" - "export PATH=/bin:/usr/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/local/sbin\n" - "sleep 1\n" - "installer -pkg \"%s\" -target /\n" - "sleep 1\n" - "rm -f \"%s\" \"%s\"\n" - "exit 0\n", - pkgp, - pkgp, - bashp); - fclose(bash); - long pid = (long)vfork(); - if (pid == 0) { - setsid(); // detach from parent so that shell isn't killed when parent is killed - signal(SIGHUP,SIG_IGN); - signal(SIGTERM,SIG_IGN); - signal(SIGQUIT,SIG_IGN); - execl("/bin/bash","/bin/bash",bashp,(char *)0); - exit(0); - } - } -#endif // __APPLE__ - -#ifdef __WINDOWS__ - /* Windows version comes in the form of .MSI package that - * takes care of everything. */ - { - char tempp[512],batp[512],msip[512],cmdline[512]; - if (GetTempPathA(sizeof(tempp),tempp) <= 0) - return; - CreateDirectoryA(tempp,(LPSECURITY_ATTRIBUTES)0); - Utils::snprintf(batp,sizeof(batp),"%s\\ZeroTierOne-update-%u.%u.%u.bat",tempp,vMajor,vMinor,vRevision); - Utils::snprintf(msip,sizeof(msip),"%s\\ZeroTierOne-update-%u.%u.%u.msi",tempp,vMajor,vMinor,vRevision); - FILE *msi = fopen(msip,"wb"); - if ((!msi)||(fwrite(fileData.data(),(size_t)fileData.length(),1,msi) != 1)) { - fclose(msi); - return; - } - fclose(msi); - FILE *bat = fopen(batp,"wb"); - if (!bat) - return; - fprintf(bat, - "TIMEOUT.EXE /T 1 /NOBREAK\r\n" - "NET.EXE STOP \"ZeroTierOneService\"\r\n" - "TIMEOUT.EXE /T 1 /NOBREAK\r\n" - "MSIEXEC.EXE /i \"%s\" /qn\r\n" - "TIMEOUT.EXE /T 1 /NOBREAK\r\n" - "NET.EXE START \"ZeroTierOneService\"\r\n" - "DEL \"%s\"\r\n" - "DEL \"%s\"\r\n", - msip, - msip, - batp); - fclose(bat); - STARTUPINFOA si; - PROCESS_INFORMATION pi; - memset(&si,0,sizeof(si)); - memset(&pi,0,sizeof(pi)); - Utils::snprintf(cmdline,sizeof(cmdline),"CMD.EXE /c \"%s\"",batp); - CreateProcessA(NULL,cmdline,NULL,NULL,FALSE,CREATE_NO_WINDOW|CREATE_NEW_PROCESS_GROUP,NULL,NULL,&si,&pi); - } -#endif // __WINDOWS__ - - /* --------------------------------------------------------------- */ - - return; - } // else try to fetch from next IP address - } - } - - void threadMain() - throw() - { - try { - this->doUpdateCheck(); - } catch ( ... ) {} - } -}; -static BackgroundSoftwareUpdateChecker backgroundSoftwareUpdateChecker; -#endif // ZT_AUTO_UPDATE - -static bool isBlacklistedLocalInterfaceForZeroTierTraffic(const char *ifn) -{ -#if defined(__linux__) || defined(linux) || defined(__LINUX__) || defined(__linux) - if ((ifn[0] == 'l')&&(ifn[1] == 'o')) return true; // loopback - if ((ifn[0] == 'z')&&(ifn[1] == 't')) return true; // sanity check: zt# - if ((ifn[0] == 't')&&(ifn[1] == 'u')&&(ifn[2] == 'n')) return true; // tun# is probably an OpenVPN tunnel or similar - if ((ifn[0] == 't')&&(ifn[1] == 'a')&&(ifn[2] == 'p')) return true; // tap# is probably an OpenVPN tunnel or similar -#endif - -#ifdef __APPLE__ - if ((ifn[0] == 'l')&&(ifn[1] == 'o')) return true; // loopback - if ((ifn[0] == 'z')&&(ifn[1] == 't')) return true; // sanity check: zt# - if ((ifn[0] == 't')&&(ifn[1] == 'u')&&(ifn[2] == 'n')) return true; // tun# is probably an OpenVPN tunnel or similar - if ((ifn[0] == 't')&&(ifn[1] == 'a')&&(ifn[2] == 'p')) return true; // tap# is probably an OpenVPN tunnel or similar - if ((ifn[0] == 'u')&&(ifn[1] == 't')&&(ifn[2] == 'u')&&(ifn[3] == 'n')) return true; // ... as is utun# -#endif - - return false; -} - static std::string _trimString(const std::string &s) { unsigned long end = (unsigned long)s.length(); @@ -396,22 +173,139 @@ static std::string _trimString(const std::string &s) 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,uint64_t nwid,void **nuptr,enum ZT_VirtualNetworkConfigOperation op,const ZT_VirtualNetworkConfig *nwconf); -static void SnodeEventCallback(ZT_Node *node,void *uptr,enum ZT_Event event,const void *metaData); -static long SnodeDataStoreGetFunction(ZT_Node *node,void *uptr,const char *name,void *buf,unsigned long bufSize,unsigned long readIndex,unsigned long *totalSize); -static int SnodeDataStorePutFunction(ZT_Node *node,void *uptr,const char *name,const void *data,unsigned long len,int secure); -static int SnodeWirePacketSendFunction(ZT_Node *node,void *uptr,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,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,const struct sockaddr_storage *localAddr,const struct sockaddr_storage *remoteAddr); +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,uint64_t nwid,const MAC &from,const MAC &to,unsigned int etherType,unsigned int vlanId,const void *data,unsigned int len); +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); @@ -486,12 +380,26 @@ public: // begin member variables -------------------------------------------------- const std::string _homePath; - BackgroundResolver _tcpFallbackResolver; -#ifdef ZT_ENABLE_NETWORK_CONTROLLER - SqliteNetworkController *_controller; -#endif + std::string _authToken; + std::string _controllerDbPath; + EmbeddedNetworkController *_controller; Phy _phy; Node *_node; + SoftwareUpdater *_updater; + bool _updateAutoApply; + unsigned int _primaryPort; + + // Local configuration and memo-ized static path definitions + json _localConfig; + Hashtable< uint64_t,std::vector > _v4Hints; + Hashtable< uint64_t,std::vector > _v6Hints; + Hashtable< uint64_t,std::vector > _v4Blacklists; + Hashtable< uint64_t,std::vector > _v6Blacklists; + std::vector< InetAddress > _globalV4Blacklist; + std::vector< InetAddress > _globalV6Blacklist; + std::vector< InetAddress > _allowManagementFrom; + std::vector< std::string > _interfacePrefixBlacklist; + Mutex _localConfig_m; /* * To attempt to handle NAT/gateway craziness we use three local UDP ports: @@ -504,7 +412,6 @@ public: * 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 @@ -513,9 +420,6 @@ public: PhySocket *_v4TcpControlSocket; PhySocket *_v6TcpControlSocket; - // JSON API handler - ControlPlane *_controlPlane; - // Time we last received a packet from a global address uint64_t _lastDirectReceiveFromGlobal; #ifdef ZT_TCP_FALLBACK_RELAY @@ -543,7 +447,7 @@ public: EthernetTap *tap; ZT_VirtualNetworkConfig config; // memcpy() of raw config from core std::vector managedIps; - std::list managedRoutes; + std::list< SharedPtr > managedRoutes; NetworkSettings settings; }; std::map _nets; @@ -559,6 +463,7 @@ public: Mutex _termReason_m; // uPnP/NAT-PMP port mapper if enabled + bool _portMappingEnabled; // local.conf settings #ifdef ZT_USE_MINIUPNPC PortMapper *_portMapper; #endif @@ -578,13 +483,15 @@ public: OneServiceImpl(const char *hp,unsigned int port) : _homePath((hp) ? hp : ".") - ,_tcpFallbackResolver(ZT_TCP_FALLBACK_RELAY) -#ifdef ZT_ENABLE_NETWORK_CONTROLLER - ,_controller((SqliteNetworkController *)0) -#endif + ,_controllerDbPath(_homePath + ZT_PATH_SEPARATOR_S ZT_CONTROLLER_DB_PATH) + ,_controller((EmbeddedNetworkController *)0) ,_phy(this,false,true) ,_node((Node *)0) - ,_controlPlane((ControlPlane *)0) + ,_updater((SoftwareUpdater *)0) + ,_updateAutoApply(false) + ,_primaryPort(port) + ,_v4TcpControlSocket((PhySocket *)0) + ,_v6TcpControlSocket((PhySocket *)0) ,_lastDirectReceiveFromGlobal(0) #ifdef ZT_TCP_FALLBACK_RELAY ,_lastSendToGlobalV4(0) @@ -593,6 +500,7 @@ public: ,_nextBackgroundTaskDeadline(0) ,_tcpFallbackTunnel((TcpConnection *)0) ,_termReason(ONE_STILL_RUNNING) + ,_portMappingEnabled(true) #ifdef ZT_USE_MINIUPNPC ,_portMapper((PortMapper *)0) #endif @@ -606,56 +514,6 @@ public: _ports[0] = 0; _ports[1] = 0; _ports[2] = 0; - - // The control socket is bound to the default/static port on localhost. If we - // can do this, we have successfully allocated a port. The binders will take - // care of binding non-local addresses for ZeroTier traffic. - const int portTrials = (port == 0) ? 256 : 1; // if port is 0, pick random - for(int k=0;k 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 @@ -733,72 +720,41 @@ public: } #ifdef ZT_USE_MINIUPNPC - // 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 (_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); } - 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]); - { - FILE *trustpaths = fopen((_homePath + ZT_PATH_SEPARATOR_S + "trustedpaths").c_str(),"r"); - uint64_t ids[ZT_MAX_TRUSTED_PATHS]; - InetAddress addresses[ZT_MAX_TRUSTED_PATHS]; - if (trustpaths) { - char buf[1024]; - unsigned int count = 0; - while ((fgets(buf,sizeof(buf),trustpaths))&&(count < ZT_MAX_TRUSTED_PATHS)) { - int fno = 0; - char *saveptr = (char *)0; - uint64_t trustedPathId = 0; - InetAddress trustedPathNetwork; - for(char *f=Utils::stok(buf,"=\r\n \t",&saveptr);(f);f=Utils::stok((char *)0,"=\r\n \t",&saveptr)) { - if (fno == 0) { - trustedPathId = Utils::hexStrToU64(f); - } else if (fno == 1) { - trustedPathNetwork = InetAddress(f); - } else break; - ++fno; - } - if ( (trustedPathId != 0) && ((trustedPathNetwork.ss_family == AF_INET)||(trustedPathNetwork.ss_family == AF_INET6)) && (trustedPathNetwork.ipScope() != InetAddress::IP_SCOPE_GLOBAL) && (trustedPathNetwork.netmaskBits() > 0) ) { - ids[count] = trustedPathId; - addresses[count] = trustedPathNetwork; - ++count; - } - } - fclose(trustpaths); - if (count) - _node->setTrustedPaths(reinterpret_cast(addresses),ids,count); - } - } - -#ifdef ZT_ENABLE_NETWORK_CONTROLLER - _controller = new SqliteNetworkController(_node,(_homePath + ZT_PATH_SEPARATOR_S + ZT_CONTROLLER_DB_PATH).c_str(),(_homePath + ZT_PATH_SEPARATOR_S + "circuitTestResults.d").c_str()); + _controller = new EmbeddedNetworkController(_node,_controllerDbPath.c_str()); _node->setNetconfMaster((void *)_controller); -#endif #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 (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) { @@ -810,7 +766,7 @@ public: 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!"; + _fatalErrorMessage = "cluster: can't determine my cluster member ID: able to bind more than one cluster message socket IP/port!"; return _termReason; } _clusterMessageSocket = cs; @@ -821,7 +777,7 @@ public: 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."; + _fatalErrorMessage = "cluster: can't determine my cluster member ID: unable to bind to any cluster message socket IP/port."; return _termReason; } @@ -845,36 +801,31 @@ public: } #endif - _controlPlane = new ControlPlane(this,_node,(_homePath + ZT_PATH_SEPARATOR_S + "ui").c_str()); - _controlPlane->addAuthToken(authToken.c_str()); - -#ifdef ZT_ENABLE_NETWORK_CONTROLLER - _controlPlane->setController(_controller); -#endif - - { // Remember networks from previous session - std::vector networksDotD(OSUtils::listDirectory((_homePath + ZT_PATH_SEPARATOR_S + "networks.d").c_str())); + { // 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); + _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); } } - - // Start two background threads to handle expensive ops out of line - Thread::start(_node); - Thread::start(_node); _nextBackgroundTaskDeadline = 0; uint64_t clockShouldBe = OSUtils::now(); _lastRestart = clockShouldBe; uint64_t lastTapMulticastGroupCheck = 0; - uint64_t lastTcpFallbackResolve = 0; uint64_t lastBindRefresh = 0; - uint64_t lastLocalInterfaceAddressCheck = (OSUtils::now() - ZT_LOCAL_INTERFACE_CHECK_INTERVAL) + 15000; // do this in 15s to give portmapper time to configure and other things time to settle -#ifdef ZT_AUTO_UPDATE - uint64_t lastSoftwareUpdateCheck = 0; -#endif // ZT_AUTO_UPDATE + 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) { @@ -889,6 +840,12 @@ public: 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)) { @@ -896,6 +853,13 @@ public: 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; @@ -915,22 +879,10 @@ public: uint64_t dl = _nextBackgroundTaskDeadline; if (dl <= now) { - _node->processBackgroundTasks(now,&_nextBackgroundTaskDeadline); + _node->processBackgroundTasks((void *)0,now,&_nextBackgroundTaskDeadline); dl = _nextBackgroundTaskDeadline; } -#ifdef ZT_AUTO_UPDATE - if ((now - lastSoftwareUpdateCheck) >= ZT_AUTO_UPDATE_CHECK_PERIOD) { - lastSoftwareUpdateCheck = now; - Thread::start(&backgroundSoftwareUpdateChecker); - } -#endif // ZT_AUTO_UPDATE - - if ((now - lastTcpFallbackResolve) >= ZT_TCP_FALLBACK_RERESOLVE_DELAY) { - lastTcpFallbackResolve = now; - _tcpFallbackResolver.resolveNow(); - } - if ((_tcpFallbackTunnel)&&((now - _lastDirectReceiveFromGlobal) < (ZT_TCP_FALLBACK_AFTER / 2))) _phy.close(_tcpFallbackTunnel->sock); @@ -942,7 +894,7 @@ public: std::vector added,removed; n->second.tap->scanMulticastGroups(added,removed); for(std::vector::iterator m(added.begin());m!=added.end();++m) - _node->multicastSubscribe(n->first,m->mac().toInt(),m->adi()); + _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()); } @@ -993,8 +945,8 @@ public: _nets.clear(); } - delete _controlPlane; - _controlPlane = (ControlPlane *)0; + delete _updater; + _updater = (SoftwareUpdater *)0; delete _node; _node = (Node *)0; @@ -1022,11 +974,6 @@ public: else return std::string(); } - virtual bool tcpFallbackActive() const - { - return (_tcpFallbackTunnel != (TcpConnection *)0); - } - virtual void terminate() { _run_m.lock(); @@ -1041,7 +988,7 @@ public: std::map::const_iterator n(_nets.find(nwid)); if (n == _nets.end()) return false; - memcpy(&settings,&(n->second.settings),sizeof(NetworkSettings)); + settings = n->second.settings; return true; } @@ -1052,7 +999,7 @@ public: std::map::iterator n(_nets.find(nwid)); if (n == _nets.end()) return false; - memcpy(&(n->second.settings),&settings,sizeof(NetworkSettings)); + n->second.settings = settings; char nlcpath[256]; Utils::snprintf(nlcpath,sizeof(nlcpath),"%s" ZT_PATH_SEPARATOR_S "networks.d" ZT_PATH_SEPARATOR_S "%.16llx.local.conf",_homePath.c_str(),nwid); @@ -1070,13 +1017,553 @@ public: return true; } - // Begin private implementation methods + // 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()) { @@ -1120,16 +1607,20 @@ public: 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()); + 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()); - } + fprintf(stderr,"ERROR: unable to add ip address %s" ZT_EOL_S, ip->toString().c_str()); + } } - +#endif n.managedIps.swap(newManagedIps); } @@ -1144,13 +1635,13 @@ public: 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::iterator mr(n.managedRoutes.begin());mr!=n.managedRoutes.end();) { + 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()))) ) { + 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() == *via)) || (tapdev == mr->device()) ) ) { + if ( ((*mr)->target() == *target) && ( ((via->ss_family == target->ss_family)&&((*mr)->via().ipsEqual(*via))) || (tapdev == (*mr)->device()) ) ) { haveRoute = true; break; } @@ -1184,10 +1675,10 @@ public: continue; // If we've already applied this route, just sync it and continue - for(std::list::iterator mr(n.managedRoutes.begin());mr!=n.managedRoutes.end();++mr) { - if ( (mr->target() == *target) && ( ((via->ss_family == target->ss_family)&&(mr->via() == *via)) || (tapdev == mr->device()) ) ) { + 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(); + (*mr)->sync(); break; } } @@ -1195,13 +1686,17 @@ public: continue; // Add and apply new routes - n.managedRoutes.push_back(ManagedRoute()); - if (!n.managedRoutes.back().set(*target,*via,tapdev)) + 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 @@ -1221,6 +1716,7 @@ public: _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 @@ -1275,12 +1771,10 @@ public: inline void phyOnTcpAccept(PhySocket *sockL,PhySocket *sockN,void **uptrL,void **uptrN,const struct sockaddr *from) { - if ((!from)||(reinterpret_cast(from)->ipScope() != InetAddress::IP_SCOPE_LOOPBACK)) { - // Non-Loopback: deny (for now) + if (!from) { _phy.close(sockN,false); return; } else { - // Loopback == HTTP JSON API request TcpConnection *tc = new TcpConnection(); _tcpConnections.insert(tc); tc->type = TcpConnection::TCP_HTTP_INCOMING; @@ -1375,6 +1869,7 @@ public: if (from) { InetAddress fakeTcpLocalInterfaceAddress((uint32_t)0xffffffff,0xffff); const ZT_ResultCode rc = _node->processWirePacket( + (void *)0, OSUtils::now(), reinterpret_cast(&fakeTcpLocalInterfaceAddress), reinterpret_cast(&from), @@ -1444,6 +1939,7 @@ public: try { char friendlyName[128]; Utils::snprintf(friendlyName,sizeof(friendlyName),"ZeroTier One [%.16llx]",nwid); + n.tap = new EthernetTap( _homePath.c_str(), MAC(nwc->mac), @@ -1461,19 +1957,42 @@ public: if (OSUtils::readFile(nlcpath,nlcbuf)) { Dictionary<4096> nc; nc.load(nlcbuf.c_str()); - n.settings.allowManaged = nc.getB("allowManaged",true); - n.settings.allowGlobal = nc.getB("allowGlobal",false); - n.settings.allowDefault = nc.getB("allowDefault",false); + 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()); + 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()); + fprintf(stderr,"ERROR: unable to configure virtual network port: %s" ZT_EOL_S,exc.what()); #endif _nets.erase(nwid); return -999; @@ -1486,6 +2005,16 @@ public: 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); @@ -1532,11 +2061,18 @@ public: case ZT_EVENT_TRACE: { if (metaData) { - ::fprintf(stderr,"%s"ZT_EOL_S,(const char *)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; } @@ -1638,16 +2174,9 @@ public: _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))) { - std::vector tunnelIps(_tcpFallbackResolver.get()); - if (tunnelIps.empty()) { - if (!_tcpFallbackResolver.running()) - _tcpFallbackResolver.resolveNow(); - } else { - bool connected = false; - InetAddress addr(tunnelIps[(unsigned long)now % tunnelIps.size()]); - addr.setPort(ZT_TCP_FALLBACK_RELAY_PORT); - _phy.tcpConnect(reinterpret_cast(&addr),connected); - } + bool connected = false; + const InetAddress addr(ZT_TCP_FALLBACK_RELAY); + _phy.tcpConnect(reinterpret_cast(&addr),connected); } } _lastSendToGlobalV4 = now; @@ -1681,33 +2210,77 @@ public: n->tap->put(MAC(sourceMac),MAC(destMac),etherType,data,len); } - inline int nodePathCheckFunction(const struct sockaddr_storage *localAddr,const struct sockaddr_storage *remoteAddr) + inline int nodePathCheckFunction(uint64_t ztaddr,const struct sockaddr_storage *localAddr,const struct sockaddr_storage *remoteAddr) { - 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; + // 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 problems with this. */ + * 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(OSUtils::now(),nwid,from.toInt(),to.toInt(),etherType,vlanId,data,len,&_nextBackgroundTaskDeadline); + _node->processVirtualNetworkFrame((void *)0,OSUtils::now(),nwid,from.toInt(),to.toInt(),etherType,vlanId,data,len,&_nextBackgroundTaskDeadline); } inline void onHttpRequestToServer(TcpConnection *tc) @@ -1717,12 +2290,34 @@ public: std::string contentType("text/plain"); // default if not changed in handleRequest() unsigned int scode = 404; - try { - if (_controlPlane) - scode = _controlPlane->handleRequest(tc->from,tc->parser.method,tc->url,tc->headers,tc->body,data,contentType); - else scode = 500; - } catch ( ... ) { - scode = 500; + 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; @@ -1764,16 +2359,38 @@ public: bool shouldBindInterface(const char *ifname,const InetAddress &ifaddr) { - if (isBlacklistedLocalInterfaceForZeroTierTraffic(ifname)) - return false; +#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 - 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; +#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; + } } } } @@ -1834,20 +2451,22 @@ public: } }; -static int SnodeVirtualNetworkConfigFunction(ZT_Node *node,void *uptr,uint64_t nwid,void **nuptr,enum ZT_VirtualNetworkConfigOperation op,const ZT_VirtualNetworkConfig *nwconf) +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,enum ZT_Event event,const void *metaData) +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,const char *name,void *buf,unsigned long bufSize,unsigned long readIndex,unsigned long *totalSize) +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,const char *name,const void *data,unsigned long len,int secure) +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,const struct sockaddr_storage *localAddr,const struct sockaddr_storage *addr,const void *data,unsigned int len,unsigned int ttl) +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,uint64_t nwid,void **nuptr,uint64_t sourceMac,uint64_t destMac,unsigned int etherType,unsigned int vlanId,const void *data,unsigned int len) +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,const struct sockaddr_storage *localAddr,const struct sockaddr_storage *remoteAddr) -{ return reinterpret_cast(uptr)->nodePathCheckFunction(localAddr,remoteAddr); } +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) @@ -1864,7 +2483,7 @@ static int SclusterGeoIpFunction(void *uptr,const struct sockaddr_storage *addr, } #endif -static void StapFrameHandler(void *uptr,uint64_t nwid,const MAC &from,const MAC &to,unsigned int etherType,unsigned int vlanId,const void *data,unsigned int len) +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) @@ -1963,30 +2582,6 @@ std::string OneService::platformDefaultHomePath() return OSUtils::platformDefaultHomePath(); } -std::string OneService::autoUpdateUrl() -{ -#ifdef ZT_AUTO_UPDATE - -/* -#if defined(__LINUX__) && ( defined(__i386__) || defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(__i386) ) - if (sizeof(void *) == 8) - return "http://download.zerotier.com/ZeroTierOneInstaller-linux-x64-LATEST.nfo"; - else return "http://download.zerotier.com/ZeroTierOneInstaller-linux-x86-LATEST.nfo"; -#endif -*/ - -#if defined(__APPLE__) && ( defined(__i386__) || defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(__i386) ) - return "http://download.zerotier.com/update/mac_intel/"; -#endif - -#ifdef __WINDOWS__ - return "http://download.zerotier.com/update/win_intel/"; -#endif - -#endif // ZT_AUTO_UPDATE - return std::string(); -} - OneService *OneService::newInstance(const char *hp,unsigned int port) { return new OneServiceImpl(hp,port); } OneService::~OneService() {} diff --git a/service/OneService.hpp b/service/OneService.hpp index cead381..3390f2a 100644 --- a/service/OneService.hpp +++ b/service/OneService.hpp @@ -20,23 +20,14 @@ #define ZT_ONESERVICE_HPP #include +#include + +#include "../node/InetAddress.hpp" namespace ZeroTier { /** * Local service for ZeroTier One as system VPN/NFV provider - * - * If built with ZT_ENABLE_NETWORK_CONTROLLER defined, this includes and - * runs controller/SqliteNetworkController with a database called - * controller.db in the specified home directory. - * - * If built with ZT_AUTO_UPDATE, an official ZeroTier update URL is - * periodically checked and updates are automatically downloaded, verified - * against a built-in list of update signing keys, and installed. This is - * only supported for certain platforms. - * - * If built with ZT_ENABLE_CLUSTER, a 'cluster' file is checked and if - * present is read to determine the identity of other cluster members. */ class OneService { @@ -77,6 +68,12 @@ public: */ 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? */ @@ -93,11 +90,6 @@ public: */ static std::string platformDefaultHomePath(); - /** - * @return Auto-update URL or empty string if auto-updates unsupported or not enabled - */ - static std::string autoUpdateUrl(); - /** * Create a new instance of the service * @@ -111,9 +103,7 @@ public: * @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); + static OneService *newInstance(const char *hp,unsigned int port); virtual ~OneService(); @@ -141,11 +131,6 @@ public: */ virtual std::string portDeviceName(uint64_t nwid) const = 0; - /** - * @return True if TCP fallback is currently active - */ - virtual bool tcpFallbackActive() const = 0; - /** * Terminate background service (can be called from other threads) */ diff --git a/service/README.md b/service/README.md index 75c437d..f5223f2 100644 --- a/service/README.md +++ b/service/README.md @@ -1,9 +1,68 @@ ZeroTier One Network Virtualization Service ====== -This is the common background service implementation for ZeroTier One, the VPN-like OS-level 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.) -It provides a ready-made core I/O loop and a local HTTP-based JSON control bus for controlling the service. This control bus HTTP server can also serve the files in ui/ if this folder's contents are installed in the ZeroTier home folder. The ui/ implements a React-based HTML5 user interface which is then wrappered for various platforms via MacGap, Windows .NET WebControl, etc. It can also be used locally from scripts or via *curl*. +### 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 @@ -21,30 +80,20 @@ A *jsonp* URL argument may be supplied to request JSONP encapsulation. A JSONP r * Methods: GET * Returns: { object } - - - - - - - - - - - -
FieldTypeDescriptionWritable
addressstring10-digit hexadecimal ZeroTier address of this nodeno
publicIdentitystringFull public ZeroTier identity of this nodeno
onlinebooleanDoes this node appear to have upstream network access?no
tcpFallbackActivebooleanIs TCP fallback mode active?no
versionMajorintegerZeroTier major versionno
versionMinorintegerZeroTier minor versionno
versionRevintegerZeroTier revisionno
versionstringVersion in major.minor.rev formatno
clockintegerNode system clock in ms since epochno
- -#### /config - - * Purpose: Get or set local configuration - * Methods: GET, POST - * Returns: { object } - -No local configuration options are exposed yet. - - - -
FieldTypeDescriptionWritable
+| 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 @@ -64,23 +113,35 @@ To join a network, POST to it. Since networks have no mandatory writable paramet Most network settings are not writable, as they are defined by the network controller. - - - - - - - - - - - - - - - - -
FieldTypeDescriptionWritable
nwidstring16-digit hex network IDno
macstringEthernet MAC address of virtual network portno
namestringNetwork short name as configured on network controllerno
statusstringNetwork status: OK, ACCESS_DENIED, PORT_ERROR, etc.no
typestringNetwork type, currently PUBLIC or PRIVATEno
mtuintegerEthernet MTUno
dhcpbooleanIf true, DHCP may be used to obtain an IP addressno
bridgebooleanIf true, this node may bridge in other Ethernet devicesno
broadcastEnabledbooleanIs Ethernet broadcast (ff:ff:ff:ff:ff:ff) allowed?no
portErrorintegerError code (if any) returned by underlying OS "tap" driverno
netconfRevisionintegerNetwork configuration revision IDno
multicastSubscriptions[string]Multicast memberships as array of MAC/ADI tuplesno
assignedAddresses[string]ZeroTier-managed IP address assignments as array of IP/netmask bits tuplesno
portDeviceNamestringOS-specific network device name (if available)no
+| 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 @@ -92,31 +153,29 @@ Getting /peer returns an array of peer objects for all current peers. See below #### /peer/\ - * Purpose: Get information about a peer - * Methods: GET + * Purpose: Get or set information about a peer + * Methods: GET, POST * Returns: { object } - - - - - - - - - - - - -
FieldTypeDescriptionWritable
addressstring10-digit hex ZeroTier addressno
lastUnicastFrameintegerTime of last unicast frame in ms since epochno
lastMulticastFrameintegerTime of last multicast frame in ms since epochno
versionMajorintegerMajor version of remote if knownno
versionMinorintegerMinor version of remote if knownno
versionRevintegerRevision of remote if knownno
versionstringVersion in major.minor.rev formatno
latencyintegerLatency in milliseconds if knownno
rolestringLEAF, HUB, or ROOTSERVERno
paths[object]Array of path objects (see below)no
+| 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 describe direct physical paths to peer. If no path objects are listed, peer is only reachable via indirect relay fallback. Path object format is: +Path objects: - - - - - - - -
FieldTypeDescriptionWritable
addressstringPhysical socket address e.g. IP/port for UDPno
lastSendintegerLast send via this path in ms since epochno
lastReceiveintegerLast receive via this path in ms since epochno
fixedbooleanIf true, this is a statically-defined "fixed" pathno
preferredbooleanIf true, this is the current preferred pathno
+| Field | Type | Description | Writable | +| --------------------- | ------------- | ------------------------------------------------- | -------- | +| address | string | Physical socket address e.g. IP/port | no | +| lastSend | integer | Time of last send through this path | no | +| lastReceive | integer | Time of last receive through this path | no | +| active | boolean | Is this path in use? | no | +| expired | boolean | Is this path expired? | no | +| preferred | boolean | Is this a current preferred path? | no | +| trustedPathId | integer | If nonzero this is a trusted path (unencrypted) | no | diff --git a/service/SoftwareUpdater.cpp b/service/SoftwareUpdater.cpp new file mode 100644 index 0000000..7ec377c --- /dev/null +++ b/service/SoftwareUpdater.cpp @@ -0,0 +1,406 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include + +#include "../node/Constants.hpp" +#include "../version.h" + +#ifdef __WINDOWS__ +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#include +#endif + +#include "SoftwareUpdater.hpp" + +#include "../node/Utils.hpp" +#include "../node/SHA512.hpp" +#include "../node/Buffer.hpp" +#include "../node/Node.hpp" + +#include "../osdep/OSUtils.hpp" + +namespace ZeroTier { + +SoftwareUpdater::SoftwareUpdater(Node &node,const std::string &homePath) : + _node(node), + _lastCheckTime(0), + _homePath(homePath), + _channel(ZT_SOFTWARE_UPDATE_DEFAULT_CHANNEL), + _distLog((FILE *)0), + _latestValid(false), + _downloadLength(0) +{ + OSUtils::rm((_homePath + ZT_PATH_SEPARATOR_S ZT_SOFTWARE_UPDATE_BIN_FILENAME).c_str()); +} + +SoftwareUpdater::~SoftwareUpdater() +{ + if (_distLog) + fclose(_distLog); +} + +void SoftwareUpdater::setUpdateDistribution(bool distribute) +{ + _dist.clear(); + if (distribute) { + _distLog = fopen((_homePath + ZT_PATH_SEPARATOR_S "update-dist.log").c_str(),"a"); + + const std::string udd(_homePath + ZT_PATH_SEPARATOR_S "update-dist.d"); + const std::vector ud(OSUtils::listDirectory(udd.c_str())); + for(std::vector::const_iterator u(ud.begin());u!=ud.end();++u) { + // Each update has a companion .json file describing it. Other files are ignored. + if ((u->length() > 5)&&(u->substr(u->length() - 5,5) == ".json")) { + + std::string buf; + if (OSUtils::readFile((udd + ZT_PATH_SEPARATOR_S + *u).c_str(),buf)) { + try { + _D d; + d.meta = OSUtils::jsonParse(buf); // throws on invalid JSON + + // If update meta is called e.g. foo.exe.json, then foo.exe is the update itself + const std::string binPath(udd + ZT_PATH_SEPARATOR_S + u->substr(0,u->length() - 5)); + const std::string metaHash(OSUtils::jsonBinFromHex(d.meta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_HASH])); + if ((metaHash.length() == ZT_SHA512_DIGEST_LEN)&&(OSUtils::readFile(binPath.c_str(),d.bin))) { + uint8_t sha512[ZT_SHA512_DIGEST_LEN]; + SHA512::hash(sha512,d.bin.data(),(unsigned int)d.bin.length()); + if (!memcmp(sha512,metaHash.data(),ZT_SHA512_DIGEST_LEN)) { // double check that hash in JSON is correct + d.meta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_SIZE] = d.bin.length(); // override with correct value -- setting this in meta json is optional + _dist[Array(sha512)] = d; + if (_distLog) { + fprintf(_distLog,".......... INIT: DISTRIBUTING %s (%u bytes)" ZT_EOL_S,binPath.c_str(),(unsigned int)d.bin.length()); + fflush(_distLog); + } + } + } + } catch ( ... ) {} // ignore bad meta JSON, etc. + } + + } + } + } else { + if (_distLog) { + fclose(_distLog); + _distLog = (FILE *)0; + } + } +} + +void SoftwareUpdater::handleSoftwareUpdateUserMessage(uint64_t origin,const void *data,unsigned int len) +{ + if (!len) return; + const MessageVerb v = (MessageVerb)reinterpret_cast(data)[0]; + try { + switch(v) { + + case VERB_GET_LATEST: + case VERB_LATEST: { + nlohmann::json req = OSUtils::jsonParse(std::string(reinterpret_cast(data) + 1,len - 1)); // throws on invalid JSON + if (req.is_object()) { + const unsigned int rvMaj = (unsigned int)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_VERSION_MAJOR],0); + const unsigned int rvMin = (unsigned int)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_VERSION_MINOR],0); + const unsigned int rvRev = (unsigned int)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_VERSION_REVISION],0); + const unsigned int rvBld = (unsigned int)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_VERSION_BUILD],0); + const unsigned int rvPlatform = (unsigned int)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_PLATFORM],0); + const unsigned int rvArch = (unsigned int)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_ARCHITECTURE],0); + const unsigned int rvVendor = (unsigned int)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_VENDOR],0); + const std::string rvChannel(OSUtils::jsonString(req[ZT_SOFTWARE_UPDATE_JSON_CHANNEL],"")); + + if (v == VERB_GET_LATEST) { + + if (_dist.size() > 0) { + const nlohmann::json *latest = (const nlohmann::json *)0; + const std::string expectedSigner = OSUtils::jsonString(req[ZT_SOFTWARE_UPDATE_JSON_EXPECT_SIGNED_BY],""); + unsigned int bestVMaj = rvMaj; + unsigned int bestVMin = rvMin; + unsigned int bestVRev = rvRev; + unsigned int bestVBld = rvBld; + for(std::map< Array,_D >::const_iterator d(_dist.begin());d!=_dist.end();++d) { + // The arch field in update description .json files can be an array for e.g. multi-arch update files + const nlohmann::json &dvArch2 = d->second.meta[ZT_SOFTWARE_UPDATE_JSON_ARCHITECTURE]; + std::vector dvArch; + if (dvArch2.is_array()) { + for(unsigned long i=0;isecond.meta[ZT_SOFTWARE_UPDATE_JSON_PLATFORM],0) == rvPlatform)&& + (std::find(dvArch.begin(),dvArch.end(),rvArch) != dvArch.end())&& + (OSUtils::jsonInt(d->second.meta[ZT_SOFTWARE_UPDATE_JSON_VENDOR],0) == rvVendor)&& + (OSUtils::jsonString(d->second.meta[ZT_SOFTWARE_UPDATE_JSON_CHANNEL],"") == rvChannel)&& + (OSUtils::jsonString(d->second.meta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_SIGNED_BY],"") == expectedSigner)) { + const unsigned int dvMaj = (unsigned int)OSUtils::jsonInt(d->second.meta[ZT_SOFTWARE_UPDATE_JSON_VERSION_MAJOR],0); + const unsigned int dvMin = (unsigned int)OSUtils::jsonInt(d->second.meta[ZT_SOFTWARE_UPDATE_JSON_VERSION_MINOR],0); + const unsigned int dvRev = (unsigned int)OSUtils::jsonInt(d->second.meta[ZT_SOFTWARE_UPDATE_JSON_VERSION_REVISION],0); + const unsigned int dvBld = (unsigned int)OSUtils::jsonInt(d->second.meta[ZT_SOFTWARE_UPDATE_JSON_VERSION_BUILD],0); + if (Utils::compareVersion(dvMaj,dvMin,dvRev,dvBld,bestVMaj,bestVMin,bestVRev,bestVBld) > 0) { + latest = &(d->second.meta); + bestVMaj = dvMaj; + bestVMin = dvMin; + bestVRev = dvRev; + bestVBld = dvBld; + } + } + } + if (latest) { + std::string lj; + lj.push_back((char)VERB_LATEST); + lj.append(OSUtils::jsonDump(*latest)); + _node.sendUserMessage((void *)0,origin,ZT_SOFTWARE_UPDATE_USER_MESSAGE_TYPE,lj.data(),(unsigned int)lj.length()); + if (_distLog) { + fprintf(_distLog,"%.10llx GET_LATEST %u.%u.%u_%u platform %u arch %u vendor %u channel %s -> LATEST %u.%u.%u_%u" ZT_EOL_S,(unsigned long long)origin,rvMaj,rvMin,rvRev,rvBld,rvPlatform,rvArch,rvVendor,rvChannel.c_str(),bestVMaj,bestVMin,bestVRev,bestVBld); + fflush(_distLog); + } + } + } // else no reply, since we have nothing to distribute + + } else { // VERB_LATEST + + if ((origin == ZT_SOFTWARE_UPDATE_SERVICE)&& + (Utils::compareVersion(rvMaj,rvMin,rvRev,rvBld,ZEROTIER_ONE_VERSION_MAJOR,ZEROTIER_ONE_VERSION_MINOR,ZEROTIER_ONE_VERSION_REVISION,ZEROTIER_ONE_VERSION_BUILD) > 0)&& + (OSUtils::jsonString(req[ZT_SOFTWARE_UPDATE_JSON_UPDATE_SIGNED_BY],"") == ZT_SOFTWARE_UPDATE_SIGNING_AUTHORITY)) { + const unsigned long len = (unsigned long)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_UPDATE_SIZE],0); + const std::string hash = OSUtils::jsonBinFromHex(req[ZT_SOFTWARE_UPDATE_JSON_UPDATE_HASH]); + if ((len <= ZT_SOFTWARE_UPDATE_MAX_SIZE)&&(hash.length() >= 16)) { + if (_latestMeta != req) { + _latestMeta = req; + _latestValid = false; + OSUtils::rm((_homePath + ZT_PATH_SEPARATOR_S ZT_SOFTWARE_UPDATE_BIN_FILENAME).c_str()); + _download = std::string(); + memcpy(_downloadHashPrefix.data,hash.data(),16); + _downloadLength = len; + } + + if ((_downloadLength > 0)&&(_download.length() < _downloadLength)) { + Buffer<128> gd; + gd.append((uint8_t)VERB_GET_DATA); + gd.append(_downloadHashPrefix.data,16); + gd.append((uint32_t)_download.length()); + _node.sendUserMessage((void *)0,ZT_SOFTWARE_UPDATE_SERVICE,ZT_SOFTWARE_UPDATE_USER_MESSAGE_TYPE,gd.data(),gd.size()); + //printf(">> GET_DATA @%u\n",(unsigned int)_download.length()); + } + } + } + } + + } + } break; + + case VERB_GET_DATA: + if ((len >= 21)&&(_dist.size() > 0)) { + unsigned long idx = (unsigned long)*(reinterpret_cast(data) + 17) << 24; + idx |= (unsigned long)*(reinterpret_cast(data) + 18) << 16; + idx |= (unsigned long)*(reinterpret_cast(data) + 19) << 8; + idx |= (unsigned long)*(reinterpret_cast(data) + 20); + //printf("<< GET_DATA @%u from %.10llx for %s\n",(unsigned int)idx,origin,Utils::hex(reinterpret_cast(data) + 1,16).c_str()); + std::map< Array,_D >::iterator d(_dist.find(Array(reinterpret_cast(data) + 1))); + if ((d != _dist.end())&&(idx < (unsigned long)d->second.bin.length())) { + Buffer buf; + buf.append((uint8_t)VERB_DATA); + buf.append(reinterpret_cast(data) + 1,16); + buf.append((uint32_t)idx); + buf.append(d->second.bin.data() + idx,std::min((unsigned long)ZT_SOFTWARE_UPDATE_CHUNK_SIZE,(unsigned long)(d->second.bin.length() - idx))); + _node.sendUserMessage((void *)0,origin,ZT_SOFTWARE_UPDATE_USER_MESSAGE_TYPE,buf.data(),buf.size()); + //printf(">> DATA @%u\n",(unsigned int)idx); + } + } + break; + + case VERB_DATA: + if ((len >= 21)&&(_downloadLength > 0)&&(!memcmp(_downloadHashPrefix.data,reinterpret_cast(data) + 1,16))) { + unsigned long idx = (unsigned long)*(reinterpret_cast(data) + 17) << 24; + idx |= (unsigned long)*(reinterpret_cast(data) + 18) << 16; + idx |= (unsigned long)*(reinterpret_cast(data) + 19) << 8; + idx |= (unsigned long)*(reinterpret_cast(data) + 20); + //printf("<< DATA @%u / %u bytes (we now have %u bytes)\n",(unsigned int)idx,(unsigned int)(len - 21),(unsigned int)_download.length()); + if (idx == (unsigned long)_download.length()) { + _download.append(reinterpret_cast(data) + 21,len - 21); + if (_download.length() < _downloadLength) { + Buffer<128> gd; + gd.append((uint8_t)VERB_GET_DATA); + gd.append(_downloadHashPrefix.data,16); + gd.append((uint32_t)_download.length()); + _node.sendUserMessage((void *)0,ZT_SOFTWARE_UPDATE_SERVICE,ZT_SOFTWARE_UPDATE_USER_MESSAGE_TYPE,gd.data(),gd.size()); + //printf(">> GET_DATA @%u\n",(unsigned int)_download.length()); + } + } + } + break; + + default: + if (_distLog) { + fprintf(_distLog,"%.10llx WARNING: bad update message verb==%u length==%u (unrecognized verb)" ZT_EOL_S,(unsigned long long)origin,(unsigned int)v,len); + fflush(_distLog); + } + break; + } + } catch ( ... ) { + if (_distLog) { + fprintf(_distLog,"%.10llx WARNING: bad update message verb==%u length==%u (unexpected exception, likely invalid JSON)" ZT_EOL_S,(unsigned long long)origin,(unsigned int)v,len); + fflush(_distLog); + } + } +} + +bool SoftwareUpdater::check(const uint64_t now) +{ + if ((now - _lastCheckTime) >= ZT_SOFTWARE_UPDATE_CHECK_PERIOD) { + _lastCheckTime = now; + char tmp[512]; + const unsigned int len = Utils::snprintf(tmp,sizeof(tmp), + "%c{\"" ZT_SOFTWARE_UPDATE_JSON_VERSION_MAJOR "\":%d," + "\"" ZT_SOFTWARE_UPDATE_JSON_VERSION_MINOR "\":%d," + "\"" ZT_SOFTWARE_UPDATE_JSON_VERSION_REVISION "\":%d," + "\"" ZT_SOFTWARE_UPDATE_JSON_VERSION_BUILD "\":%d," + "\"" ZT_SOFTWARE_UPDATE_JSON_EXPECT_SIGNED_BY "\":\"%s\"," + "\"" ZT_SOFTWARE_UPDATE_JSON_PLATFORM "\":%d," + "\"" ZT_SOFTWARE_UPDATE_JSON_ARCHITECTURE "\":%d," + "\"" ZT_SOFTWARE_UPDATE_JSON_VENDOR "\":%d," + "\"" ZT_SOFTWARE_UPDATE_JSON_CHANNEL "\":\"%s\"}", + (char)VERB_GET_LATEST, + ZEROTIER_ONE_VERSION_MAJOR, + ZEROTIER_ONE_VERSION_MINOR, + ZEROTIER_ONE_VERSION_REVISION, + ZEROTIER_ONE_VERSION_BUILD, + ZT_SOFTWARE_UPDATE_SIGNING_AUTHORITY, + ZT_BUILD_PLATFORM, + ZT_BUILD_ARCHITECTURE, + (int)ZT_VENDOR_ZEROTIER, + _channel.c_str()); + _node.sendUserMessage((void *)0,ZT_SOFTWARE_UPDATE_SERVICE,ZT_SOFTWARE_UPDATE_USER_MESSAGE_TYPE,tmp,len); + //printf(">> GET_LATEST\n"); + } + + if (_latestValid) + return true; + + if (_downloadLength > 0) { + if (_download.length() >= _downloadLength) { + // This is the very important security validation part that makes sure + // this software update doesn't have cooties. + + const std::string binPath(_homePath + ZT_PATH_SEPARATOR_S ZT_SOFTWARE_UPDATE_BIN_FILENAME); + try { + // (1) Check the hash itself to make sure the image is basically okay + uint8_t sha512[ZT_SHA512_DIGEST_LEN]; + SHA512::hash(sha512,_download.data(),(unsigned int)_download.length()); + if (Utils::hex(sha512,ZT_SHA512_DIGEST_LEN) == OSUtils::jsonString(_latestMeta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_HASH],"")) { + // (2) Check signature by signing authority + const std::string sig(OSUtils::jsonBinFromHex(_latestMeta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_SIGNATURE])); + if (Identity(ZT_SOFTWARE_UPDATE_SIGNING_AUTHORITY).verify(_download.data(),(unsigned int)_download.length(),sig.data(),(unsigned int)sig.length())) { + // (3) Try to save file, and if so we are good. + OSUtils::rm(binPath.c_str()); + if (OSUtils::writeFile(binPath.c_str(),_download)) { + OSUtils::lockDownFile(binPath.c_str(),false); + _latestValid = true; + //printf("VALID UPDATE\n%s\n",OSUtils::jsonDump(_latestMeta).c_str()); + _download = std::string(); + _downloadLength = 0; + return true; + } + } + } + } catch ( ... ) {} // any exception equals verification failure + + // If we get here, checks failed. + //printf("INVALID UPDATE (!!!)\n%s\n",OSUtils::jsonDump(_latestMeta).c_str()); + OSUtils::rm(binPath.c_str()); + _latestMeta = nlohmann::json(); + _latestValid = false; + _download = std::string(); + _downloadLength = 0; + } else { + Buffer<128> gd; + gd.append((uint8_t)VERB_GET_DATA); + gd.append(_downloadHashPrefix.data,16); + gd.append((uint32_t)_download.length()); + _node.sendUserMessage((void *)0,ZT_SOFTWARE_UPDATE_SERVICE,ZT_SOFTWARE_UPDATE_USER_MESSAGE_TYPE,gd.data(),gd.size()); + //printf(">> GET_DATA @%u\n",(unsigned int)_download.length()); + } + } + + return false; +} + +void SoftwareUpdater::apply() +{ + std::string updatePath(_homePath + ZT_PATH_SEPARATOR_S ZT_SOFTWARE_UPDATE_BIN_FILENAME); + if ((_latestMeta.is_object())&&(_latestValid)&&(OSUtils::fileExists(updatePath.c_str(),false))) { +#ifdef __WINDOWS__ + std::string cmdArgs(OSUtils::jsonString(_latestMeta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_EXEC_ARGS],"")); + if (cmdArgs.length() > 0) { + updatePath.push_back(' '); + updatePath.append(cmdArgs); + } + STARTUPINFOA si; + PROCESS_INFORMATION pi; + memset(&si,0,sizeof(si)); + memset(&pi,0,sizeof(pi)); + CreateProcessA(NULL,const_cast(updatePath.c_str()),NULL,NULL,FALSE,CREATE_NO_WINDOW|CREATE_NEW_PROCESS_GROUP,NULL,NULL,&si,&pi); + // Windows doesn't exit here -- updater will stop the service during update, etc. -- but we do want to stop multiple runs from happening + _latestMeta = nlohmann::json(); + _latestValid = false; +#else + char *argv[256]; + unsigned long ac = 0; + argv[ac++] = const_cast(updatePath.c_str()); + const std::vector argsSplit(OSUtils::split(OSUtils::jsonString(_latestMeta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_EXEC_ARGS],"").c_str()," ","\\","\"")); + for(std::vector::const_iterator a(argsSplit.begin());a!=argsSplit.end();++a) { + argv[ac] = const_cast(a->c_str()); + if (++ac == 255) break; + } + argv[ac] = (char *)0; + chmod(updatePath.c_str(),0700); + + // Close all open file descriptors except stdout/stderr/etc. + int minMyFd = STDIN_FILENO; + if (STDOUT_FILENO > minMyFd) minMyFd = STDOUT_FILENO; + if (STDERR_FILENO > minMyFd) minMyFd = STDERR_FILENO; + ++minMyFd; +#ifdef _SC_OPEN_MAX + int maxMyFd = (int)sysconf(_SC_OPEN_MAX); + if (maxMyFd <= minMyFd) + maxMyFd = 65536; +#else + int maxMyFd = 65536; +#endif + while (minMyFd < maxMyFd) + close(minMyFd++); + + execv(updatePath.c_str(),argv); + fprintf(stderr,"FATAL: unable to execute software update binary at %s\n",updatePath.c_str()); + exit(1); +#endif + } +} + +} // namespace ZeroTier diff --git a/service/SoftwareUpdater.hpp b/service/SoftwareUpdater.hpp new file mode 100644 index 0000000..4bb0ef5 --- /dev/null +++ b/service/SoftwareUpdater.hpp @@ -0,0 +1,209 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ZT_SOFTWAREUPDATER_HPP +#define ZT_SOFTWAREUPDATER_HPP + +#include +#include + +#include +#include +#include + +#include "../include/ZeroTierOne.h" + +#include "../node/Identity.hpp" +#include "../node/Array.hpp" +#include "../node/Packet.hpp" + +#include "../ext/json/json.hpp" + +/** + * VERB_USER_MESSAGE type ID for software update messages + */ +#define ZT_SOFTWARE_UPDATE_USER_MESSAGE_TYPE 100 + +/** + * ZeroTier address of node that provides software updates + */ +#define ZT_SOFTWARE_UPDATE_SERVICE 0xb1d366e81fULL + +/** + * ZeroTier identity that must be used to sign software updates + * + * df24360f3e - update-signing-key-0010 generated Fri Jan 13th, 2017 at 4:05pm PST + */ +#define ZT_SOFTWARE_UPDATE_SIGNING_AUTHORITY "df24360f3e:0:06072642959c8dfb68312904d74d90197c8a7692697caa1b3fd769eca714f4370fab462fcee6ebcb5fffb63bc5af81f28a2514b2cd68daabb42f7352c06f21db" + +/** + * Chunk size for in-band downloads (can be changed, designed to always fit in one UDP packet easily) + */ +#define ZT_SOFTWARE_UPDATE_CHUNK_SIZE (ZT_PROTO_MAX_PACKET_LENGTH - 128) + +/** + * Sanity limit for the size of an update binary image + */ +#define ZT_SOFTWARE_UPDATE_MAX_SIZE (1024 * 1024 * 256) + +/** + * How often (ms) do we check? + */ +#define ZT_SOFTWARE_UPDATE_CHECK_PERIOD (60 * 10 * 1000) + +/** + * Default update channel + */ +#define ZT_SOFTWARE_UPDATE_DEFAULT_CHANNEL "release" + +/** + * Filename for latest update's binary image + */ +#define ZT_SOFTWARE_UPDATE_BIN_FILENAME "latest-update.exe" + +#define ZT_SOFTWARE_UPDATE_JSON_VERSION_MAJOR "vMajor" +#define ZT_SOFTWARE_UPDATE_JSON_VERSION_MINOR "vMinor" +#define ZT_SOFTWARE_UPDATE_JSON_VERSION_REVISION "vRev" +#define ZT_SOFTWARE_UPDATE_JSON_VERSION_BUILD "vBuild" +#define ZT_SOFTWARE_UPDATE_JSON_PLATFORM "platform" +#define ZT_SOFTWARE_UPDATE_JSON_ARCHITECTURE "arch" +#define ZT_SOFTWARE_UPDATE_JSON_VENDOR "vendor" +#define ZT_SOFTWARE_UPDATE_JSON_CHANNEL "channel" +#define ZT_SOFTWARE_UPDATE_JSON_EXPECT_SIGNED_BY "expectedSigner" +#define ZT_SOFTWARE_UPDATE_JSON_UPDATE_SIGNED_BY "signer" +#define ZT_SOFTWARE_UPDATE_JSON_UPDATE_SIGNATURE "signature" +#define ZT_SOFTWARE_UPDATE_JSON_UPDATE_HASH "hash" +#define ZT_SOFTWARE_UPDATE_JSON_UPDATE_SIZE "size" +#define ZT_SOFTWARE_UPDATE_JSON_UPDATE_EXEC_ARGS "execArgs" +#define ZT_SOFTWARE_UPDATE_JSON_UPDATE_URL "url" + +namespace ZeroTier { + +class Node; + +/** + * This class handles retrieving and executing updates, or serving them + */ +class SoftwareUpdater +{ +public: + /** + * Each message begins with an 8-bit message verb + */ + enum MessageVerb + { + /** + * Payload: JSON containing current system platform, version, etc. + */ + VERB_GET_LATEST = 1, + + /** + * Payload: JSON describing latest update for this target. (No response is sent if there is none.) + */ + VERB_LATEST = 2, + + /** + * Payload: + * <[16] first 128 bits of hash of data object> + * <[4] 32-bit index of chunk to get> + */ + VERB_GET_DATA = 3, + + /** + * Payload: + * <[16] first 128 bits of hash of data object> + * <[4] 32-bit index of chunk> + * <[...] chunk data> + */ + VERB_DATA = 4 + }; + + SoftwareUpdater(Node &node,const std::string &homePath); + ~SoftwareUpdater(); + + /** + * Set whether or not we will distribute updates + * + * @param distribute If true, scan update-dist.d now and distribute updates found there -- if false, clear and stop distributing + */ + void setUpdateDistribution(bool distribute); + + /** + * Handle a software update user message + * + * @param origin ZeroTier address of message origin + * @param data Message payload + * @param len Length of message + */ + void handleSoftwareUpdateUserMessage(uint64_t origin,const void *data,unsigned int len); + + /** + * Check for updates and do other update-related housekeeping + * + * It should be called about every 10 seconds. + * + * @return True if we've downloaded and verified an update + */ + bool check(const uint64_t now); + + /** + * @return Meta-data for downloaded update or NULL if none + */ + inline const nlohmann::json &pending() const { return _latestMeta; } + + /** + * Apply any ready update now + * + * Depending on the platform this function may never return and may forcibly + * exit the process. It does nothing if no update is ready. + */ + void apply(); + + /** + * Set software update channel + * + * @param channel 'release', 'beta', etc. + */ + inline void setChannel(const std::string &channel) { _channel = channel; } + +private: + Node &_node; + uint64_t _lastCheckTime; + std::string _homePath; + std::string _channel; + FILE *_distLog; + + // Offered software updates if we are an update host (we have update-dist.d and update hosting is enabled) + struct _D + { + nlohmann::json meta; + std::string bin; + }; + std::map< Array,_D > _dist; // key is first 16 bytes of hash + + nlohmann::json _latestMeta; + bool _latestValid; + + std::string _download; + Array _downloadHashPrefix; + unsigned long _downloadLength; +}; + +} // namespace ZeroTier + +#endif diff --git a/tcp-proxy/tcp-proxy.cpp b/tcp-proxy/tcp-proxy.cpp index 2fe500d..a7906aa 100644 --- a/tcp-proxy/tcp-proxy.cpp +++ b/tcp-proxy/tcp-proxy.cpp @@ -120,7 +120,7 @@ struct TcpProxyService return (PhySocket *)0; } - void phyOnDatagram(PhySocket *sock,void **uptr,const struct sockaddr *from,void *data,unsigned long len) + void phyOnDatagram(PhySocket *sock,void **uptr,const struct sockaddr *localAddr,const struct sockaddr *from,void *data,unsigned long len) { if (!*uptr) return; @@ -134,7 +134,7 @@ struct TcpProxyService if ((c.tcpWritePtr + 5 + mlen) <= sizeof(c.tcpWriteBuf)) { if (!c.tcpWritePtr) - phy->tcpSetNotifyWritable(c.tcp,true); + phy->setNotifyWritable(c.tcp,true); c.tcpWriteBuf[c.tcpWritePtr++] = 0x17; // look like TLS data c.tcpWriteBuf[c.tcpWritePtr++] = 0x03; // look like TLS 1.2 @@ -257,13 +257,13 @@ struct TcpProxyService { Client &c = *((Client *)*uptr); if (c.tcpWritePtr) { - long n = phy->tcpSend(sock,c.tcpWriteBuf,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->tcpSetNotifyWritable(sock,false); + phy->setNotifyWritable(sock,false); } - } else phy->tcpSetNotifyWritable(sock,false); + } else phy->setNotifyWritable(sock,false); } void doHousekeeping() diff --git a/version.h b/version.h index 1830011..d20f614 100644 --- a/version.h +++ b/version.h @@ -27,11 +27,27 @@ /** * Minor version */ -#define ZEROTIER_ONE_VERSION_MINOR 1 +#define ZEROTIER_ONE_VERSION_MINOR 2 /** * Revision */ -#define ZEROTIER_ONE_VERSION_REVISION 14 +#define ZEROTIER_ONE_VERSION_REVISION 4 + +/** + * Build version + * + * This starts at 0 for each major.minor.rev tuple and can be incremented + * to force a minor update without an actual version number change. It's + * not part of the actual release version number. + */ +#define ZEROTIER_ONE_VERSION_BUILD 0 + +#ifndef ZT_BUILD_ARCHITECTURE +#define ZT_BUILD_ARCHITECTURE 0 +#endif +#ifndef ZT_BUILD_PLATFORM +#define ZT_BUILD_PLATFORM 0 +#endif #endif diff --git a/windows/TapDriver6/TapDriver6.vcxproj b/windows/TapDriver6/TapDriver6.vcxproj index b1f9ae1..cf6b150 100644 --- a/windows/TapDriver6/TapDriver6.vcxproj +++ b/windows/TapDriver6/TapDriver6.vcxproj @@ -63,7 +63,6 @@ $(VCTargetsPath11) - WindowsKernelModeDriver8.0 Driver KMDF @@ -71,54 +70,66 @@ Windows8 true + WindowsKernelModeDriver10.0 Windows8 false + WindowsKernelModeDriver10.0 Windows7 true + WindowsKernelModeDriver10.0 Windows7 false + WindowsKernelModeDriver10.0 Vista true + WindowsKernelModeDriver8.0 Vista false 1 7 + WindowsKernelModeDriver8.0 Windows8 true + WindowsKernelModeDriver10.0 Windows8 false + WindowsKernelModeDriver10.0 Windows7 true + WindowsKernelModeDriver10.0 Windows7 false + WindowsKernelModeDriver10.0 Vista true + WindowsKernelModeDriver8.0 Vista false 1 7 + WindowsKernelModeDriver8.0 @@ -218,10 +229,10 @@ C:\WinDDK\7600.16385.1\lib\win7\amd64\ndis.lib;C:\WinDDK\7600.16385.1\lib\win7\amd64\ntstrsafe.lib;C:\WinDDK\7600.16385.1\lib\win7\amd64\wdmsec.lib;%(AdditionalDependencies) - C:\WinDDK\7600.16385.1\lib\win7\amd64\ndis.lib;C:\WinDDK\7600.16385.1\lib\win7\amd64\ntstrsafe.lib;C:\WinDDK\7600.16385.1\lib\win7\amd64\wdmsec.lib;%(AdditionalDependencies) + $(DDK_LIB_PATH)ndis.lib;$(DDK_LIB_PATH)ntstrsafe.lib;$(DDK_LIB_PATH)wdmsec.lib;%(AdditionalDependencies) - C:\WinDDK\7600.16385.1\lib\win7\amd64\ndis.lib;C:\WinDDK\7600.16385.1\lib\win7\amd64\ntstrsafe.lib;C:\WinDDK\7600.16385.1\lib\win7\amd64\wdmsec.lib;%(AdditionalDependencies) + $(DDK_LIB_PATH)ndis.lib;$(DDK_LIB_PATH)ntstrsafe.lib;$(DDK_LIB_PATH)wdmsec.lib;%(AdditionalDependencies) C:\WinDDK\7600.16385.1\lib\win7\amd64\ndis.lib;C:\WinDDK\7600.16385.1\lib\win7\amd64\ntstrsafe.lib;C:\WinDDK\7600.16385.1\lib\win7\amd64\wdmsec.lib;%(AdditionalDependencies) @@ -236,10 +247,10 @@ C:\WinDDK\7600.16385.1\lib\win7\i386\ndis.lib;C:\WinDDK\7600.16385.1\lib\win7\i386\ntstrsafe.lib;C:\WinDDK\7600.16385.1\lib\win7\i386\wdmsec.lib;%(AdditionalDependencies) - C:\WinDDK\7600.16385.1\lib\win7\i386\ndis.lib;C:\WinDDK\7600.16385.1\lib\win7\i386\ntstrsafe.lib;C:\WinDDK\7600.16385.1\lib\win7\i386\wdmsec.lib;%(AdditionalDependencies) + $(DDK_LIB_PATH)ndis.lib;$(DDK_LIB_PATH)ntstrsafe.lib;$(DDK_LIB_PATH)wdmsec.lib;%(AdditionalDependencies) - C:\WinDDK\7600.16385.1\lib\win7\i386\ndis.lib;C:\WinDDK\7600.16385.1\lib\win7\i386\ntstrsafe.lib;C:\WinDDK\7600.16385.1\lib\win7\i386\wdmsec.lib;%(AdditionalDependencies) + $(DDK_LIB_PATH)ndis.lib;$(DDK_LIB_PATH)ntstrsafe.lib;$(DDK_LIB_PATH)wdmsec.lib;%(AdditionalDependencies) C:\WinDDK\7600.16385.1\lib\win7\i386\ndis.lib;C:\WinDDK\7600.16385.1\lib\win7\i386\ntstrsafe.lib;C:\WinDDK\7600.16385.1\lib\win7\i386\wdmsec.lib;%(AdditionalDependencies) @@ -261,13 +272,18 @@ 3.00.00.0 - false - false + true + true + + + zttap300.cat - 3.00.00.0 + + false false + 3.00.00.0 @@ -291,13 +307,18 @@ 3.00.00.0 - false - false + true + true + + + zttap300.cat + -v "3.00.00.0" %(AdditionalOptions) 3.00.00.0 - false - false + true + true + -v "3.00.00.0" %(AdditionalOptions) 3.00.00.0 diff --git a/windows/TapDriver6/tap-windows.h b/windows/TapDriver6/tap-windows.h index 7e01846..fd41a79 100644 --- a/windows/TapDriver6/tap-windows.h +++ b/windows/TapDriver6/tap-windows.h @@ -42,7 +42,9 @@ //#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) +#if DBG +#define TAP_WIN_IOCTL_GET_LOG_LINE TAP_WIN_CONTROL_CODE (8, METHOD_BUFFERED) +#endif //#define TAP_WIN_IOCTL_CONFIG_DHCP_SET_OPT TAP_WIN_CONTROL_CODE (9, METHOD_BUFFERED) /* Added in 8.2 */ diff --git a/windows/WinUI/APIHandler.cs b/windows/WinUI/APIHandler.cs index 92b8302..419a11c 100644 --- a/windows/WinUI/APIHandler.cs +++ b/windows/WinUI/APIHandler.cs @@ -7,6 +7,7 @@ using System.Net; using System.IO; using System.Windows; using Newtonsoft.Json; +using System.Diagnostics; namespace WinUI { @@ -18,18 +19,135 @@ namespace WinUI private string url = null; - public APIHandler() + private static volatile APIHandler instance; + private static object syncRoot = new Object(); + + public delegate void NetworkListCallback(List networks); + public delegate void StatusCallback(ZeroTierStatus status); + + public static APIHandler Instance + { + get + { + if (instance == null) + { + lock (syncRoot) + { + if (instance == null) + { + if (!initHandler()) + { + return null; + } + } + } + } + + return instance; + } + } + + private static bool initHandler(bool resetToken = false) + { + String localZtDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\ZeroTier\\One"; + String globalZtDir = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + "\\ZeroTier\\One"; + + String authToken = ""; + Int32 port = 9993; + + if (resetToken) + { + instance = null; + if (File.Exists(localZtDir + "\\authtoken.secret")) + { + File.Delete(localZtDir + "\\authtoken.secret"); + } + + if (File.Exists(localZtDir + "\\zerotier-one.port")) + { + File.Delete(localZtDir + "\\zerotier-one.port"); + } + } + + if (!File.Exists(localZtDir + "\\authtoken.secret") || !File.Exists(localZtDir + "\\zerotier-one.port")) + { + // launch external process to copy file into place + String curPath = System.Reflection.Assembly.GetEntryAssembly().Location; + int index = curPath.LastIndexOf("\\"); + curPath = curPath.Substring(0, index); + ProcessStartInfo startInfo = new ProcessStartInfo(curPath + "\\copyutil.exe", "\""+globalZtDir+"\"" + " " + "\""+localZtDir+"\""); + startInfo.Verb = "runas"; + + + var process = Process.Start(startInfo); + process.WaitForExit(); + } + + authToken = readAuthToken(localZtDir + "\\authtoken.secret"); + + if ((authToken == null) || (authToken.Length <= 0)) + { + MessageBox.Show("Unable to read ZeroTier One authtoken", "ZeroTier One"); + return false; + } + + port = readPort(localZtDir + "\\zerotier-one.port"); + instance = new APIHandler(port, authToken); + return true; + } + + private static String readAuthToken(String path) + { + String authToken = ""; + + if (File.Exists(path)) + { + try + { + byte[] tmp = File.ReadAllBytes(path); + authToken = System.Text.Encoding.UTF8.GetString(tmp).Trim(); + } + catch + { + MessageBox.Show("Unable to read ZeroTier One Auth Token from:\r\n" + path, "ZeroTier One"); + } + } + + return authToken; + } + + private static Int32 readPort(String path) + { + Int32 port = 9993; + + try + { + byte[] tmp = File.ReadAllBytes(path); + port = Int32.Parse(System.Text.Encoding.ASCII.GetString(tmp).Trim()); + if ((port <= 0) || (port > 65535)) + port = 9993; + } + catch + { + } + + return port; + } + + private APIHandler() { url = "http://127.0.0.1:9993"; } public APIHandler(int port, string authtoken) { - url = "http://localhost:" + port; + url = "http://127.0.0.1:" + port; this.authtoken = authtoken; } - public ZeroTierStatus GetStatus() + + + public void GetStatus(StatusCallback cb) { var request = WebRequest.Create(url + "/status" + "?auth=" + authtoken) as HttpWebRequest; if (request != null) @@ -41,73 +159,110 @@ namespace WinUI try { var httpResponse = (HttpWebResponse)request.GetResponse(); - using (var streamReader = new StreamReader(httpResponse.GetResponseStream())) - { - var responseText = streamReader.ReadToEnd(); + if (httpResponse.StatusCode == HttpStatusCode.OK) + { + using (var streamReader = new StreamReader(httpResponse.GetResponseStream())) + { + var responseText = streamReader.ReadToEnd(); - ZeroTierStatus status = null; - try - { - status = JsonConvert.DeserializeObject(responseText); - } - catch (JsonReaderException e) - { - Console.WriteLine(e.ToString()); - } - return status; - } + ZeroTierStatus status = null; + try + { + status = JsonConvert.DeserializeObject(responseText); + } + catch (JsonReaderException e) + { + Console.WriteLine(e.ToString()); + } + cb(status); + } + } + else if (httpResponse.StatusCode == HttpStatusCode.Unauthorized) + { + APIHandler.initHandler(true); + } } catch (System.Net.Sockets.SocketException) { - return null; + cb(null); } - catch (System.Net.WebException) + catch (System.Net.WebException e) { - return null; + if (((HttpWebResponse)e.Response).StatusCode == HttpStatusCode.Unauthorized) + { + APIHandler.initHandler(true); + } + else + { + cb(null); + } } } - public List GetNetworks() + + + public void GetNetworks(NetworkListCallback cb) { var request = WebRequest.Create(url + "/network" + "?auth=" + authtoken) as HttpWebRequest; if (request == null) { - return null; + cb(null); } request.Method = "GET"; request.ContentType = "application/json"; + request.Timeout = 10000; try { var httpResponse = (HttpWebResponse)request.GetResponse(); - using (var streamReader = new StreamReader(httpResponse.GetResponseStream())) - { - var responseText = streamReader.ReadToEnd(); - List networkList = null; - try - { - networkList = JsonConvert.DeserializeObject>(responseText); - } - catch (JsonReaderException e) - { - Console.WriteLine(e.ToString()); - } - return networkList; - } + if (httpResponse.StatusCode == HttpStatusCode.OK) + { + using (var streamReader = new StreamReader(httpResponse.GetResponseStream())) + { + var responseText = streamReader.ReadToEnd(); + + List networkList = null; + try + { + networkList = JsonConvert.DeserializeObject>(responseText); + foreach (ZeroTierNetwork n in networkList) + { + // all networks received via JSON are connected by definition + n.IsConnected = true; + } + } + catch (JsonReaderException e) + { + Console.WriteLine(e.ToString()); + } + cb(networkList); + } + } + else if (httpResponse.StatusCode == HttpStatusCode.Unauthorized) + { + APIHandler.initHandler(true); + } } catch (System.Net.Sockets.SocketException) { - return null; + cb(null); } - catch (System.Net.WebException) + catch (System.Net.WebException e) { - return null; + if (((HttpWebResponse)e.Response).StatusCode == HttpStatusCode.Unauthorized) + { + APIHandler.initHandler(true); + } + else + { + cb(null); + } } } - public void JoinNetwork(string nwid) + public void JoinNetwork(string nwid, bool allowManaged = true, bool allowGlobal = false, bool allowDefault = false) { var request = WebRequest.Create(url + "/network/" + nwid + "?auth=" + authtoken) as HttpWebRequest; if (request == null) @@ -116,12 +271,35 @@ namespace WinUI } request.Method = "POST"; + request.ContentType = "applicaiton/json"; + request.Timeout = 10000; + try + { + using (var streamWriter = new StreamWriter(((HttpWebRequest)request).GetRequestStream())) + { + string json = "{\"allowManaged\":" + (allowManaged ? "true" : "false") + "," + + "\"allowGlobal\":" + (allowGlobal ? "true" : "false") + "," + + "\"allowDefault\":" + (allowDefault ? "true" : "false") + "}"; + streamWriter.Write(json); + streamWriter.Flush(); + streamWriter.Close(); + } + } + catch (System.Net.WebException) + { + MessageBox.Show("Error Joining Network: Cannot connect to ZeroTier service."); + return; + } try { var httpResponse = (HttpWebResponse)request.GetResponse(); - if (httpResponse.StatusCode != HttpStatusCode.OK) + if (httpResponse.StatusCode == HttpStatusCode.Unauthorized) + { + APIHandler.initHandler(true); + } + else if (httpResponse.StatusCode != HttpStatusCode.OK) { Console.WriteLine("Error sending join network message"); } @@ -130,9 +308,13 @@ namespace WinUI { MessageBox.Show("Error Joining Network: Cannot connect to ZeroTier service."); } - catch (System.Net.WebException) + catch (System.Net.WebException e) { - MessageBox.Show("Error Joining Network: Cannot connect to ZeroTier service."); + if (((HttpWebResponse)e.Response).StatusCode == HttpStatusCode.Unauthorized) + { + APIHandler.initHandler(true); + } + MessageBox.Show("Error Joining Network: Cannot connect to ZeroTier service."); } } @@ -145,12 +327,17 @@ namespace WinUI } request.Method = "DELETE"; + request.Timeout = 10000; try { var httpResponse = (HttpWebResponse)request.GetResponse(); - if (httpResponse.StatusCode != HttpStatusCode.OK) + if (httpResponse.StatusCode == HttpStatusCode.Unauthorized) + { + APIHandler.initHandler(true); + } + else if (httpResponse.StatusCode != HttpStatusCode.OK) { Console.WriteLine("Error sending leave network message"); } @@ -159,18 +346,28 @@ namespace WinUI { MessageBox.Show("Error Leaving Network: Cannot connect to ZeroTier service."); } - catch (System.Net.WebException) + catch (System.Net.WebException e) { - MessageBox.Show("Error Leaving Network: Cannot connect to ZeroTier service."); + if (((HttpWebResponse)e.Response).StatusCode == HttpStatusCode.Unauthorized) + { + APIHandler.initHandler(true); + } + MessageBox.Show("Error Leaving Network: Cannot connect to ZeroTier service."); + } + catch + { + Console.WriteLine("Error leaving network: Unknown error"); } } - public List GetPeers() + public delegate void PeersCallback(List peers); + + public void GetPeers(PeersCallback cb) { var request = WebRequest.Create(url + "/peer" + "?auth=" + authtoken) as HttpWebRequest; if (request == null) { - return null; + cb(null); } request.Method = "GET"; @@ -179,29 +376,43 @@ namespace WinUI try { var httpResponse = (HttpWebResponse)request.GetResponse(); - using (var streamReader = new StreamReader(httpResponse.GetResponseStream())) - { - var responseText = streamReader.ReadToEnd(); - //Console.WriteLine(responseText); - List peerList = null; - try - { - peerList = JsonConvert.DeserializeObject>(responseText); - } - catch (JsonReaderException e) - { - Console.WriteLine(e.ToString()); - } - return peerList; - } + if (httpResponse.StatusCode == HttpStatusCode.OK) + { + using (var streamReader = new StreamReader(httpResponse.GetResponseStream())) + { + var responseText = streamReader.ReadToEnd(); + //Console.WriteLine(responseText); + List peerList = null; + try + { + peerList = JsonConvert.DeserializeObject>(responseText); + } + catch (JsonReaderException e) + { + Console.WriteLine(e.ToString()); + } + cb(peerList); + } + } + else if (httpResponse.StatusCode == HttpStatusCode.Unauthorized) + { + APIHandler.initHandler(true); + } } catch (System.Net.Sockets.SocketException) { - return null; + cb(null); } - catch (System.Net.WebException) + catch (System.Net.WebException e) { - return null; + if (((HttpWebResponse)e.Response).StatusCode == HttpStatusCode.Unauthorized) + { + APIHandler.initHandler(true); + } + else + { + cb(null); + } } } } diff --git a/windows/WinUI/AboutView.xaml b/windows/WinUI/AboutView.xaml new file mode 100644 index 0000000..f207af4 --- /dev/null +++ b/windows/WinUI/AboutView.xaml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/windows/WinUI/AboutView.xaml.cs b/windows/WinUI/AboutView.xaml.cs new file mode 100644 index 0000000..9c48493 --- /dev/null +++ b/windows/WinUI/AboutView.xaml.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace WinUI +{ + /// + /// Interaction logic for AboutView.xaml + /// + public partial class AboutView : Window + { + public AboutView() + { + InitializeComponent(); + } + + private void Hyperlink_MouseLeftButtonDown(object sender, RequestNavigateEventArgs e) + { + var hyperlink = (Hyperlink)sender; + Process.Start(hyperlink.NavigateUri.ToString()); + } + } +} diff --git a/windows/WinUI/App.xaml b/windows/WinUI/App.xaml index 08b9b79..12ed85f 100644 --- a/windows/WinUI/App.xaml +++ b/windows/WinUI/App.xaml @@ -1,7 +1,7 @@  + StartupUri="ToolbarItem.xaml"> diff --git a/windows/WinUI/App.xaml.cs b/windows/WinUI/App.xaml.cs index a97edde..53ef2f6 100644 --- a/windows/WinUI/App.xaml.cs +++ b/windows/WinUI/App.xaml.cs @@ -5,6 +5,7 @@ using System.Data; using System.Linq; using System.Threading.Tasks; using System.Windows; +using Hardcodet.Wpf.TaskbarNotification; namespace WinUI { @@ -13,5 +14,12 @@ namespace WinUI /// public partial class App : Application { + private TaskbarIcon tb; + + private void InitApplication() + { + tb = (TaskbarIcon)FindResource("NotifyIcon"); + tb.Visibility = Visibility.Visible; + } } } diff --git a/windows/WinUI/JoinNetworkView.xaml b/windows/WinUI/JoinNetworkView.xaml new file mode 100644 index 0000000..1cd1e98 --- /dev/null +++ b/windows/WinUI/JoinNetworkView.xaml @@ -0,0 +1,16 @@ + + + + + + +