mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-04-14 22:36:33 +00:00
Merge master into multipath
This commit is contained in:
commit
77ae929eb3
46
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
46
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
|
||||
---
|
||||
**Alternative, faster ways to get help**
|
||||
If you have just started using ZeroTier, here are some places to get help:
|
||||
- my.zerotier.com has a _Community_ tab. It's a live chat with other users and the developers.
|
||||
- [ZeroTier Knowledge Base](https://zerotier.atlassian.net/wiki/spaces/SD/overview)
|
||||
- www.zerotier.com has a Contact Us button
|
||||
- email contact@zerotier.com
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Create a Network '...'
|
||||
2. Install zerotier-one '....'
|
||||
3. '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots or console output to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. Mac, Linux, Windows, BSD]
|
||||
- OS/Distribution Version
|
||||
- ZeroTier Version [e.g. 1.2.4]
|
||||
- Hardware [e.g. raspberry pi 3]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Version [e.g. 1.2.4]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
- ZeroTier Network Configuration
|
||||
- Router Config
|
||||
- Firewall Config (try turning the firewall off)
|
||||
- General Network Environment: [ e.g Home, University Campus, Corporate LAN ]
|
||||
|
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -51,6 +51,7 @@ enc_temp_folder
|
||||
/world/mkworld
|
||||
/world/*.c25519
|
||||
zt1-src.tar.gz
|
||||
/MacEthernetTapAgent
|
||||
|
||||
# Miscellaneous temporaries, build files, etc.
|
||||
*.log
|
||||
@ -117,3 +118,5 @@ ext/librethinkdbxx/build
|
||||
.vscode
|
||||
__pycache__
|
||||
*~
|
||||
attic/world/*.c25519
|
||||
attic/world/mkworld
|
||||
|
14
AUTHORS.md
14
AUTHORS.md
@ -2,6 +2,7 @@
|
||||
|
||||
* ZeroTier Core and ZeroTier One virtual networking service<br>
|
||||
Adam Ierymenko / adam.ierymenko@zerotier.com
|
||||
Joseph Henry / joseph.henry@zerotier.com (QoS and multipath)
|
||||
|
||||
* Java JNI Interface to enable Android application development, and Android app itself (code for that is elsewhere)<br>
|
||||
Grant Limberg / glimberg@gmail.com
|
||||
@ -45,13 +46,6 @@ ZeroTier includes the following third party code, either in ext/ or incorporated
|
||||
* Home page: https://github.com/nlohmann/json
|
||||
* License grant: MIT
|
||||
|
||||
* TunTapOSX by Mattias Nissler
|
||||
|
||||
* Files: ext/tap-mac/tuntap/*
|
||||
* Home page: http://tuntaposx.sourceforge.net/
|
||||
* License grant: BSD attribution no-endorsement
|
||||
* ZeroTier Modifications: change interface name to zt#, increase max MTU, increase max devices
|
||||
|
||||
* tap-windows6 by the OpenVPN project
|
||||
|
||||
* Files: windows/TapDriver6/*
|
||||
@ -71,3 +65,9 @@ ZeroTier includes the following third party code, either in ext/ or incorporated
|
||||
* Files: ext/libnatpmp/* ext/miniupnpc/*
|
||||
* Home page: http://miniupnp.free.fr/
|
||||
* License grant: BSD attribution no-endorsement
|
||||
|
||||
* cpp-httplib by yhirose
|
||||
|
||||
* Files: ext/cpp-httplib/*
|
||||
* Home page: https://github.com/yhirose/cpp-httplib
|
||||
* License grant: MIT
|
||||
|
2
COPYING
2
COPYING
@ -1,5 +1,5 @@
|
||||
ZeroTier One, an endpoint server for the ZeroTier virtual network layer.
|
||||
Copyright © 2011–2018 ZeroTier, Inc.
|
||||
Copyright © 2011–2019 ZeroTier, Inc.
|
||||
|
||||
ZeroTier One is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
96
Jenkinsfile
vendored
96
Jenkinsfile
vendored
@ -24,61 +24,61 @@ parallel 'centos7': {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}, 'android-ndk': {
|
||||
node('android-ndk') {
|
||||
try {
|
||||
checkout scm
|
||||
// }, 'android-ndk': {
|
||||
// node('android-ndk') {
|
||||
// try {
|
||||
// checkout scm
|
||||
|
||||
stage('Build Android NDK') {
|
||||
sh "/android/android-ndk-r15b/ndk-build -C $WORKSPACE/java ZT1=${WORKSPACE}"
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
currentBuild.result = "FAILURE"
|
||||
mattermostSend color: '#ff0000', message: "${env.JOB_NAME} broken on Android NDK (<${env.BUILD_URL}|Open>)"
|
||||
// stage('Build Android NDK') {
|
||||
// sh "/android/android-ndk-r15b/ndk-build -C $WORKSPACE/java ZT1=${WORKSPACE}"
|
||||
// }
|
||||
// }
|
||||
// catch (err) {
|
||||
// currentBuild.result = "FAILURE"
|
||||
// mattermostSend color: '#ff0000', message: "${env.JOB_NAME} broken on Android NDK (<${env.BUILD_URL}|Open>)"
|
||||
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}, 'macOS': {
|
||||
node('macOS') {
|
||||
try {
|
||||
checkout scm
|
||||
// throw err
|
||||
// }
|
||||
// }
|
||||
// }, 'macOS': {
|
||||
// node('macOS') {
|
||||
// try {
|
||||
// checkout scm
|
||||
|
||||
stage('Build macOS') {
|
||||
sh 'make -f make-mac.mk'
|
||||
}
|
||||
// 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"
|
||||
mattermostSend color: '#ff0000', message: "${env.JOB_NAME} broken on macOS (<${env.BUILD_URL}|Open>)"
|
||||
// stage('Build macOS UI') {
|
||||
// sh 'cd macui && xcodebuild -target "ZeroTier One" -configuration Debug'
|
||||
// }
|
||||
// }
|
||||
// catch (err) {
|
||||
// currentBuild.result = "FAILURE"
|
||||
// mattermostSend color: '#ff0000', message: "${env.JOB_NAME} broken on macOS (<${env.BUILD_URL}|Open>)"
|
||||
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}, 'windows': {
|
||||
node('windows') {
|
||||
try {
|
||||
checkout scm
|
||||
// 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"
|
||||
mattermostSend color: '#ff0000', message: "${env.JOB_NAME} broken on Windows (<${env.BUILD_URL}|Open>)"
|
||||
// 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"
|
||||
// mattermostSend color: '#ff0000', message: "${env.JOB_NAME} broken on Windows (<${env.BUILD_URL}|Open>)"
|
||||
|
||||
throw err
|
||||
}
|
||||
}
|
||||
// throw err
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
mattermostSend color: "#00ff00", message: "${env.JOB_NAME} #${env.BUILD_NUMBER} Complete (<${env.BUILD_URL}|Show More...>)"
|
||||
|
@ -1,5 +1,5 @@
|
||||
ZeroTier One - Network Virtualization Everywhere
|
||||
Copyright (C) 2011-2017 ZeroTier, Inc. https://www.zerotier.com/
|
||||
Copyright (C) 2011-2019 ZeroTier, Inc. https://www.zerotier.com/
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -13,7 +13,6 @@ The version must be incremented in all of the following files:
|
||||
/zerotier-one.spec
|
||||
/debian/changelog
|
||||
/ext/installfiles/mac/ZeroTier One.pkgproj
|
||||
/ext/installfiles/windows/chocolatey/zerotier-one.nuspec
|
||||
/ext/installfiles/windows/ZeroTier One.aip
|
||||
/windows/WinUI/AboutView.xaml
|
||||
|
||||
@ -29,21 +28,6 @@ Mac's easy. Just type:
|
||||
|
||||
You will need [Packages](http://s.sudre.free.fr/Software/Packages/about.html) and our release signing key in the keychain.
|
||||
|
||||
## Linux
|
||||
|
||||
See `LinuxBuild` environment on `linux-build` VM and use: `chroots/mount-build.sh`, `chroots/build.sh`, and the scripts in `build/` to make APT and RPM repositories.
|
||||
|
||||
## Windows
|
||||
|
||||
First load the Visual Studio solution and rebuild the UI and ZeroTier One in both x64 and i386 `Release` mode. Then load [Advanced Installer Enterprise](http://www.advancedinstaller.com/), check that the version is correct, and build. The build will fail if any build artifacts are missing, and Windows must have our product singing key (from DigiCert) available to sign the resulting MSI file. The MSI must then be tested on at least a few different CLEAN Windows VMs to ensure that the installer is valid and properly signed.
|
||||
|
||||
*After the MSI is published to download.zerotier.com in the proper RELEASE/#.#.#/dist subfolder for its version* the Chocolatey package must be rebuilt and published. Open a command prompt, change to `ext/installfiles/windows/chocolatey`, and type `choco pack`. Then use `choco push` to push it to Chocolatey (API key required).
|
||||
|
||||
choco pack
|
||||
choco push zerotier-one.#.#.#.nupkg -s https://chocolatey.org/
|
||||
|
||||
Note that this does not cover rebuilding the drivers or their containing MSI projects, as this is typically not necessary and they are shipped in binary form in the repository for convenience.
|
||||
|
||||
## iOS, Android
|
||||
|
||||
... no docs here yet since this is done entirely out of band with regular installs.
|
||||
|
33
README.md
33
README.md
@ -1,62 +1,57 @@
|
||||
ZeroTier - A Planetary Ethernet Switch
|
||||
ZeroTier - Global Area Networking
|
||||
======
|
||||
|
||||
ZeroTier is a smart programmable Ethernet switch for planet Earth.
|
||||
ZeroTier is a smart programmable Ethernet switch for planet Earth. It allows networked devices and applications to be managed as if the entire world is one data center or cloud region.
|
||||
|
||||
It replaces the physical LAN/WAN boundary with a virtual one, allowing devices of any type at any location to be managed as if they all reside in the same cloud region or data center. All traffic is encrypted end-to-end and takes the most direct path available for minimum latency and maximum performance. The goals and design of ZeroTier are inspired by among other things the original [Google BeyondCorp](https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/43231.pdf) paper and the [Jericho Forum](https://en.wikipedia.org/wiki/Jericho_Forum).
|
||||
|
||||
Visit [ZeroTier's site](https://www.zerotier.com/?pk_campaign=github_ZeroTierOne) for more information and [pre-built binary packages](https://www.zerotier.com/download.shtml?pk_campaign=github_ZeroTierOne). Apps for Android and iOS are available for free in the Google Play and Apple app stores.
|
||||
Visit [ZeroTier's site](https://www.zerotier.com/) for more information and [pre-built binary packages](https://www.zerotier.com/download/). Apps for Android and iOS are available for free in the Google Play and Apple app stores.
|
||||
|
||||
### Getting Started
|
||||
|
||||
Everything in the ZeroTier world is controlled by two types of identifier: 40-bit/10-digit *ZeroTier addresses* and 64-bit/16-digit *network IDs*. A ZeroTier address identifies a node or "device" (laptop, phone, server, VM, app, etc.) while a network ID identifies a virtual Ethernet network that can be joined by devices.
|
||||
|
||||
Another way of thinking about it is that ZeroTier addresses are port numbers on a giant planetary-sized smart switch while network IDs are VLANs to which these ports can be assigned. For more details read about VL1 and VL2 in [the ZeroTier manual](https://www.zerotier.com/manual.shtml).
|
||||
Another way of thinking about it is that ZeroTier addresses are port numbers on a giant planetary-sized smart switch while network IDs are VLANs to which these ports can be assigned. For more details read about VL1 and VL2 in [the ZeroTier manual](https://www.zerotier.com/manual/).
|
||||
|
||||
*Network controllers* are ZeroTier nodes that act as access control certificate authorities and configuration managers for virtual networks. The first 40 bits (or 10 digits) of a network ID is the ZeroTier address of its controller. You can create networks with our [hosted controllers](https://my.zerotier.com/) and web UI/API or [host your own](controller/) if you don't mind posting some JSON configuration info or writing a script to do so.
|
||||
|
||||
### Project Layout
|
||||
|
||||
The base path contains the ZeroTier One service main entry point (`one.cpp`), self test code, makefiles, etc.
|
||||
|
||||
- `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.
|
||||
- `docker/`: Dockerfile to build as a container for containerized Linux systems and Kubernetes clusters.
|
||||
- `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.
|
||||
- `rule-compiler/`: JavaScript rules language compiler for defining network-level rules.
|
||||
- `service/`: the ZeroTier One service, which wraps the ZeroTier core and provides VPN-like connectivity to virtual networks for desktops, laptops, servers, VMs, and containers.
|
||||
- `tcp-proxy/`: TCP proxy code run by ZeroTier, Inc. to provide TCP fallback (this will die soon!).
|
||||
- `windows/`: Visual Studio solution files, Windows service code for ZeroTier One, and the Windows task bar app UI.
|
||||
|
||||
The base path contains the ZeroTier One service main entry point (`one.cpp`), self test code, makefiles, etc.
|
||||
|
||||
### Build and Platform Notes
|
||||
|
||||
To build on Mac and Linux just type `make`. On FreeBSD and OpenBSD `gmake` (GNU make) is required and can be installed from packages or ports. For Windows there is a Visual Studio solution in `windows/'.
|
||||
|
||||
- **Mac**
|
||||
- Xcode command line tools for OSX 10.7 or newer are required.
|
||||
- Tap device driver kext source is in `ext/tap-mac` and a signed pre-built binary can be found in `ext/bin/tap-mac`. You should not need to build it yourself. It's a fork of [tuntaposx](http://tuntaposx.sourceforge.net) with device names changed to `zt#`, support for a larger MTU, and tun functionality removed.
|
||||
- Xcode command line tools for OSX 10.8 or newer are required.
|
||||
- **Linux**
|
||||
- The minimum compiler versions required are GCC/G++ 4.9.3 or CLANG/CLANG++ 3.4.2.
|
||||
- The minimum compiler versions required are GCC/G++ 4.9.3 or CLANG/CLANG++ 3.4.2. (Install `clang` on CentOS 7 as G++ is too old.)
|
||||
- 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 is supported. This *may* work on Vista but isn't officially supported there. It will not work on Windows XP.
|
||||
- We build with Visual Studio 2015. Older versions may not work. Clang or MinGW will also probably work but may require some makefile hacking.
|
||||
- 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. We don't recommend trying to build Windows drivers from scratch unless you know what you're doing. One does not simply "build" a Windows driver.
|
||||
- We build with Visual Studio 2017. Older versions may not work. Clang or MinGW will also probably work but may require some makefile hacking.
|
||||
- **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.
|
||||
- GNU make is required. 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.
|
||||
- There is a limit of four network memberships on OpenBSD as there are only four tap devices (`/dev/tap0` through `/dev/tap3`).
|
||||
- GNU make is required. Type `gmake` to build.
|
||||
|
||||
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.
|
||||
|
||||
|
@ -1,6 +1,24 @@
|
||||
ZeroTier Release Notes
|
||||
======
|
||||
|
||||
# 2019-07-29 -- Version 1.4.0
|
||||
|
||||
### Major Changes
|
||||
|
||||
* Mac version no longer requires a kernel extension, instead making use of the [feth interfaces](https://apple.stackexchange.com/questions/337715/fake-ethernet-interfaces-feth-if-fake-anyone-ever-seen-this).
|
||||
* Added support for concurrent multipath (multiple paths at once) with traffic weighting by link quality and faster recovery from lost links.
|
||||
* Added under-the-hood support for QoS (not yet exposed) that will eventually be configurable via our rules engine.
|
||||
|
||||
### Minor Changes and Bug Fixes
|
||||
|
||||
* Experimental controller DB driver for [LF](https://github.com/zerotier/lf) to store network controller data (LFDB.cpp / LFDB.hpp).
|
||||
* Modified credential push and direct path push timings and algorithms to somewhat reduce "chattiness" of the protocol when idle. More radical background overhead reductions will have to wait for the 2.x line.
|
||||
* Removed our beta/half-baked integration of Central with the Windows UI. We're going to do a whole new UI of some kind in the future at least for Windows and Mac.
|
||||
* Fixed stack overflow issues on Linux versions using musl libc.
|
||||
* Fixed some alignment problems reported on ARM and ARM64, but some reports we could not reproduce so please report any issues with exact chip, OS/distro, and ZeroTier version in use.
|
||||
* Fixed numerous other small issues and bugs such as ARM alignment issues causing crashes on some devices.
|
||||
* Windows now sets the adapter name such that it is consistent in both the Windows UI and command line utilities.
|
||||
|
||||
# 2018-07-27 -- Version 1.2.12
|
||||
|
||||
* Fixed a bug that caused exits to take a long time on Mac due to huge numbers of redundant attempts to delete managed routes.
|
||||
|
BIN
artwork/AppIcon_90x90.png
Normal file
BIN
artwork/AppIcon_90x90.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.6 KiB |
1042
attic/Cluster.cpp
1042
attic/Cluster.cpp
File diff suppressed because it is too large
Load Diff
@ -1,463 +0,0 @@
|
||||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2017 ZeroTier, Inc. https://www.zerotier.com/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* You can be released from the requirements of the license by purchasing
|
||||
* a commercial license. Buying such a license is mandatory as soon as you
|
||||
* develop commercial closed-source software that incorporates or links
|
||||
* directly against ZeroTier software without disclosing the source code
|
||||
* of your own application.
|
||||
*/
|
||||
|
||||
#ifndef ZT_CLUSTER_HPP
|
||||
#define ZT_CLUSTER_HPP
|
||||
|
||||
#ifdef ZT_ENABLE_CLUSTER
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "Constants.hpp"
|
||||
#include "../include/ZeroTierOne.h"
|
||||
#include "Address.hpp"
|
||||
#include "InetAddress.hpp"
|
||||
#include "SHA512.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "Buffer.hpp"
|
||||
#include "Mutex.hpp"
|
||||
#include "SharedPtr.hpp"
|
||||
#include "Hashtable.hpp"
|
||||
#include "Packet.hpp"
|
||||
#include "SharedPtr.hpp"
|
||||
|
||||
/**
|
||||
* Timeout for cluster members being considered "alive"
|
||||
*
|
||||
* A cluster member is considered dead and will no longer have peers
|
||||
* redirected to it if we have not heard a heartbeat in this long.
|
||||
*/
|
||||
#define ZT_CLUSTER_TIMEOUT 5000
|
||||
|
||||
/**
|
||||
* Desired period between doPeriodicTasks() in milliseconds
|
||||
*/
|
||||
#define ZT_CLUSTER_PERIODIC_TASK_PERIOD 20
|
||||
|
||||
/**
|
||||
* How often to flush outgoing message queues (maximum interval)
|
||||
*/
|
||||
#define ZT_CLUSTER_FLUSH_PERIOD ZT_CLUSTER_PERIODIC_TASK_PERIOD
|
||||
|
||||
/**
|
||||
* Maximum number of queued outgoing packets per sender address
|
||||
*/
|
||||
#define ZT_CLUSTER_MAX_QUEUE_PER_SENDER 16
|
||||
|
||||
/**
|
||||
* Expiration time for send queue entries
|
||||
*/
|
||||
#define ZT_CLUSTER_QUEUE_EXPIRATION 3000
|
||||
|
||||
/**
|
||||
* Chunk size for allocating queue entries
|
||||
*
|
||||
* Queue entries are allocated in chunks of this many and are added to a pool.
|
||||
* ZT_CLUSTER_MAX_QUEUE_GLOBAL must be evenly divisible by this.
|
||||
*/
|
||||
#define ZT_CLUSTER_QUEUE_CHUNK_SIZE 32
|
||||
|
||||
/**
|
||||
* Maximum number of chunks to ever allocate
|
||||
*
|
||||
* This is a global sanity limit to prevent resource exhaustion attacks. It
|
||||
* works out to about 600mb of RAM. You'll never see this on a normal edge
|
||||
* node. We're unlikely to see this on a root server unless someone is DOSing
|
||||
* us. In that case cluster relaying will be affected but other functions
|
||||
* should continue to operate normally.
|
||||
*/
|
||||
#define ZT_CLUSTER_MAX_QUEUE_CHUNKS 8194
|
||||
|
||||
/**
|
||||
* Max data per queue entry
|
||||
*/
|
||||
#define ZT_CLUSTER_SEND_QUEUE_DATA_MAX 1500
|
||||
|
||||
/**
|
||||
* We won't send WANT_PEER to other members more than every (ms) per recipient
|
||||
*/
|
||||
#define ZT_CLUSTER_WANT_PEER_EVERY 1000
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
class RuntimeEnvironment;
|
||||
class MulticastGroup;
|
||||
class Peer;
|
||||
class Identity;
|
||||
|
||||
// Internal class implemented inside Cluster.cpp
|
||||
class _ClusterSendQueue;
|
||||
|
||||
/**
|
||||
* Multi-homing cluster state replication and packet relaying
|
||||
*
|
||||
* Multi-homing means more than one node sharing the same ZeroTier identity.
|
||||
* There is nothing in the protocol to prevent this, but to make it work well
|
||||
* requires the devices sharing an identity to cooperate and share some
|
||||
* information.
|
||||
*
|
||||
* There are three use cases we want to fulfill:
|
||||
*
|
||||
* (1) Multi-homing of root servers with handoff for efficient routing,
|
||||
* HA, and load balancing across many commodity nodes.
|
||||
* (2) Multi-homing of network controllers for the same reason.
|
||||
* (3) Multi-homing of nodes on virtual networks, such as domain servers
|
||||
* and other important endpoints.
|
||||
*
|
||||
* These use cases are in order of escalating difficulty. The initial
|
||||
* version of Cluster is aimed at satisfying the first, though you are
|
||||
* free to try #2 and #3.
|
||||
*/
|
||||
class Cluster
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* State message types
|
||||
*/
|
||||
enum StateMessageType
|
||||
{
|
||||
CLUSTER_MESSAGE_NOP = 0,
|
||||
|
||||
/**
|
||||
* This cluster member is alive:
|
||||
* <[2] version minor>
|
||||
* <[2] version major>
|
||||
* <[2] version revision>
|
||||
* <[1] protocol version>
|
||||
* <[4] X location (signed 32-bit)>
|
||||
* <[4] Y location (signed 32-bit)>
|
||||
* <[4] Z location (signed 32-bit)>
|
||||
* <[8] local clock at this member>
|
||||
* <[8] load average>
|
||||
* <[8] number of peers>
|
||||
* <[8] flags (currently unused, must be zero)>
|
||||
* <[1] number of preferred ZeroTier endpoints>
|
||||
* <[...] InetAddress(es) of preferred ZeroTier endpoint(s)>
|
||||
*
|
||||
* Cluster members constantly broadcast an alive heartbeat and will only
|
||||
* receive peer redirects if they've done so within the timeout.
|
||||
*/
|
||||
CLUSTER_MESSAGE_ALIVE = 1,
|
||||
|
||||
/**
|
||||
* Cluster member has this peer:
|
||||
* <[...] serialized identity of peer>
|
||||
*
|
||||
* This is typically sent in response to WANT_PEER but can also be pushed
|
||||
* to prepopulate if this makes sense.
|
||||
*/
|
||||
CLUSTER_MESSAGE_HAVE_PEER = 2,
|
||||
|
||||
/**
|
||||
* Cluster member wants this peer:
|
||||
* <[5] ZeroTier address of peer>
|
||||
*
|
||||
* Members that have a direct link to this peer will respond with
|
||||
* HAVE_PEER.
|
||||
*/
|
||||
CLUSTER_MESSAGE_WANT_PEER = 3,
|
||||
|
||||
/**
|
||||
* A remote packet that we should also possibly respond to:
|
||||
* <[2] 16-bit length of remote packet>
|
||||
* <[...] remote packet payload>
|
||||
*
|
||||
* Cluster members may relay requests by relaying the request packet.
|
||||
* These may include requests such as WHOIS and MULTICAST_GATHER. The
|
||||
* packet must be already decrypted, decompressed, and authenticated.
|
||||
*
|
||||
* This can only be used for small request packets as per the cluster
|
||||
* message size limit, but since these are the only ones in question
|
||||
* this is fine.
|
||||
*
|
||||
* If a response is generated it is sent via PROXY_SEND.
|
||||
*/
|
||||
CLUSTER_MESSAGE_REMOTE_PACKET = 4,
|
||||
|
||||
/**
|
||||
* Request that VERB_RENDEZVOUS be sent to a peer that we have:
|
||||
* <[5] ZeroTier address of peer on recipient's side>
|
||||
* <[5] ZeroTier address of peer on sender's side>
|
||||
* <[1] 8-bit number of sender's peer's active path addresses>
|
||||
* <[...] series of serialized InetAddresses of sender's peer's paths>
|
||||
*
|
||||
* This requests that we perform NAT-t introduction between a peer that
|
||||
* we have and one on the sender's side. The sender furnishes contact
|
||||
* info for its peer, and we send VERB_RENDEZVOUS to both sides: to ours
|
||||
* directly and with PROXY_SEND to theirs.
|
||||
*/
|
||||
CLUSTER_MESSAGE_PROXY_UNITE = 5,
|
||||
|
||||
/**
|
||||
* Request that a cluster member send a packet to a locally-known peer:
|
||||
* <[5] ZeroTier address of recipient>
|
||||
* <[1] packet verb>
|
||||
* <[2] length of packet payload>
|
||||
* <[...] packet payload>
|
||||
*
|
||||
* This differs from RELAY in that it requests the receiving cluster
|
||||
* member to actually compose a ZeroTier Packet from itself to the
|
||||
* provided recipient. RELAY simply says "please forward this blob."
|
||||
* RELAY is used to implement peer-to-peer relaying with RENDEZVOUS,
|
||||
* while PROXY_SEND is used to implement proxy sending (which right
|
||||
* now is only used to send RENDEZVOUS).
|
||||
*/
|
||||
CLUSTER_MESSAGE_PROXY_SEND = 6,
|
||||
|
||||
/**
|
||||
* Replicate a network config for a network we belong to:
|
||||
* <[...] network config chunk>
|
||||
*
|
||||
* This is used by clusters to avoid every member having to query
|
||||
* for the same netconf for networks all members belong to.
|
||||
*
|
||||
* The first field of a network config chunk is the network ID,
|
||||
* so this can be checked to look up the network on receipt.
|
||||
*/
|
||||
CLUSTER_MESSAGE_NETWORK_CONFIG = 7
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct a new cluster
|
||||
*/
|
||||
Cluster(
|
||||
const RuntimeEnvironment *renv,
|
||||
uint16_t id,
|
||||
const std::vector<InetAddress> &zeroTierPhysicalEndpoints,
|
||||
int32_t x,
|
||||
int32_t y,
|
||||
int32_t z,
|
||||
void (*sendFunction)(void *,unsigned int,const void *,unsigned int),
|
||||
void *sendFunctionArg,
|
||||
int (*addressToLocationFunction)(void *,const struct sockaddr_storage *,int *,int *,int *),
|
||||
void *addressToLocationFunctionArg);
|
||||
|
||||
~Cluster();
|
||||
|
||||
/**
|
||||
* @return This cluster member's ID
|
||||
*/
|
||||
inline uint16_t id() const throw() { return _id; }
|
||||
|
||||
/**
|
||||
* Handle an incoming intra-cluster message
|
||||
*
|
||||
* @param data Message data
|
||||
* @param len Message length (max: ZT_CLUSTER_MAX_MESSAGE_LENGTH)
|
||||
*/
|
||||
void handleIncomingStateMessage(const void *msg,unsigned int len);
|
||||
|
||||
/**
|
||||
* Broadcast that we have a given peer
|
||||
*
|
||||
* This should be done when new peers are first contacted.
|
||||
*
|
||||
* @param id Identity of peer
|
||||
*/
|
||||
void broadcastHavePeer(const Identity &id);
|
||||
|
||||
/**
|
||||
* Broadcast a network config chunk to other members of cluster
|
||||
*
|
||||
* @param chunk Chunk data
|
||||
* @param len Length of chunk
|
||||
*/
|
||||
void broadcastNetworkConfigChunk(const void *chunk,unsigned int len);
|
||||
|
||||
/**
|
||||
* If the cluster has this peer, prepare the packet to send via cluster
|
||||
*
|
||||
* Note that outp is only armored (or modified at all) if the return value is a member ID.
|
||||
*
|
||||
* @param toPeerAddress Value of outp.destination(), simply to save additional lookup
|
||||
* @param ts Result: set to time of last HAVE_PEER from the cluster
|
||||
* @param peerSecret Result: Buffer to fill with peer secret on valid return value, must be at least ZT_PEER_SECRET_KEY_LENGTH bytes
|
||||
* @return -1 if cluster does not know this peer, or a member ID to pass to sendViaCluster()
|
||||
*/
|
||||
int checkSendViaCluster(const Address &toPeerAddress,uint64_t &mostRecentTs,void *peerSecret);
|
||||
|
||||
/**
|
||||
* Send data via cluster front plane (packet head or fragment)
|
||||
*
|
||||
* @param haveMemberId Member ID that has this peer as returned by prepSendviaCluster()
|
||||
* @param toPeerAddress Destination peer address
|
||||
* @param data Packet or packet fragment data
|
||||
* @param len Length of packet or fragment
|
||||
* @return True if packet was sent (and outp was modified via armoring)
|
||||
*/
|
||||
bool sendViaCluster(int haveMemberId,const Address &toPeerAddress,const void *data,unsigned int len);
|
||||
|
||||
/**
|
||||
* Relay a packet via the cluster
|
||||
*
|
||||
* This is used in the outgoing packet and relaying logic in Switch to
|
||||
* relay packets to other cluster members. It isn't PROXY_SEND-- that is
|
||||
* used internally in Cluster to send responses to peer queries.
|
||||
*
|
||||
* @param fromPeerAddress Source peer address (if known, should be NULL for fragments)
|
||||
* @param toPeerAddress Destination peer address
|
||||
* @param data Packet or packet fragment data
|
||||
* @param len Length of packet or fragment
|
||||
* @param unite If true, also request proxy unite across cluster
|
||||
*/
|
||||
void relayViaCluster(const Address &fromPeerAddress,const Address &toPeerAddress,const void *data,unsigned int len,bool unite);
|
||||
|
||||
/**
|
||||
* Send a distributed query to other cluster members
|
||||
*
|
||||
* Some queries such as WHOIS or MULTICAST_GATHER need a response from other
|
||||
* cluster members. Replies (if any) will be sent back to the peer via
|
||||
* PROXY_SEND across the cluster.
|
||||
*
|
||||
* @param pkt Packet to distribute
|
||||
*/
|
||||
void sendDistributedQuery(const Packet &pkt);
|
||||
|
||||
/**
|
||||
* Call every ~ZT_CLUSTER_PERIODIC_TASK_PERIOD milliseconds.
|
||||
*/
|
||||
void doPeriodicTasks();
|
||||
|
||||
/**
|
||||
* Add a member ID to this cluster
|
||||
*
|
||||
* @param memberId Member ID
|
||||
*/
|
||||
void addMember(uint16_t memberId);
|
||||
|
||||
/**
|
||||
* Remove a member ID from this cluster
|
||||
*
|
||||
* @param memberId Member ID to remove
|
||||
*/
|
||||
void removeMember(uint16_t memberId);
|
||||
|
||||
/**
|
||||
* Find a better cluster endpoint for this peer (if any)
|
||||
*
|
||||
* @param redirectTo InetAddress to be set to a better endpoint (if there is one)
|
||||
* @param peerAddress Address of peer to (possibly) redirect
|
||||
* @param peerPhysicalAddress Physical address of peer's current best path (where packet was most recently received or getBestPath()->address())
|
||||
* @param offload Always redirect if possible -- can be used to offload peers during shutdown
|
||||
* @return True if redirectTo was set to a new address, false if redirectTo was not modified
|
||||
*/
|
||||
bool findBetterEndpoint(InetAddress &redirectTo,const Address &peerAddress,const InetAddress &peerPhysicalAddress,bool offload);
|
||||
|
||||
/**
|
||||
* @param ip Address to check
|
||||
* @return True if this is a cluster frontplane address (excluding our addresses)
|
||||
*/
|
||||
bool isClusterPeerFrontplane(const InetAddress &ip) const;
|
||||
|
||||
/**
|
||||
* Fill out ZT_ClusterStatus structure (from core API)
|
||||
*
|
||||
* @param status Reference to structure to hold result (anything there is replaced)
|
||||
*/
|
||||
void status(ZT_ClusterStatus &status) const;
|
||||
|
||||
private:
|
||||
void _send(uint16_t memberId,StateMessageType type,const void *msg,unsigned int len);
|
||||
void _flush(uint16_t memberId);
|
||||
|
||||
void _doREMOTE_WHOIS(uint64_t fromMemberId,const Packet &remotep);
|
||||
void _doREMOTE_MULTICAST_GATHER(uint64_t fromMemberId,const Packet &remotep);
|
||||
|
||||
// These are initialized in the constructor and remain immutable ------------
|
||||
uint16_t _masterSecret[ZT_SHA512_DIGEST_LEN / sizeof(uint16_t)];
|
||||
unsigned char _key[ZT_PEER_SECRET_KEY_LENGTH];
|
||||
const RuntimeEnvironment *RR;
|
||||
_ClusterSendQueue *const _sendQueue;
|
||||
void (*_sendFunction)(void *,unsigned int,const void *,unsigned int);
|
||||
void *_sendFunctionArg;
|
||||
int (*_addressToLocationFunction)(void *,const struct sockaddr_storage *,int *,int *,int *);
|
||||
void *_addressToLocationFunctionArg;
|
||||
const int32_t _x;
|
||||
const int32_t _y;
|
||||
const int32_t _z;
|
||||
const uint16_t _id;
|
||||
const std::vector<InetAddress> _zeroTierPhysicalEndpoints;
|
||||
// end immutable fields -----------------------------------------------------
|
||||
|
||||
struct _Member
|
||||
{
|
||||
unsigned char key[ZT_PEER_SECRET_KEY_LENGTH];
|
||||
|
||||
uint64_t lastReceivedAliveAnnouncement;
|
||||
uint64_t lastAnnouncedAliveTo;
|
||||
|
||||
uint64_t load;
|
||||
uint64_t peers;
|
||||
int32_t x,y,z;
|
||||
|
||||
std::vector<InetAddress> zeroTierPhysicalEndpoints;
|
||||
|
||||
Buffer<ZT_CLUSTER_MAX_MESSAGE_LENGTH> q;
|
||||
|
||||
Mutex lock;
|
||||
|
||||
inline void clear()
|
||||
{
|
||||
lastReceivedAliveAnnouncement = 0;
|
||||
lastAnnouncedAliveTo = 0;
|
||||
load = 0;
|
||||
peers = 0;
|
||||
x = 0;
|
||||
y = 0;
|
||||
z = 0;
|
||||
zeroTierPhysicalEndpoints.clear();
|
||||
q.clear();
|
||||
}
|
||||
|
||||
_Member() { this->clear(); }
|
||||
~_Member() { Utils::burn(key,sizeof(key)); }
|
||||
};
|
||||
_Member *const _members;
|
||||
|
||||
std::vector<uint16_t> _memberIds;
|
||||
Mutex _memberIds_m;
|
||||
|
||||
struct _RemotePeer
|
||||
{
|
||||
_RemotePeer() : lastHavePeerReceived(0),lastSentWantPeer(0) {}
|
||||
~_RemotePeer() { Utils::burn(key,ZT_PEER_SECRET_KEY_LENGTH); }
|
||||
uint64_t lastHavePeerReceived;
|
||||
uint64_t lastSentWantPeer;
|
||||
uint8_t key[ZT_PEER_SECRET_KEY_LENGTH]; // secret key from identity agreement
|
||||
};
|
||||
std::map< std::pair<Address,unsigned int>,_RemotePeer > _remotePeers; // we need ordered behavior and lower_bound here
|
||||
Mutex _remotePeers_m;
|
||||
|
||||
uint64_t _lastFlushed;
|
||||
uint64_t _lastCleanedRemotePeers;
|
||||
uint64_t _lastCleanedQueue;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif // ZT_ENABLE_CLUSTER
|
||||
|
||||
#endif
|
@ -1,168 +0,0 @@
|
||||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2017 ZeroTier, Inc. https://www.zerotier.com/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* You can be released from the requirements of the license by purchasing
|
||||
* a commercial license. Buying such a license is mandatory as soon as you
|
||||
* develop commercial closed-source software that incorporates or links
|
||||
* directly against ZeroTier software without disclosing the source code
|
||||
* of your own application.
|
||||
*/
|
||||
|
||||
#ifndef ZT_CLUSTERDEFINITION_HPP
|
||||
#define ZT_CLUSTERDEFINITION_HPP
|
||||
|
||||
#ifdef ZT_ENABLE_CLUSTER
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
#include "../node/Constants.hpp"
|
||||
#include "../node/Utils.hpp"
|
||||
#include "../node/NonCopyable.hpp"
|
||||
#include "../osdep/OSUtils.hpp"
|
||||
|
||||
#include "ClusterGeoIpService.hpp"
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
/**
|
||||
* Parser for cluster definition file
|
||||
*/
|
||||
class ClusterDefinition : NonCopyable
|
||||
{
|
||||
public:
|
||||
struct MemberDefinition
|
||||
{
|
||||
MemberDefinition() : id(0),x(0),y(0),z(0) { name[0] = (char)0; }
|
||||
|
||||
unsigned int id;
|
||||
int x,y,z;
|
||||
char name[256];
|
||||
InetAddress clusterEndpoint;
|
||||
std::vector<InetAddress> zeroTierEndpoints;
|
||||
};
|
||||
|
||||
/**
|
||||
* Load and initialize cluster definition and GeoIP data if any
|
||||
*
|
||||
* @param myAddress My ZeroTier address
|
||||
* @param pathToClusterFile Path to cluster definition file
|
||||
* @throws std::runtime_error Invalid cluster definition or unable to load data
|
||||
*/
|
||||
ClusterDefinition(uint64_t myAddress,const char *pathToClusterFile)
|
||||
{
|
||||
std::string cf;
|
||||
if (!OSUtils::readFile(pathToClusterFile,cf))
|
||||
return;
|
||||
|
||||
char myAddressStr[64];
|
||||
Utils::ztsnprintf(myAddressStr,sizeof(myAddressStr),"%.10llx",myAddress);
|
||||
|
||||
std::vector<std::string> lines(OSUtils::split(cf.c_str(),"\r\n","",""));
|
||||
for(std::vector<std::string>::iterator l(lines.begin());l!=lines.end();++l) {
|
||||
std::vector<std::string> fields(OSUtils::split(l->c_str()," \t","",""));
|
||||
if ((fields.size() < 5)||(fields[0][0] == '#')||(fields[0] != myAddressStr))
|
||||
continue;
|
||||
|
||||
// <address> geo <CSV path> <ip start column> <ip end column> <latitutde column> <longitude column>
|
||||
if (fields[1] == "geo") {
|
||||
if ((fields.size() >= 7)&&(OSUtils::fileExists(fields[2].c_str()))) {
|
||||
int ipStartColumn = Utils::strToInt(fields[3].c_str());
|
||||
int ipEndColumn = Utils::strToInt(fields[4].c_str());
|
||||
int latitudeColumn = Utils::strToInt(fields[5].c_str());
|
||||
int longitudeColumn = Utils::strToInt(fields[6].c_str());
|
||||
if (_geo.load(fields[2].c_str(),ipStartColumn,ipEndColumn,latitudeColumn,longitudeColumn) <= 0)
|
||||
throw std::runtime_error(std::string("failed to load geo-ip data from ")+fields[2]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// <address> <ID> <name> <backplane IP/port(s)> <ZT frontplane IP/port(s)> <x,y,z>
|
||||
int id = Utils::strToUInt(fields[1].c_str());
|
||||
if ((id < 0)||(id > ZT_CLUSTER_MAX_MEMBERS))
|
||||
throw std::runtime_error(std::string("invalid cluster member ID: ")+fields[1]);
|
||||
MemberDefinition &md = _md[id];
|
||||
|
||||
md.id = (unsigned int)id;
|
||||
if (fields.size() >= 6) {
|
||||
std::vector<std::string> xyz(OSUtils::split(fields[5].c_str(),",","",""));
|
||||
md.x = (xyz.size() > 0) ? Utils::strToInt(xyz[0].c_str()) : 0;
|
||||
md.y = (xyz.size() > 1) ? Utils::strToInt(xyz[1].c_str()) : 0;
|
||||
md.z = (xyz.size() > 2) ? Utils::strToInt(xyz[2].c_str()) : 0;
|
||||
}
|
||||
Utils::scopy(md.name,sizeof(md.name),fields[2].c_str());
|
||||
md.clusterEndpoint.fromString(fields[3]);
|
||||
if (!md.clusterEndpoint)
|
||||
continue;
|
||||
std::vector<std::string> zips(OSUtils::split(fields[4].c_str(),",","",""));
|
||||
for(std::vector<std::string>::iterator zip(zips.begin());zip!=zips.end();++zip) {
|
||||
InetAddress i;
|
||||
i.fromString(*zip);
|
||||
if (i)
|
||||
md.zeroTierEndpoints.push_back(i);
|
||||
}
|
||||
|
||||
_ids.push_back((unsigned int)id);
|
||||
}
|
||||
|
||||
std::sort(_ids.begin(),_ids.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return All member definitions in this cluster by ID (ID is array index)
|
||||
*/
|
||||
inline const MemberDefinition &operator[](unsigned int id) const throw() { return _md[id]; }
|
||||
|
||||
/**
|
||||
* @return Number of members in this cluster
|
||||
*/
|
||||
inline unsigned int size() const throw() { return (unsigned int)_ids.size(); }
|
||||
|
||||
/**
|
||||
* @return IDs of members in this cluster sorted by ID
|
||||
*/
|
||||
inline const std::vector<unsigned int> &ids() const throw() { return _ids; }
|
||||
|
||||
/**
|
||||
* @return GeoIP service for this cluster
|
||||
*/
|
||||
inline ClusterGeoIpService &geo() throw() { return _geo; }
|
||||
|
||||
/**
|
||||
* @return A vector (new copy) containing all cluster members
|
||||
*/
|
||||
inline std::vector<MemberDefinition> members() const
|
||||
{
|
||||
std::vector<MemberDefinition> m;
|
||||
for(std::vector<unsigned int>::const_iterator i(_ids.begin());i!=_ids.end();++i)
|
||||
m.push_back(_md[*i]);
|
||||
return m;
|
||||
}
|
||||
|
||||
private:
|
||||
MemberDefinition _md[ZT_CLUSTER_MAX_MEMBERS];
|
||||
std::vector<unsigned int> _ids;
|
||||
ClusterGeoIpService _geo;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif // ZT_ENABLE_CLUSTER
|
||||
|
||||
#endif
|
@ -1,243 +0,0 @@
|
||||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2017 ZeroTier, Inc. https://www.zerotier.com/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* You can be released from the requirements of the license by purchasing
|
||||
* a commercial license. Buying such a license is mandatory as soon as you
|
||||
* develop commercial closed-source software that incorporates or links
|
||||
* directly against ZeroTier software without disclosing the source code
|
||||
* of your own application.
|
||||
*/
|
||||
|
||||
#ifdef ZT_ENABLE_CLUSTER
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "ClusterGeoIpService.hpp"
|
||||
|
||||
#include "../node/Utils.hpp"
|
||||
#include "../osdep/OSUtils.hpp"
|
||||
|
||||
#define ZT_CLUSTERGEOIPSERVICE_FILE_MODIFICATION_CHECK_EVERY 10000
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
ClusterGeoIpService::ClusterGeoIpService() :
|
||||
_pathToCsv(),
|
||||
_ipStartColumn(-1),
|
||||
_ipEndColumn(-1),
|
||||
_latitudeColumn(-1),
|
||||
_longitudeColumn(-1),
|
||||
_lastFileCheckTime(0),
|
||||
_csvModificationTime(0),
|
||||
_csvFileSize(0)
|
||||
{
|
||||
}
|
||||
|
||||
ClusterGeoIpService::~ClusterGeoIpService()
|
||||
{
|
||||
}
|
||||
|
||||
bool ClusterGeoIpService::locate(const InetAddress &ip,int &x,int &y,int &z)
|
||||
{
|
||||
Mutex::Lock _l(_lock);
|
||||
|
||||
if ((_pathToCsv.length() > 0)&&((OSUtils::now() - _lastFileCheckTime) > ZT_CLUSTERGEOIPSERVICE_FILE_MODIFICATION_CHECK_EVERY)) {
|
||||
_lastFileCheckTime = OSUtils::now();
|
||||
if ((_csvFileSize != OSUtils::getFileSize(_pathToCsv.c_str()))||(_csvModificationTime != OSUtils::getLastModified(_pathToCsv.c_str())))
|
||||
_load(_pathToCsv.c_str(),_ipStartColumn,_ipEndColumn,_latitudeColumn,_longitudeColumn);
|
||||
}
|
||||
|
||||
/* We search by looking up the upper bound of the sorted vXdb vectors
|
||||
* and then iterating down for a matching IP range. We stop when we hit
|
||||
* the beginning or an entry whose start and end are before the IP we
|
||||
* are searching. */
|
||||
|
||||
if ((ip.ss_family == AF_INET)&&(_v4db.size() > 0)) {
|
||||
_V4E key;
|
||||
key.start = Utils::ntoh((uint32_t)(reinterpret_cast<const struct sockaddr_in *>(&ip)->sin_addr.s_addr));
|
||||
std::vector<_V4E>::const_iterator i(std::upper_bound(_v4db.begin(),_v4db.end(),key));
|
||||
while (i != _v4db.begin()) {
|
||||
--i;
|
||||
if ((key.start >= i->start)&&(key.start <= i->end)) {
|
||||
x = i->x;
|
||||
y = i->y;
|
||||
z = i->z;
|
||||
//printf("%s : %f,%f %d,%d,%d\n",ip.toIpString().c_str(),i->lat,i->lon,x,y,z);
|
||||
return true;
|
||||
} else if ((key.start > i->start)&&(key.start > i->end))
|
||||
break;
|
||||
}
|
||||
} else if ((ip.ss_family == AF_INET6)&&(_v6db.size() > 0)) {
|
||||
_V6E key;
|
||||
memcpy(key.start,reinterpret_cast<const struct sockaddr_in6 *>(&ip)->sin6_addr.s6_addr,16);
|
||||
std::vector<_V6E>::const_iterator i(std::upper_bound(_v6db.begin(),_v6db.end(),key));
|
||||
while (i != _v6db.begin()) {
|
||||
--i;
|
||||
const int s_vs_s = memcmp(key.start,i->start,16);
|
||||
const int s_vs_e = memcmp(key.start,i->end,16);
|
||||
if ((s_vs_s >= 0)&&(s_vs_e <= 0)) {
|
||||
x = i->x;
|
||||
y = i->y;
|
||||
z = i->z;
|
||||
//printf("%s : %f,%f %d,%d,%d\n",ip.toIpString().c_str(),i->lat,i->lon,x,y,z);
|
||||
return true;
|
||||
} else if ((s_vs_s > 0)&&(s_vs_e > 0))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ClusterGeoIpService::_parseLine(const char *line,std::vector<_V4E> &v4db,std::vector<_V6E> &v6db,int ipStartColumn,int ipEndColumn,int latitudeColumn,int longitudeColumn)
|
||||
{
|
||||
std::vector<std::string> ls(OSUtils::split(line,",\t","\\","\"'"));
|
||||
if ( ((ipStartColumn >= 0)&&(ipStartColumn < (int)ls.size()))&&
|
||||
((ipEndColumn >= 0)&&(ipEndColumn < (int)ls.size()))&&
|
||||
((latitudeColumn >= 0)&&(latitudeColumn < (int)ls.size()))&&
|
||||
((longitudeColumn >= 0)&&(longitudeColumn < (int)ls.size())) ) {
|
||||
InetAddress ipStart(ls[ipStartColumn].c_str(),0);
|
||||
InetAddress ipEnd(ls[ipEndColumn].c_str(),0);
|
||||
const double lat = strtod(ls[latitudeColumn].c_str(),(char **)0);
|
||||
const double lon = strtod(ls[longitudeColumn].c_str(),(char **)0);
|
||||
|
||||
if ((ipStart.ss_family == ipEnd.ss_family)&&(ipStart)&&(ipEnd)&&(std::isfinite(lat))&&(std::isfinite(lon))) {
|
||||
const double latRadians = lat * 0.01745329251994; // PI / 180
|
||||
const double lonRadians = lon * 0.01745329251994; // PI / 180
|
||||
const double cosLat = cos(latRadians);
|
||||
const int x = (int)round((-6371.0) * cosLat * cos(lonRadians)); // 6371 == Earth's approximate radius in kilometers
|
||||
const int y = (int)round(6371.0 * sin(latRadians));
|
||||
const int z = (int)round(6371.0 * cosLat * sin(lonRadians));
|
||||
|
||||
if (ipStart.ss_family == AF_INET) {
|
||||
v4db.push_back(_V4E());
|
||||
v4db.back().start = Utils::ntoh((uint32_t)(reinterpret_cast<const struct sockaddr_in *>(&ipStart)->sin_addr.s_addr));
|
||||
v4db.back().end = Utils::ntoh((uint32_t)(reinterpret_cast<const struct sockaddr_in *>(&ipEnd)->sin_addr.s_addr));
|
||||
v4db.back().lat = (float)lat;
|
||||
v4db.back().lon = (float)lon;
|
||||
v4db.back().x = x;
|
||||
v4db.back().y = y;
|
||||
v4db.back().z = z;
|
||||
//printf("%s - %s : %d,%d,%d\n",ipStart.toIpString().c_str(),ipEnd.toIpString().c_str(),x,y,z);
|
||||
} else if (ipStart.ss_family == AF_INET6) {
|
||||
v6db.push_back(_V6E());
|
||||
memcpy(v6db.back().start,reinterpret_cast<const struct sockaddr_in6 *>(&ipStart)->sin6_addr.s6_addr,16);
|
||||
memcpy(v6db.back().end,reinterpret_cast<const struct sockaddr_in6 *>(&ipEnd)->sin6_addr.s6_addr,16);
|
||||
v6db.back().lat = (float)lat;
|
||||
v6db.back().lon = (float)lon;
|
||||
v6db.back().x = x;
|
||||
v6db.back().y = y;
|
||||
v6db.back().z = z;
|
||||
//printf("%s - %s : %d,%d,%d\n",ipStart.toIpString().c_str(),ipEnd.toIpString().c_str(),x,y,z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
long ClusterGeoIpService::_load(const char *pathToCsv,int ipStartColumn,int ipEndColumn,int latitudeColumn,int longitudeColumn)
|
||||
{
|
||||
// assumes _lock is locked
|
||||
|
||||
FILE *f = fopen(pathToCsv,"rb");
|
||||
if (!f)
|
||||
return -1;
|
||||
|
||||
std::vector<_V4E> v4db;
|
||||
std::vector<_V6E> v6db;
|
||||
v4db.reserve(16777216);
|
||||
v6db.reserve(16777216);
|
||||
|
||||
char buf[4096];
|
||||
char linebuf[1024];
|
||||
unsigned int lineptr = 0;
|
||||
for(;;) {
|
||||
int n = (int)fread(buf,1,sizeof(buf),f);
|
||||
if (n <= 0)
|
||||
break;
|
||||
for(int i=0;i<n;++i) {
|
||||
if ((buf[i] == '\r')||(buf[i] == '\n')||(buf[i] == (char)0)) {
|
||||
if (lineptr) {
|
||||
linebuf[lineptr] = (char)0;
|
||||
_parseLine(linebuf,v4db,v6db,ipStartColumn,ipEndColumn,latitudeColumn,longitudeColumn);
|
||||
}
|
||||
lineptr = 0;
|
||||
} else if (lineptr < (unsigned int)sizeof(linebuf))
|
||||
linebuf[lineptr++] = buf[i];
|
||||
}
|
||||
}
|
||||
if (lineptr) {
|
||||
linebuf[lineptr] = (char)0;
|
||||
_parseLine(linebuf,v4db,v6db,ipStartColumn,ipEndColumn,latitudeColumn,longitudeColumn);
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
|
||||
if ((v4db.size() > 0)||(v6db.size() > 0)) {
|
||||
std::sort(v4db.begin(),v4db.end());
|
||||
std::sort(v6db.begin(),v6db.end());
|
||||
|
||||
_pathToCsv = pathToCsv;
|
||||
_ipStartColumn = ipStartColumn;
|
||||
_ipEndColumn = ipEndColumn;
|
||||
_latitudeColumn = latitudeColumn;
|
||||
_longitudeColumn = longitudeColumn;
|
||||
|
||||
_lastFileCheckTime = OSUtils::now();
|
||||
_csvModificationTime = OSUtils::getLastModified(pathToCsv);
|
||||
_csvFileSize = OSUtils::getFileSize(pathToCsv);
|
||||
|
||||
_v4db.swap(v4db);
|
||||
_v6db.swap(v6db);
|
||||
|
||||
return (long)(_v4db.size() + _v6db.size());
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif // ZT_ENABLE_CLUSTER
|
||||
|
||||
/*
|
||||
int main(int argc,char **argv)
|
||||
{
|
||||
char buf[1024];
|
||||
|
||||
ZeroTier::ClusterGeoIpService gip;
|
||||
printf("loading...\n");
|
||||
gip.load("/Users/api/Code/ZeroTier/Infrastructure/root-servers/zerotier-one/cluster-geoip.csv",0,1,5,6);
|
||||
printf("... done!\n"); fflush(stdout);
|
||||
|
||||
while (gets(buf)) { // unsafe, testing only
|
||||
ZeroTier::InetAddress addr(buf,0);
|
||||
printf("looking up: %s\n",addr.toString().c_str()); fflush(stdout);
|
||||
int x = 0,y = 0,z = 0;
|
||||
if (gip.locate(addr,x,y,z)) {
|
||||
//printf("%s: %d,%d,%d\n",addr.toString().c_str(),x,y,z); fflush(stdout);
|
||||
} else {
|
||||
printf("%s: not found!\n",addr.toString().c_str()); fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
*/
|
@ -1,151 +0,0 @@
|
||||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2017 ZeroTier, Inc. https://www.zerotier.com/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* You can be released from the requirements of the license by purchasing
|
||||
* a commercial license. Buying such a license is mandatory as soon as you
|
||||
* develop commercial closed-source software that incorporates or links
|
||||
* directly against ZeroTier software without disclosing the source code
|
||||
* of your own application.
|
||||
*/
|
||||
|
||||
#ifndef ZT_CLUSTERGEOIPSERVICE_HPP
|
||||
#define ZT_CLUSTERGEOIPSERVICE_HPP
|
||||
|
||||
#ifdef ZT_ENABLE_CLUSTER
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
|
||||
#include "../node/Constants.hpp"
|
||||
#include "../node/Mutex.hpp"
|
||||
#include "../node/NonCopyable.hpp"
|
||||
#include "../node/InetAddress.hpp"
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
/**
|
||||
* Loads a GeoIP CSV into memory for fast lookup, reloading as needed
|
||||
*
|
||||
* This was designed around the CSV from https://db-ip.com but can be used
|
||||
* with any similar GeoIP CSV database that is presented in the form of an
|
||||
* IP range and lat/long coordinates.
|
||||
*
|
||||
* It loads the whole database into memory, which can be kind of large. If
|
||||
* the CSV file changes, the changes are loaded automatically.
|
||||
*/
|
||||
class ClusterGeoIpService : NonCopyable
|
||||
{
|
||||
public:
|
||||
ClusterGeoIpService();
|
||||
~ClusterGeoIpService();
|
||||
|
||||
/**
|
||||
* Load or reload CSV file
|
||||
*
|
||||
* CSV column indexes start at zero. CSVs can be quoted with single or
|
||||
* double quotes. Whitespace before or after commas is ignored. Backslash
|
||||
* may be used for escaping whitespace as well.
|
||||
*
|
||||
* @param pathToCsv Path to (uncompressed) CSV file
|
||||
* @param ipStartColumn Column with IP range start
|
||||
* @param ipEndColumn Column with IP range end (inclusive)
|
||||
* @param latitudeColumn Column with latitude
|
||||
* @param longitudeColumn Column with longitude
|
||||
* @return Number of valid records loaded or -1 on error (invalid file, not found, etc.)
|
||||
*/
|
||||
inline long load(const char *pathToCsv,int ipStartColumn,int ipEndColumn,int latitudeColumn,int longitudeColumn)
|
||||
{
|
||||
Mutex::Lock _l(_lock);
|
||||
return _load(pathToCsv,ipStartColumn,ipEndColumn,latitudeColumn,longitudeColumn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to locate an IP
|
||||
*
|
||||
* This returns true if x, y, and z are set. If the return value is false
|
||||
* the values of x, y, and z are undefined.
|
||||
*
|
||||
* @param ip IPv4 or IPv6 address
|
||||
* @param x Reference to variable to receive X
|
||||
* @param y Reference to variable to receive Y
|
||||
* @param z Reference to variable to receive Z
|
||||
* @return True if coordinates were set
|
||||
*/
|
||||
bool locate(const InetAddress &ip,int &x,int &y,int &z);
|
||||
|
||||
/**
|
||||
* @return True if IP database/service is available for queries (otherwise locate() will always be false)
|
||||
*/
|
||||
inline bool available() const
|
||||
{
|
||||
Mutex::Lock _l(_lock);
|
||||
return ((_v4db.size() + _v6db.size()) > 0);
|
||||
}
|
||||
|
||||
private:
|
||||
struct _V4E
|
||||
{
|
||||
uint32_t start;
|
||||
uint32_t end;
|
||||
float lat,lon;
|
||||
int16_t x,y,z;
|
||||
|
||||
inline bool operator<(const _V4E &e) const { return (start < e.start); }
|
||||
};
|
||||
|
||||
struct _V6E
|
||||
{
|
||||
uint8_t start[16];
|
||||
uint8_t end[16];
|
||||
float lat,lon;
|
||||
int16_t x,y,z;
|
||||
|
||||
inline bool operator<(const _V6E &e) const { return (memcmp(start,e.start,16) < 0); }
|
||||
};
|
||||
|
||||
static void _parseLine(const char *line,std::vector<_V4E> &v4db,std::vector<_V6E> &v6db,int ipStartColumn,int ipEndColumn,int latitudeColumn,int longitudeColumn);
|
||||
long _load(const char *pathToCsv,int ipStartColumn,int ipEndColumn,int latitudeColumn,int longitudeColumn);
|
||||
|
||||
std::string _pathToCsv;
|
||||
int _ipStartColumn;
|
||||
int _ipEndColumn;
|
||||
int _latitudeColumn;
|
||||
int _longitudeColumn;
|
||||
|
||||
uint64_t _lastFileCheckTime;
|
||||
uint64_t _csvModificationTime;
|
||||
int64_t _csvFileSize;
|
||||
|
||||
std::vector<_V4E> _v4db;
|
||||
std::vector<_V6E> _v6db;
|
||||
|
||||
Mutex _lock;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif // ZT_ENABLE_CLUSTER
|
||||
|
||||
#endif
|
101
attic/FCV.hpp
101
attic/FCV.hpp
@ -1,101 +0,0 @@
|
||||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2018 ZeroTier, Inc. https://www.zerotier.com/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* You can be released from the requirements of the license by purchasing
|
||||
* a commercial license. Buying such a license is mandatory as soon as you
|
||||
* develop commercial closed-source software that incorporates or links
|
||||
* directly against ZeroTier software without disclosing the source code
|
||||
* of your own application.
|
||||
*/
|
||||
|
||||
#include "Constants.hpp"
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
/**
|
||||
* A really simple fixed capacity vector
|
||||
*
|
||||
* This class does no bounds checking, so the user must ensure that
|
||||
* no more than C elements are ever added and that accesses are in
|
||||
* bounds.
|
||||
*
|
||||
* @tparam T Type to contain
|
||||
* @tparam C Capacity of vector
|
||||
*/
|
||||
template<typename T,unsigned long C>
|
||||
class FCV
|
||||
{
|
||||
public:
|
||||
FCV() : _s(0) {}
|
||||
~FCV() { clear(); }
|
||||
|
||||
FCV(const FCV &v) :
|
||||
_s(v._s)
|
||||
{
|
||||
for(unsigned long i=0;i<_s;++i) {
|
||||
new (reinterpret_cast<T *>(_mem + (sizeof(T) * i))) T(reinterpret_cast<const T *>(v._mem)[i]);
|
||||
}
|
||||
}
|
||||
|
||||
inline FCV &operator=(const FCV &v)
|
||||
{
|
||||
clear();
|
||||
_s = v._s;
|
||||
for(unsigned long i=0;i<_s;++i) {
|
||||
new (reinterpret_cast<T *>(_mem + (sizeof(T) * i))) T(reinterpret_cast<const T *>(v._mem)[i]);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
typedef T * iterator;
|
||||
typedef const T * const_iterator;
|
||||
typedef unsigned long size_type;
|
||||
|
||||
inline iterator begin() { return (T *)_mem; }
|
||||
inline iterator end() { return (T *)(_mem + (sizeof(T) * _s)); }
|
||||
inline iterator begin() const { return (const T *)_mem; }
|
||||
inline iterator end() const { return (const T *)(_mem + (sizeof(T) * _s)); }
|
||||
|
||||
inline T &operator[](const size_type i) { return reinterpret_cast<T *>(_mem)[i]; }
|
||||
inline const T &operator[](const size_type i) const { return reinterpret_cast<const T *>(_mem)[i]; }
|
||||
|
||||
inline T &front() { return reinterpret_cast<T *>(_mem)[0]; }
|
||||
inline const T &front() const { return reinterpret_cast<const T *>(_mem)[0]; }
|
||||
inline T &back() { return reinterpret_cast<T *>(_mem)[_s - 1]; }
|
||||
inline const T &back() const { return reinterpret_cast<const T *>(_mem)[_s - 1]; }
|
||||
|
||||
inline void push_back(const T &v) { new (reinterpret_cast<T *>(_mem + (sizeof(T) * _s++))) T(v); }
|
||||
inline void pop_back() { reinterpret_cast<T *>(_mem + (sizeof(T) * --_s))->~T(); }
|
||||
|
||||
inline size_type size() const { return _s; }
|
||||
inline size_type capacity() const { return C; }
|
||||
|
||||
inline void clear()
|
||||
{
|
||||
for(unsigned long i=0;i<_s;++i)
|
||||
reinterpret_cast<T *>(_mem + (sizeof(T) * i))->~T();
|
||||
_s = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
char _mem[sizeof(T) * C];
|
||||
unsigned long _s;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
@ -1,650 +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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* 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 <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <net/route.h>
|
||||
#include <net/if.h>
|
||||
#include <net/if_arp.h>
|
||||
#include <net/if_dl.h>
|
||||
#include <net/if_media.h>
|
||||
#include <netinet6/in6_var.h>
|
||||
#include <netinet/in_var.h>
|
||||
#include <netinet/icmp6.h>
|
||||
|
||||
#include <pcap/pcap.h>
|
||||
|
||||
// OSX compile fix... in6_var defines this in a struct which namespaces it for C++ ... why?!?
|
||||
struct prf_ra {
|
||||
u_char onlink : 1;
|
||||
u_char autonomous : 1;
|
||||
u_char reserved : 6;
|
||||
} prf_ra;
|
||||
|
||||
#include <netinet6/nd6.h>
|
||||
#include <ifaddrs.h>
|
||||
|
||||
// These are KERNEL_PRIVATE... why?
|
||||
#ifndef SIOCAUTOCONF_START
|
||||
#define SIOCAUTOCONF_START _IOWR('i', 132, struct in6_ifreq) /* accept rtadvd on this interface */
|
||||
#endif
|
||||
#ifndef SIOCAUTOCONF_STOP
|
||||
#define SIOCAUTOCONF_STOP _IOWR('i', 133, struct in6_ifreq) /* stop accepting rtadv for this interface */
|
||||
#endif
|
||||
|
||||
#ifndef ETH_ALEN
|
||||
#define ETH_ALEN 6
|
||||
#endif
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// --------------------------------------------------------------------------
|
||||
// This source is from:
|
||||
// http://www.opensource.apple.com/source/Libinfo/Libinfo-406.17/gen.subproj/getifmaddrs.c?txt
|
||||
// It's here because OSX 10.6 does not have this convenience function.
|
||||
|
||||
#define SALIGN (sizeof(uint32_t) - 1)
|
||||
#define SA_RLEN(sa) ((sa)->sa_len ? (((sa)->sa_len + SALIGN) & ~SALIGN) : \
|
||||
(SALIGN + 1))
|
||||
#define MAX_SYSCTL_TRY 5
|
||||
#define RTA_MASKS (RTA_GATEWAY | RTA_IFP | RTA_IFA)
|
||||
|
||||
/* FreeBSD uses NET_RT_IFMALIST and RTM_NEWMADDR from <sys/socket.h> */
|
||||
/* We can use NET_RT_IFLIST2 and RTM_NEWMADDR2 on Darwin */
|
||||
//#define DARWIN_COMPAT
|
||||
|
||||
//#ifdef DARWIN_COMPAT
|
||||
#define GIM_SYSCTL_MIB NET_RT_IFLIST2
|
||||
#define GIM_RTM_ADDR RTM_NEWMADDR2
|
||||
//#else
|
||||
//#define GIM_SYSCTL_MIB NET_RT_IFMALIST
|
||||
//#define GIM_RTM_ADDR RTM_NEWMADDR
|
||||
//#endif
|
||||
|
||||
// Not in 10.6 includes so use our own
|
||||
struct _intl_ifmaddrs {
|
||||
struct _intl_ifmaddrs *ifma_next;
|
||||
struct sockaddr *ifma_name;
|
||||
struct sockaddr *ifma_addr;
|
||||
struct sockaddr *ifma_lladdr;
|
||||
};
|
||||
|
||||
static inline int _intl_getifmaddrs(struct _intl_ifmaddrs **pif)
|
||||
{
|
||||
int icnt = 1;
|
||||
int dcnt = 0;
|
||||
int ntry = 0;
|
||||
size_t len;
|
||||
size_t needed;
|
||||
int mib[6];
|
||||
int i;
|
||||
char *buf;
|
||||
char *data;
|
||||
char *next;
|
||||
char *p;
|
||||
struct ifma_msghdr2 *ifmam;
|
||||
struct _intl_ifmaddrs *ifa, *ift;
|
||||
struct rt_msghdr *rtm;
|
||||
struct sockaddr *sa;
|
||||
|
||||
mib[0] = CTL_NET;
|
||||
mib[1] = PF_ROUTE;
|
||||
mib[2] = 0; /* protocol */
|
||||
mib[3] = 0; /* wildcard address family */
|
||||
mib[4] = GIM_SYSCTL_MIB;
|
||||
mib[5] = 0; /* no flags */
|
||||
do {
|
||||
if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0)
|
||||
return (-1);
|
||||
if ((buf = (char *)malloc(needed)) == NULL)
|
||||
return (-1);
|
||||
if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) {
|
||||
if (errno != ENOMEM || ++ntry >= MAX_SYSCTL_TRY) {
|
||||
free(buf);
|
||||
return (-1);
|
||||
}
|
||||
free(buf);
|
||||
buf = NULL;
|
||||
}
|
||||
} while (buf == NULL);
|
||||
|
||||
for (next = buf; next < buf + needed; next += rtm->rtm_msglen) {
|
||||
rtm = (struct rt_msghdr *)(void *)next;
|
||||
if (rtm->rtm_version != RTM_VERSION)
|
||||
continue;
|
||||
switch (rtm->rtm_type) {
|
||||
case GIM_RTM_ADDR:
|
||||
ifmam = (struct ifma_msghdr2 *)(void *)rtm;
|
||||
if ((ifmam->ifmam_addrs & RTA_IFA) == 0)
|
||||
break;
|
||||
icnt++;
|
||||
p = (char *)(ifmam + 1);
|
||||
for (i = 0; i < RTAX_MAX; i++) {
|
||||
if ((RTA_MASKS & ifmam->ifmam_addrs &
|
||||
(1 << i)) == 0)
|
||||
continue;
|
||||
sa = (struct sockaddr *)(void *)p;
|
||||
len = SA_RLEN(sa);
|
||||
dcnt += len;
|
||||
p += len;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
data = (char *)malloc(sizeof(struct _intl_ifmaddrs) * icnt + dcnt);
|
||||
if (data == NULL) {
|
||||
free(buf);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
ifa = (struct _intl_ifmaddrs *)(void *)data;
|
||||
data += sizeof(struct _intl_ifmaddrs) * icnt;
|
||||
|
||||
memset(ifa, 0, sizeof(struct _intl_ifmaddrs) * icnt);
|
||||
ift = ifa;
|
||||
|
||||
for (next = buf; next < buf + needed; next += rtm->rtm_msglen) {
|
||||
rtm = (struct rt_msghdr *)(void *)next;
|
||||
if (rtm->rtm_version != RTM_VERSION)
|
||||
continue;
|
||||
|
||||
switch (rtm->rtm_type) {
|
||||
case GIM_RTM_ADDR:
|
||||
ifmam = (struct ifma_msghdr2 *)(void *)rtm;
|
||||
if ((ifmam->ifmam_addrs & RTA_IFA) == 0)
|
||||
break;
|
||||
|
||||
p = (char *)(ifmam + 1);
|
||||
for (i = 0; i < RTAX_MAX; i++) {
|
||||
if ((RTA_MASKS & ifmam->ifmam_addrs &
|
||||
(1 << i)) == 0)
|
||||
continue;
|
||||
sa = (struct sockaddr *)(void *)p;
|
||||
len = SA_RLEN(sa);
|
||||
switch (i) {
|
||||
case RTAX_GATEWAY:
|
||||
ift->ifma_lladdr =
|
||||
(struct sockaddr *)(void *)data;
|
||||
memcpy(data, p, len);
|
||||
data += len;
|
||||
break;
|
||||
|
||||
case RTAX_IFP:
|
||||
ift->ifma_name =
|
||||
(struct sockaddr *)(void *)data;
|
||||
memcpy(data, p, len);
|
||||
data += len;
|
||||
break;
|
||||
|
||||
case RTAX_IFA:
|
||||
ift->ifma_addr =
|
||||
(struct sockaddr *)(void *)data;
|
||||
memcpy(data, p, len);
|
||||
data += len;
|
||||
break;
|
||||
|
||||
default:
|
||||
data += len;
|
||||
break;
|
||||
}
|
||||
p += len;
|
||||
}
|
||||
ift->ifma_next = ift + 1;
|
||||
ift = ift->ifma_next;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free(buf);
|
||||
|
||||
if (ift > ifa) {
|
||||
ift--;
|
||||
ift->ifma_next = NULL;
|
||||
*pif = ifa;
|
||||
} else {
|
||||
*pif = NULL;
|
||||
free(ifa);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
static inline void _intl_freeifmaddrs(struct _intl_ifmaddrs *ifmp)
|
||||
{
|
||||
free(ifmp);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
|
||||
#include "../node/Constants.hpp"
|
||||
#include "../node/Utils.hpp"
|
||||
#include "../node/Mutex.hpp"
|
||||
#include "../node/Dictionary.hpp"
|
||||
#include "OSUtils.hpp"
|
||||
#include "OSXEthernetTap.hpp"
|
||||
|
||||
// ff:ff:ff:ff:ff:ff with no ADI
|
||||
static const ZeroTier::MulticastGroup _blindWildcardMulticastGroup(ZeroTier::MAC(0xff),0);
|
||||
|
||||
static inline bool _setIpv6Stuff(const char *ifname,bool performNUD,bool acceptRouterAdverts)
|
||||
{
|
||||
struct in6_ndireq nd;
|
||||
struct in6_ifreq ifr;
|
||||
|
||||
int s = socket(AF_INET6,SOCK_DGRAM,0);
|
||||
if (s <= 0)
|
||||
return false;
|
||||
|
||||
memset(&nd,0,sizeof(nd));
|
||||
strncpy(nd.ifname,ifname,sizeof(nd.ifname));
|
||||
|
||||
if (ioctl(s,SIOCGIFINFO_IN6,&nd)) {
|
||||
close(s);
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned long oldFlags = (unsigned long)nd.ndi.flags;
|
||||
|
||||
if (performNUD)
|
||||
nd.ndi.flags |= ND6_IFF_PERFORMNUD;
|
||||
else nd.ndi.flags &= ~ND6_IFF_PERFORMNUD;
|
||||
|
||||
if (oldFlags != (unsigned long)nd.ndi.flags) {
|
||||
if (ioctl(s,SIOCSIFINFO_FLAGS,&nd)) {
|
||||
close(s);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
memset(&ifr,0,sizeof(ifr));
|
||||
strncpy(ifr.ifr_name,ifname,sizeof(ifr.ifr_name));
|
||||
if (ioctl(s,acceptRouterAdverts ? SIOCAUTOCONF_START : SIOCAUTOCONF_STOP,&ifr)) {
|
||||
close(s);
|
||||
return false;
|
||||
}
|
||||
|
||||
close(s);
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
static std::set<std::string> globalDeviceNames;
|
||||
static Mutex globalTapCreateLock;
|
||||
|
||||
OSXEthernetTap::OSXEthernetTap(
|
||||
const char *homePath,
|
||||
const MAC &mac,
|
||||
unsigned int mtu,
|
||||
unsigned int metric,
|
||||
uint64_t nwid,
|
||||
const char *friendlyName,
|
||||
void (*handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *data,unsigned int len),
|
||||
void *arg) :
|
||||
_handler(handler),
|
||||
_arg(arg),
|
||||
_pcap((void *)0),
|
||||
_nwid(nwid),
|
||||
_mac(mac),
|
||||
_homePath(homePath),
|
||||
_mtu(mtu),
|
||||
_metric(metric),
|
||||
_enabled(true)
|
||||
{
|
||||
char errbuf[PCAP_ERRBUF_SIZE];
|
||||
char devname[64],ethaddr[64],mtustr[32],metstr[32],nwids[32];
|
||||
|
||||
Utils::snprintf(nwids,sizeof(nwids),"%.16llx",nwid);
|
||||
|
||||
if (mtu > 2800)
|
||||
throw std::runtime_error("max tap MTU is 2800");
|
||||
|
||||
Mutex::Lock _gl(globalTapCreateLock);
|
||||
|
||||
std::string desiredDevice;
|
||||
Dictionary devmap;
|
||||
{
|
||||
std::string devmapbuf;
|
||||
if (OSUtils::readFile((_homePath + ZT_PATH_SEPARATOR_S + "devicemap").c_str(),devmapbuf)) {
|
||||
devmap.fromString(devmapbuf);
|
||||
desiredDevice = devmap.get(nwids,"");
|
||||
}
|
||||
}
|
||||
|
||||
if ((desiredDevice.length() >= 9)&&(desiredDevice.substr(0,6) == "bridge")) {
|
||||
// length() >= 9 matches bridge### or bridge####
|
||||
_dev = desiredDevice;
|
||||
} else {
|
||||
if (globalDeviceNames.size() >= (10000 - 128)) // sanity check... this would be nuts
|
||||
throw std::runtime_error("too many devices!");
|
||||
unsigned int pseudoBridgeNo = (unsigned int)((nwid ^ (nwid >> 32)) % (10000 - 128)) + 128; // range: bridge128 to bridge9999
|
||||
sprintf(devname,"bridge%u",pseudoBridgeNo);
|
||||
while (globalDeviceNames.count(std::string(devname)) > 0) {
|
||||
++pseudoBridgeNo;
|
||||
if (pseudoBridgeNo > 9999)
|
||||
pseudoBridgeNo = 64;
|
||||
sprintf(devname,"bridge%u",pseudoBridgeNo);
|
||||
}
|
||||
_dev = devname;
|
||||
}
|
||||
|
||||
// Configure MAC address and MTU, bring interface up
|
||||
long cpid = (long)vfork();
|
||||
if (cpid == 0) {
|
||||
::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"create",(const char *)0);
|
||||
::_exit(-1);
|
||||
} else if (cpid > 0) {
|
||||
int exitcode = -1;
|
||||
::waitpid(cpid,&exitcode,0);
|
||||
if (exitcode != 0)
|
||||
throw std::runtime_error("ifconfig failure setting link-layer address and activating tap interface");
|
||||
} else throw std::runtime_error("unable to fork()");
|
||||
Utils::snprintf(ethaddr,sizeof(ethaddr),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(int)mac[0],(int)mac[1],(int)mac[2],(int)mac[3],(int)mac[4],(int)mac[5]);
|
||||
Utils::snprintf(mtustr,sizeof(mtustr),"%u",_mtu);
|
||||
Utils::snprintf(metstr,sizeof(metstr),"%u",_metric);
|
||||
cpid = (long)vfork();
|
||||
if (cpid == 0) {
|
||||
::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"lladdr",ethaddr,"mtu",mtustr,"metric",metstr,"up",(const char *)0);
|
||||
::_exit(-1);
|
||||
} else if (cpid > 0) {
|
||||
int exitcode = -1;
|
||||
::waitpid(cpid,&exitcode,0);
|
||||
if (exitcode != 0)
|
||||
throw std::runtime_error("ifconfig failure setting link-layer address and activating tap interface");
|
||||
} else throw std::runtime_error("unable to fork()");
|
||||
|
||||
_setIpv6Stuff(_dev.c_str(),true,false);
|
||||
|
||||
_pcap = (void *)pcap_create(_dev.c_str(),errbuf);
|
||||
if (!_pcap) {
|
||||
cpid = (long)vfork();
|
||||
if (cpid == 0) {
|
||||
::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"destroy",(const char *)0);
|
||||
::_exit(-1);
|
||||
} else if (cpid > 0) {
|
||||
int exitcode = -1;
|
||||
::waitpid(cpid,&exitcode,0);
|
||||
}
|
||||
throw std::runtime_error((std::string("pcap_create() on new bridge device failed: ") + errbuf).c_str());
|
||||
}
|
||||
pcap_set_promisc(reinterpret_cast<pcap_t *>(_pcap),1);
|
||||
pcap_set_timeout(reinterpret_cast<pcap_t *>(_pcap),120000);
|
||||
pcap_set_immediate_mode(reinterpret_cast<pcap_t *>(_pcap),1);
|
||||
if (pcap_set_buffer_size(reinterpret_cast<pcap_t *>(_pcap),1024 * 1024 * 16) != 0) // 16MB
|
||||
fprintf(stderr,"WARNING: pcap_set_buffer_size() failed!\n");
|
||||
if (pcap_set_snaplen(reinterpret_cast<pcap_t *>(_pcap),4096) != 0)
|
||||
fprintf(stderr,"WARNING: pcap_set_snaplen() failed!\n");
|
||||
if (pcap_activate(reinterpret_cast<pcap_t *>(_pcap)) != 0) {
|
||||
pcap_close(reinterpret_cast<pcap_t *>(_pcap));
|
||||
cpid = (long)vfork();
|
||||
if (cpid == 0) {
|
||||
::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"destroy",(const char *)0);
|
||||
::_exit(-1);
|
||||
} else if (cpid > 0) {
|
||||
int exitcode = -1;
|
||||
::waitpid(cpid,&exitcode,0);
|
||||
}
|
||||
throw std::runtime_error("pcap_activate() on new bridge device failed.");
|
||||
}
|
||||
|
||||
globalDeviceNames.insert(_dev);
|
||||
|
||||
devmap[nwids] = _dev;
|
||||
OSUtils::writeFile((_homePath + ZT_PATH_SEPARATOR_S + "devicemap").c_str(),devmap.toString());
|
||||
|
||||
_thread = Thread::start(this);
|
||||
}
|
||||
|
||||
OSXEthernetTap::~OSXEthernetTap()
|
||||
{
|
||||
_enabled = false;
|
||||
|
||||
Mutex::Lock _gl(globalTapCreateLock);
|
||||
globalDeviceNames.erase(_dev);
|
||||
|
||||
long cpid = (long)vfork();
|
||||
if (cpid == 0) {
|
||||
::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"destroy",(const char *)0);
|
||||
::_exit(-1);
|
||||
} else if (cpid > 0) {
|
||||
int exitcode = -1;
|
||||
::waitpid(cpid,&exitcode,0);
|
||||
if (exitcode == 0) {
|
||||
// Destroying the interface nukes pcap and terminates the thread.
|
||||
Thread::join(_thread);
|
||||
}
|
||||
}
|
||||
|
||||
pcap_close(reinterpret_cast<pcap_t *>(_pcap));
|
||||
}
|
||||
|
||||
static bool ___removeIp(const std::string &_dev,const InetAddress &ip)
|
||||
{
|
||||
long cpid = (long)vfork();
|
||||
if (cpid == 0) {
|
||||
execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"inet",ip.toIpString().c_str(),"-alias",(const char *)0);
|
||||
_exit(-1);
|
||||
} else if (cpid > 0) {
|
||||
int exitcode = -1;
|
||||
waitpid(cpid,&exitcode,0);
|
||||
return (exitcode == 0);
|
||||
}
|
||||
return false; // never reached, make compiler shut up about return value
|
||||
}
|
||||
|
||||
bool OSXEthernetTap::addIp(const InetAddress &ip)
|
||||
{
|
||||
if (!ip)
|
||||
return false;
|
||||
|
||||
std::vector<InetAddress> allIps(ips());
|
||||
if (std::binary_search(allIps.begin(),allIps.end(),ip))
|
||||
return true;
|
||||
|
||||
// Remove and reconfigure if address is the same but netmask is different
|
||||
for(std::vector<InetAddress>::iterator i(allIps.begin());i!=allIps.end();++i) {
|
||||
if ((i->ipsEqual(ip))&&(i->netmaskBits() != ip.netmaskBits())) {
|
||||
if (___removeIp(_dev,*i))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
long cpid = (long)vfork();
|
||||
if (cpid == 0) {
|
||||
::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),ip.isV4() ? "inet" : "inet6",ip.toString().c_str(),"alias",(const char *)0);
|
||||
::_exit(-1);
|
||||
} else if (cpid > 0) {
|
||||
int exitcode = -1;
|
||||
::waitpid(cpid,&exitcode,0);
|
||||
return (exitcode == 0);
|
||||
} // else return false...
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OSXEthernetTap::removeIp(const InetAddress &ip)
|
||||
{
|
||||
if (!ip)
|
||||
return true;
|
||||
std::vector<InetAddress> allIps(ips());
|
||||
if (!std::binary_search(allIps.begin(),allIps.end(),ip)) {
|
||||
if (___removeIp(_dev,ip))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<InetAddress> OSXEthernetTap::ips() const
|
||||
{
|
||||
struct ifaddrs *ifa = (struct ifaddrs *)0;
|
||||
if (getifaddrs(&ifa))
|
||||
return std::vector<InetAddress>();
|
||||
|
||||
std::vector<InetAddress> r;
|
||||
|
||||
struct ifaddrs *p = ifa;
|
||||
while (p) {
|
||||
if ((!strcmp(p->ifa_name,_dev.c_str()))&&(p->ifa_addr)&&(p->ifa_netmask)&&(p->ifa_addr->sa_family == p->ifa_netmask->sa_family)) {
|
||||
switch(p->ifa_addr->sa_family) {
|
||||
case AF_INET: {
|
||||
struct sockaddr_in *sin = (struct sockaddr_in *)p->ifa_addr;
|
||||
struct sockaddr_in *nm = (struct sockaddr_in *)p->ifa_netmask;
|
||||
r.push_back(InetAddress(&(sin->sin_addr.s_addr),4,Utils::countBits((uint32_t)nm->sin_addr.s_addr)));
|
||||
} break;
|
||||
case AF_INET6: {
|
||||
struct sockaddr_in6 *sin = (struct sockaddr_in6 *)p->ifa_addr;
|
||||
struct sockaddr_in6 *nm = (struct sockaddr_in6 *)p->ifa_netmask;
|
||||
uint32_t b[4];
|
||||
memcpy(b,nm->sin6_addr.s6_addr,sizeof(b));
|
||||
r.push_back(InetAddress(sin->sin6_addr.s6_addr,16,Utils::countBits(b[0]) + Utils::countBits(b[1]) + Utils::countBits(b[2]) + Utils::countBits(b[3])));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
p = p->ifa_next;
|
||||
}
|
||||
|
||||
if (ifa)
|
||||
freeifaddrs(ifa);
|
||||
|
||||
std::sort(r.begin(),r.end());
|
||||
std::unique(r.begin(),r.end());
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void OSXEthernetTap::put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len)
|
||||
{
|
||||
char putBuf[4096];
|
||||
if ((len <= _mtu)&&(_enabled)) {
|
||||
to.copyTo(putBuf,6);
|
||||
from.copyTo(putBuf + 6,6);
|
||||
*((uint16_t *)(putBuf + 12)) = htons((uint16_t)etherType);
|
||||
memcpy(putBuf + 14,data,len);
|
||||
len += 14;
|
||||
int r = pcap_inject(reinterpret_cast<pcap_t *>(_pcap),putBuf,len);
|
||||
if (r <= 0) {
|
||||
printf("%s: pcap_inject() failed\n",_dev.c_str());
|
||||
return;
|
||||
}
|
||||
printf("%s: inject %s -> %s etherType==%u len=%u r==%d\n",_dev.c_str(),from.toString().c_str(),to.toString().c_str(),etherType,len,r);
|
||||
}
|
||||
}
|
||||
|
||||
std::string OSXEthernetTap::deviceName() const
|
||||
{
|
||||
return _dev;
|
||||
}
|
||||
|
||||
void OSXEthernetTap::setFriendlyName(const char *friendlyName)
|
||||
{
|
||||
}
|
||||
|
||||
void OSXEthernetTap::scanMulticastGroups(std::vector<MulticastGroup> &added,std::vector<MulticastGroup> &removed)
|
||||
{
|
||||
std::vector<MulticastGroup> newGroups;
|
||||
|
||||
struct _intl_ifmaddrs *ifmap = (struct _intl_ifmaddrs *)0;
|
||||
if (!_intl_getifmaddrs(&ifmap)) {
|
||||
struct _intl_ifmaddrs *p = ifmap;
|
||||
while (p) {
|
||||
if (p->ifma_addr->sa_family == AF_LINK) {
|
||||
struct sockaddr_dl *in = (struct sockaddr_dl *)p->ifma_name;
|
||||
struct sockaddr_dl *la = (struct sockaddr_dl *)p->ifma_addr;
|
||||
if ((la->sdl_alen == 6)&&(in->sdl_nlen <= _dev.length())&&(!memcmp(_dev.data(),in->sdl_data,in->sdl_nlen)))
|
||||
newGroups.push_back(MulticastGroup(MAC(la->sdl_data + la->sdl_nlen,6),0));
|
||||
}
|
||||
p = p->ifma_next;
|
||||
}
|
||||
_intl_freeifmaddrs(ifmap);
|
||||
}
|
||||
|
||||
std::vector<InetAddress> allIps(ips());
|
||||
for(std::vector<InetAddress>::iterator ip(allIps.begin());ip!=allIps.end();++ip)
|
||||
newGroups.push_back(MulticastGroup::deriveMulticastGroupForAddressResolution(*ip));
|
||||
|
||||
std::sort(newGroups.begin(),newGroups.end());
|
||||
std::unique(newGroups.begin(),newGroups.end());
|
||||
|
||||
for(std::vector<MulticastGroup>::iterator m(newGroups.begin());m!=newGroups.end();++m) {
|
||||
if (!std::binary_search(_multicastGroups.begin(),_multicastGroups.end(),*m))
|
||||
added.push_back(*m);
|
||||
}
|
||||
for(std::vector<MulticastGroup>::iterator m(_multicastGroups.begin());m!=_multicastGroups.end();++m) {
|
||||
if (!std::binary_search(newGroups.begin(),newGroups.end(),*m))
|
||||
removed.push_back(*m);
|
||||
}
|
||||
|
||||
_multicastGroups.swap(newGroups);
|
||||
}
|
||||
|
||||
static void _pcapHandler(u_char *ptr,const struct pcap_pkthdr *hdr,const u_char *data)
|
||||
{
|
||||
OSXEthernetTap *tap = reinterpret_cast<OSXEthernetTap *>(ptr);
|
||||
if (hdr->caplen > 14) {
|
||||
MAC to(data,6);
|
||||
MAC from(data + 6,6);
|
||||
if (from == tap->_mac) {
|
||||
unsigned int etherType = ntohs(((const uint16_t *)data)[6]);
|
||||
printf("%s: %s -> %s etherType==%u len==%u\n",tap->_dev.c_str(),from.toString().c_str(),to.toString().c_str(),etherType,(unsigned int)hdr->caplen);
|
||||
// TODO: VLAN support
|
||||
tap->_handler(tap->_arg,tap->_nwid,from,to,etherType,0,(const void *)(data + 14),hdr->len - 14);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OSXEthernetTap::threadMain()
|
||||
throw()
|
||||
{
|
||||
pcap_loop(reinterpret_cast<pcap_t *>(_pcap),-1,&_pcapHandler,reinterpret_cast<u_char *>(this));
|
||||
}
|
||||
|
||||
} // namespace ZeroTier
|
@ -1,831 +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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* 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 <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/sys_domain.h>
|
||||
#include <sys/kern_control.h>
|
||||
#include <net/if_utun.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <net/route.h>
|
||||
#include <net/if.h>
|
||||
#include <net/if_arp.h>
|
||||
#include <net/if_dl.h>
|
||||
#include <net/if_media.h>
|
||||
#include <netinet6/in6_var.h>
|
||||
#include <netinet/in_var.h>
|
||||
#include <netinet/icmp6.h>
|
||||
|
||||
// OSX compile fix... in6_var defines this in a struct which namespaces it for C++ ... why?!?
|
||||
struct prf_ra {
|
||||
u_char onlink : 1;
|
||||
u_char autonomous : 1;
|
||||
u_char reserved : 6;
|
||||
} prf_ra;
|
||||
|
||||
#include <netinet6/nd6.h>
|
||||
#include <ifaddrs.h>
|
||||
|
||||
// These are KERNEL_PRIVATE... why?
|
||||
#ifndef SIOCAUTOCONF_START
|
||||
#define SIOCAUTOCONF_START _IOWR('i', 132, struct in6_ifreq) /* accept rtadvd on this interface */
|
||||
#endif
|
||||
#ifndef SIOCAUTOCONF_STOP
|
||||
#define SIOCAUTOCONF_STOP _IOWR('i', 133, struct in6_ifreq) /* stop accepting rtadv for this interface */
|
||||
#endif
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// --------------------------------------------------------------------------
|
||||
// This source is from:
|
||||
// http://www.opensource.apple.com/source/Libinfo/Libinfo-406.17/gen.subproj/getifmaddrs.c?txt
|
||||
// It's here because OSX 10.6 does not have this convenience function.
|
||||
|
||||
#define SALIGN (sizeof(uint32_t) - 1)
|
||||
#define SA_RLEN(sa) ((sa)->sa_len ? (((sa)->sa_len + SALIGN) & ~SALIGN) : \
|
||||
(SALIGN + 1))
|
||||
#define MAX_SYSCTL_TRY 5
|
||||
#define RTA_MASKS (RTA_GATEWAY | RTA_IFP | RTA_IFA)
|
||||
|
||||
/* FreeBSD uses NET_RT_IFMALIST and RTM_NEWMADDR from <sys/socket.h> */
|
||||
/* We can use NET_RT_IFLIST2 and RTM_NEWMADDR2 on Darwin */
|
||||
//#define DARWIN_COMPAT
|
||||
|
||||
//#ifdef DARWIN_COMPAT
|
||||
#define GIM_SYSCTL_MIB NET_RT_IFLIST2
|
||||
#define GIM_RTM_ADDR RTM_NEWMADDR2
|
||||
//#else
|
||||
//#define GIM_SYSCTL_MIB NET_RT_IFMALIST
|
||||
//#define GIM_RTM_ADDR RTM_NEWMADDR
|
||||
//#endif
|
||||
|
||||
// Not in 10.6 includes so use our own
|
||||
struct _intl_ifmaddrs {
|
||||
struct _intl_ifmaddrs *ifma_next;
|
||||
struct sockaddr *ifma_name;
|
||||
struct sockaddr *ifma_addr;
|
||||
struct sockaddr *ifma_lladdr;
|
||||
};
|
||||
|
||||
static inline int _intl_getifmaddrs(struct _intl_ifmaddrs **pif)
|
||||
{
|
||||
int icnt = 1;
|
||||
int dcnt = 0;
|
||||
int ntry = 0;
|
||||
size_t len;
|
||||
size_t needed;
|
||||
int mib[6];
|
||||
int i;
|
||||
char *buf;
|
||||
char *data;
|
||||
char *next;
|
||||
char *p;
|
||||
struct ifma_msghdr2 *ifmam;
|
||||
struct _intl_ifmaddrs *ifa, *ift;
|
||||
struct rt_msghdr *rtm;
|
||||
struct sockaddr *sa;
|
||||
|
||||
mib[0] = CTL_NET;
|
||||
mib[1] = PF_ROUTE;
|
||||
mib[2] = 0; /* protocol */
|
||||
mib[3] = 0; /* wildcard address family */
|
||||
mib[4] = GIM_SYSCTL_MIB;
|
||||
mib[5] = 0; /* no flags */
|
||||
do {
|
||||
if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0)
|
||||
return (-1);
|
||||
if ((buf = (char *)malloc(needed)) == NULL)
|
||||
return (-1);
|
||||
if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) {
|
||||
if (errno != ENOMEM || ++ntry >= MAX_SYSCTL_TRY) {
|
||||
free(buf);
|
||||
return (-1);
|
||||
}
|
||||
free(buf);
|
||||
buf = NULL;
|
||||
}
|
||||
} while (buf == NULL);
|
||||
|
||||
for (next = buf; next < buf + needed; next += rtm->rtm_msglen) {
|
||||
rtm = (struct rt_msghdr *)(void *)next;
|
||||
if (rtm->rtm_version != RTM_VERSION)
|
||||
continue;
|
||||
switch (rtm->rtm_type) {
|
||||
case GIM_RTM_ADDR:
|
||||
ifmam = (struct ifma_msghdr2 *)(void *)rtm;
|
||||
if ((ifmam->ifmam_addrs & RTA_IFA) == 0)
|
||||
break;
|
||||
icnt++;
|
||||
p = (char *)(ifmam + 1);
|
||||
for (i = 0; i < RTAX_MAX; i++) {
|
||||
if ((RTA_MASKS & ifmam->ifmam_addrs &
|
||||
(1 << i)) == 0)
|
||||
continue;
|
||||
sa = (struct sockaddr *)(void *)p;
|
||||
len = SA_RLEN(sa);
|
||||
dcnt += len;
|
||||
p += len;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
data = (char *)malloc(sizeof(struct _intl_ifmaddrs) * icnt + dcnt);
|
||||
if (data == NULL) {
|
||||
free(buf);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
ifa = (struct _intl_ifmaddrs *)(void *)data;
|
||||
data += sizeof(struct _intl_ifmaddrs) * icnt;
|
||||
|
||||
memset(ifa, 0, sizeof(struct _intl_ifmaddrs) * icnt);
|
||||
ift = ifa;
|
||||
|
||||
for (next = buf; next < buf + needed; next += rtm->rtm_msglen) {
|
||||
rtm = (struct rt_msghdr *)(void *)next;
|
||||
if (rtm->rtm_version != RTM_VERSION)
|
||||
continue;
|
||||
|
||||
switch (rtm->rtm_type) {
|
||||
case GIM_RTM_ADDR:
|
||||
ifmam = (struct ifma_msghdr2 *)(void *)rtm;
|
||||
if ((ifmam->ifmam_addrs & RTA_IFA) == 0)
|
||||
break;
|
||||
|
||||
p = (char *)(ifmam + 1);
|
||||
for (i = 0; i < RTAX_MAX; i++) {
|
||||
if ((RTA_MASKS & ifmam->ifmam_addrs &
|
||||
(1 << i)) == 0)
|
||||
continue;
|
||||
sa = (struct sockaddr *)(void *)p;
|
||||
len = SA_RLEN(sa);
|
||||
switch (i) {
|
||||
case RTAX_GATEWAY:
|
||||
ift->ifma_lladdr =
|
||||
(struct sockaddr *)(void *)data;
|
||||
memcpy(data, p, len);
|
||||
data += len;
|
||||
break;
|
||||
|
||||
case RTAX_IFP:
|
||||
ift->ifma_name =
|
||||
(struct sockaddr *)(void *)data;
|
||||
memcpy(data, p, len);
|
||||
data += len;
|
||||
break;
|
||||
|
||||
case RTAX_IFA:
|
||||
ift->ifma_addr =
|
||||
(struct sockaddr *)(void *)data;
|
||||
memcpy(data, p, len);
|
||||
data += len;
|
||||
break;
|
||||
|
||||
default:
|
||||
data += len;
|
||||
break;
|
||||
}
|
||||
p += len;
|
||||
}
|
||||
ift->ifma_next = ift + 1;
|
||||
ift = ift->ifma_next;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free(buf);
|
||||
|
||||
if (ift > ifa) {
|
||||
ift--;
|
||||
ift->ifma_next = NULL;
|
||||
*pif = ifa;
|
||||
} else {
|
||||
*pif = NULL;
|
||||
free(ifa);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
static inline void _intl_freeifmaddrs(struct _intl_ifmaddrs *ifmp)
|
||||
{
|
||||
free(ifmp);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
|
||||
#include "../node/Constants.hpp"
|
||||
#include "../node/Utils.hpp"
|
||||
#include "../node/Mutex.hpp"
|
||||
#include "../node/Dictionary.hpp"
|
||||
#include "Arp.hpp"
|
||||
#include "OSUtils.hpp"
|
||||
#include "OSXEthernetTap.hpp"
|
||||
|
||||
// ff:ff:ff:ff:ff:ff with no ADI
|
||||
static const ZeroTier::MulticastGroup _blindWildcardMulticastGroup(ZeroTier::MAC(0xff),0);
|
||||
|
||||
static inline bool _setIpv6Stuff(const char *ifname,bool performNUD,bool acceptRouterAdverts)
|
||||
{
|
||||
struct in6_ndireq nd;
|
||||
struct in6_ifreq ifr;
|
||||
|
||||
int s = socket(AF_INET6,SOCK_DGRAM,0);
|
||||
if (s <= 0)
|
||||
return false;
|
||||
|
||||
memset(&nd,0,sizeof(nd));
|
||||
strncpy(nd.ifname,ifname,sizeof(nd.ifname));
|
||||
|
||||
if (ioctl(s,SIOCGIFINFO_IN6,&nd)) {
|
||||
close(s);
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned long oldFlags = (unsigned long)nd.ndi.flags;
|
||||
|
||||
if (performNUD)
|
||||
nd.ndi.flags |= ND6_IFF_PERFORMNUD;
|
||||
else nd.ndi.flags &= ~ND6_IFF_PERFORMNUD;
|
||||
|
||||
if (oldFlags != (unsigned long)nd.ndi.flags) {
|
||||
if (ioctl(s,SIOCSIFINFO_FLAGS,&nd)) {
|
||||
close(s);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
memset(&ifr,0,sizeof(ifr));
|
||||
strncpy(ifr.ifr_name,ifname,sizeof(ifr.ifr_name));
|
||||
if (ioctl(s,acceptRouterAdverts ? SIOCAUTOCONF_START : SIOCAUTOCONF_STOP,&ifr)) {
|
||||
close(s);
|
||||
return false;
|
||||
}
|
||||
|
||||
close(s);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create an OSX-native utun device (utun# where # is desiredNumber)
|
||||
// Adapted from public domain utun example code by Jonathan Levin
|
||||
static int _make_utun(int desiredNumber)
|
||||
{
|
||||
struct sockaddr_ctl sc;
|
||||
struct ctl_info ctlInfo;
|
||||
struct ifreq ifr;
|
||||
|
||||
memset(&ctlInfo, 0, sizeof(ctlInfo));
|
||||
if (strlcpy(ctlInfo.ctl_name, UTUN_CONTROL_NAME, sizeof(ctlInfo.ctl_name)) >= sizeof(ctlInfo.ctl_name)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int fd = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL);
|
||||
if (fd == -1)
|
||||
return -1;
|
||||
if (ioctl(fd, CTLIOCGINFO, &ctlInfo) == -1) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
sc.sc_id = ctlInfo.ctl_id;
|
||||
sc.sc_len = sizeof(sc);
|
||||
sc.sc_family = AF_SYSTEM;
|
||||
sc.ss_sysaddr = AF_SYS_CONTROL;
|
||||
sc.sc_unit = desiredNumber + 1;
|
||||
|
||||
if (connect(fd, (struct sockaddr *)&sc, sizeof(sc)) == -1) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(&ifr,0,sizeof(ifr));
|
||||
sprintf(ifr.ifr_name,"utun%d",desiredNumber);
|
||||
if (ioctl(fd,SIOCGIFFLAGS,(void *)&ifr) < 0) {
|
||||
printf("SIOCGIFFLAGS failed\n");
|
||||
}
|
||||
ifr.ifr_flags &= ~IFF_POINTOPOINT;
|
||||
if (ioctl(fd,SIOCSIFFLAGS,(void *)&ifr) < 0) {
|
||||
printf("clear IFF_POINTOPOINT failed\n");
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
static long globalTapsRunning = 0;
|
||||
static Mutex globalTapCreateLock;
|
||||
|
||||
OSXEthernetTap::OSXEthernetTap(
|
||||
const char *homePath,
|
||||
const MAC &mac,
|
||||
unsigned int mtu,
|
||||
unsigned int metric,
|
||||
uint64_t nwid,
|
||||
const char *friendlyName,
|
||||
void (*handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *data,unsigned int len),
|
||||
void *arg) :
|
||||
_handler(handler),
|
||||
_arg(arg),
|
||||
_arp((Arp *)0),
|
||||
_nwid(nwid),
|
||||
_homePath(homePath),
|
||||
_mtu(mtu),
|
||||
_metric(metric),
|
||||
_fd(0),
|
||||
_utun(false),
|
||||
_enabled(true)
|
||||
{
|
||||
char devpath[64],ethaddr[64],mtustr[32],metstr[32],nwids[32];
|
||||
struct stat stattmp;
|
||||
|
||||
Utils::snprintf(nwids,sizeof(nwids),"%.16llx",nwid);
|
||||
|
||||
if (mtu > 2800)
|
||||
throw std::runtime_error("max tap MTU is 2800");
|
||||
|
||||
Mutex::Lock _gl(globalTapCreateLock);
|
||||
|
||||
// Read remembered previous device name, if any -- we'll try to reuse
|
||||
Dictionary devmap;
|
||||
std::string desiredDevice;
|
||||
{
|
||||
std::string devmapbuf;
|
||||
if (OSUtils::readFile((_homePath + ZT_PATH_SEPARATOR_S + "devicemap").c_str(),devmapbuf)) {
|
||||
devmap.fromString(devmapbuf);
|
||||
desiredDevice = devmap.get(nwids,"");
|
||||
}
|
||||
}
|
||||
|
||||
if (::stat((_homePath + ZT_PATH_SEPARATOR_S + "tap.kext").c_str(),&stattmp) == 0) {
|
||||
// Try to init kext if it's there, otherwise revert to utun mode
|
||||
|
||||
if (::stat("/dev/zt0",&stattmp)) {
|
||||
long kextpid = (long)vfork();
|
||||
if (kextpid == 0) {
|
||||
::chdir(homePath);
|
||||
OSUtils::redirectUnixOutputs("/dev/null",(const char *)0);
|
||||
::execl("/sbin/kextload","/sbin/kextload","-q","-repository",homePath,"tap.kext",(const char *)0);
|
||||
::_exit(-1);
|
||||
} else if (kextpid > 0) {
|
||||
int exitcode = -1;
|
||||
::waitpid(kextpid,&exitcode,0);
|
||||
}
|
||||
::usleep(500); // give tap device driver time to start up and try again
|
||||
if (::stat("/dev/zt0",&stattmp))
|
||||
_utun = true;
|
||||
}
|
||||
|
||||
if (!_utun) {
|
||||
// See if we can re-use the last device we had.
|
||||
bool recalledDevice = false;
|
||||
if (desiredDevice.length() > 2) {
|
||||
Utils::snprintf(devpath,sizeof(devpath),"/dev/%s",desiredDevice.c_str());
|
||||
if (stat(devpath,&stattmp) == 0) {
|
||||
_fd = ::open(devpath,O_RDWR);
|
||||
if (_fd > 0) {
|
||||
_dev = desiredDevice;
|
||||
recalledDevice = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Open the first unused tap device if we didn't recall a previous one.
|
||||
if (!recalledDevice) {
|
||||
for(int i=0;i<64;++i) {
|
||||
Utils::snprintf(devpath,sizeof(devpath),"/dev/zt%d",i);
|
||||
if (stat(devpath,&stattmp)) {
|
||||
_utun = true;
|
||||
break;
|
||||
}
|
||||
_fd = ::open(devpath,O_RDWR);
|
||||
if (_fd > 0) {
|
||||
char foo[16];
|
||||
Utils::snprintf(foo,sizeof(foo),"zt%d",i);
|
||||
_dev = foo;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_fd <= 0)
|
||||
_utun = true;
|
||||
}
|
||||
} else {
|
||||
_utun = true;
|
||||
}
|
||||
|
||||
if (_utun) {
|
||||
// Use OSX built-in utun device if kext is not available or doesn't work
|
||||
|
||||
int utunNo = 0;
|
||||
|
||||
if ((desiredDevice.length() > 4)&&(desiredDevice.substr(0,4) == "utun")) {
|
||||
utunNo = Utils::strToInt(desiredDevice.substr(4).c_str());
|
||||
if (utunNo >= 0)
|
||||
_fd = _make_utun(utunNo);
|
||||
}
|
||||
|
||||
if (_fd <= 0) {
|
||||
// Start at utun8 to leave lower utuns unused since other stuff might
|
||||
// want them -- OpenVPN, cjdns, etc. I'm not sure if those are smart
|
||||
// enough to scan upward like this.
|
||||
for(utunNo=8;utunNo<=256;++utunNo) {
|
||||
if ((_fd = _make_utun(utunNo)) > 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_fd <= 0)
|
||||
throw std::runtime_error("unable to find/load ZeroTier tap driver OR use built-in utun driver in OSX; permission or system problem or too many open devices?");
|
||||
|
||||
Utils::snprintf(devpath,sizeof(devpath),"utun%d",utunNo);
|
||||
_dev = devpath;
|
||||
|
||||
// Configure address and bring it up
|
||||
Utils::snprintf(mtustr,sizeof(mtustr),"%u",_mtu);
|
||||
Utils::snprintf(metstr,sizeof(metstr),"%u",_metric);
|
||||
long cpid = (long)vfork();
|
||||
if (cpid == 0) {
|
||||
::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"mtu",mtustr,"metric",metstr,"up",(const char *)0);
|
||||
::_exit(-1);
|
||||
} else if (cpid > 0) {
|
||||
int exitcode = -1;
|
||||
::waitpid(cpid,&exitcode,0);
|
||||
if (exitcode) {
|
||||
::close(_fd);
|
||||
throw std::runtime_error("ifconfig failure activating utun interface");
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Use our ZeroTier OSX tun/tap driver for zt# Ethernet tap device
|
||||
|
||||
if (fcntl(_fd,F_SETFL,fcntl(_fd,F_GETFL) & ~O_NONBLOCK) == -1) {
|
||||
::close(_fd);
|
||||
throw std::runtime_error("unable to set flags on file descriptor for TAP device");
|
||||
}
|
||||
|
||||
// Configure MAC address and MTU, bring interface up
|
||||
Utils::snprintf(ethaddr,sizeof(ethaddr),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(int)mac[0],(int)mac[1],(int)mac[2],(int)mac[3],(int)mac[4],(int)mac[5]);
|
||||
Utils::snprintf(mtustr,sizeof(mtustr),"%u",_mtu);
|
||||
Utils::snprintf(metstr,sizeof(metstr),"%u",_metric);
|
||||
long cpid = (long)vfork();
|
||||
if (cpid == 0) {
|
||||
::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"lladdr",ethaddr,"mtu",mtustr,"metric",metstr,"up",(const char *)0);
|
||||
::_exit(-1);
|
||||
} else if (cpid > 0) {
|
||||
int exitcode = -1;
|
||||
::waitpid(cpid,&exitcode,0);
|
||||
if (exitcode) {
|
||||
::close(_fd);
|
||||
throw std::runtime_error("ifconfig failure setting link-layer address and activating tap interface");
|
||||
}
|
||||
}
|
||||
|
||||
_setIpv6Stuff(_dev.c_str(),true,false);
|
||||
}
|
||||
|
||||
// Set close-on-exec so that devices cannot persist if we fork/exec for update
|
||||
fcntl(_fd,F_SETFD,fcntl(_fd,F_GETFD) | FD_CLOEXEC);
|
||||
|
||||
::pipe(_shutdownSignalPipe);
|
||||
|
||||
++globalTapsRunning;
|
||||
|
||||
devmap[nwids] = _dev;
|
||||
OSUtils::writeFile((_homePath + ZT_PATH_SEPARATOR_S + "devicemap").c_str(),devmap.toString());
|
||||
|
||||
_thread = Thread::start(this);
|
||||
}
|
||||
|
||||
OSXEthernetTap::~OSXEthernetTap()
|
||||
{
|
||||
Mutex::Lock _gl(globalTapCreateLock);
|
||||
|
||||
::write(_shutdownSignalPipe[1],(const void *)this,1); // writing a byte causes thread to exit
|
||||
Thread::join(_thread);
|
||||
|
||||
::close(_fd);
|
||||
::close(_shutdownSignalPipe[0]);
|
||||
::close(_shutdownSignalPipe[1]);
|
||||
|
||||
if (_utun) {
|
||||
delete _arp;
|
||||
} else {
|
||||
if (--globalTapsRunning <= 0) {
|
||||
globalTapsRunning = 0; // sanity check -- should not be possible
|
||||
|
||||
char tmp[16384];
|
||||
sprintf(tmp,"%s/%s",_homePath.c_str(),"tap.kext");
|
||||
long kextpid = (long)vfork();
|
||||
if (kextpid == 0) {
|
||||
OSUtils::redirectUnixOutputs("/dev/null",(const char *)0);
|
||||
::execl("/sbin/kextunload","/sbin/kextunload",tmp,(const char *)0);
|
||||
::_exit(-1);
|
||||
} else if (kextpid > 0) {
|
||||
int exitcode = -1;
|
||||
::waitpid(kextpid,&exitcode,0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OSXEthernetTap::setEnabled(bool en)
|
||||
{
|
||||
_enabled = en;
|
||||
// TODO: interface status change
|
||||
}
|
||||
|
||||
bool OSXEthernetTap::enabled() const
|
||||
{
|
||||
return _enabled;
|
||||
}
|
||||
|
||||
static bool ___removeIp(const std::string &_dev,const InetAddress &ip)
|
||||
{
|
||||
long cpid = (long)vfork();
|
||||
if (cpid == 0) {
|
||||
execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"inet",ip.toIpString().c_str(),"-alias",(const char *)0);
|
||||
_exit(-1);
|
||||
} else if (cpid > 0) {
|
||||
int exitcode = -1;
|
||||
waitpid(cpid,&exitcode,0);
|
||||
return (exitcode == 0);
|
||||
}
|
||||
return false; // never reached, make compiler shut up about return value
|
||||
}
|
||||
|
||||
bool OSXEthernetTap::addIp(const InetAddress &ip)
|
||||
{
|
||||
if (!ip)
|
||||
return false;
|
||||
|
||||
std::vector<InetAddress> allIps(ips());
|
||||
if (std::binary_search(allIps.begin(),allIps.end(),ip))
|
||||
return true;
|
||||
|
||||
// Remove and reconfigure if address is the same but netmask is different
|
||||
for(std::vector<InetAddress>::iterator i(allIps.begin());i!=allIps.end();++i) {
|
||||
if ((i->ipsEqual(ip))&&(i->netmaskBits() != ip.netmaskBits())) {
|
||||
if (___removeIp(_dev,*i))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_utun) {
|
||||
long cpid = (long)vfork();
|
||||
if (cpid == 0) {
|
||||
if (ip.ss_family == AF_INET6) {
|
||||
::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"inet6",ip.toString().c_str(),"alias",(const char *)0);
|
||||
} else {
|
||||
::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),ip.toString().c_str(),ip.toIpString().c_str(),"alias",(const char *)0);
|
||||
}
|
||||
::_exit(-1);
|
||||
} else if (cpid > 0) {
|
||||
int exitcode = -1;
|
||||
::waitpid(cpid,&exitcode,0);
|
||||
|
||||
if (exitcode == 0) {
|
||||
if (ip.ss_family == AF_INET) {
|
||||
// Add route to network over tun for IPv4 -- otherwise it behaves
|
||||
// as a simple point to point tunnel instead of a true route.
|
||||
cpid = (long)vfork();
|
||||
if (cpid == 0) {
|
||||
::close(STDERR_FILENO);
|
||||
::close(STDOUT_FILENO);
|
||||
::execl("/sbin/route","/sbin/route","add",ip.network().toString().c_str(),ip.toIpString().c_str(),(const char *)0);
|
||||
::exit(-1);
|
||||
} else if (cpid > 0) {
|
||||
int exitcode = -1;
|
||||
::waitpid(cpid,&exitcode,0);
|
||||
return (exitcode == 0);
|
||||
}
|
||||
} else return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
long cpid = (long)vfork();
|
||||
if (cpid == 0) {
|
||||
::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),ip.isV4() ? "inet" : "inet6",ip.toString().c_str(),"alias",(const char *)0);
|
||||
::_exit(-1);
|
||||
} else if (cpid > 0) {
|
||||
int exitcode = -1;
|
||||
::waitpid(cpid,&exitcode,0);
|
||||
return (exitcode == 0);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OSXEthernetTap::removeIp(const InetAddress &ip)
|
||||
{
|
||||
if (!ip)
|
||||
return true;
|
||||
std::vector<InetAddress> allIps(ips());
|
||||
if (!std::binary_search(allIps.begin(),allIps.end(),ip)) {
|
||||
if (___removeIp(_dev,ip))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<InetAddress> OSXEthernetTap::ips() const
|
||||
{
|
||||
struct ifaddrs *ifa = (struct ifaddrs *)0;
|
||||
if (getifaddrs(&ifa))
|
||||
return std::vector<InetAddress>();
|
||||
|
||||
std::vector<InetAddress> r;
|
||||
|
||||
struct ifaddrs *p = ifa;
|
||||
while (p) {
|
||||
if ((!strcmp(p->ifa_name,_dev.c_str()))&&(p->ifa_addr)&&(p->ifa_netmask)&&(p->ifa_addr->sa_family == p->ifa_netmask->sa_family)) {
|
||||
switch(p->ifa_addr->sa_family) {
|
||||
case AF_INET: {
|
||||
struct sockaddr_in *sin = (struct sockaddr_in *)p->ifa_addr;
|
||||
struct sockaddr_in *nm = (struct sockaddr_in *)p->ifa_netmask;
|
||||
r.push_back(InetAddress(&(sin->sin_addr.s_addr),4,Utils::countBits((uint32_t)nm->sin_addr.s_addr)));
|
||||
} break;
|
||||
case AF_INET6: {
|
||||
struct sockaddr_in6 *sin = (struct sockaddr_in6 *)p->ifa_addr;
|
||||
struct sockaddr_in6 *nm = (struct sockaddr_in6 *)p->ifa_netmask;
|
||||
uint32_t b[4];
|
||||
memcpy(b,nm->sin6_addr.s6_addr,sizeof(b));
|
||||
r.push_back(InetAddress(sin->sin6_addr.s6_addr,16,Utils::countBits(b[0]) + Utils::countBits(b[1]) + Utils::countBits(b[2]) + Utils::countBits(b[3])));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
p = p->ifa_next;
|
||||
}
|
||||
|
||||
if (ifa)
|
||||
freeifaddrs(ifa);
|
||||
|
||||
std::sort(r.begin(),r.end());
|
||||
std::unique(r.begin(),r.end());
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void OSXEthernetTap::put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len)
|
||||
{
|
||||
char putBuf[4096];
|
||||
if ((_fd > 0)&&(len <= _mtu)&&(_enabled)) {
|
||||
to.copyTo(putBuf,6);
|
||||
from.copyTo(putBuf + 6,6);
|
||||
*((uint16_t *)(putBuf + 12)) = htons((uint16_t)etherType);
|
||||
memcpy(putBuf + 14,data,len);
|
||||
len += 14;
|
||||
::write(_fd,putBuf,len);
|
||||
}
|
||||
}
|
||||
|
||||
std::string OSXEthernetTap::deviceName() const
|
||||
{
|
||||
return _dev;
|
||||
}
|
||||
|
||||
void OSXEthernetTap::setFriendlyName(const char *friendlyName)
|
||||
{
|
||||
}
|
||||
|
||||
void OSXEthernetTap::scanMulticastGroups(std::vector<MulticastGroup> &added,std::vector<MulticastGroup> &removed)
|
||||
{
|
||||
std::vector<MulticastGroup> newGroups;
|
||||
|
||||
struct _intl_ifmaddrs *ifmap = (struct _intl_ifmaddrs *)0;
|
||||
if (!_intl_getifmaddrs(&ifmap)) {
|
||||
struct _intl_ifmaddrs *p = ifmap;
|
||||
while (p) {
|
||||
if (p->ifma_addr->sa_family == AF_LINK) {
|
||||
struct sockaddr_dl *in = (struct sockaddr_dl *)p->ifma_name;
|
||||
struct sockaddr_dl *la = (struct sockaddr_dl *)p->ifma_addr;
|
||||
if ((la->sdl_alen == 6)&&(in->sdl_nlen <= _dev.length())&&(!memcmp(_dev.data(),in->sdl_data,in->sdl_nlen)))
|
||||
newGroups.push_back(MulticastGroup(MAC(la->sdl_data + la->sdl_nlen,6),0));
|
||||
}
|
||||
p = p->ifma_next;
|
||||
}
|
||||
_intl_freeifmaddrs(ifmap);
|
||||
}
|
||||
|
||||
std::vector<InetAddress> allIps(ips());
|
||||
for(std::vector<InetAddress>::iterator ip(allIps.begin());ip!=allIps.end();++ip)
|
||||
newGroups.push_back(MulticastGroup::deriveMulticastGroupForAddressResolution(*ip));
|
||||
|
||||
std::sort(newGroups.begin(),newGroups.end());
|
||||
std::unique(newGroups.begin(),newGroups.end());
|
||||
|
||||
for(std::vector<MulticastGroup>::iterator m(newGroups.begin());m!=newGroups.end();++m) {
|
||||
if (!std::binary_search(_multicastGroups.begin(),_multicastGroups.end(),*m))
|
||||
added.push_back(*m);
|
||||
}
|
||||
for(std::vector<MulticastGroup>::iterator m(_multicastGroups.begin());m!=_multicastGroups.end();++m) {
|
||||
if (!std::binary_search(newGroups.begin(),newGroups.end(),*m))
|
||||
removed.push_back(*m);
|
||||
}
|
||||
|
||||
_multicastGroups.swap(newGroups);
|
||||
}
|
||||
|
||||
void OSXEthernetTap::threadMain()
|
||||
throw()
|
||||
{
|
||||
fd_set readfds,nullfds;
|
||||
MAC to,from;
|
||||
int n,nfds,r;
|
||||
char getBuf[8194];
|
||||
|
||||
Thread::sleep(500);
|
||||
|
||||
FD_ZERO(&readfds);
|
||||
FD_ZERO(&nullfds);
|
||||
nfds = (int)std::max(_shutdownSignalPipe[0],_fd) + 1;
|
||||
|
||||
r = 0;
|
||||
for(;;) {
|
||||
FD_SET(_shutdownSignalPipe[0],&readfds);
|
||||
FD_SET(_fd,&readfds);
|
||||
select(nfds,&readfds,&nullfds,&nullfds,(struct timeval *)0);
|
||||
|
||||
if (FD_ISSET(_shutdownSignalPipe[0],&readfds)) // writes to shutdown pipe terminate thread
|
||||
break;
|
||||
|
||||
if (FD_ISSET(_fd,&readfds)) {
|
||||
n = (int)::read(_fd,getBuf + r,sizeof(getBuf) - r);
|
||||
if (n < 0) {
|
||||
if ((errno != EINTR)&&(errno != ETIMEDOUT))
|
||||
break;
|
||||
} else {
|
||||
// Some tap drivers like to send the ethernet frame and the
|
||||
// payload in two chunks, so handle that by accumulating
|
||||
// data until we have at least a frame.
|
||||
r += n;
|
||||
if (r > 14) {
|
||||
if (r > ((int)_mtu + 14)) // sanity check for weird TAP behavior on some platforms
|
||||
r = _mtu + 14;
|
||||
|
||||
if (_enabled) {
|
||||
to.setTo(getBuf,6);
|
||||
from.setTo(getBuf + 6,6);
|
||||
unsigned int etherType = ntohs(((const uint16_t *)getBuf)[6]);
|
||||
// TODO: VLAN support
|
||||
_handler(_arg,_nwid,from,to,etherType,0,(const void *)(getBuf + 14),r - 14);
|
||||
}
|
||||
|
||||
r = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ZeroTier
|
@ -1,96 +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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* ZeroTier may be used and distributed under the terms of the GPLv3, which
|
||||
* are available at: http://www.gnu.org/licenses/gpl-3.0.html
|
||||
*
|
||||
* If you would like to embed ZeroTier into a commercial application or
|
||||
* redistribute it in a modified binary form, please contact ZeroTier Networks
|
||||
* LLC. Start here: http://www.zerotier.com/
|
||||
*/
|
||||
|
||||
#ifndef ZT_OSXETHERNETTAP_HPP
|
||||
#define ZT_OSXETHERNETTAP_HPP
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../node/Constants.hpp"
|
||||
#include "../node/MAC.hpp"
|
||||
#include "../node/InetAddress.hpp"
|
||||
#include "../node/MulticastGroup.hpp"
|
||||
|
||||
#include "Thread.hpp"
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
/**
|
||||
* OSX Ethernet tap using ZeroTier kernel extension zt# devices
|
||||
*/
|
||||
class OSXEthernetTap
|
||||
{
|
||||
public:
|
||||
OSXEthernetTap(
|
||||
const char *homePath,
|
||||
const MAC &mac,
|
||||
unsigned int mtu,
|
||||
unsigned int metric,
|
||||
uint64_t nwid,
|
||||
const char *friendlyName,
|
||||
void (*handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int),
|
||||
void *arg);
|
||||
|
||||
~OSXEthernetTap();
|
||||
|
||||
inline void setEnabled(bool en) { _enabled = en; }
|
||||
inline bool enabled() const { return _enabled; }
|
||||
bool addIp(const InetAddress &ip);
|
||||
bool removeIp(const InetAddress &ip);
|
||||
std::vector<InetAddress> ips() const;
|
||||
void put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len);
|
||||
std::string deviceName() const;
|
||||
void setFriendlyName(const char *friendlyName);
|
||||
void scanMulticastGroups(std::vector<MulticastGroup> &added,std::vector<MulticastGroup> &removed);
|
||||
|
||||
void threadMain()
|
||||
throw();
|
||||
|
||||
// Private members of OSXEthernetTap have public visibility to be accessable
|
||||
// from an internal bounce function; don't modify directly.
|
||||
void (*_handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int);
|
||||
void *_arg;
|
||||
void *_pcap; // pcap_t *
|
||||
uint64_t _nwid;
|
||||
MAC _mac;
|
||||
Thread _thread;
|
||||
std::string _homePath;
|
||||
std::string _dev;
|
||||
std::vector<MulticastGroup> _multicastGroups;
|
||||
unsigned int _mtu;
|
||||
unsigned int _metric;
|
||||
volatile bool _enabled;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
@ -1,101 +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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* ZeroTier may be used and distributed under the terms of the GPLv3, which
|
||||
* are available at: http://www.gnu.org/licenses/gpl-3.0.html
|
||||
*
|
||||
* If you would like to embed ZeroTier into a commercial application or
|
||||
* redistribute it in a modified binary form, please contact ZeroTier Networks
|
||||
* LLC. Start here: http://www.zerotier.com/
|
||||
*/
|
||||
|
||||
#ifndef ZT_OSXETHERNETTAP_HPP
|
||||
#define ZT_OSXETHERNETTAP_HPP
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../node/Constants.hpp"
|
||||
#include "../node/MAC.hpp"
|
||||
#include "../node/InetAddress.hpp"
|
||||
#include "../node/MulticastGroup.hpp"
|
||||
|
||||
#include "Thread.hpp"
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
class Arp;
|
||||
|
||||
/**
|
||||
* OSX Ethernet tap supporting either ZeroTier tun/tap kext or OSX-native utun
|
||||
*/
|
||||
class OSXEthernetTap
|
||||
{
|
||||
public:
|
||||
OSXEthernetTap(
|
||||
const char *homePath,
|
||||
const MAC &mac,
|
||||
unsigned int mtu,
|
||||
unsigned int metric,
|
||||
uint64_t nwid,
|
||||
const char *friendlyName,
|
||||
void (*handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int),
|
||||
void *arg);
|
||||
|
||||
~OSXEthernetTap();
|
||||
|
||||
void setEnabled(bool en);
|
||||
bool enabled() const;
|
||||
bool addIp(const InetAddress &ip);
|
||||
bool removeIp(const InetAddress &ip);
|
||||
std::vector<InetAddress> ips() const;
|
||||
void put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len);
|
||||
std::string deviceName() const;
|
||||
void setFriendlyName(const char *friendlyName);
|
||||
void scanMulticastGroups(std::vector<MulticastGroup> &added,std::vector<MulticastGroup> &removed);
|
||||
|
||||
inline bool isNativeUtun() const { return _utun; }
|
||||
|
||||
void threadMain()
|
||||
throw();
|
||||
|
||||
private:
|
||||
void (*_handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int);
|
||||
void *_arg;
|
||||
Arp *_arp; // created and used if utun is enabled
|
||||
uint64_t _nwid;
|
||||
Thread _thread;
|
||||
std::string _homePath;
|
||||
std::string _dev;
|
||||
std::vector<MulticastGroup> _multicastGroups;
|
||||
unsigned int _mtu;
|
||||
unsigned int _metric;
|
||||
int _fd;
|
||||
int _shutdownSignalPipe[2];
|
||||
bool _utun;
|
||||
volatile bool _enabled;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
@ -1,4 +0,0 @@
|
||||
Retired Code and Miscellaneous Junk
|
||||
======
|
||||
|
||||
This directory is for old code that isn't used but we don't want to lose track of, and for anything else random like debug scripts.
|
@ -1,18 +0,0 @@
|
||||
{
|
||||
"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/"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
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"]
|
@ -1,150 +0,0 @@
|
||||
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/<nwid>.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 `<nwid>.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 <nwid>` 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.
|
@ -1,23 +0,0 @@
|
||||
#!/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
|
@ -1,8 +0,0 @@
|
||||
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);
|
@ -1,25 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
/* This is a utility to convert latitude/longitude into X,Y,Z coordinates as used by clustering. */
|
||||
|
||||
if (process.argv.length !== 4) {
|
||||
console.log('Usage: node lat_lon_to_xyz.js <latitude> <longitude');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var lat = parseFloat(process.argv[2])||0.0;
|
||||
var lon = parseFloat(process.argv[3])||0.0;
|
||||
|
||||
var latRadians = lat * 0.01745329251994; // PI / 180
|
||||
var lonRadians = lon * 0.01745329251994; // PI / 180
|
||||
var cosLat = Math.cos(latRadians);
|
||||
|
||||
console.log({
|
||||
lat: lat,
|
||||
lon: lon,
|
||||
x: Math.round((-6371.0) * cosLat * Math.cos(lonRadians)),
|
||||
y: Math.round(6371.0 * Math.sin(latRadians)),
|
||||
z: Math.round(6371.0 * cosLat * Math.sin(lonRadians))
|
||||
});
|
||||
|
||||
process.exit(0);
|
@ -1 +1,3 @@
|
||||
c++ -I.. -o mkworld ../node/C25519.cpp ../node/Salsa20.cpp ../node/SHA512.cpp ../node/Identity.cpp ../node/Utils.cpp ../node/InetAddress.cpp ../osdep/OSUtils.cpp mkworld.cpp
|
||||
#!/bin/bash
|
||||
|
||||
c++ -std=c++11 -I../.. -I.. -O -o mkworld ../../node/C25519.cpp ../../node/Salsa20.cpp ../../node/SHA512.cpp ../../node/Identity.cpp ../../node/Utils.cpp ../../node/InetAddress.cpp ../../osdep/OSUtils.cpp mkworld.cpp -lm
|
||||
|
@ -61,11 +61,11 @@ int main(int argc,char **argv)
|
||||
current = previous;
|
||||
OSUtils::writeFile("previous.c25519",previous);
|
||||
OSUtils::writeFile("current.c25519",current);
|
||||
fprintf(stderr,"INFO: created initial world keys: previous.c25519 and current.c25519 (both initially the same)"ZT_EOL_S);
|
||||
fprintf(stderr,"INFO: created initial world keys: previous.c25519 and current.c25519 (both initially the same)" ZT_EOL_S);
|
||||
}
|
||||
|
||||
if ((previous.length() != (ZT_C25519_PUBLIC_KEY_LEN + ZT_C25519_PRIVATE_KEY_LEN))||(current.length() != (ZT_C25519_PUBLIC_KEY_LEN + ZT_C25519_PRIVATE_KEY_LEN))) {
|
||||
fprintf(stderr,"FATAL: previous.c25519 or current.c25519 empty or invalid"ZT_EOL_S);
|
||||
fprintf(stderr,"FATAL: previous.c25519 or current.c25519 empty or invalid" ZT_EOL_S);
|
||||
return 1;
|
||||
}
|
||||
C25519::Pair previousKP;
|
||||
@ -81,7 +81,12 @@ int main(int argc,char **argv)
|
||||
std::vector<World::Root> roots;
|
||||
|
||||
const uint64_t id = ZT_WORLD_ID_EARTH;
|
||||
const uint64_t ts = 1532555817048ULL; // July 25th, 2018
|
||||
const uint64_t ts = 1562631342273ULL; // July 8th, 2019
|
||||
|
||||
roots.push_back(World::Root());
|
||||
roots.back().identity = Identity("3a46f1bf30:0:76e66fab33e28549a62ee2064d1843273c2c300ba45c3f20bef02dbad225723bb59a9bb4b13535730961aeecf5a163ace477cceb0727025b99ac14a5166a09a3");
|
||||
roots.back().stableEndpoints.push_back(InetAddress("185.180.13.82/9993"));
|
||||
roots.back().stableEndpoints.push_back(InetAddress("2a02:6ea0:c815::/9993"));
|
||||
|
||||
// Alice
|
||||
roots.push_back(World::Root());
|
||||
@ -118,7 +123,7 @@ int main(int argc,char **argv)
|
||||
// END WORLD DEFINITION
|
||||
// =========================================================================
|
||||
|
||||
fprintf(stderr,"INFO: generating and signing id==%llu ts==%llu"ZT_EOL_S,(unsigned long long)id,(unsigned long long)ts);
|
||||
fprintf(stderr,"INFO: generating and signing id==%llu ts==%llu" ZT_EOL_S,(unsigned long long)id,(unsigned long long)ts);
|
||||
|
||||
World nw = World::make(World::TYPE_PLANET,id,ts,currentKP.pub,roots,previousKP);
|
||||
|
||||
@ -127,15 +132,15 @@ int main(int argc,char **argv)
|
||||
World testw;
|
||||
testw.deserialize(outtmp,0);
|
||||
if (testw != nw) {
|
||||
fprintf(stderr,"FATAL: serialization test failed!"ZT_EOL_S);
|
||||
fprintf(stderr,"FATAL: serialization test failed!" ZT_EOL_S);
|
||||
return 1;
|
||||
}
|
||||
|
||||
OSUtils::writeFile("world.bin",std::string((const char *)outtmp.data(),outtmp.size()));
|
||||
fprintf(stderr,"INFO: world.bin written with %u bytes of binary world data."ZT_EOL_S,outtmp.size());
|
||||
fprintf(stderr,"INFO: world.bin written with %u bytes of binary world data." ZT_EOL_S,outtmp.size());
|
||||
|
||||
fprintf(stdout,ZT_EOL_S);
|
||||
fprintf(stdout,"#define ZT_DEFAULT_WORLD_LENGTH %u"ZT_EOL_S,outtmp.size());
|
||||
fprintf(stdout,"#define ZT_DEFAULT_WORLD_LENGTH %u" ZT_EOL_S,outtmp.size());
|
||||
fprintf(stdout,"static const unsigned char ZT_DEFAULT_WORLD[ZT_DEFAULT_WORLD_LENGTH] = {");
|
||||
for(unsigned int i=0;i<outtmp.size();++i) {
|
||||
const unsigned char *d = (const unsigned char *)outtmp.data();
|
||||
@ -143,7 +148,7 @@ int main(int argc,char **argv)
|
||||
fprintf(stdout,",");
|
||||
fprintf(stdout,"0x%.2x",(unsigned int)d[i]);
|
||||
}
|
||||
fprintf(stdout,"};"ZT_EOL_S);
|
||||
fprintf(stdout,"};" ZT_EOL_S);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,3 +1,3 @@
|
||||
|
||||
#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,0x64,0xd3,0x71,0xf0,0x58,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,0xbf,0xfd,0xd5,0x32,0xf7,0x15,0x6e,0x02,0x6f,0xb9,0x01,0x0d,0xb5,0x7b,0x04,0xd8,0x3a,0xc5,0x17,0x39,0x04,0x36,0xfd,0x9d,0xc6,0x3d,0xa8,0xf3,0x8e,0x79,0xe7,0xc8,0x77,0x8d,0xcc,0x79,0xb8,0xab,0xc6,0x98,0x7c,0x9f,0x34,0x25,0x14,0xe1,0x2f,0xd7,0x97,0x11,0xec,0x34,0x4c,0x9f,0x0f,0xb4,0x85,0x0d,0x9b,0x11,0xd1,0xc2,0xce,0x00,0xc4,0x0a,0x13,0x4b,0xcb,0xc3,0xae,0x2e,0x16,0x00,0x4b,0xdc,0x90,0x5e,0x7e,0x9b,0x44,0x07,0x15,0x36,0x61,0x3c,0x64,0xaa,0xe9,0x46,0x78,0x3c,0xa7,0x18,0xc8,0xd8,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,0x7d,0x00,0x01,0x00,0x00,0x00,0x00,0x27,0x09,0x04,0x9a,0x42,0xc5,0x21,0x27,0x09,0x06,0x2c,0x0f,0xf8,0x50,0x01,0x54,0x01,0x97,0x00,0x33,0xcc,0x08,0xf8,0xfa,0xcc,0x08,0x27,0x09,0x04,0x9f,0xcb,0x61,0xab,0x27,0x09,0x06,0x26,0x04,0xa8,0x80,0x08,0x00,0x00,0xa1,0x00,0x54,0x60,0x01,0x00,0xfc,0xcc,0x08,0x27,0x09,0x04,0x83,0xff,0x06,0x10,0x27,0x09,0x06,0x28,0x03,0xeb,0x80,0x00,0x00,0x00,0x0e,0x00,0x02,0x60,0x01,0x00,0xfc,0xcc,0x08,0x27,0x09,0x04,0x6b,0xaa,0xc5,0x0e,0x27,0x09,0x06,0x26,0x04,0xa8,0x80,0x00,0x01,0x00,0x20,0x02,0x00,0xe0,0x01,0x08,0xfe,0xcc,0x08,0x27,0x09,0x04,0x80,0xc7,0xc5,0xd9,0x27,0x09,0x06,0x24,0x00,0x61,0x80,0x00,0x00,0x00,0xd0,0x00,0xb7,0x40,0x01,0x08,0xfe,0xcc,0x08,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,0x6a,0x30,0x01,0x78,0x00,0xcd,0x08,0x27,0x09,0x04,0x6b,0xbf,0x2e,0xd2,0x27,0x09,0x06,0x20,0x01,0x19,0xf0,0x68,0x00,0x83,0xa4,0x00,0x64,0xcd,0x08,0x80,0x01,0xcd,0x08,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,0x26,0x70,0x01,0xfe,0x15,0xc4,0xf5,0x27,0x09};
|
||||
#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,0x6b,0xd4,0x16,0x08,0xc1,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,0x16,0x93,0xf4,0xe5,0xbd,0x20,0xda,0x10,0xad,0xc7,0x05,0xf4,0x99,0xfe,0x04,0x08,0x9b,0xe0,0x9e,0x77,0x1d,0x9f,0x47,0x16,0xaa,0x92,0x4f,0x10,0x16,0x3d,0xc7,0xec,0xd3,0x90,0x9e,0xd1,0x74,0xfc,0xb3,0xb5,0x07,0x9c,0x4d,0x95,0xc5,0x17,0x8b,0x3d,0x0b,0x60,0x76,0xe8,0x51,0xbb,0xb6,0x3d,0x74,0xb5,0x21,0x83,0x7b,0x95,0x1d,0x02,0x9b,0xcd,0xaf,0x5c,0x3e,0x96,0xdf,0x37,0x2c,0x56,0x6d,0xfa,0x75,0x0f,0xda,0x55,0x85,0x13,0xf4,0x76,0x1a,0x66,0x4d,0x3b,0x8d,0xcf,0x12,0xc9,0x34,0xb9,0x0d,0x61,0x03,0x3a,0x46,0xf1,0xbf,0x30,0x00,0x76,0xe6,0x6f,0xab,0x33,0xe2,0x85,0x49,0xa6,0x2e,0xe2,0x06,0x4d,0x18,0x43,0x27,0x3c,0x2c,0x30,0x0b,0xa4,0x5c,0x3f,0x20,0xbe,0xf0,0x2d,0xba,0xd2,0x25,0x72,0x3b,0xb5,0x9a,0x9b,0xb4,0xb1,0x35,0x35,0x73,0x09,0x61,0xae,0xec,0xf5,0xa1,0x63,0xac,0xe4,0x77,0xcc,0xeb,0x07,0x27,0x02,0x5b,0x99,0xac,0x14,0xa5,0x16,0x6a,0x09,0xa3,0x00,0x02,0x04,0xb9,0xb4,0x0d,0x52,0x27,0x09,0x06,0x2a,0x02,0x6e,0xa0,0xc8,0x15,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,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,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,0x83,0xff,0x06,0x10,0x27,0x09,0x06,0x28,0x03,0xeb,0x80,0x00,0x00,0x00,0x0e,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};
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2018 ZeroTier, Inc.
|
||||
* Copyright (C) 2011-2019 ZeroTier, Inc. https://www.zerotier.com/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -13,7 +13,15 @@
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* You can be released from the requirements of the license by purchasing
|
||||
* a commercial license. Buying such a license is mandatory as soon as you
|
||||
* develop commercial closed-source software that incorporates or links
|
||||
* directly against ZeroTier software without disclosing the source code
|
||||
* of your own application.
|
||||
*/
|
||||
|
||||
#include "DB.hpp"
|
||||
@ -96,8 +104,7 @@ void DB::cleanMember(nlohmann::json &member)
|
||||
member.erase("lastRequestMetaData");
|
||||
}
|
||||
|
||||
DB::DB(EmbeddedNetworkController *const nc,const Identity &myId,const char *path) :
|
||||
_controller(nc),
|
||||
DB::DB(const Identity &myId,const char *path) :
|
||||
_myId(myId),
|
||||
_myAddress(myId.address()),
|
||||
_path((path) ? path : "")
|
||||
@ -107,9 +114,7 @@ DB::DB(EmbeddedNetworkController *const nc,const Identity &myId,const char *path
|
||||
_myAddressStr = tmp;
|
||||
}
|
||||
|
||||
DB::~DB()
|
||||
{
|
||||
}
|
||||
DB::~DB() {}
|
||||
|
||||
bool DB::get(const uint64_t networkId,nlohmann::json &network)
|
||||
{
|
||||
@ -221,7 +226,7 @@ void DB::networks(std::vector<uint64_t> &networks)
|
||||
networks.push_back(n->first);
|
||||
}
|
||||
|
||||
void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool push)
|
||||
void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool initialized)
|
||||
{
|
||||
uint64_t memberId = 0;
|
||||
uint64_t networkId = 0;
|
||||
@ -305,8 +310,12 @@ void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool pu
|
||||
}
|
||||
}
|
||||
|
||||
if (push)
|
||||
_controller->onNetworkMemberUpdate(networkId,memberId);
|
||||
if (initialized) {
|
||||
std::lock_guard<std::mutex> ll(_changeListeners_l);
|
||||
for(auto i=_changeListeners.begin();i!=_changeListeners.end();++i) {
|
||||
(*i)->onNetworkMemberUpdate(networkId,memberId,memberConfig);
|
||||
}
|
||||
}
|
||||
} else if (memberId) {
|
||||
if (nw) {
|
||||
std::lock_guard<std::mutex> l(nw->lock);
|
||||
@ -324,20 +333,24 @@ void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool pu
|
||||
}
|
||||
}
|
||||
|
||||
if ((push)&&((wasAuth)&&(!isAuth)&&(networkId)&&(memberId)))
|
||||
_controller->onNetworkMemberDeauthorize(networkId,memberId);
|
||||
if ((initialized)&&((wasAuth)&&(!isAuth)&&(networkId)&&(memberId))) {
|
||||
std::lock_guard<std::mutex> ll(_changeListeners_l);
|
||||
for(auto i=_changeListeners.begin();i!=_changeListeners.end();++i) {
|
||||
(*i)->onNetworkMemberDeauthorize(networkId,memberId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DB::_networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool push)
|
||||
void DB::_networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool initialized)
|
||||
{
|
||||
if (networkConfig.is_object()) {
|
||||
const std::string ids = networkConfig["id"];
|
||||
const uint64_t id = Utils::hexStrToU64(ids.c_str());
|
||||
if (id) {
|
||||
const uint64_t networkId = Utils::hexStrToU64(ids.c_str());
|
||||
if (networkId) {
|
||||
std::shared_ptr<_Network> nw;
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_networks_l);
|
||||
std::shared_ptr<_Network> &nw2 = _networks[id];
|
||||
std::shared_ptr<_Network> &nw2 = _networks[networkId];
|
||||
if (!nw2)
|
||||
nw2.reset(new _Network);
|
||||
nw = nw2;
|
||||
@ -346,15 +359,19 @@ void DB::_networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool
|
||||
std::lock_guard<std::mutex> l2(nw->lock);
|
||||
nw->config = networkConfig;
|
||||
}
|
||||
if (push)
|
||||
_controller->onNetworkUpdate(id);
|
||||
if (initialized) {
|
||||
std::lock_guard<std::mutex> ll(_changeListeners_l);
|
||||
for(auto i=_changeListeners.begin();i!=_changeListeners.end();++i) {
|
||||
(*i)->onNetworkUpdate(networkId,networkConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (old.is_object()) {
|
||||
const std::string ids = old["id"];
|
||||
const uint64_t id = Utils::hexStrToU64(ids.c_str());
|
||||
if (id) {
|
||||
const uint64_t networkId = Utils::hexStrToU64(ids.c_str());
|
||||
if (networkId) {
|
||||
std::lock_guard<std::mutex> l(_networks_l);
|
||||
_networks.erase(id);
|
||||
_networks.erase(networkId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2018 ZeroTier, Inc.
|
||||
* Copyright (C) 2011-2019 ZeroTier, Inc. https://www.zerotier.com/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -13,7 +13,15 @@
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* You can be released from the requirements of the license by purchasing
|
||||
* a commercial license. Buying such a license is mandatory as soon as you
|
||||
* develop commercial closed-source software that incorporates or links
|
||||
* directly against ZeroTier software without disclosing the source code
|
||||
* of your own application.
|
||||
*/
|
||||
|
||||
#ifndef ZT_CONTROLLER_DB_HPP
|
||||
@ -32,22 +40,29 @@
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
||||
#include "../ext/json/json.hpp"
|
||||
|
||||
#define ZT_CONTROLLER_RETHINKDB_COMMIT_THREADS 2
|
||||
|
||||
namespace ZeroTier
|
||||
{
|
||||
|
||||
class EmbeddedNetworkController;
|
||||
|
||||
/**
|
||||
* Base class with common infrastructure for all controller DB implementations
|
||||
*/
|
||||
class DB
|
||||
{
|
||||
public:
|
||||
class ChangeListener
|
||||
{
|
||||
public:
|
||||
ChangeListener() {}
|
||||
virtual ~ChangeListener() {}
|
||||
virtual void onNetworkUpdate(uint64_t networkId,const nlohmann::json &network) {}
|
||||
virtual void onNetworkMemberUpdate(uint64_t networkId,uint64_t memberId,const nlohmann::json &member) {}
|
||||
virtual void onNetworkMemberDeauthorize(uint64_t networkId,uint64_t memberId) {}
|
||||
};
|
||||
|
||||
struct NetworkSummaryInfo
|
||||
{
|
||||
NetworkSummaryInfo() : authorizedMemberCount(0),totalMemberCount(0),mostRecentDeauthTime(0) {}
|
||||
@ -58,27 +73,12 @@ public:
|
||||
int64_t mostRecentDeauthTime;
|
||||
};
|
||||
|
||||
/**
|
||||
* Ensure that all network fields are present
|
||||
*/
|
||||
static void initNetwork(nlohmann::json &network);
|
||||
|
||||
/**
|
||||
* Ensure that all member fields are present
|
||||
*/
|
||||
static void initMember(nlohmann::json &member);
|
||||
|
||||
/**
|
||||
* Remove old and temporary network fields
|
||||
*/
|
||||
static void cleanNetwork(nlohmann::json &network);
|
||||
|
||||
/**
|
||||
* Remove old and temporary member fields
|
||||
*/
|
||||
static void cleanMember(nlohmann::json &member);
|
||||
|
||||
DB(EmbeddedNetworkController *const nc,const Identity &myId,const char *path);
|
||||
DB(const Identity &myId,const char *path);
|
||||
virtual ~DB();
|
||||
|
||||
virtual bool waitForReady() = 0;
|
||||
@ -94,19 +94,20 @@ public:
|
||||
bool get(const uint64_t networkId,nlohmann::json &network,const uint64_t memberId,nlohmann::json &member);
|
||||
bool get(const uint64_t networkId,nlohmann::json &network,const uint64_t memberId,nlohmann::json &member,NetworkSummaryInfo &info);
|
||||
bool get(const uint64_t networkId,nlohmann::json &network,std::vector<nlohmann::json> &members);
|
||||
|
||||
bool summary(const uint64_t networkId,NetworkSummaryInfo &info);
|
||||
|
||||
void networks(std::vector<uint64_t> &networks);
|
||||
|
||||
virtual void save(nlohmann::json *orig,nlohmann::json &record) = 0;
|
||||
|
||||
virtual void eraseNetwork(const uint64_t networkId) = 0;
|
||||
|
||||
virtual void eraseMember(const uint64_t networkId,const uint64_t memberId) = 0;
|
||||
|
||||
virtual void nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress) = 0;
|
||||
|
||||
inline void addListener(DB::ChangeListener *const listener)
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_changeListeners_l);
|
||||
_changeListeners.push_back(listener);
|
||||
}
|
||||
|
||||
protected:
|
||||
struct _Network
|
||||
{
|
||||
@ -120,18 +121,19 @@ protected:
|
||||
std::mutex lock;
|
||||
};
|
||||
|
||||
void _memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool push);
|
||||
void _networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool push);
|
||||
void _memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool initialized);
|
||||
void _networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool initialized);
|
||||
void _fillSummaryInfo(const std::shared_ptr<_Network> &nw,NetworkSummaryInfo &info);
|
||||
|
||||
EmbeddedNetworkController *const _controller;
|
||||
const Identity _myId;
|
||||
const Address _myAddress;
|
||||
const std::string _path;
|
||||
std::string _myAddressStr;
|
||||
|
||||
std::vector<DB::ChangeListener *> _changeListeners;
|
||||
std::unordered_map< uint64_t,std::shared_ptr<_Network> > _networks;
|
||||
std::unordered_multimap< uint64_t,uint64_t > _networkByMember;
|
||||
mutable std::mutex _changeListeners_l;
|
||||
mutable std::mutex _networks_l;
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2018 ZeroTier, Inc
|
||||
* Copyright (C) 2011-2019 ZeroTier, Inc. https://www.zerotier.com/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -13,7 +13,15 @@
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* You can be released from the requirements of the license by purchasing
|
||||
* a commercial license. Buying such a license is mandatory as soon as you
|
||||
* develop commercial closed-source software that incorporates or links
|
||||
* directly against ZeroTier software without disclosing the source code
|
||||
* of your own application.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
@ -38,6 +46,11 @@
|
||||
#include "../version.h"
|
||||
|
||||
#include "EmbeddedNetworkController.hpp"
|
||||
#include "LFDB.hpp"
|
||||
#include "FileDB.hpp"
|
||||
#ifdef ZT_CONTROLLER_USE_LIBPQ
|
||||
#include "PostgreSQL.hpp"
|
||||
#endif
|
||||
|
||||
#include "../node/Node.hpp"
|
||||
#include "../node/CertificateOfMembership.hpp"
|
||||
@ -336,14 +349,14 @@ static bool _parseRule(json &r,ZT_VirtualNetworkRule &rule)
|
||||
} else if (t == "MATCH_IPV6_SOURCE") {
|
||||
rule.t |= ZT_NETWORK_RULE_MATCH_IPV6_SOURCE;
|
||||
InetAddress ip(OSUtils::jsonString(r["ip"],"::0").c_str());
|
||||
ZT_FAST_MEMCPY(rule.v.ipv6.ip,reinterpret_cast<struct sockaddr_in6 *>(&ip)->sin6_addr.s6_addr,16);
|
||||
memcpy(rule.v.ipv6.ip,reinterpret_cast<struct sockaddr_in6 *>(&ip)->sin6_addr.s6_addr,16);
|
||||
rule.v.ipv6.mask = Utils::ntoh(reinterpret_cast<struct sockaddr_in6 *>(&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").c_str());
|
||||
ZT_FAST_MEMCPY(rule.v.ipv6.ip,reinterpret_cast<struct sockaddr_in6 *>(&ip)->sin6_addr.s6_addr,16);
|
||||
memcpy(rule.v.ipv6.ip,reinterpret_cast<struct sockaddr_in6 *>(&ip)->sin6_addr.s6_addr,16);
|
||||
rule.v.ipv6.mask = Utils::ntoh(reinterpret_cast<struct sockaddr_in6 *>(&ip)->sin6_port) & 0xff;
|
||||
if (rule.v.ipv6.mask > 128) rule.v.ipv6.mask = 128;
|
||||
return true;
|
||||
@ -456,11 +469,13 @@ static bool _parseRule(json &r,ZT_VirtualNetworkRule &rule)
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
EmbeddedNetworkController::EmbeddedNetworkController(Node *node,const char *dbPath) :
|
||||
EmbeddedNetworkController::EmbeddedNetworkController(Node *node,const char *dbPath, int listenPort, MQConfig *mqc) :
|
||||
_startTime(OSUtils::now()),
|
||||
_listenPort(listenPort),
|
||||
_node(node),
|
||||
_path(dbPath),
|
||||
_sender((NetworkController::Sender *)0)
|
||||
_sender((NetworkController::Sender *)0),
|
||||
_mqc(mqc)
|
||||
{
|
||||
}
|
||||
|
||||
@ -478,12 +493,51 @@ void EmbeddedNetworkController::init(const Identity &signingId,Sender *sender)
|
||||
_signingId = signingId;
|
||||
_sender = sender;
|
||||
_signingIdAddressString = signingId.address().toString(tmp);
|
||||
#ifdef ZT_CONTROLLER_USE_RETHINKDB
|
||||
if ((_path.length() > 10)&&(_path.substr(0,10) == "rethinkdb:"))
|
||||
_db.reset(new RethinkDB(this,_signingId,_path.c_str()));
|
||||
else // else use FileDB after endif
|
||||
|
||||
#ifdef ZT_CONTROLLER_USE_LIBPQ
|
||||
if ((_path.length() > 9)&&(_path.substr(0,9) == "postgres:")) {
|
||||
_db.reset(new PostgreSQL(_signingId,_path.substr(9).c_str(), _listenPort, _mqc));
|
||||
} else {
|
||||
#endif
|
||||
_db.reset(new FileDB(this,_signingId,_path.c_str()));
|
||||
|
||||
std::string lfJSON;
|
||||
OSUtils::readFile((_path + ZT_PATH_SEPARATOR_S ".." ZT_PATH_SEPARATOR_S "local.conf").c_str(),lfJSON);
|
||||
if (lfJSON.length() > 0) {
|
||||
nlohmann::json lfConfig(OSUtils::jsonParse(lfJSON));
|
||||
nlohmann::json &settings = lfConfig["settings"];
|
||||
if (settings.is_object()) {
|
||||
nlohmann::json &controllerDb = settings["controllerDb"];
|
||||
if (controllerDb.is_object()) {
|
||||
std::string type = controllerDb["type"];
|
||||
if (type == "lf") {
|
||||
std::string lfOwner = controllerDb["owner"];
|
||||
std::string lfHost = controllerDb["host"];
|
||||
int lfPort = controllerDb["port"];
|
||||
bool storeOnlineState = controllerDb["storeOnlineState"];
|
||||
if ((lfOwner.length())&&(lfHost.length())&&(lfPort > 0)&&(lfPort < 65536)) {
|
||||
std::size_t pubHdrLoc = lfOwner.find("Public: ");
|
||||
if ((pubHdrLoc > 0)&&((pubHdrLoc + 8) < lfOwner.length())) {
|
||||
std::string lfOwnerPublic = lfOwner.substr(pubHdrLoc + 8);
|
||||
std::size_t pubHdrEnd = lfOwnerPublic.find_first_of("\n\r\t ");
|
||||
if (pubHdrEnd != std::string::npos) {
|
||||
lfOwnerPublic = lfOwnerPublic.substr(0,pubHdrEnd);
|
||||
_db.reset(new LFDB(_signingId,_path.c_str(),lfOwner.c_str(),lfOwnerPublic.c_str(),lfHost.c_str(),lfPort,storeOnlineState));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!_db)
|
||||
_db.reset(new FileDB(_signingId,_path.c_str()));
|
||||
|
||||
_db->addListener(this);
|
||||
|
||||
#ifdef ZT_CONTROLLER_USE_LIBPQ
|
||||
}
|
||||
#endif
|
||||
|
||||
_db->waitForReady();
|
||||
}
|
||||
|
||||
@ -1043,6 +1097,7 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpDELETE(
|
||||
|
||||
json network,member;
|
||||
_db->get(nwid,network,address,member);
|
||||
_db->eraseMember(nwid, address);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_memberStatus_l);
|
||||
@ -1135,7 +1190,7 @@ void EmbeddedNetworkController::handleRemoteTrace(const ZT_RemoteTrace &rt)
|
||||
}
|
||||
}
|
||||
|
||||
void EmbeddedNetworkController::onNetworkUpdate(const uint64_t networkId)
|
||||
void EmbeddedNetworkController::onNetworkUpdate(const uint64_t networkId,const nlohmann::json &network)
|
||||
{
|
||||
// Send an update to all members of the network that are online
|
||||
const int64_t now = OSUtils::now();
|
||||
@ -1146,7 +1201,7 @@ void EmbeddedNetworkController::onNetworkUpdate(const uint64_t networkId)
|
||||
}
|
||||
}
|
||||
|
||||
void EmbeddedNetworkController::onNetworkMemberUpdate(const uint64_t networkId,const uint64_t memberId)
|
||||
void EmbeddedNetworkController::onNetworkMemberUpdate(const uint64_t networkId,const uint64_t memberId,const nlohmann::json &member)
|
||||
{
|
||||
// Push update to member if online
|
||||
try {
|
||||
@ -1511,13 +1566,13 @@ void EmbeddedNetworkController::_request(
|
||||
const std::string ips = ipAssignments[i];
|
||||
InetAddress ip(ips.c_str());
|
||||
|
||||
// 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 = -1;
|
||||
for(unsigned int rk=0;rk<nc->routeCount;++rk) {
|
||||
if ( (!nc->routes[rk].via.ss_family) && (reinterpret_cast<const InetAddress *>(&(nc->routes[rk].target))->containsAddress(ip)) )
|
||||
routedNetmaskBits = reinterpret_cast<const InetAddress *>(&(nc->routes[rk].target))->netmaskBits();
|
||||
if (reinterpret_cast<const InetAddress *>(&(nc->routes[rk].target))->containsAddress(ip)) {
|
||||
const int nb = (int)(reinterpret_cast<const InetAddress *>(&(nc->routes[rk].target))->netmaskBits());
|
||||
if (nb > routedNetmaskBits)
|
||||
routedNetmaskBits = nb;
|
||||
}
|
||||
}
|
||||
|
||||
if (routedNetmaskBits >= 0) {
|
||||
@ -1544,8 +1599,8 @@ void EmbeddedNetworkController::_request(
|
||||
InetAddress ipRangeEnd(OSUtils::jsonString(pool["ipRangeEnd"],"").c_str());
|
||||
if ( (ipRangeStart.ss_family == AF_INET6) && (ipRangeEnd.ss_family == AF_INET6) ) {
|
||||
uint64_t s[2],e[2],x[2],xx[2];
|
||||
ZT_FAST_MEMCPY(s,ipRangeStart.rawIpData(),16);
|
||||
ZT_FAST_MEMCPY(e,ipRangeEnd.rawIpData(),16);
|
||||
memcpy(s,ipRangeStart.rawIpData(),16);
|
||||
memcpy(e,ipRangeEnd.rawIpData(),16);
|
||||
s[0] = Utils::ntoh(s[0]);
|
||||
s[1] = Utils::ntoh(s[1]);
|
||||
e[0] = Utils::ntoh(e[0]);
|
||||
@ -1609,18 +1664,20 @@ void EmbeddedNetworkController::_request(
|
||||
if ( (ipRangeStartIA.ss_family == AF_INET) && (ipRangeEndIA.ss_family == AF_INET) ) {
|
||||
uint32_t ipRangeStart = Utils::ntoh((uint32_t)(reinterpret_cast<struct sockaddr_in *>(&ipRangeStartIA)->sin_addr.s_addr));
|
||||
uint32_t ipRangeEnd = Utils::ntoh((uint32_t)(reinterpret_cast<struct sockaddr_in *>(&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)
|
||||
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;
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2018 ZeroTier, Inc.
|
||||
* Copyright (C) 2011-2019 ZeroTier, Inc. https://www.zerotier.com/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -13,7 +13,15 @@
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* You can be released from the requirements of the license by purchasing
|
||||
* a commercial license. Buying such a license is mandatory as soon as you
|
||||
* develop commercial closed-source software that incorporates or links
|
||||
* directly against ZeroTier software without disclosing the source code
|
||||
* of your own application.
|
||||
*/
|
||||
|
||||
#ifndef ZT_SQLITENETWORKCONTROLLER_HPP
|
||||
@ -43,23 +51,21 @@
|
||||
#include "../ext/json/json.hpp"
|
||||
|
||||
#include "DB.hpp"
|
||||
#include "FileDB.hpp"
|
||||
#ifdef ZT_CONTROLLER_USE_RETHINKDB
|
||||
#include "RethinkDB.hpp"
|
||||
#endif
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
class Node;
|
||||
|
||||
class EmbeddedNetworkController : public NetworkController
|
||||
struct MQConfig;
|
||||
|
||||
class EmbeddedNetworkController : public NetworkController,public DB::ChangeListener
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @param node Parent node
|
||||
* @param dbPath Database path (file path or database credentials)
|
||||
*/
|
||||
EmbeddedNetworkController(Node *node,const char *dbPath);
|
||||
EmbeddedNetworkController(Node *node,const char *dbPath, int listenPort, MQConfig *mqc = NULL);
|
||||
virtual ~EmbeddedNetworkController();
|
||||
|
||||
virtual void init(const Identity &signingId,Sender *sender);
|
||||
@ -95,10 +101,9 @@ public:
|
||||
|
||||
void handleRemoteTrace(const ZT_RemoteTrace &rt);
|
||||
|
||||
// Called on update via POST or by JSONDB on external update of network or network member records
|
||||
void onNetworkUpdate(const uint64_t networkId);
|
||||
void onNetworkMemberUpdate(const uint64_t networkId,const uint64_t memberId);
|
||||
void onNetworkMemberDeauthorize(const uint64_t networkId,const uint64_t memberId);
|
||||
virtual void onNetworkUpdate(const uint64_t networkId,const nlohmann::json &network);
|
||||
virtual void onNetworkMemberUpdate(const uint64_t networkId,const uint64_t memberId,const nlohmann::json &member);
|
||||
virtual void onNetworkMemberDeauthorize(const uint64_t networkId,const uint64_t memberId);
|
||||
|
||||
private:
|
||||
void _request(uint64_t nwid,const InetAddress &fromAddr,uint64_t requestPacketId,const Identity &identity,const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData);
|
||||
@ -141,17 +146,23 @@ private:
|
||||
};
|
||||
|
||||
const int64_t _startTime;
|
||||
int _listenPort;
|
||||
Node *const _node;
|
||||
std::string _path;
|
||||
Identity _signingId;
|
||||
std::string _signingIdAddressString;
|
||||
NetworkController::Sender *_sender;
|
||||
|
||||
std::unique_ptr<DB> _db;
|
||||
BlockingQueue< _RQEntry * > _queue;
|
||||
|
||||
std::vector<std::thread> _threads;
|
||||
std::mutex _threads_l;
|
||||
|
||||
std::unordered_map< _MemberStatusKey,_MemberStatus,_MemberStatusHash > _memberStatus;
|
||||
std::mutex _memberStatus_l;
|
||||
|
||||
MQConfig *_mqc;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2018 ZeroTier, Inc.
|
||||
* Copyright (C) 2011-2019 ZeroTier, Inc. https://www.zerotier.com/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -13,7 +13,15 @@
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* You can be released from the requirements of the license by purchasing
|
||||
* a commercial license. Buying such a license is mandatory as soon as you
|
||||
* develop commercial closed-source software that incorporates or links
|
||||
* directly against ZeroTier software without disclosing the source code
|
||||
* of your own application.
|
||||
*/
|
||||
|
||||
#include "FileDB.hpp"
|
||||
@ -21,10 +29,12 @@
|
||||
namespace ZeroTier
|
||||
{
|
||||
|
||||
FileDB::FileDB(EmbeddedNetworkController *const nc,const Identity &myId,const char *path) :
|
||||
DB(nc,myId,path),
|
||||
FileDB::FileDB(const Identity &myId,const char *path) :
|
||||
DB(myId,path),
|
||||
_networksPath(_path + ZT_PATH_SEPARATOR_S + "network"),
|
||||
_tracePath(_path + ZT_PATH_SEPARATOR_S + "trace")
|
||||
_tracePath(_path + ZT_PATH_SEPARATOR_S + "trace"),
|
||||
_onlineChanged(false),
|
||||
_running(true)
|
||||
{
|
||||
OSUtils::mkdir(_path.c_str());
|
||||
OSUtils::lockDownFile(_path.c_str(),true);
|
||||
@ -61,9 +71,65 @@ FileDB::FileDB(EmbeddedNetworkController *const nc,const Identity &myId,const ch
|
||||
} catch ( ... ) {}
|
||||
}
|
||||
}
|
||||
|
||||
_onlineUpdateThread = std::thread([this]() {
|
||||
unsigned int cnt = 0;
|
||||
while (this->_running) {
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(100));
|
||||
if ((++cnt % 20) == 0) { // 5 seconds
|
||||
std::lock_guard<std::mutex> l(this->_online_l);
|
||||
if (!this->_running) return;
|
||||
if (this->_onlineChanged) {
|
||||
char p[4096],atmp[64];
|
||||
for(auto nw=this->_online.begin();nw!=this->_online.end();++nw) {
|
||||
OSUtils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "%.16llx-online.json",_networksPath.c_str(),(unsigned long long)nw->first);
|
||||
FILE *f = fopen(p,"wb");
|
||||
if (f) {
|
||||
fprintf(f,"{");
|
||||
const char *memberPrefix = "";
|
||||
for(auto m=nw->second.begin();m!=nw->second.end();++m) {
|
||||
fprintf(f,"%s\"%.10llx\":{" ZT_EOL_S,memberPrefix,(unsigned long long)m->first);
|
||||
memberPrefix = ",";
|
||||
InetAddress lastAddr;
|
||||
const char *timestampPrefix = " ";
|
||||
int cnt = 0;
|
||||
for(auto ts=m->second.rbegin();ts!=m->second.rend();) {
|
||||
if (cnt < 25) {
|
||||
if (lastAddr != ts->second) {
|
||||
lastAddr = ts->second;
|
||||
fprintf(f,"%s\"%lld\":\"%s\"" ZT_EOL_S,timestampPrefix,(long long)ts->first,ts->second.toString(atmp));
|
||||
timestampPrefix = ",";
|
||||
++cnt;
|
||||
++ts;
|
||||
} else {
|
||||
ts = std::map<int64_t,InetAddress>::reverse_iterator(m->second.erase(std::next(ts).base()));
|
||||
}
|
||||
} else {
|
||||
ts = std::map<int64_t,InetAddress>::reverse_iterator(m->second.erase(std::next(ts).base()));
|
||||
}
|
||||
}
|
||||
fprintf(f,"}");
|
||||
}
|
||||
fprintf(f,"}" ZT_EOL_S);
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
this->_onlineChanged = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
FileDB::~FileDB() {}
|
||||
FileDB::~FileDB()
|
||||
{
|
||||
try {
|
||||
_online_l.lock();
|
||||
_running = false;
|
||||
_online_l.unlock();
|
||||
_onlineUpdateThread.join();
|
||||
} catch ( ... ) {}
|
||||
}
|
||||
|
||||
bool FileDB::waitForReady() { return true; }
|
||||
bool FileDB::isReady() { return true; }
|
||||
@ -86,14 +152,10 @@ void FileDB::save(nlohmann::json *orig,nlohmann::json &record)
|
||||
if (nwid) {
|
||||
nlohmann::json old;
|
||||
get(nwid,old);
|
||||
|
||||
if ((!old.is_object())||(old != record)) {
|
||||
OSUtils::ztsnprintf(p1,sizeof(p1),"%s" ZT_PATH_SEPARATOR_S "%.16llx.json.new",_networksPath.c_str(),nwid);
|
||||
OSUtils::ztsnprintf(p2,sizeof(p2),"%s" ZT_PATH_SEPARATOR_S "%.16llx.json",_networksPath.c_str(),nwid);
|
||||
OSUtils::ztsnprintf(p1,sizeof(p1),"%s" ZT_PATH_SEPARATOR_S "%.16llx.json",_networksPath.c_str(),nwid);
|
||||
if (!OSUtils::writeFile(p1,OSUtils::jsonDump(record,-1)))
|
||||
fprintf(stderr,"WARNING: controller unable to write to path: %s" ZT_EOL_S,p1);
|
||||
OSUtils::rename(p1,p2);
|
||||
|
||||
_networkChanged(old,record,true);
|
||||
}
|
||||
}
|
||||
@ -103,10 +165,9 @@ void FileDB::save(nlohmann::json *orig,nlohmann::json &record)
|
||||
if ((id)&&(nwid)) {
|
||||
nlohmann::json network,old;
|
||||
get(nwid,network,id,old);
|
||||
|
||||
if ((!old.is_object())||(old != record)) {
|
||||
OSUtils::ztsnprintf(pb,sizeof(pb),"%s" ZT_PATH_SEPARATOR_S "%.16llx" ZT_PATH_SEPARATOR_S "member",_networksPath.c_str(),(unsigned long long)nwid);
|
||||
OSUtils::ztsnprintf(p1,sizeof(p1),"%s" ZT_PATH_SEPARATOR_S "%.10llx.json.new",pb,(unsigned long long)id);
|
||||
OSUtils::ztsnprintf(p1,sizeof(p1),"%s" ZT_PATH_SEPARATOR_S "%.10llx.json",pb,(unsigned long long)id);
|
||||
if (!OSUtils::writeFile(p1,OSUtils::jsonDump(record,-1))) {
|
||||
OSUtils::ztsnprintf(p2,sizeof(p2),"%s" ZT_PATH_SEPARATOR_S "%.16llx",_networksPath.c_str(),(unsigned long long)nwid);
|
||||
OSUtils::mkdir(p2);
|
||||
@ -114,9 +175,6 @@ void FileDB::save(nlohmann::json *orig,nlohmann::json &record)
|
||||
if (!OSUtils::writeFile(p1,OSUtils::jsonDump(record,-1)))
|
||||
fprintf(stderr,"WARNING: controller unable to write to path: %s" ZT_EOL_S,p1);
|
||||
}
|
||||
OSUtils::ztsnprintf(p2,sizeof(p2),"%s" ZT_PATH_SEPARATOR_S "%.10llx.json",pb,(unsigned long long)id);
|
||||
OSUtils::rename(p1,p2);
|
||||
|
||||
_memberChanged(old,record,true);
|
||||
}
|
||||
}
|
||||
@ -137,16 +195,38 @@ void FileDB::eraseNetwork(const uint64_t networkId)
|
||||
char p[16384];
|
||||
OSUtils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "%.16llx.json",_networksPath.c_str(),networkId);
|
||||
OSUtils::rm(p);
|
||||
OSUtils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "%.16llx-online.json",_networksPath.c_str(),networkId);
|
||||
OSUtils::rm(p);
|
||||
OSUtils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "%.16llx" ZT_PATH_SEPARATOR_S "member",_networksPath.c_str(),(unsigned long long)networkId);
|
||||
OSUtils::rmDashRf(p);
|
||||
_networkChanged(network,nullJson,true);
|
||||
std::lock_guard<std::mutex> l(this->_online_l);
|
||||
this->_online.erase(networkId);
|
||||
this->_onlineChanged = true;
|
||||
}
|
||||
|
||||
void FileDB::eraseMember(const uint64_t networkId,const uint64_t memberId)
|
||||
{
|
||||
nlohmann::json network,member,nullJson;
|
||||
get(networkId,network);
|
||||
get(memberId,member);
|
||||
char p[4096];
|
||||
OSUtils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "%.16llx" ZT_PATH_SEPARATOR_S "member" ZT_PATH_SEPARATOR_S "%.10llx.json",_networksPath.c_str(),networkId,memberId);
|
||||
OSUtils::rm(p);
|
||||
_memberChanged(member,nullJson,true);
|
||||
std::lock_guard<std::mutex> l(this->_online_l);
|
||||
this->_online[networkId].erase(memberId);
|
||||
this->_onlineChanged = true;
|
||||
}
|
||||
|
||||
void FileDB::nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress)
|
||||
{
|
||||
// Nothing to do here right now in the filesystem store mode since we can just get this from the peer list
|
||||
char mid[32],atmp[64];
|
||||
OSUtils::ztsnprintf(mid,sizeof(mid),"%.10llx",(unsigned long long)memberId);
|
||||
physicalAddress.toString(atmp);
|
||||
std::lock_guard<std::mutex> l(this->_online_l);
|
||||
this->_online[networkId][memberId][OSUtils::now()] = physicalAddress;
|
||||
this->_onlineChanged = true;
|
||||
}
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2018 ZeroTier, Inc.
|
||||
* Copyright (C) 2011-2019 ZeroTier, Inc. https://www.zerotier.com/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -13,7 +13,15 @@
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* You can be released from the requirements of the license by purchasing
|
||||
* a commercial license. Buying such a license is mandatory as soon as you
|
||||
* develop commercial closed-source software that incorporates or links
|
||||
* directly against ZeroTier software without disclosing the source code
|
||||
* of your own application.
|
||||
*/
|
||||
|
||||
#ifndef ZT_CONTROLLER_FILEDB_HPP
|
||||
@ -27,7 +35,7 @@ namespace ZeroTier
|
||||
class FileDB : public DB
|
||||
{
|
||||
public:
|
||||
FileDB(EmbeddedNetworkController *const nc,const Identity &myId,const char *path);
|
||||
FileDB(const Identity &myId,const char *path);
|
||||
virtual ~FileDB();
|
||||
|
||||
virtual bool waitForReady();
|
||||
@ -40,6 +48,11 @@ public:
|
||||
protected:
|
||||
std::string _networksPath;
|
||||
std::string _tracePath;
|
||||
std::thread _onlineUpdateThread;
|
||||
std::map< uint64_t,std::map<uint64_t,std::map<int64_t,InetAddress> > > _online;
|
||||
std::mutex _online_l;
|
||||
bool _onlineChanged;
|
||||
bool _running;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
400
controller/LFDB.cpp
Normal file
400
controller/LFDB.cpp
Normal file
@ -0,0 +1,400 @@
|
||||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2019 ZeroTier, Inc. https://www.zerotier.com/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* You can be released from the requirements of the license by purchasing
|
||||
* a commercial license. Buying such a license is mandatory as soon as you
|
||||
* develop commercial closed-source software that incorporates or links
|
||||
* directly against ZeroTier software without disclosing the source code
|
||||
* of your own application.
|
||||
*/
|
||||
|
||||
#include "LFDB.hpp"
|
||||
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include "../osdep/OSUtils.hpp"
|
||||
#include "../ext/cpp-httplib/httplib.h"
|
||||
|
||||
namespace ZeroTier
|
||||
{
|
||||
|
||||
LFDB::LFDB(const Identity &myId,const char *path,const char *lfOwnerPrivate,const char *lfOwnerPublic,const char *lfNodeHost,int lfNodePort,bool storeOnlineState) :
|
||||
DB(myId,path),
|
||||
_myId(myId),
|
||||
_lfOwnerPrivate((lfOwnerPrivate) ? lfOwnerPrivate : ""),
|
||||
_lfOwnerPublic((lfOwnerPublic) ? lfOwnerPublic : ""),
|
||||
_lfNodeHost((lfNodeHost) ? lfNodeHost : "127.0.0.1"),
|
||||
_lfNodePort(((lfNodePort > 0)&&(lfNodePort < 65536)) ? lfNodePort : 9980),
|
||||
_running(true),
|
||||
_ready(false),
|
||||
_storeOnlineState(storeOnlineState)
|
||||
{
|
||||
_syncThread = std::thread([this]() {
|
||||
char controllerAddress[24];
|
||||
const uint64_t controllerAddressInt = _myId.address().toInt();
|
||||
_myId.address().toString(controllerAddress);
|
||||
std::string networksSelectorName("com.zerotier.controller.lfdb:"); networksSelectorName.append(controllerAddress); networksSelectorName.append("/network");
|
||||
std::string membersSelectorName("com.zerotier.controller.lfdb:"); membersSelectorName.append(controllerAddress); membersSelectorName.append("/member");
|
||||
|
||||
// LF record masking key is the first 32 bytes of SHA512(controller private key) in hex,
|
||||
// hiding record values from anything but the controller or someone who has its key.
|
||||
uint8_t sha512pk[64];
|
||||
_myId.sha512PrivateKey(sha512pk);
|
||||
char maskingKey [128];
|
||||
Utils::hex(sha512pk,32,maskingKey);
|
||||
|
||||
httplib::Client htcli(_lfNodeHost.c_str(),_lfNodePort,600);
|
||||
int64_t timeRangeStart = 0;
|
||||
while (_running) {
|
||||
{
|
||||
std::lock_guard<std::mutex> sl(_state_l);
|
||||
for(auto ns=_state.begin();ns!=_state.end();++ns) {
|
||||
if (ns->second.dirty) {
|
||||
nlohmann::json network;
|
||||
if (get(ns->first,network)) {
|
||||
nlohmann::json newrec,selector0;
|
||||
selector0["Name"] = networksSelectorName;
|
||||
selector0["Ordinal"] = ns->first;
|
||||
newrec["Selectors"].push_back(selector0);
|
||||
newrec["Value"] = network.dump();
|
||||
newrec["OwnerPrivate"] = _lfOwnerPrivate;
|
||||
newrec["MaskingKey"] = maskingKey;
|
||||
newrec["PulseIfUnchanged"] = true;
|
||||
auto resp = htcli.Post("/makerecord",newrec.dump(),"application/json");
|
||||
if (resp) {
|
||||
if (resp->status == 200) {
|
||||
ns->second.dirty = false;
|
||||
printf("SET network %.16llx %s\n",ns->first,resp->body.c_str());
|
||||
} else {
|
||||
fprintf(stderr,"ERROR: LFDB: %d from node (create/update network): %s" ZT_EOL_S,resp->status,resp->body.c_str());
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr,"ERROR: LFDB: node is offline" ZT_EOL_S);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(auto ms=ns->second.members.begin();ms!=ns->second.members.end();++ms) {
|
||||
if ((_storeOnlineState)&&(ms->second.lastOnlineDirty)&&(ms->second.lastOnlineAddress)) {
|
||||
nlohmann::json newrec,selector0,selector1,selectors,ip;
|
||||
char tmp[1024],tmp2[128];
|
||||
OSUtils::ztsnprintf(tmp,sizeof(tmp),"com.zerotier.controller.lfdb:%s/network/%.16llx/online",controllerAddress,(unsigned long long)ns->first);
|
||||
ms->second.lastOnlineAddress.toIpString(tmp2);
|
||||
selector0["Name"] = tmp;
|
||||
selector0["Ordinal"] = ms->first;
|
||||
selector1["Name"] = tmp2;
|
||||
selector1["Ordinal"] = 0;
|
||||
selectors.push_back(selector0);
|
||||
selectors.push_back(selector1);
|
||||
newrec["Selectors"] = selectors;
|
||||
const uint8_t *const rawip = (const uint8_t *)ms->second.lastOnlineAddress.rawIpData();
|
||||
switch(ms->second.lastOnlineAddress.ss_family) {
|
||||
case AF_INET:
|
||||
for(int j=0;j<4;++j)
|
||||
ip.push_back((unsigned int)rawip[j]);
|
||||
break;
|
||||
case AF_INET6:
|
||||
for(int j=0;j<16;++j)
|
||||
ip.push_back((unsigned int)rawip[j]);
|
||||
break;
|
||||
default:
|
||||
ip = tmp2; // should never happen since only IP transport is currently supported
|
||||
break;
|
||||
}
|
||||
newrec["Value"] = ip;
|
||||
newrec["OwnerPrivate"] = _lfOwnerPrivate;
|
||||
newrec["MaskingKey"] = maskingKey;
|
||||
newrec["Timestamp"] = ms->second.lastOnlineTime;
|
||||
newrec["PulseIfUnchanged"] = true;
|
||||
auto resp = htcli.Post("/makerecord",newrec.dump(),"application/json");
|
||||
if (resp) {
|
||||
if (resp->status == 200) {
|
||||
ms->second.lastOnlineDirty = false;
|
||||
printf("SET member online %.16llx %.10llx %s\n",ns->first,ms->first,resp->body.c_str());
|
||||
} else {
|
||||
fprintf(stderr,"ERROR: LFDB: %d from node (create/update member online status): %s" ZT_EOL_S,resp->status,resp->body.c_str());
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr,"ERROR: LFDB: node is offline" ZT_EOL_S);
|
||||
}
|
||||
}
|
||||
|
||||
if (ms->second.dirty) {
|
||||
nlohmann::json network,member;
|
||||
if (get(ns->first,network,ms->first,member)) {
|
||||
nlohmann::json newrec,selector0,selector1,selectors;
|
||||
selector0["Name"] = networksSelectorName;
|
||||
selector0["Ordinal"] = ns->first;
|
||||
selector1["Name"] = membersSelectorName;
|
||||
selector1["Ordinal"] = ms->first;
|
||||
selectors.push_back(selector0);
|
||||
selectors.push_back(selector1);
|
||||
newrec["Selectors"] = selectors;
|
||||
newrec["Value"] = member.dump();
|
||||
newrec["OwnerPrivate"] = _lfOwnerPrivate;
|
||||
newrec["MaskingKey"] = maskingKey;
|
||||
newrec["PulseIfUnchanged"] = true;
|
||||
auto resp = htcli.Post("/makerecord",newrec.dump(),"application/json");
|
||||
if (resp) {
|
||||
if (resp->status == 200) {
|
||||
ms->second.dirty = false;
|
||||
printf("SET member %.16llx %.10llx %s\n",ns->first,ms->first,resp->body.c_str());
|
||||
} else {
|
||||
fprintf(stderr,"ERROR: LFDB: %d from node (create/update member): %s" ZT_EOL_S,resp->status,resp->body.c_str());
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr,"ERROR: LFDB: node is offline" ZT_EOL_S);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::ostringstream query;
|
||||
query
|
||||
<< '{'
|
||||
<< "\"Ranges\":[{"
|
||||
<< "\"Name\":\"" << networksSelectorName << "\","
|
||||
<< "\"Range\":[0,18446744073709551615]"
|
||||
<< "}],"
|
||||
<< "\"TimeRange\":[" << timeRangeStart << ",18446744073709551615],"
|
||||
<< "\"MaskingKey\":\"" << maskingKey << "\","
|
||||
<< "\"Owners\":[\"" << _lfOwnerPublic << "\"]"
|
||||
<< '}';
|
||||
auto resp = htcli.Post("/query",query.str(),"application/json");
|
||||
if (resp) {
|
||||
if (resp->status == 200) {
|
||||
nlohmann::json results(OSUtils::jsonParse(resp->body));
|
||||
if ((results.is_array())&&(results.size() > 0)) {
|
||||
for(std::size_t ri=0;ri<results.size();++ri) {
|
||||
nlohmann::json &rset = results[ri];
|
||||
if ((rset.is_array())&&(rset.size() > 0)) {
|
||||
nlohmann::json &result = rset[0];
|
||||
if (result.is_object()) {
|
||||
nlohmann::json &record = result["Record"];
|
||||
if (record.is_object()) {
|
||||
const std::string recordValue = result["Value"];
|
||||
printf("GET network %s\n",recordValue.c_str());
|
||||
nlohmann::json network(OSUtils::jsonParse(recordValue));
|
||||
if (network.is_object()) {
|
||||
const std::string idstr = network["id"];
|
||||
const uint64_t id = Utils::hexStrToU64(idstr.c_str());
|
||||
if ((id >> 24) == controllerAddressInt) { // sanity check
|
||||
|
||||
std::lock_guard<std::mutex> sl(_state_l);
|
||||
_NetworkState &ns = _state[id];
|
||||
if (!ns.dirty) {
|
||||
nlohmann::json oldNetwork;
|
||||
if (get(id,oldNetwork)) {
|
||||
const uint64_t revision = network["revision"];
|
||||
const uint64_t prevRevision = oldNetwork["revision"];
|
||||
if (prevRevision < revision) {
|
||||
_networkChanged(oldNetwork,network,timeRangeStart > 0);
|
||||
}
|
||||
} else {
|
||||
nlohmann::json nullJson;
|
||||
_networkChanged(nullJson,network,timeRangeStart > 0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr,"ERROR: LFDB: %d from node: %s" ZT_EOL_S,resp->status,resp->body.c_str());
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr,"ERROR: LFDB: node is offline" ZT_EOL_S);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::ostringstream query;
|
||||
query
|
||||
<< '{'
|
||||
<< "\"Ranges\":[{"
|
||||
<< "\"Name\":\"" << networksSelectorName << "\","
|
||||
<< "\"Range\":[0,18446744073709551615]"
|
||||
<< "},{"
|
||||
<< "\"Name\":\"" << membersSelectorName << "\","
|
||||
<< "\"Range\":[0,18446744073709551615]"
|
||||
<< "}],"
|
||||
<< "\"TimeRange\":[" << timeRangeStart << ",18446744073709551615],"
|
||||
<< "\"MaskingKey\":\"" << maskingKey << "\","
|
||||
<< "\"Owners\":[\"" << _lfOwnerPublic << "\"]"
|
||||
<< '}';
|
||||
auto resp = htcli.Post("/query",query.str(),"application/json");
|
||||
if (resp) {
|
||||
if (resp->status == 200) {
|
||||
nlohmann::json results(OSUtils::jsonParse(resp->body));
|
||||
if ((results.is_array())&&(results.size() > 0)) {
|
||||
for(std::size_t ri=0;ri<results.size();++ri) {
|
||||
nlohmann::json &rset = results[ri];
|
||||
if ((rset.is_array())&&(rset.size() > 0)) {
|
||||
nlohmann::json &result = rset[0];
|
||||
if (result.is_object()) {
|
||||
nlohmann::json &record = result["Record"];
|
||||
if (record.is_object()) {
|
||||
const std::string recordValue = result["Value"];
|
||||
printf("GET member %s\n",recordValue.c_str());
|
||||
nlohmann::json member(OSUtils::jsonParse(recordValue));
|
||||
if (member.is_object()) {
|
||||
const std::string nwidstr = member["nwid"];
|
||||
const std::string idstr = member["id"];
|
||||
const uint64_t nwid = Utils::hexStrToU64(nwidstr.c_str());
|
||||
const uint64_t id = Utils::hexStrToU64(idstr.c_str());
|
||||
if ((id)&&((nwid >> 24) == controllerAddressInt)) { // sanity check
|
||||
|
||||
std::lock_guard<std::mutex> sl(_state_l);
|
||||
auto ns = _state.find(nwid);
|
||||
if ((ns == _state.end())||(!ns->second.members[id].dirty)) {
|
||||
nlohmann::json network,oldMember;
|
||||
if (get(nwid,network,id,oldMember)) {
|
||||
const uint64_t revision = member["revision"];
|
||||
const uint64_t prevRevision = oldMember["revision"];
|
||||
if (prevRevision < revision)
|
||||
_memberChanged(oldMember,member,timeRangeStart > 0);
|
||||
}
|
||||
} else {
|
||||
nlohmann::json nullJson;
|
||||
_memberChanged(nullJson,member,timeRangeStart > 0);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr,"ERROR: LFDB: %d from node: %s" ZT_EOL_S,resp->status,resp->body.c_str());
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr,"ERROR: LFDB: node is offline" ZT_EOL_S);
|
||||
}
|
||||
}
|
||||
|
||||
timeRangeStart = time(nullptr) - 120; // start next query 2m before now to avoid losing updates
|
||||
_ready = true;
|
||||
|
||||
for(int k=0;k<20;++k) { // 2s delay between queries for remotely modified networks or members
|
||||
if (!_running)
|
||||
return;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
LFDB::~LFDB()
|
||||
{
|
||||
_running = false;
|
||||
_syncThread.join();
|
||||
}
|
||||
|
||||
bool LFDB::waitForReady()
|
||||
{
|
||||
while (!_ready) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LFDB::isReady()
|
||||
{
|
||||
return (_ready);
|
||||
}
|
||||
|
||||
void LFDB::save(nlohmann::json *orig,nlohmann::json &record)
|
||||
{
|
||||
if (orig) {
|
||||
if (*orig != record) {
|
||||
record["revision"] = OSUtils::jsonInt(record["revision"],0ULL) + 1;
|
||||
}
|
||||
} else {
|
||||
record["revision"] = 1;
|
||||
}
|
||||
|
||||
const std::string objtype = record["objtype"];
|
||||
if (objtype == "network") {
|
||||
const uint64_t nwid = OSUtils::jsonIntHex(record["id"],0ULL);
|
||||
if (nwid) {
|
||||
nlohmann::json old;
|
||||
get(nwid,old);
|
||||
if ((!old.is_object())||(old != record)) {
|
||||
_networkChanged(old,record,true);
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_state_l);
|
||||
_state[nwid].dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (objtype == "member") {
|
||||
const uint64_t nwid = OSUtils::jsonIntHex(record["nwid"],0ULL);
|
||||
const uint64_t id = OSUtils::jsonIntHex(record["id"],0ULL);
|
||||
if ((id)&&(nwid)) {
|
||||
nlohmann::json network,old;
|
||||
get(nwid,network,id,old);
|
||||
if ((!old.is_object())||(old != record)) {
|
||||
_memberChanged(old,record,true);
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_state_l);
|
||||
_state[nwid].members[id].dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LFDB::eraseNetwork(const uint64_t networkId)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
void LFDB::eraseMember(const uint64_t networkId,const uint64_t memberId)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
void LFDB::nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress)
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_state_l);
|
||||
auto nw = _state.find(networkId);
|
||||
if (nw != _state.end()) {
|
||||
auto m = nw->second.members.find(memberId);
|
||||
if (m != nw->second.members.end()) {
|
||||
m->second.lastOnlineTime = OSUtils::now();
|
||||
if (physicalAddress)
|
||||
m->second.lastOnlineAddress = physicalAddress;
|
||||
m->second.lastOnlineDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ZeroTier
|
102
controller/LFDB.hpp
Normal file
102
controller/LFDB.hpp
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2019 ZeroTier, Inc. https://www.zerotier.com/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* You can be released from the requirements of the license by purchasing
|
||||
* a commercial license. Buying such a license is mandatory as soon as you
|
||||
* develop commercial closed-source software that incorporates or links
|
||||
* directly against ZeroTier software without disclosing the source code
|
||||
* of your own application.
|
||||
*/
|
||||
|
||||
#ifndef ZT_CONTROLLER_LFDB_HPP
|
||||
#define ZT_CONTROLLER_LFDB_HPP
|
||||
|
||||
#include "DB.hpp"
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <atomic>
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
/**
|
||||
* DB implementation for controller that stores data in LF
|
||||
*/
|
||||
class LFDB : public DB
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @param myId Identity of controller node (with secret)
|
||||
* @param path Base path for ZeroTier node itself
|
||||
* @param lfOwnerPrivate LF owner private in PEM format
|
||||
* @param lfOwnerPublic LF owner public in @base62 format
|
||||
* @param lfNodeHost LF node host
|
||||
* @param lfNodePort LF node http (not https) port
|
||||
* @param storeOnlineState If true, store online/offline state and IP info in LF (a lot of data, only for private networks!)
|
||||
*/
|
||||
LFDB(const Identity &myId,const char *path,const char *lfOwnerPrivate,const char *lfOwnerPublic,const char *lfNodeHost,int lfNodePort,bool storeOnlineState);
|
||||
virtual ~LFDB();
|
||||
|
||||
virtual bool waitForReady();
|
||||
virtual bool isReady();
|
||||
virtual void save(nlohmann::json *orig,nlohmann::json &record);
|
||||
virtual void eraseNetwork(const uint64_t networkId);
|
||||
virtual void eraseMember(const uint64_t networkId,const uint64_t memberId);
|
||||
virtual void nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress);
|
||||
|
||||
protected:
|
||||
const Identity _myId;
|
||||
|
||||
std::string _lfOwnerPrivate,_lfOwnerPublic;
|
||||
std::string _lfNodeHost;
|
||||
int _lfNodePort;
|
||||
|
||||
struct _MemberState
|
||||
{
|
||||
_MemberState() :
|
||||
lastOnlineAddress(),
|
||||
lastOnlineTime(0),
|
||||
dirty(false),
|
||||
lastOnlineDirty(false) {}
|
||||
InetAddress lastOnlineAddress;
|
||||
int64_t lastOnlineTime;
|
||||
bool dirty;
|
||||
bool lastOnlineDirty;
|
||||
};
|
||||
struct _NetworkState
|
||||
{
|
||||
_NetworkState() :
|
||||
members(),
|
||||
dirty(false) {}
|
||||
std::unordered_map<uint64_t,_MemberState> members;
|
||||
bool dirty;
|
||||
};
|
||||
std::unordered_map<uint64_t,_NetworkState> _state;
|
||||
std::mutex _state_l;
|
||||
|
||||
std::atomic_bool _running;
|
||||
std::atomic_bool _ready;
|
||||
std::thread _syncThread;
|
||||
bool _storeOnlineState;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
1571
controller/PostgreSQL.cpp
Normal file
1571
controller/PostgreSQL.cpp
Normal file
File diff suppressed because it is too large
Load Diff
117
controller/PostgreSQL.hpp
Normal file
117
controller/PostgreSQL.hpp
Normal file
@ -0,0 +1,117 @@
|
||||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2019 ZeroTier, Inc. https://www.zerotier.com/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* You can be released from the requirements of the license by purchasing
|
||||
* a commercial license. Buying such a license is mandatory as soon as you
|
||||
* develop commercial closed-source software that incorporates or links
|
||||
* directly against ZeroTier software without disclosing the source code
|
||||
* of your own application.
|
||||
*/
|
||||
|
||||
#ifdef ZT_CONTROLLER_USE_LIBPQ
|
||||
|
||||
#ifndef ZT_CONTROLLER_LIBPQ_HPP
|
||||
#define ZT_CONTROLLER_LIBPQ_HPP
|
||||
|
||||
#include "DB.hpp"
|
||||
|
||||
#define ZT_CENTRAL_CONTROLLER_COMMIT_THREADS 4
|
||||
|
||||
extern "C" {
|
||||
typedef struct pg_conn PGconn;
|
||||
}
|
||||
|
||||
namespace ZeroTier
|
||||
{
|
||||
|
||||
struct MQConfig;
|
||||
|
||||
/**
|
||||
* A controller database driver that talks to PostgreSQL
|
||||
*
|
||||
* This is for use with ZeroTier Central. Others are free to build and use it
|
||||
* but be aware taht we might change it at any time.
|
||||
*/
|
||||
class PostgreSQL : public DB
|
||||
{
|
||||
public:
|
||||
PostgreSQL(const Identity &myId, const char *path, int listenPort, MQConfig *mqc = NULL);
|
||||
virtual ~PostgreSQL();
|
||||
|
||||
virtual bool waitForReady();
|
||||
virtual bool isReady();
|
||||
virtual void save(nlohmann::json *orig, nlohmann::json &record);
|
||||
virtual void eraseNetwork(const uint64_t networkId);
|
||||
virtual void eraseMember(const uint64_t networkId, const uint64_t memberId);
|
||||
virtual void nodeIsOnline(const uint64_t networkId, const uint64_t memberId, const InetAddress &physicalAddress);
|
||||
|
||||
protected:
|
||||
struct _PairHasher
|
||||
{
|
||||
inline std::size_t operator()(const std::pair<uint64_t,uint64_t> &p) const { return (std::size_t)(p.first ^ p.second); }
|
||||
};
|
||||
|
||||
private:
|
||||
void initializeNetworks(PGconn *conn);
|
||||
void initializeMembers(PGconn *conn);
|
||||
void heartbeat();
|
||||
void membersDbWatcher();
|
||||
void _membersWatcher_Postgres(PGconn *conn);
|
||||
void _membersWatcher_RabbitMQ();
|
||||
void networksDbWatcher();
|
||||
void _networksWatcher_Postgres(PGconn *conn);
|
||||
void _networksWatcher_RabbitMQ();
|
||||
|
||||
void commitThread();
|
||||
void onlineNotificationThread();
|
||||
|
||||
enum OverrideMode {
|
||||
ALLOW_PGBOUNCER_OVERRIDE = 0,
|
||||
NO_OVERRIDE = 1
|
||||
};
|
||||
|
||||
PGconn * getPgConn( OverrideMode m = ALLOW_PGBOUNCER_OVERRIDE );
|
||||
|
||||
std::string _connString;
|
||||
|
||||
BlockingQueue<nlohmann::json *> _commitQueue;
|
||||
|
||||
std::thread _heartbeatThread;
|
||||
std::thread _membersDbWatcher;
|
||||
std::thread _networksDbWatcher;
|
||||
std::thread _commitThread[ZT_CENTRAL_CONTROLLER_COMMIT_THREADS];
|
||||
std::thread _onlineNotificationThread;
|
||||
|
||||
std::unordered_map< std::pair<uint64_t,uint64_t>,std::pair<int64_t,InetAddress>,_PairHasher > _lastOnline;
|
||||
|
||||
mutable std::mutex _lastOnline_l;
|
||||
mutable std::mutex _readyLock;
|
||||
std::atomic<int> _ready, _connected, _run;
|
||||
mutable volatile bool _waitNoticePrinted;
|
||||
|
||||
int _listenPort;
|
||||
|
||||
MQConfig *_mqc;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // ZT_CONTROLLER_LIBPQ_HPP
|
||||
|
||||
#endif // ZT_CONTROLLER_USE_LIBPQ
|
@ -19,9 +19,9 @@ Since ZeroTier nodes are mobile and do not need static IPs, implementing high av
|
||||
|
||||
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.
|
||||
|
||||
### RethinkDB Database Implementation
|
||||
### PostgreSQL Database Implementation
|
||||
|
||||
The default controller stores its data in the filesystem in `controller.d` under ZeroTier's home folder. There's an alternative implementation that stores data in RethinkDB that can be built with `make central-controller`. Right now this is only guaranteed to build and run on Linux and is designed for use with [ZeroTier Central](https://my.zerotier.com/). You're welcome to use it but we don't "officially" support it for end-user use and it could change at any time.
|
||||
The default controller stores its data in the filesystem in `controller.d` under ZeroTier's home folder. There's an alternative implementation that stores data in PostgreSQL that can be built with `make central-controller`. Right now this is only guaranteed to build and run on Centos 7 Linux with PostgreSQL 10 installed via the [PostgreSQL Yum Repository](https://www.postgresql.org/download/linux/redhat/) and is designed for use with [ZeroTier Central](https://my.zerotier.com/). You're welcome to use it but we don't "officially" support it for end-user use and it could change at any time.
|
||||
|
||||
### Upgrading from Older (1.1.14 or earlier) Versions
|
||||
|
||||
@ -208,14 +208,6 @@ Important notes about rules engine behavior:
|
||||
|
||||
This returns a JSON object containing all member IDs as keys and their `memberRevisionCounter` values as values.
|
||||
|
||||
#### `/controller/network/<network ID>/active`
|
||||
|
||||
* Purpose: Get a set of all active members on this network
|
||||
* Methods: GET
|
||||
* Returns: { object }
|
||||
|
||||
This returns an object containing all currently online members and the most recent `recentLog` entries for their last request.
|
||||
|
||||
#### `/controller/network/<network ID>/member/<address>`
|
||||
|
||||
* Purpose: Create, authorize, or remove a network member
|
||||
|
107
controller/RabbitMQ.cpp
Normal file
107
controller/RabbitMQ.cpp
Normal file
@ -0,0 +1,107 @@
|
||||
#include "RabbitMQ.hpp"
|
||||
|
||||
#ifdef ZT_CONTROLLER_USE_LIBPQ
|
||||
|
||||
#include <amqp.h>
|
||||
#include <amqp_tcp_socket.h>
|
||||
#include <stdexcept>
|
||||
#include <cstring>
|
||||
|
||||
namespace ZeroTier
|
||||
{
|
||||
|
||||
RabbitMQ::RabbitMQ(MQConfig *cfg, const char *queueName)
|
||||
: _mqc(cfg)
|
||||
, _qName(queueName)
|
||||
, _socket(NULL)
|
||||
, _status(0)
|
||||
{
|
||||
}
|
||||
|
||||
RabbitMQ::~RabbitMQ()
|
||||
{
|
||||
amqp_channel_close(_conn, _channel, AMQP_REPLY_SUCCESS);
|
||||
amqp_connection_close(_conn, AMQP_REPLY_SUCCESS);
|
||||
amqp_destroy_connection(_conn);
|
||||
}
|
||||
|
||||
void RabbitMQ::init()
|
||||
{
|
||||
struct timeval tval;
|
||||
memset(&tval, 0, sizeof(struct timeval));
|
||||
tval.tv_sec = 5;
|
||||
|
||||
fprintf(stderr, "Initializing RabbitMQ %s\n", _qName);
|
||||
_conn = amqp_new_connection();
|
||||
_socket = amqp_tcp_socket_new(_conn);
|
||||
if (!_socket) {
|
||||
throw std::runtime_error("Can't create socket for RabbitMQ");
|
||||
}
|
||||
|
||||
_status = amqp_socket_open_noblock(_socket, _mqc->host, _mqc->port, &tval);
|
||||
if (_status) {
|
||||
throw std::runtime_error("Can't connect to RabbitMQ");
|
||||
}
|
||||
|
||||
amqp_rpc_reply_t r = amqp_login(_conn, "/", 0, 131072, 0, AMQP_SASL_METHOD_PLAIN,
|
||||
_mqc->username, _mqc->password);
|
||||
if (r.reply_type != AMQP_RESPONSE_NORMAL) {
|
||||
throw std::runtime_error("RabbitMQ Login Error");
|
||||
}
|
||||
|
||||
static int chan = 0;
|
||||
{
|
||||
Mutex::Lock l(_chan_m);
|
||||
_channel = ++chan;
|
||||
}
|
||||
amqp_channel_open(_conn, _channel);
|
||||
r = amqp_get_rpc_reply(_conn);
|
||||
if(r.reply_type != AMQP_RESPONSE_NORMAL) {
|
||||
throw std::runtime_error("Error opening communication channel");
|
||||
}
|
||||
|
||||
_q = amqp_queue_declare(_conn, _channel, amqp_cstring_bytes(_qName), 0, 0, 0, 0, amqp_empty_table);
|
||||
r = amqp_get_rpc_reply(_conn);
|
||||
if (r.reply_type != AMQP_RESPONSE_NORMAL) {
|
||||
throw std::runtime_error("Error declaring queue " + std::string(_qName));
|
||||
}
|
||||
|
||||
amqp_basic_consume(_conn, _channel, amqp_cstring_bytes(_qName), amqp_empty_bytes, 0, 1, 0, amqp_empty_table);
|
||||
r = amqp_get_rpc_reply(_conn);
|
||||
if (r.reply_type != AMQP_RESPONSE_NORMAL) {
|
||||
throw std::runtime_error("Error consuming queue " + std::string(_qName));
|
||||
}
|
||||
fprintf(stderr, "RabbitMQ Init OK %s\n", _qName);
|
||||
}
|
||||
|
||||
std::string RabbitMQ::consume()
|
||||
{
|
||||
amqp_rpc_reply_t res;
|
||||
amqp_envelope_t envelope;
|
||||
amqp_maybe_release_buffers(_conn);
|
||||
|
||||
struct timeval timeout;
|
||||
timeout.tv_sec = 1;
|
||||
timeout.tv_usec = 0;
|
||||
|
||||
res = amqp_consume_message(_conn, &envelope, &timeout, 0);
|
||||
if (res.reply_type != AMQP_RESPONSE_NORMAL) {
|
||||
if (res.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION && res.library_error == AMQP_STATUS_TIMEOUT) {
|
||||
// timeout waiting for message. Return empty string
|
||||
return "";
|
||||
} else {
|
||||
throw std::runtime_error("Error getting message");
|
||||
}
|
||||
}
|
||||
|
||||
std::string msg(
|
||||
(const char*)envelope.message.body.bytes,
|
||||
envelope.message.body.len
|
||||
);
|
||||
amqp_destroy_envelope(&envelope);
|
||||
return msg;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // ZT_CONTROLLER_USE_LIBPQ
|
79
controller/RabbitMQ.hpp
Normal file
79
controller/RabbitMQ.hpp
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2019 ZeroTier, Inc. https://www.zerotier.com/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* You can be released from the requirements of the license by purchasing
|
||||
* a commercial license. Buying such a license is mandatory as soon as you
|
||||
* develop commercial closed-source software that incorporates or links
|
||||
* directly against ZeroTier software without disclosing the source code
|
||||
* of your own application.
|
||||
*/
|
||||
#ifndef ZT_CONTROLLER_RABBITMQ_HPP
|
||||
#define ZT_CONTROLLER_RABBITMQ_HPP
|
||||
|
||||
namespace ZeroTier
|
||||
{
|
||||
struct MQConfig {
|
||||
const char *host;
|
||||
int port;
|
||||
const char *username;
|
||||
const char *password;
|
||||
};
|
||||
}
|
||||
|
||||
#ifdef ZT_CONTROLLER_USE_LIBPQ
|
||||
|
||||
#include "../node/Mutex.hpp"
|
||||
|
||||
#include <amqp.h>
|
||||
#include <amqp_tcp_socket.h>
|
||||
#include <string>
|
||||
|
||||
namespace ZeroTier
|
||||
{
|
||||
|
||||
class RabbitMQ {
|
||||
public:
|
||||
RabbitMQ(MQConfig *cfg, const char *queueName);
|
||||
~RabbitMQ();
|
||||
|
||||
void init();
|
||||
|
||||
std::string consume();
|
||||
|
||||
private:
|
||||
MQConfig *_mqc;
|
||||
const char *_qName;
|
||||
|
||||
amqp_socket_t *_socket;
|
||||
amqp_connection_state_t _conn;
|
||||
amqp_queue_declare_ok_t *_q;
|
||||
int _status;
|
||||
|
||||
int _channel;
|
||||
|
||||
Mutex _chan_m;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // ZT_CONTROLLER_USE_LIBPQ
|
||||
|
||||
#endif // ZT_CONTROLLER_RABBITMQ_HPP
|
||||
|
@ -1,497 +0,0 @@
|
||||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2018 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//#define ZT_CONTROLLER_USE_RETHINKDB
|
||||
|
||||
#ifdef ZT_CONTROLLER_USE_RETHINKDB
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "RethinkDB.hpp"
|
||||
#include "EmbeddedNetworkController.hpp"
|
||||
|
||||
#include "../version.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "../ext/librethinkdbxx/build/include/rethinkdb.h"
|
||||
|
||||
namespace R = RethinkDB;
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
static const char *_timestr()
|
||||
{
|
||||
time_t t = time(0);
|
||||
char *ts = ctime(&t);
|
||||
char *p = ts;
|
||||
if (!p)
|
||||
return "";
|
||||
while (*p) {
|
||||
if (*p == '\n') {
|
||||
*p = (char)0;
|
||||
break;
|
||||
}
|
||||
++p;
|
||||
}
|
||||
return ts;
|
||||
}
|
||||
|
||||
RethinkDB::RethinkDB(EmbeddedNetworkController *const nc,const Identity &myId,const char *path) :
|
||||
DB(nc,myId,path),
|
||||
_ready(2), // two tables need to be synchronized before we're ready, so this is ready when it reaches 0
|
||||
_run(1),
|
||||
_waitNoticePrinted(false)
|
||||
{
|
||||
// rethinkdb:host:port:db[:auth]
|
||||
std::vector<std::string> ps(OSUtils::split(path,":","",""));
|
||||
if ((ps.size() < 4)||(ps[0] != "rethinkdb"))
|
||||
throw std::runtime_error("invalid rethinkdb database url");
|
||||
_host = ps[1];
|
||||
_port = Utils::strToInt(ps[2].c_str());
|
||||
_db = ps[3];
|
||||
if (ps.size() > 4)
|
||||
_auth = ps[4];
|
||||
|
||||
_readyLock.lock();
|
||||
|
||||
_membersDbWatcher = std::thread([this]() {
|
||||
try {
|
||||
while (_run == 1) {
|
||||
try {
|
||||
std::unique_ptr<R::Connection> rdb(R::connect(this->_host,this->_port,this->_auth));
|
||||
if (rdb) {
|
||||
_membersDbWatcherConnection = (void *)rdb.get();
|
||||
auto cur = R::db(this->_db).table("Member",R::optargs("read_mode","outdated")).get_all(this->_myAddressStr,R::optargs("index","controllerId")).changes(R::optargs("squash",0.05,"include_initial",true,"include_types",true,"include_states",true)).run(*rdb);
|
||||
while (cur.has_next()) {
|
||||
if (_run != 1) break;
|
||||
json tmp(json::parse(cur.next().as_json()));
|
||||
if ((tmp["type"] == "state")&&(tmp["state"] == "ready")) {
|
||||
if (--this->_ready == 0) {
|
||||
if (_waitNoticePrinted)
|
||||
fprintf(stderr,"[%s] NOTICE: %.10llx controller RethinkDB data download complete." ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt());
|
||||
this->_readyLock.unlock();
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
json &ov = tmp["old_val"];
|
||||
json &nv = tmp["new_val"];
|
||||
json oldConfig,newConfig;
|
||||
if (ov.is_object()) oldConfig = ov["config"];
|
||||
if (nv.is_object()) newConfig = nv["config"];
|
||||
if (oldConfig.is_object()||newConfig.is_object())
|
||||
this->_memberChanged(oldConfig,newConfig,(this->_ready <= 0));
|
||||
} catch ( ... ) {} // ignore bad records
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (std::exception &e) {
|
||||
fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (member change stream): %s" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt(),e.what());
|
||||
} catch (R::Error &e) {
|
||||
fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (member change stream): %s" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt(),e.message.c_str());
|
||||
} catch ( ... ) {
|
||||
fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (member change stream): unknown exception" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt());
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
||||
}
|
||||
} catch ( ... ) {}
|
||||
});
|
||||
|
||||
_networksDbWatcher = std::thread([this]() {
|
||||
try {
|
||||
while (_run == 1) {
|
||||
try {
|
||||
std::unique_ptr<R::Connection> rdb(R::connect(this->_host,this->_port,this->_auth));
|
||||
if (rdb) {
|
||||
_networksDbWatcherConnection = (void *)rdb.get();
|
||||
auto cur = R::db(this->_db).table("Network",R::optargs("read_mode","outdated")).get_all(this->_myAddressStr,R::optargs("index","controllerId")).changes(R::optargs("squash",0.05,"include_initial",true,"include_types",true,"include_states",true)).run(*rdb);
|
||||
while (cur.has_next()) {
|
||||
if (_run != 1) break;
|
||||
json tmp(json::parse(cur.next().as_json()));
|
||||
if ((tmp["type"] == "state")&&(tmp["state"] == "ready")) {
|
||||
if (--this->_ready == 0) {
|
||||
if (_waitNoticePrinted)
|
||||
fprintf(stderr,"[%s] NOTICE: %.10llx controller RethinkDB data download complete." ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt());
|
||||
this->_readyLock.unlock();
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
json &ov = tmp["old_val"];
|
||||
json &nv = tmp["new_val"];
|
||||
json oldConfig,newConfig;
|
||||
if (ov.is_object()) oldConfig = ov["config"];
|
||||
if (nv.is_object()) newConfig = nv["config"];
|
||||
if (oldConfig.is_object()||newConfig.is_object())
|
||||
this->_networkChanged(oldConfig,newConfig,(this->_ready <= 0));
|
||||
} catch ( ... ) {} // ignore bad records
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (std::exception &e) {
|
||||
fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (network change stream): %s" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt(),e.what());
|
||||
} catch (R::Error &e) {
|
||||
fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (network change stream): %s" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt(),e.message.c_str());
|
||||
} catch ( ... ) {
|
||||
fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (network change stream): unknown exception" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt());
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
||||
}
|
||||
} catch ( ... ) {}
|
||||
});
|
||||
|
||||
for(int t=0;t<ZT_CONTROLLER_RETHINKDB_COMMIT_THREADS;++t) {
|
||||
_commitThread[t] = std::thread([this]() {
|
||||
try {
|
||||
std::unique_ptr<R::Connection> rdb;
|
||||
nlohmann::json *config = (nlohmann::json *)0;
|
||||
while ((this->_commitQueue.get(config))&&(_run == 1)) {
|
||||
if (!config)
|
||||
continue;
|
||||
nlohmann::json record;
|
||||
const char *table = (const char *)0;
|
||||
std::string deleteId;
|
||||
try {
|
||||
const std::string objtype = (*config)["objtype"];
|
||||
if (objtype == "member") {
|
||||
const std::string nwid = (*config)["nwid"];
|
||||
const std::string id = (*config)["id"];
|
||||
record["id"] = nwid + "-" + id;
|
||||
record["controllerId"] = this->_myAddressStr;
|
||||
record["networkId"] = nwid;
|
||||
record["nodeId"] = id;
|
||||
record["config"] = *config;
|
||||
table = "Member";
|
||||
} else if (objtype == "network") {
|
||||
const std::string id = (*config)["id"];
|
||||
record["id"] = id;
|
||||
record["controllerId"] = this->_myAddressStr;
|
||||
record["config"] = *config;
|
||||
table = "Network";
|
||||
} else if (objtype == "trace") {
|
||||
record = *config;
|
||||
table = "RemoteTrace";
|
||||
} else if (objtype == "_delete_network") {
|
||||
deleteId = (*config)["id"];
|
||||
table = "Network";
|
||||
} else if (objtype == "_delete_member") {
|
||||
deleteId = (*config)["nwid"];
|
||||
deleteId.push_back('-');
|
||||
const std::string tmp = (*config)["id"];
|
||||
deleteId.append(tmp);
|
||||
table = "Member";
|
||||
}
|
||||
} catch (std::exception &e) {
|
||||
fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (insert/update record creation): %s" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt(),e.what());
|
||||
table = (const char *)0;
|
||||
} catch (R::Error &e) {
|
||||
fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (insert/update record creation): %s" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt(),e.message.c_str());
|
||||
table = (const char *)0;
|
||||
} catch ( ... ) {
|
||||
fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (insert/update record creation): unknown exception" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt());
|
||||
table = (const char *)0;
|
||||
}
|
||||
delete config;
|
||||
if (!table)
|
||||
continue;
|
||||
const std::string jdump(OSUtils::jsonDump(record,-1));
|
||||
|
||||
while (_run == 1) {
|
||||
try {
|
||||
if (!rdb)
|
||||
rdb = R::connect(this->_host,this->_port,this->_auth);
|
||||
if (rdb) {
|
||||
if (deleteId.length() > 0) {
|
||||
//printf("DELETE: %s" ZT_EOL_S,deleteId.c_str());
|
||||
R::db(this->_db).table(table).get(deleteId).delete_().run(*rdb);
|
||||
} else {
|
||||
//printf("UPSERT: %s" ZT_EOL_S,record.dump().c_str());
|
||||
R::db(this->_db).table(table).insert(R::Datum::from_json(jdump),R::optargs("conflict","update","return_changes",false)).run(*rdb);
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (insert/update): connect failed (will retry)" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt());
|
||||
rdb.reset();
|
||||
}
|
||||
} catch (std::exception &e) {
|
||||
fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (insert/update): %s [%s]" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt(),e.what(),jdump.c_str());
|
||||
rdb.reset();
|
||||
} catch (R::Error &e) {
|
||||
fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (insert/update): %s [%s]" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt(),e.message.c_str(),jdump.c_str());
|
||||
rdb.reset();
|
||||
} catch ( ... ) {
|
||||
fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (insert/update): unknown exception [%s]" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt(),jdump.c_str());
|
||||
rdb.reset();
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
||||
}
|
||||
}
|
||||
} catch (std::exception &e) {
|
||||
fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (insert/update outer loop): %s" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt(),e.what());
|
||||
} catch (R::Error &e) {
|
||||
fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (insert/update outer loop): %s" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt(),e.message.c_str());
|
||||
} catch ( ... ) {
|
||||
fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (insert/update outer loop): unknown exception" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_onlineNotificationThread = std::thread([this]() {
|
||||
int64_t lastUpdatedNetworkStatus = 0;
|
||||
std::unordered_map< std::pair<uint64_t,uint64_t>,int64_t,_PairHasher > lastOnlineCumulative;
|
||||
try {
|
||||
std::unique_ptr<R::Connection> rdb;
|
||||
while (_run == 1) {
|
||||
try {
|
||||
if (!rdb) {
|
||||
_connected = 0;
|
||||
rdb = R::connect(this->_host,this->_port,this->_auth);
|
||||
}
|
||||
|
||||
if (rdb) {
|
||||
_connected = 1;
|
||||
R::Array batch;
|
||||
R::Object tmpobj;
|
||||
|
||||
std::unordered_map< std::pair<uint64_t,uint64_t>,std::pair<int64_t,InetAddress>,_PairHasher > lastOnline;
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_lastOnline_l);
|
||||
lastOnline.swap(_lastOnline);
|
||||
}
|
||||
|
||||
for(auto i=lastOnline.begin();i!=lastOnline.end();++i) {
|
||||
lastOnlineCumulative[i->first] = i->second.first;
|
||||
char tmp[64],tmp2[64];
|
||||
OSUtils::ztsnprintf(tmp,sizeof(tmp),"%.16llx-%.10llx",i->first.first,i->first.second);
|
||||
tmpobj["id"] = tmp;
|
||||
tmpobj["ts"] = i->second.first;
|
||||
tmpobj["phy"] = i->second.second.toIpString(tmp2);
|
||||
batch.emplace_back(tmpobj);
|
||||
if (batch.size() >= 1024) {
|
||||
R::db(this->_db).table("MemberStatus",R::optargs("read_mode","outdated")).insert(batch,R::optargs("conflict","update")).run(*rdb);
|
||||
batch.clear();
|
||||
}
|
||||
}
|
||||
if (batch.size() > 0) {
|
||||
R::db(this->_db).table("MemberStatus",R::optargs("read_mode","outdated")).insert(batch,R::optargs("conflict","update")).run(*rdb);
|
||||
batch.clear();
|
||||
}
|
||||
tmpobj.clear();
|
||||
|
||||
const int64_t now = OSUtils::now();
|
||||
if ((now - lastUpdatedNetworkStatus) > 10000) {
|
||||
lastUpdatedNetworkStatus = now;
|
||||
|
||||
std::vector< std::pair< uint64_t,std::shared_ptr<_Network> > > networks;
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_networks_l);
|
||||
networks.reserve(_networks.size() + 1);
|
||||
for(auto i=_networks.begin();i!=_networks.end();++i)
|
||||
networks.push_back(*i);
|
||||
}
|
||||
|
||||
for(auto i=networks.begin();i!=networks.end();++i) {
|
||||
char tmp[64];
|
||||
Utils::hex(i->first,tmp);
|
||||
tmpobj["id"] = tmp;
|
||||
{
|
||||
std::lock_guard<std::mutex> l2(i->second->lock);
|
||||
tmpobj["authorizedMemberCount"] = i->second->authorizedMembers.size();
|
||||
tmpobj["totalMemberCount"] = i->second->members.size();
|
||||
unsigned long onlineMemberCount = 0;
|
||||
for(auto m=i->second->members.begin();m!=i->second->members.end();++m) {
|
||||
auto lo = lastOnlineCumulative.find(std::pair<uint64_t,uint64_t>(i->first,m->first));
|
||||
if (lo != lastOnlineCumulative.end()) {
|
||||
if ((now - lo->second) <= (ZT_NETWORK_AUTOCONF_DELAY * 2))
|
||||
++onlineMemberCount;
|
||||
else lastOnlineCumulative.erase(lo);
|
||||
}
|
||||
}
|
||||
tmpobj["onlineMemberCount"] = onlineMemberCount;
|
||||
tmpobj["bridgeCount"] = i->second->activeBridgeMembers.size();
|
||||
tmpobj["ts"] = now;
|
||||
}
|
||||
batch.emplace_back(tmpobj);
|
||||
if (batch.size() >= 1024) {
|
||||
R::db(this->_db).table("NetworkStatus",R::optargs("read_mode","outdated")).insert(batch,R::optargs("conflict","update")).run(*rdb);
|
||||
batch.clear();
|
||||
}
|
||||
}
|
||||
if (batch.size() > 0) {
|
||||
R::db(this->_db).table("NetworkStatus",R::optargs("read_mode","outdated")).insert(batch,R::optargs("conflict","update")).run(*rdb);
|
||||
batch.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (std::exception &e) {
|
||||
fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (node status update): %s" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt(),e.what());
|
||||
rdb.reset();
|
||||
} catch (R::Error &e) {
|
||||
fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (node status update): %s" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt(),e.message.c_str());
|
||||
rdb.reset();
|
||||
} catch ( ... ) {
|
||||
fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (node status update): unknown exception" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt());
|
||||
rdb.reset();
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
||||
}
|
||||
} catch ( ... ) {}
|
||||
});
|
||||
|
||||
_heartbeatThread = std::thread([this]() {
|
||||
try {
|
||||
R::Object controllerRecord;
|
||||
std::unique_ptr<R::Connection> rdb;
|
||||
|
||||
{
|
||||
char publicId[1024];
|
||||
//char secretId[1024];
|
||||
char hostname[1024];
|
||||
this->_myId.toString(false,publicId);
|
||||
//this->_myId.toString(true,secretId);
|
||||
if (gethostname(hostname,sizeof(hostname)) != 0) {
|
||||
hostname[0] = (char)0;
|
||||
} else {
|
||||
for(int i=0;i<sizeof(hostname);++i) {
|
||||
if ((hostname[i] == '.')||(hostname[i] == 0)) {
|
||||
hostname[i] = (char)0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
controllerRecord["id"] = this->_myAddressStr.c_str();
|
||||
controllerRecord["publicIdentity"] = publicId;
|
||||
//controllerRecord["secretIdentity"] = secretId;
|
||||
if (hostname[0])
|
||||
controllerRecord["clusterHost"] = hostname;
|
||||
controllerRecord["vMajor"] = ZEROTIER_ONE_VERSION_MAJOR;
|
||||
controllerRecord["vMinor"] = ZEROTIER_ONE_VERSION_MINOR;
|
||||
controllerRecord["vRev"] = ZEROTIER_ONE_VERSION_REVISION;
|
||||
controllerRecord["vBuild"] = ZEROTIER_ONE_VERSION_BUILD;
|
||||
}
|
||||
|
||||
while (_run == 1) {
|
||||
try {
|
||||
if (!rdb)
|
||||
rdb = R::connect(this->_host,this->_port,this->_auth);
|
||||
if (rdb) {
|
||||
controllerRecord["lastAlive"] = OSUtils::now();
|
||||
//printf("HEARTBEAT: %s" ZT_EOL_S,tmp);
|
||||
R::db(this->_db).table("Controller",R::optargs("read_mode","outdated")).insert(controllerRecord,R::optargs("conflict","update")).run(*rdb);
|
||||
}
|
||||
} catch ( ... ) {
|
||||
rdb.reset();
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
}
|
||||
} catch ( ... ) {}
|
||||
});
|
||||
}
|
||||
|
||||
RethinkDB::~RethinkDB()
|
||||
{
|
||||
_run = 0;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
_commitQueue.stop();
|
||||
for(int t=0;t<ZT_CONTROLLER_RETHINKDB_COMMIT_THREADS;++t)
|
||||
_commitThread[t].join();
|
||||
if (_membersDbWatcherConnection)
|
||||
((R::Connection *)_membersDbWatcherConnection)->close();
|
||||
if (_networksDbWatcherConnection)
|
||||
((R::Connection *)_networksDbWatcherConnection)->close();
|
||||
_membersDbWatcher.join();
|
||||
_networksDbWatcher.join();
|
||||
_heartbeatThread.join();
|
||||
_onlineNotificationThread.join();
|
||||
}
|
||||
|
||||
bool RethinkDB::waitForReady()
|
||||
{
|
||||
while (_ready > 0) {
|
||||
if (!_waitNoticePrinted) {
|
||||
_waitNoticePrinted = true;
|
||||
fprintf(stderr,"[%s] NOTICE: %.10llx controller RethinkDB waiting for initial data download..." ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt());
|
||||
}
|
||||
_readyLock.lock();
|
||||
_readyLock.unlock();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RethinkDB::isReady()
|
||||
{
|
||||
return ((_ready)&&(_connected));
|
||||
}
|
||||
|
||||
void RethinkDB::save(nlohmann::json *orig,nlohmann::json &record)
|
||||
{
|
||||
if (!record.is_object()) // sanity check
|
||||
return;
|
||||
waitForReady();
|
||||
if (orig) {
|
||||
if (*orig != record) {
|
||||
record["revision"] = OSUtils::jsonInt(record["revision"],0ULL) + 1;
|
||||
_commitQueue.post(new nlohmann::json(record));
|
||||
}
|
||||
} else {
|
||||
record["revision"] = 1;
|
||||
_commitQueue.post(new nlohmann::json(record));
|
||||
}
|
||||
}
|
||||
|
||||
void RethinkDB::eraseNetwork(const uint64_t networkId)
|
||||
{
|
||||
char tmp2[24];
|
||||
waitForReady();
|
||||
Utils::hex(networkId,tmp2);
|
||||
json *tmp = new json();
|
||||
(*tmp)["id"] = tmp2;
|
||||
(*tmp)["objtype"] = "_delete_network"; // pseudo-type, tells thread to delete network
|
||||
_commitQueue.post(tmp);
|
||||
}
|
||||
|
||||
void RethinkDB::eraseMember(const uint64_t networkId,const uint64_t memberId)
|
||||
{
|
||||
char tmp2[24];
|
||||
json *tmp = new json();
|
||||
waitForReady();
|
||||
Utils::hex(networkId,tmp2);
|
||||
(*tmp)["nwid"] = tmp2;
|
||||
Utils::hex10(memberId,tmp2);
|
||||
(*tmp)["id"] = tmp2;
|
||||
(*tmp)["objtype"] = "_delete_member"; // pseudo-type, tells thread to delete network
|
||||
_commitQueue.post(tmp);
|
||||
}
|
||||
|
||||
void RethinkDB::nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress)
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_lastOnline_l);
|
||||
std::pair<int64_t,InetAddress> &i = _lastOnline[std::pair<uint64_t,uint64_t>(networkId,memberId)];
|
||||
i.first = OSUtils::now();
|
||||
if (physicalAddress)
|
||||
i.second = physicalAddress;
|
||||
}
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif // ZT_CONTROLLER_USE_RETHINKDB
|
@ -1,84 +0,0 @@
|
||||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2018 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef ZT_CONTROLLER_USE_RETHINKDB
|
||||
|
||||
#ifndef ZT_CONTROLLER_RETHINKDB_HPP
|
||||
#define ZT_CONTROLLER_RETHINKDB_HPP
|
||||
|
||||
#include "DB.hpp"
|
||||
|
||||
#define ZT_CONTROLLER_RETHINKDB_COMMIT_THREADS 4
|
||||
|
||||
namespace ZeroTier
|
||||
{
|
||||
|
||||
/**
|
||||
* A controller database driver that talks to RethinkDB
|
||||
*
|
||||
* This is for use with ZeroTier Central. Others are free to build and use it
|
||||
* but be aware that we might change it at any time.
|
||||
*/
|
||||
class RethinkDB : public DB
|
||||
{
|
||||
public:
|
||||
RethinkDB(EmbeddedNetworkController *const nc,const Identity &myId,const char *path);
|
||||
virtual ~RethinkDB();
|
||||
|
||||
virtual bool waitForReady();
|
||||
virtual bool isReady();
|
||||
virtual void save(nlohmann::json *orig,nlohmann::json &record);
|
||||
virtual void eraseNetwork(const uint64_t networkId);
|
||||
virtual void eraseMember(const uint64_t networkId,const uint64_t memberId);
|
||||
virtual void nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress);
|
||||
|
||||
protected:
|
||||
struct _PairHasher
|
||||
{
|
||||
inline std::size_t operator()(const std::pair<uint64_t,uint64_t> &p) const { return (std::size_t)(p.first ^ p.second); }
|
||||
};
|
||||
|
||||
std::string _host;
|
||||
std::string _db;
|
||||
std::string _auth;
|
||||
int _port;
|
||||
|
||||
void *_networksDbWatcherConnection;
|
||||
void *_membersDbWatcherConnection;
|
||||
std::thread _networksDbWatcher;
|
||||
std::thread _membersDbWatcher;
|
||||
|
||||
BlockingQueue< nlohmann::json * > _commitQueue;
|
||||
std::thread _commitThread[ZT_CONTROLLER_RETHINKDB_COMMIT_THREADS];
|
||||
|
||||
std::unordered_map< std::pair<uint64_t,uint64_t>,std::pair<int64_t,InetAddress>,_PairHasher > _lastOnline;
|
||||
mutable std::mutex _lastOnline_l;
|
||||
std::thread _onlineNotificationThread;
|
||||
|
||||
std::thread _heartbeatThread;
|
||||
|
||||
mutable std::mutex _readyLock; // locked until ready
|
||||
std::atomic<int> _ready,_connected,_run;
|
||||
mutable volatile bool _waitNoticePrinted;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
||||
|
||||
#endif // ZT_CONTROLLER_USE_RETHINKDB
|
9
cycle_controllers.sh
Executable file
9
cycle_controllers.sh
Executable file
@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
CONTROLLERS=`kubectl get pods -o=name | grep controller | sed "s/^.\{4\}//"`
|
||||
|
||||
for c in ${CONTROLLERS[@]}
|
||||
do
|
||||
kubectl delete pod ${c}
|
||||
sleep 30
|
||||
done
|
6
debian/changelog
vendored
6
debian/changelog
vendored
@ -1,3 +1,9 @@
|
||||
zerotier-one (1.4.0) unstable; urgency=medium
|
||||
|
||||
* See https://github.com/zerotier/ZeroTierOne for release notes.
|
||||
|
||||
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 29 Jul 2019 01:00:00 -0700
|
||||
|
||||
zerotier-one (1.2.12) unstable; urgency=medium
|
||||
|
||||
* See https://github.com/zerotier/ZeroTierOne for release notes.
|
||||
|
19
docker/Dockerfile
Normal file
19
docker/Dockerfile
Normal file
@ -0,0 +1,19 @@
|
||||
# Dockerfile for ZeroTier Central Controllers
|
||||
FROM centos:7
|
||||
MAINTAINER Adam Ierymekno <adam.ierymenko@zerotier.com>, Grant Limberg <grant.limberg@zerotier.com>
|
||||
|
||||
RUN yum update -y
|
||||
RUN yum install -y https://download.postgresql.org/pub/repos/yum/10/redhat/rhel-7-x86_64/pgdg-centos10-10-2.noarch.rpm
|
||||
RUN yum install -y bash postgresql10 libpqxx-devel
|
||||
|
||||
RUN yum -y install epel-release && yum -y update && yum clean all
|
||||
RUN yum -y install clang jemalloc jemalloc-devel
|
||||
|
||||
|
||||
ADD zerotier-one /usr/local/bin/zerotier-one
|
||||
RUN chmod a+x /usr/local/bin/zerotier-one
|
||||
|
||||
ADD docker/main.sh /
|
||||
RUN chmod a+x /main.sh
|
||||
|
||||
ENTRYPOINT /main.sh
|
80
docker/main.sh
Normal file
80
docker/main.sh
Normal file
@ -0,0 +1,80 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ -z "$ZT_IDENTITY_PATH" ]; then
|
||||
echo '*** FAILED: ZT_IDENTITY_PATH environment variable is not defined'
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$ZT_DB_HOST" ]; then
|
||||
echo '*** FAILED: ZT_DB_HOST environment variable not defined'
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$ZT_DB_PORT" ]; then
|
||||
echo '*** FAILED: ZT_DB_PORT environment variable not defined'
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$ZT_DB_NAME" ]; then
|
||||
echo '*** FAILED: ZT_DB_NAME environment variable not defined'
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$ZT_DB_USER" ]; then
|
||||
echo '*** FAILED: ZT_DB_USER environment variable not defined'
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$ZT_DB_PASSWORD" ]; then
|
||||
echo '*** FAILED: ZT_DB_PASSWORD environment variable not defined'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
RMQ=""
|
||||
if [ "$ZT_USE_RABBITMQ" == "true" ]; then
|
||||
if [ -z "$RABBITMQ_HOST" ]; then
|
||||
echo '*** FAILED: RABBITMQ_HOST environment variable not defined'
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$RABBITMQ_PORT" ]; then
|
||||
echo '*** FAILED: RABBITMQ_PORT environment variable not defined'
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$RABBITMQ_USERNAME" ]; then
|
||||
echo '*** FAILED: RABBITMQ_USERNAME environment variable not defined'
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$RABBITMQ_PASSWORD" ]; then
|
||||
echo '*** FAILED: RABBITMQ_PASSWORD environment variable not defined'
|
||||
exit 1
|
||||
fi
|
||||
RMQ=", \"rabbitmq\": {
|
||||
\"host\": \"${RABBITMQ_HOST}\",
|
||||
\"port\": ${RABBITMQ_PORT},
|
||||
\"username\": \"${RABBITMQ_USERNAME}\",
|
||||
\"password\": \"${RABBITMQ_PASSWORD}\"
|
||||
}"
|
||||
fi
|
||||
|
||||
mkdir -p /var/lib/zerotier-one
|
||||
|
||||
pushd /var/lib/zerotier-one
|
||||
ln -s $ZT_IDENTITY_PATH/identity.public identity.public
|
||||
ln -s $ZT_IDENTITY_PATH/identity.secret identity.secret
|
||||
popd
|
||||
|
||||
DEFAULT_PORT=9993
|
||||
|
||||
echo "{
|
||||
\"settings\": {
|
||||
\"portMappingEnabled\": true,
|
||||
\"softwareUpdate\": \"disable\",
|
||||
\"interfacePrefixBlacklist\": [
|
||||
\"inot\",
|
||||
\"nat64\"
|
||||
],
|
||||
\"controllerDbPath\": \"postgres:host=${ZT_DB_HOST} port=${ZT_DB_PORT} dbname=${ZT_DB_NAME} user=${ZT_DB_USER} password=${ZT_DB_PASSWORD} sslmode=prefer sslcert=${DB_CLIENT_CERT} sslkey=${DB_CLIENT_KEY} sslrootcert=${DB_SERVER_CA}\"
|
||||
${RMQ}
|
||||
}
|
||||
}
|
||||
" > /var/lib/zerotier-one/local.conf
|
||||
|
||||
export GLIBCXX_FORCE_NEW=1
|
||||
export GLIBCPP_FORCE_NEW=1
|
||||
export LD_PRELOAD="/usr/lib64/libjemalloc.so"
|
||||
exec /usr/local/bin/zerotier-one -p${ZT_CONTROLLER_PORT:-$DEFAULT_PORT} /var/lib/zerotier-one
|
@ -5,8 +5,10 @@
|
||||
#include <sys/auxv.h>
|
||||
#include <asm/hwcap.h>
|
||||
#define zt_arm_has_neon() ((getauxval(AT_HWCAP) & HWCAP_NEON) != 0)
|
||||
#else
|
||||
#elif defined(__ARM_NEON__) || defined(__ARM_NEON)
|
||||
#define zt_arm_has_neon() (true)
|
||||
#else
|
||||
#define zt_arm_has_neon() (false)
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
@ -1,36 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>tap</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.zerotier.tap</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>tap</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>KEXT</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>20150118</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>OSBundleLibraries</key>
|
||||
<dict>
|
||||
<key>com.apple.kpi.mach</key>
|
||||
<string>8.0</string>
|
||||
<key>com.apple.kpi.bsd</key>
|
||||
<string>8.0</string>
|
||||
<key>com.apple.kpi.libkern</key>
|
||||
<string>8.0</string>
|
||||
<key>com.apple.kpi.unsupported</key>
|
||||
<string>8.0</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
|
Binary file not shown.
@ -1,105 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>files</key>
|
||||
<dict/>
|
||||
<key>files2</key>
|
||||
<dict/>
|
||||
<key>rules</key>
|
||||
<dict>
|
||||
<key>^Resources/</key>
|
||||
<true/>
|
||||
<key>^Resources/.*\.lproj/</key>
|
||||
<dict>
|
||||
<key>optional</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>1000</real>
|
||||
</dict>
|
||||
<key>^Resources/.*\.lproj/locversion.plist$</key>
|
||||
<dict>
|
||||
<key>omit</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>1100</real>
|
||||
</dict>
|
||||
<key>^version.plist$</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>rules2</key>
|
||||
<dict>
|
||||
<key>.*\.dSYM($|/)</key>
|
||||
<dict>
|
||||
<key>weight</key>
|
||||
<real>11</real>
|
||||
</dict>
|
||||
<key>^(.*/)?\.DS_Store$</key>
|
||||
<dict>
|
||||
<key>omit</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>2000</real>
|
||||
</dict>
|
||||
<key>^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/</key>
|
||||
<dict>
|
||||
<key>nested</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>10</real>
|
||||
</dict>
|
||||
<key>^.*</key>
|
||||
<true/>
|
||||
<key>^Info\.plist$</key>
|
||||
<dict>
|
||||
<key>omit</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>20</real>
|
||||
</dict>
|
||||
<key>^PkgInfo$</key>
|
||||
<dict>
|
||||
<key>omit</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>20</real>
|
||||
</dict>
|
||||
<key>^Resources/</key>
|
||||
<dict>
|
||||
<key>weight</key>
|
||||
<real>20</real>
|
||||
</dict>
|
||||
<key>^Resources/.*\.lproj/</key>
|
||||
<dict>
|
||||
<key>optional</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>1000</real>
|
||||
</dict>
|
||||
<key>^Resources/.*\.lproj/locversion.plist$</key>
|
||||
<dict>
|
||||
<key>omit</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>1100</real>
|
||||
</dict>
|
||||
<key>^[^/]+$</key>
|
||||
<dict>
|
||||
<key>nested</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>10</real>
|
||||
</dict>
|
||||
<key>^embedded\.provisionprofile$</key>
|
||||
<dict>
|
||||
<key>weight</key>
|
||||
<real>20</real>
|
||||
</dict>
|
||||
<key>^version\.plist$</key>
|
||||
<dict>
|
||||
<key>weight</key>
|
||||
<real>20</real>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
22
ext/cpp-httplib/LICENSE
Normal file
22
ext/cpp-httplib/LICENSE
Normal file
@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 yhirose
|
||||
|
||||
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.
|
||||
|
259
ext/cpp-httplib/README.md
Normal file
259
ext/cpp-httplib/README.md
Normal file
@ -0,0 +1,259 @@
|
||||
cpp-httplib
|
||||
===========
|
||||
|
||||
[](https://travis-ci.org/yhirose/cpp-httplib)
|
||||
[](https://ci.appveyor.com/project/yhirose/cpp-httplib)
|
||||
|
||||
A C++ header-only cross platform HTTP/HTTPS library.
|
||||
|
||||
It's extremely easy to setup. Just include **httplib.h** file in your code!
|
||||
|
||||
Inspired by [Sinatra](http://www.sinatrarb.com/) and [express](https://github.com/visionmedia/express).
|
||||
|
||||
Server Example
|
||||
--------------
|
||||
|
||||
```c++
|
||||
#include <httplib.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
using namespace httplib;
|
||||
|
||||
Server svr;
|
||||
|
||||
svr.Get("/hi", [](const Request& req, Response& res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) {
|
||||
auto numbers = req.matches[1];
|
||||
res.set_content(numbers, "text/plain");
|
||||
});
|
||||
|
||||
svr.listen("localhost", 1234);
|
||||
}
|
||||
```
|
||||
|
||||
`Post`, `Put`, `Delete` and `Options` methods are also supported.
|
||||
|
||||
### Bind a socket to multiple interfaces and any available port
|
||||
|
||||
```cpp
|
||||
int port = svr.bind_to_any_port("0.0.0.0");
|
||||
svr.listen_after_bind();
|
||||
```
|
||||
|
||||
### Method Chain
|
||||
|
||||
```cpp
|
||||
svr.Get("/get", [](const auto& req, auto& res) {
|
||||
res.set_content("get", "text/plain");
|
||||
})
|
||||
.Post("/post", [](const auto& req, auto& res) {
|
||||
res.set_content(req.body(), "text/plain");
|
||||
})
|
||||
.listen("localhost", 1234);
|
||||
```
|
||||
|
||||
### Static File Server
|
||||
|
||||
```cpp
|
||||
svr.set_base_dir("./www");
|
||||
```
|
||||
|
||||
### Logging
|
||||
|
||||
```cpp
|
||||
svr.set_logger([](const auto& req, const auto& res) {
|
||||
your_logger(req, res);
|
||||
});
|
||||
```
|
||||
|
||||
### Error Handler
|
||||
|
||||
```cpp
|
||||
svr.set_error_handler([](const auto& req, auto& res) {
|
||||
const char* fmt = "<p>Error Status: <span style='color:red;'>%d</span></p>";
|
||||
char buf[BUFSIZ];
|
||||
snprintf(buf, sizeof(buf), fmt, res.status);
|
||||
res.set_content(buf, "text/html");
|
||||
});
|
||||
```
|
||||
|
||||
### 'multipart/form-data' POST data
|
||||
|
||||
```cpp
|
||||
svr.Post("/multipart", [&](const auto& req, auto& res) {
|
||||
auto size = req.files.size();
|
||||
auto ret = req.has_file("name1"));
|
||||
const auto& file = req.get_file_value("name1");
|
||||
// file.filename;
|
||||
// file.content_type;
|
||||
auto body = req.body.substr(file.offset, file.length));
|
||||
})
|
||||
```
|
||||
|
||||
Client Example
|
||||
--------------
|
||||
|
||||
### GET
|
||||
|
||||
```c++
|
||||
#include <httplib.h>
|
||||
#include <iostream>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
httplib::Client cli("localhost", 1234);
|
||||
|
||||
auto res = cli.Get("/hi");
|
||||
if (res && res->status == 200) {
|
||||
std::cout << res->body << std::endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### GET with Content Receiver
|
||||
|
||||
```c++
|
||||
std::string body;
|
||||
auto res = cli.Get("/large-data", [&](const char *data, size_t len) {
|
||||
body.append(data, len);
|
||||
});
|
||||
assert(res->body.empty());
|
||||
```
|
||||
|
||||
### POST
|
||||
|
||||
```c++
|
||||
res = cli.Post("/post", "text", "text/plain");
|
||||
res = cli.Post("/person", "name=john1¬e=coder", "application/x-www-form-urlencoded");
|
||||
```
|
||||
|
||||
### POST with parameters
|
||||
|
||||
```c++
|
||||
httplib::Params params;
|
||||
params.emplace("name", "john");
|
||||
params.emplace("note", "coder");
|
||||
|
||||
auto res = cli.Post("/post", params);
|
||||
```
|
||||
or
|
||||
|
||||
```c++
|
||||
httplib::Params params{
|
||||
{ "name", "john" },
|
||||
{ "note", "coder" }
|
||||
};
|
||||
|
||||
auto res = cli.Post("/post", params);
|
||||
```
|
||||
|
||||
### PUT
|
||||
|
||||
```c++
|
||||
res = cli.Put("/resource/foo", "text", "text/plain");
|
||||
```
|
||||
|
||||
### DELETE
|
||||
|
||||
```c++
|
||||
res = cli.Delete("/resource/foo");
|
||||
```
|
||||
|
||||
### OPTIONS
|
||||
|
||||
```c++
|
||||
res = cli.Options("*");
|
||||
res = cli.Options("/resource/foo");
|
||||
```
|
||||
|
||||
### Connection Timeout
|
||||
|
||||
```c++
|
||||
httplib::Client cli("localhost", 8080, 5); // timeouts in 5 seconds
|
||||
```
|
||||
### With Progress Callback
|
||||
|
||||
```cpp
|
||||
httplib::Client client(url, port);
|
||||
|
||||
// prints: 0 / 000 bytes => 50% complete
|
||||
std::shared_ptr<httplib::Response> res =
|
||||
cli.Get("/", [](uint64_t len, uint64_t total) {
|
||||
printf("%lld / %lld bytes => %d%% complete\n",
|
||||
len, total,
|
||||
(int)((len/total)*100));
|
||||
return true; // return 'false' if you want to cancel the request.
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||

|
||||
|
||||
This feature was contributed by [underscorediscovery](https://github.com/yhirose/cpp-httplib/pull/23).
|
||||
|
||||
### Basic Authentication
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("httplib.org");
|
||||
|
||||
auto res = cli.Get("/basic-auth/hello/world", {
|
||||
httplib::make_basic_authentication_header("hello", "world")
|
||||
});
|
||||
// res->status should be 200
|
||||
// res->body should be "{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n".
|
||||
```
|
||||
|
||||
### Range
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("httpbin.org");
|
||||
|
||||
auto res = cli.Get("/range/32", {
|
||||
httplib::make_range_header(1, 10) // 'Range: bytes=1-10'
|
||||
});
|
||||
// res->status should be 206.
|
||||
// res->body should be "bcdefghijk".
|
||||
```
|
||||
|
||||
OpenSSL Support
|
||||
---------------
|
||||
|
||||
SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked.
|
||||
|
||||
```c++
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
|
||||
SSLServer svr("./cert.pem", "./key.pem");
|
||||
|
||||
SSLClient cli("localhost", 8080);
|
||||
cli.set_ca_cert_path("./ca-bundle.crt");
|
||||
cli.enable_server_certificate_verification(true);
|
||||
```
|
||||
|
||||
Zlib Support
|
||||
------------
|
||||
|
||||
'gzip' compression is available with `CPPHTTPLIB_ZLIB_SUPPORT`.
|
||||
|
||||
The server applies gzip compression to the following MIME type contents:
|
||||
|
||||
* all text types
|
||||
* image/svg+xml
|
||||
* application/javascript
|
||||
* application/json
|
||||
* application/xml
|
||||
* application/xhtml+xml
|
||||
|
||||
NOTE
|
||||
----
|
||||
|
||||
g++ 4.8 cannot build this library since `<regex>` in g++4.8 is [broken](https://stackoverflow.com/questions/12530406/is-gcc-4-8-or-earlier-buggy-about-regular-expressions).
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
MIT license (© 2019 Yuji Hirose)
|
2779
ext/cpp-httplib/httplib.h
Normal file
2779
ext/cpp-httplib/httplib.h
Normal file
File diff suppressed because it is too large
Load Diff
@ -124,18 +124,15 @@ void get_hram(unsigned char *hram, const unsigned char *sm, const unsigned char
|
||||
|
||||
extern void ZT_sha512internal(void *digest,const void *data,unsigned int len);
|
||||
|
||||
extern void ed25519_amd64_asm_sign(const unsigned char *sk,const unsigned char *pk,const unsigned char *m,const unsigned int mlen,unsigned char *sig)
|
||||
extern void ed25519_amd64_asm_sign(const unsigned char *sk,const unsigned char *pk,const unsigned char *digest,unsigned char *sig)
|
||||
{
|
||||
unsigned char az[64];
|
||||
unsigned char nonce[64];
|
||||
unsigned char hram[64];
|
||||
sc25519 sck, scs, scsk;
|
||||
ge25519 ger;
|
||||
unsigned char digest[64];
|
||||
unsigned int i;
|
||||
|
||||
ZT_sha512internal(digest,m,mlen);
|
||||
|
||||
ZT_sha512internal(az,sk,32);
|
||||
az[0] &= 248;
|
||||
az[31] &= 127;
|
||||
|
@ -1,20 +1,32 @@
|
||||
FROM alpine:latest
|
||||
MAINTAINER Adam Ierymenko <adam.ierymenko@zerotier.com>
|
||||
## NOTE: to retain configuration; mount a Docker volume, or use a bind-mount, on /var/lib/zerotier-one
|
||||
|
||||
LABEL version="1.2.4"
|
||||
FROM debian:buster-slim as builder
|
||||
|
||||
## Supports x86_64, x86, arm, and arm64
|
||||
|
||||
RUN apt-get update && apt-get install -y curl gnupg
|
||||
RUN apt-key adv --keyserver ha.pool.sks-keyservers.net --recv-keys 0x1657198823e52a61 && \
|
||||
echo "deb http://download.zerotier.com/debian/buster buster main" > /etc/apt/sources.list.d/zerotier.list
|
||||
RUN apt-get update && apt-get install -y zerotier-one=1.2.12
|
||||
RUN curl https://raw.githubusercontent.com/zerotier/ZeroTierOne/master/ext/installfiles/linux/zerotier-containerized/main.sh > /var/lib/zerotier-one/main.sh
|
||||
|
||||
FROM alpine:latest
|
||||
LABEL version="1.2.12"
|
||||
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 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
|
||||
# ZeroTier relies on UDP port 9993
|
||||
EXPOSE 9993/udp
|
||||
|
||||
RUN mkdir -p /var/lib/zerotier-one
|
||||
COPY --from=builder /usr/sbin/zerotier-cli /usr/sbin/zerotier-cli
|
||||
COPY --from=builder /usr/sbin/zerotier-idtool /usr/sbin/zerotier-idtool
|
||||
COPY --from=builder /usr/sbin/zerotier-one /usr/sbin/zerotier-one
|
||||
COPY --from=builder /var/lib/zerotier-one/main.sh /main.sh
|
||||
|
||||
ADD main.sh /
|
||||
RUN chmod 0755 /main.sh
|
||||
|
||||
ENTRYPOINT /main.sh
|
||||
ENTRYPOINT ["/main.sh"]
|
||||
CMD ["zerotier-one"]
|
||||
|
@ -7,4 +7,4 @@ if [ ! -e /dev/net/tun ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec /zerotier-one
|
||||
exec "$@"
|
||||
|
@ -109,9 +109,9 @@
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<integer>80</integer>
|
||||
<key>PATH</key>
|
||||
<string>../../bin/tap-mac/tap.kext</string>
|
||||
<string>../../../MacEthernetTapAgent</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
@ -664,7 +664,7 @@
|
||||
<key>USE_HFS+_COMPRESSION</key>
|
||||
<false/>
|
||||
<key>VERSION</key>
|
||||
<string>1.2.12</string>
|
||||
<string>1.4.0.1</string>
|
||||
</dict>
|
||||
<key>PROJECT_COMMENTS</key>
|
||||
<dict>
|
||||
|
@ -27,7 +27,7 @@ kextunload '/Library/Application Support/ZeroTier/One/tap.kext' >>/dev/null 2>&1
|
||||
echo "Removing ZeroTier One files..."
|
||||
|
||||
rm -rf '/Applications/ZeroTier One.app'
|
||||
rm -f '/usr/bin/zerotier-one' '/usr/bin/zerotier-idtool' '/usr/bin/zerotier-cli' '/Library/LaunchDaemons/com.zerotier.one.plist'
|
||||
rm -f '/usr/local/bin/zerotier-one' '/usr/local/bin/zerotier-idtool' '/usr/local/bin/zerotier-cli' '/Library/LaunchDaemons/com.zerotier.one.plist'
|
||||
|
||||
cd '/Library/Application Support/ZeroTier/One'
|
||||
if [ "`pwd`" = '/Library/Application Support/ZeroTier/One' ]; then
|
||||
|
@ -27,10 +27,10 @@
|
||||
<ROW Property="CTRLS" Value="2"/>
|
||||
<ROW Property="MSIFASTINSTALL" MultiBuildValue="DefaultBuild:2"/>
|
||||
<ROW Property="Manufacturer" Value="ZeroTier, Inc."/>
|
||||
<ROW Property="ProductCode" Value="1033:{855E8629-580C-4BDF-8B59-B9290C7E7BA5} " Type="16"/>
|
||||
<ROW Property="ProductCode" Value="1033:{FF7D9C9B-E9F3-460A-8EA9-9074AF186E1E} " Type="16"/>
|
||||
<ROW Property="ProductLanguage" Value="1033"/>
|
||||
<ROW Property="ProductName" Value="ZeroTier One"/>
|
||||
<ROW Property="ProductVersion" Value="1.2.12" Type="32"/>
|
||||
<ROW Property="ProductVersion" Value="1.4.0" Type="32"/>
|
||||
<ROW Property="REBOOT" MultiBuildValue="DefaultBuild:ReallySuppress"/>
|
||||
<ROW Property="RUNAPPLICATION" Value="1" Type="4"/>
|
||||
<ROW Property="SecureCustomProperties" Value="OLDPRODUCTS;AI_NEWERPRODUCTFOUND;AI_SETUPEXEPATH;SETUPEXEDIR"/>
|
||||
@ -64,7 +64,7 @@
|
||||
<ROW Directory="x86_Dir" Directory_Parent="tapwindows_Dir" DefaultDir="x86"/>
|
||||
</COMPONENT>
|
||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiCompsComponent">
|
||||
<ROW Component="AI_CustomARPName" ComponentId="{92D9A995-E340-41B2-98F5-F2DB3F6E8AD8}" Directory_="APPDIR" Attributes="4" KeyPath="DisplayName" Options="1"/>
|
||||
<ROW Component="AI_CustomARPName" ComponentId="{3771DD83-BFE1-467A-88C3-DE78086A2B44}" Directory_="APPDIR" Attributes="4" KeyPath="DisplayName" Options="1"/>
|
||||
<ROW Component="AI_DisableModify" ComponentId="{020DCABD-5D56-49B9-AF48-F07F0B55E590}" Directory_="APPDIR" Attributes="4" KeyPath="NoModify" Options="1"/>
|
||||
<ROW Component="AI_ExePath" ComponentId="{8E02B36C-7A19-429B-A93E-77A9261AC918}" Directory_="APPDIR" Attributes="4" KeyPath="AI_ExePath"/>
|
||||
<ROW Component="Hardcodet.Wpf.TaskbarNotification.dll" ComponentId="{BEA825AF-2555-44AF-BE40-47FFC16DCBA6}" Directory_="APPDIR" Attributes="0" KeyPath="Hardcodet.Wpf.TaskbarNotification.dll"/>
|
||||
@ -454,10 +454,10 @@
|
||||
<ROW XmlAttribute="xsischemaLocation" XmlElement="swidsoftware_identification_tag" Name="xsi:schemaLocation" Flags="14" Order="3" Value="http://standards.iso.org/iso/19770/-2/2008/schema.xsd software_identification_tag.xsd"/>
|
||||
</COMPONENT>
|
||||
<COMPONENT cid="caphyon.advinst.msicomp.XmlElementComponent">
|
||||
<ROW XmlElement="swidbuild" ParentElement="swidnumeric" Name="swid:build" Condition="1" Order="2" Flags="14" Text="12"/>
|
||||
<ROW XmlElement="swidbuild" ParentElement="swidnumeric" Name="swid:build" Condition="1" Order="2" Flags="14" Text="0"/>
|
||||
<ROW XmlElement="swidentitlement_required_indicator" ParentElement="swidsoftware_identification_tag" Name="swid:entitlement_required_indicator" Condition="1" Order="0" Flags="14" Text="false"/>
|
||||
<ROW XmlElement="swidmajor" ParentElement="swidnumeric" Name="swid:major" Condition="1" Order="0" Flags="14" Text="1"/>
|
||||
<ROW XmlElement="swidminor" ParentElement="swidnumeric" Name="swid:minor" Condition="1" Order="1" Flags="14" Text="2"/>
|
||||
<ROW XmlElement="swidminor" ParentElement="swidnumeric" Name="swid:minor" Condition="1" Order="1" Flags="14" Text="4"/>
|
||||
<ROW XmlElement="swidname" ParentElement="swidproduct_version" Name="swid:name" Condition="1" Order="0" Flags="14" Text="[ProductVersion]"/>
|
||||
<ROW XmlElement="swidname_1" ParentElement="swidsoftware_creator" Name="swid:name" Condition="1" Order="0" Flags="14" Text="ZeroTier, Inc."/>
|
||||
<ROW XmlElement="swidname_2" ParentElement="swidsoftware_licensor" Name="swid:name" Condition="1" Order="0" Flags="14" Text="ZeroTier, Inc."/>
|
||||
|
@ -1,11 +0,0 @@
|
||||
From: https://raw.githubusercontent.com/zerotier/ZeroTierOne/master/COPYING
|
||||
|
||||
LICENSE
|
||||
|
||||
ZeroTier One is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or (at
|
||||
your option) any later version.
|
||||
|
||||
See the file ‘LICENSE.GPL-3’ for the text of the GNU GPL version 3.
|
||||
If that file is not present, see <http://www.gnu.org/licenses/>.
|
@ -1,5 +0,0 @@
|
||||
VERIFICATION
|
||||
Verification is intended to assist the Chocolatey moderators and community
|
||||
in verifying that this package's contents are trustworthy.
|
||||
|
||||
Our MSI installer should be signed by ZeroTier, Inc. using a certificate from DigiCert.
|
@ -1,8 +0,0 @@
|
||||
$packageName = 'zerotier-one'
|
||||
$installerType = 'msi'
|
||||
$url = 'https://download.zerotier.com/RELEASES/1.2.4/dist/ZeroTier%20One.msi'
|
||||
$url64 = 'https://download.zerotier.com/RELEASES/1.2.4/dist/ZeroTier%20One.msi'
|
||||
$silentArgs = '/quiet'
|
||||
$validExitCodes = @(0,3010)
|
||||
|
||||
Install-ChocolateyPackage $packageName $installerType $silentArgs $url $url64 -validExitCodes $validExitCodes
|
@ -1,30 +0,0 @@
|
||||
$ErrorActionPreference = 'Stop';
|
||||
|
||||
$packageName = 'zerotier-one'
|
||||
$softwareName = 'ZeroTier One*'
|
||||
$installerType = 'MSI'
|
||||
|
||||
$silentArgs = '/qn /norestart'
|
||||
$validExitCodes = @(0, 3010, 1605, 1614, 1641)
|
||||
$uninstalled = $false
|
||||
|
||||
[array]$key = Get-UninstallRegistryKey -SoftwareName $softwareName
|
||||
|
||||
if ($key.Count -eq 1) {
|
||||
$key | % {
|
||||
$silentArgs = "$($_.PSChildName) $silentArgs"
|
||||
$file = ''
|
||||
Uninstall-ChocolateyPackage -PackageName $packageName `
|
||||
-FileType $installerType `
|
||||
-SilentArgs "$silentArgs" `
|
||||
-ValidExitCodes $validExitCodes `
|
||||
-File "$file"
|
||||
}
|
||||
} elseif ($key.Count -eq 0) {
|
||||
Write-Warning "$packageName has already been uninstalled by other means."
|
||||
} elseif ($key.Count -gt 1) {
|
||||
Write-Warning "$key.Count matches found!"
|
||||
Write-Warning "To prevent accidental data loss, no programs will be uninstalled."
|
||||
Write-Warning "Please alert package maintainer the following keys were matched:"
|
||||
$key | % {Write-Warning "- $_.DisplayName"}
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Read this before creating packages: https://github.com/chocolatey/chocolatey/wiki/CreatePackages -->
|
||||
<!-- It is especially important to read the above link to understand additional requirements when publishing packages to the community feed aka dot org (https://chocolatey.org/packages). -->
|
||||
|
||||
<!-- Test your packages in a test environment: https://github.com/chocolatey/chocolatey-test-environment -->
|
||||
|
||||
<!--
|
||||
This is a nuspec. It mostly adheres to https://docs.nuget.org/create/Nuspec-Reference. Chocolatey uses a special version of NuGet.Core that allows us to do more than was initially possible. As such there are certain things to be aware of:
|
||||
|
||||
* the package xmlns schema url may cause issues with nuget.exe
|
||||
* Any of the following elements can ONLY be used by choco tools - projectSourceUrl, docsUrl, mailingListUrl, bugTrackerUrl, packageSourceUrl, provides, conflicts, replaces
|
||||
* nuget.exe can still install packages with those elements but they are ignored. Any authoring tools or commands will error on those elements
|
||||
-->
|
||||
|
||||
<!-- You can embed software files directly into packages, as long as you are not bound by distribution rights. -->
|
||||
<!-- * If you are an organization making private packages, you probably have no issues here -->
|
||||
<!-- * If you are releasing to the community feed, you need to consider distribution rights. -->
|
||||
<!-- Do not remove this test for UTF-8: if “Ω” doesn’t appear as greek uppercase omega letter enclosed in quotation marks, you should use an editor that supports UTF-8, not this one. -->
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
|
||||
<metadata>
|
||||
<!-- == PACKAGE SPECIFIC SECTION == -->
|
||||
<!-- This section is about this package, although id and version have ties back to the software -->
|
||||
<!-- id is lowercase and if you want a good separator for words, use '-', not '.'. Dots are only acceptable as suffixes for certain types of packages, e.g. .install, .portable, .extension, .template -->
|
||||
<!-- If the software is cross-platform, attempt to use the same id as the debian/rpm package(s) if possible. -->
|
||||
<id>zerotier-one</id>
|
||||
<!-- version should MATCH as closely as possible with the underlying software -->
|
||||
<!-- Is the version a prerelease of a version? https://docs.nuget.org/create/versioning#creating-prerelease-packages -->
|
||||
<!-- Note that unstable versions like 0.0.1 can be considered a released version, but it's possible that one can release a 0.0.1-beta before you release a 0.0.1 version. If the version number is final, that is considered a released version and not a prerelease. -->
|
||||
<version>1.2.12</version>
|
||||
<!-- <packageSourceUrl>Where is this Chocolatey package located (think GitHub)? packageSourceUrl is highly recommended for the community feed</packageSourceUrl>-->
|
||||
<!-- owners is a poor name for maintainers of the package. It sticks around by this name for compatibility reasons. It basically means you. -->
|
||||
<!--<owners>ZeroTier, Inc.</owners>-->
|
||||
<!-- ============================== -->
|
||||
|
||||
<!-- == SOFTWARE SPECIFIC SECTION == -->
|
||||
<!-- This section is about the software itself -->
|
||||
<title>zerotier-one (Install)</title>
|
||||
<authors>ZeroTier, Inc.</authors>
|
||||
<!-- projectUrl is required for the community feed -->
|
||||
<projectUrl>https://www.zerotier.com/</projectUrl>
|
||||
<!--<iconUrl>https://www.zerotier.com/img/ZeroTierIcon.png</iconUrl>-->
|
||||
<!-- <copyright>2011-2016 ZeroTier, Inc.</copyright> -->
|
||||
<!-- If there is a license Url available, it is is required for the community feed -->
|
||||
<!-- <licenseUrl>https://raw.githubusercontent.com/zerotier/ZeroTierOne/master/COPYING</licenseUrl>
|
||||
<requireLicenseAcceptance>true</requireLicenseAcceptance>-->
|
||||
<!--<projectSourceUrl>https://github.com/zerotier/ZeroTierOne</projectSourceUrl>-->
|
||||
<!--<docsUrl>https://www.zerotier.com/</docsUrl>-->
|
||||
<!--<mailingListUrl></mailingListUrl>-->
|
||||
<!--<bugTrackerUrl>https://github.com/zerotier/ZeroTierOne/issues</bugTrackerUrl>-->
|
||||
<tags>zerotier-one admin</tags>
|
||||
<summary>ZeroTier One Virtual Network Endpoint for Windows</summary>
|
||||
<description>ZeroTier is a smart switch for Earth with VLAN capability. See https://www.zerotier.com/ for more information.</description>
|
||||
<!-- <releaseNotes>__REPLACE_OR_REMOVE__MarkDown_Okay</releaseNotes> -->
|
||||
<!-- =============================== -->
|
||||
|
||||
<!-- Specifying dependencies and version ranges? https://docs.nuget.org/create/versioning#specifying-version-ranges-in-.nuspec-files -->
|
||||
<!--<dependencies>
|
||||
<dependency id="" version="__MINIMUM_VERSION__" />
|
||||
<dependency id="" version="[__EXACT_VERSION__]" />
|
||||
<dependency id="" version="[_MIN_VERSION_INCLUSIVE, MAX_VERSION_INCLUSIVE]" />
|
||||
<dependency id="" version="[_MIN_VERSION_INCLUSIVE, MAX_VERSION_EXCLUSIVE)" />
|
||||
<dependency id="" />
|
||||
<dependency id="chocolatey-uninstall.extension" />
|
||||
</dependencies>-->
|
||||
<!-- chocolatey-uninstall.extension - If supporting 0.9.9.x (or below) and including a chocolateyUninstall.ps1 file to uninstall an EXE/MSI, you probably want to include chocolatey-uninstall.extension as a dependency. Please verify whether you are using a helper function from that package. -->
|
||||
|
||||
<!--<provides>NOT YET IMPLEMENTED</provides>-->
|
||||
<!--<conflicts>NOT YET IMPLEMENTED</conflicts>-->
|
||||
<!--<replaces>NOT YET IMPLEMENTED</replaces>-->
|
||||
</metadata>
|
||||
<files>
|
||||
<!-- this section controls what actually gets packaged into the Chocolatey package -->
|
||||
<file src="tools\**" target="tools" />
|
||||
<!--Building from Linux? You may need this instead: <file src="tools/**" target="tools" />-->
|
||||
</files>
|
||||
</package>
|
@ -5,11 +5,11 @@
|
||||
[](https://coveralls.io/r/nlohmann/json)
|
||||
[](https://scan.coverity.com/projects/nlohmann-json)
|
||||
[](https://www.codacy.com/app/nlohmann/json?utm_source=github.com&utm_medium=referral&utm_content=nlohmann/json&utm_campaign=Badge_Grade)
|
||||
[](https://wandbox.org/permlink/Op57X0V7fTf2tdwl)
|
||||
[](https://wandbox.org/permlink/TarF5pPn9NtHQjhf)
|
||||
[](http://nlohmann.github.io/json)
|
||||
[](https://raw.githubusercontent.com/nlohmann/json/master/LICENSE.MIT)
|
||||
[](https://github.com/nlohmann/json/releases)
|
||||
[](http://github.com/nlohmann/json/issues)
|
||||
[](https://github.com/nlohmann/json/releases)
|
||||
[](http://github.com/nlohmann/json/issues)
|
||||
[](http://isitmaintained.com/project/nlohmann/json "Average time to resolve an issue")
|
||||
[](https://bestpractices.coreinfrastructure.org/projects/289)
|
||||
|
||||
@ -21,9 +21,10 @@
|
||||
- [STL-like access](#stl-like-access)
|
||||
- [Conversion from STL containers](#conversion-from-stl-containers)
|
||||
- [JSON Pointer and JSON Patch](#json-pointer-and-json-patch)
|
||||
- [JSON Merge Patch](#json-merge-patch)
|
||||
- [Implicit conversions](#implicit-conversions)
|
||||
- [Conversions to/from arbitrary types](#arbitrary-types-conversions)
|
||||
- [Binary formats (CBOR and MessagePack)](#binary-formats-cbor-and-messagepack)
|
||||
- [Binary formats (CBOR, MessagePack, and UBJSON)](#binary-formats-cbor-messagepack-and-ubjson)
|
||||
- [Supported compilers](#supported-compilers)
|
||||
- [License](#license)
|
||||
- [Contact](#contact)
|
||||
@ -39,9 +40,9 @@ There are myriads of [JSON](http://json.org) libraries out there, and each may e
|
||||
|
||||
- **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.
|
||||
- **Trivial integration**. Our whole code consists of a single header file [`json.hpp`](https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json.hpp). That's it. No library, no subproject, no dependencies, no complex build system. The class is written in vanilla C++11. All in all, everything should require no adjustment of your compiler flags or project settings.
|
||||
|
||||
- **Serious testing**. Our class is heavily [unit-tested](https://github.com/nlohmann/json/blob/master/test/src/unit.cpp) and covers [100%](https://coveralls.io/r/nlohmann/json) of the code, including all exceptional behavior. Furthermore, we checked with [Valgrind](http://valgrind.org) that there are no memory leaks. To maintain high quality, the project is following the [Core Infrastructure Initiative (CII) best practices](https://bestpractices.coreinfrastructure.org/projects/289).
|
||||
- **Serious testing**. Our class is heavily [unit-tested](https://github.com/nlohmann/json/tree/develop/test/src) and covers [100%](https://coveralls.io/r/nlohmann/json) of the code, including all exceptional behavior. Furthermore, we checked with [Valgrind](http://valgrind.org) and the [Clang Sanitizers](https://clang.llvm.org/docs/index.html) that there are no memory leaks. [Google OSS-Fuzz](https://github.com/google/oss-fuzz/tree/master/projects/json) additionally runs fuzz tests agains all parsers 24/7, effectively executing billions of tests so far. 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:
|
||||
|
||||
@ -54,31 +55,42 @@ See the [contribution guidelines](https://github.com/nlohmann/json/blob/master/.
|
||||
|
||||
## Integration
|
||||
|
||||
The single required source, file `json.hpp` is in the `src` directory or [released here](https://github.com/nlohmann/json/releases). All you need to do is add
|
||||
[`json.hpp`](https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json.hpp) is the single required file in `single_include/nlohmann` or [released here](https://github.com/nlohmann/json/releases). You need to add
|
||||
|
||||
```cpp
|
||||
#include "json.hpp"
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// for convenience
|
||||
using json = nlohmann::json;
|
||||
```
|
||||
|
||||
to the files you want to use JSON objects. That's it. Do not forget to set the necessary switches to enable C++11 (e.g., `-std=c++11` for GCC and Clang).
|
||||
to the files you want to process JSON and set the necessary switches to enable C++11 (e.g., `-std=c++11` for GCC and Clang).
|
||||
|
||||
You can further use file [`include/nlohmann/json_fwd.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/json_fwd.hpp) for forward-declarations. The installation of json_fwd.hpp (as part of cmake's install step), can be achieved by setting `-DJSON_MultipleHeaders=ON`.
|
||||
|
||||
### Package Managers
|
||||
|
||||
:beer: If you are using OS X and [Homebrew](http://brew.sh), just type `brew tap nlohmann/json` and `brew install nlohmann_json` and you're set. If you want the bleeding edge rather than the latest release, use `brew install nlohmann_json --HEAD`.
|
||||
|
||||
If you are using the [Meson Build System](http://mesonbuild.com), then you can wrap this repo as a subproject.
|
||||
If you are using the [Meson Build System](http://mesonbuild.com), then you can wrap this repository as a subproject.
|
||||
|
||||
If you are using [Conan](https://www.conan.io/) to manage your dependencies, merely add `jsonformoderncpp/x.y.z@vthiery/stable` to your `conanfile.py`'s requires, where `x.y.z` is the release version you want to use. Please file issues [here](https://github.com/vthiery/conan-jsonformoderncpp/issues) if you experience problems with the packages.
|
||||
|
||||
If you are using [Spack](https://www.spack.io/) to manage your dependencies, you can use the `nlohmann_json` package. Please see the [spack project](https://github.com/spack/spack) for any issues regarding the packaging.
|
||||
|
||||
If you are using [hunter](https://github.com/ruslo/hunter/) on your project for external dependencies, then you can use the [nlohmann_json package](https://docs.hunter.sh/en/latest/packages/pkg/nlohmann_json.html). Please see the hunter project for any issues regarding the packaging.
|
||||
|
||||
If you are using [Buckaroo](https://buckaroo.pm), you can install this library's module with `buckaroo install nlohmann/json`. Please file issues [here](https://github.com/LoopPerfect/buckaroo-recipes/issues/new?title=nlohmann/nlohmann/json).
|
||||
|
||||
If you are using [vcpkg](https://github.com/Microsoft/vcpkg/) on your project for external dependencies, then you can use the [nlohmann-json package](https://github.com/Microsoft/vcpkg/tree/master/ports/nlohmann-json). Please see the vcpkg project for any issues regarding the packaging.
|
||||
|
||||
If you are using [cget](http://cget.readthedocs.io/en/latest/), you can install the latest development version with `cget install nlohmann/json`. A specific version can be installed with `cget install nlohmann/json@v3.1.0`. Also, the multiple header version can be installed by adding the `-DJSON_MultipleHeaders=ON` flag (i.e., `cget install nlohmann/json -DJSON_MultipleHeaders=ON`).
|
||||
|
||||
If you are using [CocoaPods](https://cocoapods.org), you can use the library by adding pod `"nlohmann_json", '~>3.1.2'` to your podfile (see [an example](https://bitbucket.org/benman/nlohmann_json-cocoapod/src/master/)). Please file issues [here](https://bitbucket.org/benman/nlohmann_json-cocoapod/issues?status=new&status=open).
|
||||
|
||||
## Examples
|
||||
|
||||
Beside the examples below, you may want to check the [documentation](https://nlohmann.github.io/json/) where each function contains a separate code example (e.g., check out [`emplace()`](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_a602f275f0359ab181221384989810604.html#a602f275f0359ab181221384989810604)). All [example files](https://github.com/nlohmann/json/tree/develop/doc/examples) can be compiled and executed on their own (e.g., file [emplace.cpp](https://github.com/nlohmann/json/blob/develop/doc/examples/emplace.cpp)).
|
||||
Beside the examples below, you may want to check the [documentation](https://nlohmann.github.io/json/) where each function contains a separate code example (e.g., check out [`emplace()`](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_a5338e282d1d02bed389d852dd670d98d.html#a5338e282d1d02bed389d852dd670d98d)). All [example files](https://github.com/nlohmann/json/tree/develop/doc/examples) can be compiled and executed on their own (e.g., file [emplace.cpp](https://github.com/nlohmann/json/blob/develop/doc/examples/emplace.cpp)).
|
||||
|
||||
### JSON as first-class data type
|
||||
|
||||
@ -103,7 +115,7 @@ Assume you want to create the JSON object
|
||||
}
|
||||
```
|
||||
|
||||
With the JSON class, you could write:
|
||||
With this library, you could write:
|
||||
|
||||
```cpp
|
||||
// create an empty structure (null)
|
||||
@ -147,7 +159,7 @@ json j2 = {
|
||||
};
|
||||
```
|
||||
|
||||
Note that in all these cases, you never need to "tell" the compiler which JSON value you want to use. If you want to be explicit or express some edge cases, the functions `json::array` and `json::object` will help:
|
||||
Note that in all these cases, you never need to "tell" the compiler which JSON value type you want to use. If you want to be explicit or express some edge cases, the functions [`json::array`](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_aa80485befaffcadaa39965494e0b4d2e.html#aa80485befaffcadaa39965494e0b4d2e) and [`json::object`](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_aa13f7c0615867542ce80337cbcf13ada.html#aa13f7c0615867542ce80337cbcf13ada) will help:
|
||||
|
||||
```cpp
|
||||
// a way to express the empty array []
|
||||
@ -161,12 +173,11 @@ json empty_object_explicit = json::object();
|
||||
json array_not_object = json::array({ {"currency", "USD"}, {"value", 42.99} });
|
||||
```
|
||||
|
||||
|
||||
### Serialization / Deserialization
|
||||
|
||||
#### To/from strings
|
||||
|
||||
You can create an object (deserialization) by appending `_json` to a string literal:
|
||||
You can create a JSON value (deserialization) by appending `_json` to a string literal:
|
||||
|
||||
```cpp
|
||||
// create object from string literal
|
||||
@ -183,14 +194,14 @@ auto j2 = R"(
|
||||
|
||||
Note that without appending the `_json` suffix, the passed string literal is not parsed, but just used as JSON string value. That is, `json j = "{ \"happy\": true, \"pi\": 3.141 }"` would just store the string `"{ "happy": true, "pi": 3.141 }"` rather than parsing the actual object.
|
||||
|
||||
The above example can also be expressed explicitly using `json::parse()`:
|
||||
The above example can also be expressed explicitly using [`json::parse()`](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_aa9676414f2e36383c4b181fe856aa3c0.html#aa9676414f2e36383c4b181fe856aa3c0):
|
||||
|
||||
```cpp
|
||||
// parse explicitly
|
||||
auto j3 = json::parse("{ \"happy\": true, \"pi\": 3.141 }");
|
||||
```
|
||||
|
||||
You can also get a string representation (serialize):
|
||||
You can also get a string representation of a JSON value (serialize):
|
||||
|
||||
```cpp
|
||||
// explicit conversion to string
|
||||
@ -225,8 +236,9 @@ std::cout << cpp_string << " == " << cpp_string2 << " == " << j_string.get<std::
|
||||
std::cout << j_string << " == " << serialized_string << std::endl;
|
||||
```
|
||||
|
||||
`.dump()` always returns the serialized value, and `.get<std::string>()` returns the originally stored string value.
|
||||
[`.dump()`](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_a5adea76fedba9898d404fef8598aa663.html#a5adea76fedba9898d404fef8598aa663) always returns the serialized value, and [`.get<std::string>()`](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_a16f9445f7629f634221a42b967cdcd43.html#a16f9445f7629f634221a42b967cdcd43) returns the originally stored string value.
|
||||
|
||||
Note the library only supports UTF-8. When you store strings with different encodings in the library, calling [`dump()`](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_a5adea76fedba9898d404fef8598aa663.html#a5adea76fedba9898d404fef8598aa663) may throw an exception.
|
||||
|
||||
#### To/from streams (e.g. files, string streams)
|
||||
|
||||
@ -261,7 +273,7 @@ Please note that setting the exception bit for `failbit` is inappropriate for th
|
||||
|
||||
#### Read from iterator range
|
||||
|
||||
You can also read JSON from an iterator range; that is, from any container accessible by iterators whose content is stored as contiguous byte sequence, for instance a `std::vector<std::uint8_t>`:
|
||||
You can also parse JSON from an iterator range; that is, from any container accessible by iterators whose content is stored as contiguous byte sequence, for instance a `std::vector<std::uint8_t>`:
|
||||
|
||||
```cpp
|
||||
std::vector<std::uint8_t> v = {'t', 'r', 'u', 'e'};
|
||||
@ -275,10 +287,53 @@ std::vector<std::uint8_t> v = {'t', 'r', 'u', 'e'};
|
||||
json j = json::parse(v);
|
||||
```
|
||||
|
||||
#### SAX interface
|
||||
|
||||
The library uses a SAX-like interface with the following functions:
|
||||
|
||||
```cpp
|
||||
// called when null is parsed
|
||||
bool null();
|
||||
|
||||
// called when a boolean is parsed; value is passed
|
||||
bool boolean(bool val);
|
||||
|
||||
// called when a signed or unsigned integer number is parsed; value is passed
|
||||
bool number_integer(number_integer_t val);
|
||||
bool number_unsigned(number_unsigned_t val);
|
||||
|
||||
// called when a floating-point number is parsed; value and original string is passed
|
||||
bool number_float(number_float_t val, const string_t& s);
|
||||
|
||||
// called when a string is parsed; value is passed and can be safely moved away
|
||||
bool string(string_t& val);
|
||||
|
||||
// called when an object or array begins or ends, resp. The number of elements is passed (or -1 if not known)
|
||||
bool start_object(std::size_t elements);
|
||||
bool end_object();
|
||||
bool start_array(std::size_t elements);
|
||||
bool end_array();
|
||||
// called when an object key is parsed; value is passed and can be safely moved away
|
||||
bool key(string_t& val);
|
||||
|
||||
// called when a parse error occurs; byte position, the last token, and an exception is passed
|
||||
bool parse_error(std::size_t position, const std::string& last_token, const detail::exception& ex);
|
||||
```
|
||||
|
||||
The return value of each function determines whether parsing should proceed.
|
||||
|
||||
To implement your own SAX handler, proceed as follows:
|
||||
|
||||
1. Implement the SAX interface in a class. You can use class `nlohmann::json_sax<json>` as base class, but you can also use any class where the functions described above are implemented and public.
|
||||
2. Create an object of your SAX interface class, e.g. `my_sax`.
|
||||
3. Call `bool json::sax_parse(input, &my_sax)`; where the first parameter can be any input like a string or an input stream and the second parameter is a pointer to your SAX interface.
|
||||
|
||||
Note the `sax_parse` function only returns a `bool` indicating the result of the last executed SAX event. It does not return a `json` value - it is up to you to decide what to do with the SAX events. Furthermore, no exceptions are thrown in case of a parse error - it is up to you what to do with the exception object passed to your `parse_error` implementation. Internally, the SAX interface is used for the DOM parser (class `json_sax_dom_parser`) as well as the acceptor (`json_sax_acceptor`), see file [`json_sax.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/json_sax.hpp).
|
||||
|
||||
|
||||
### STL-like access
|
||||
|
||||
We designed the JSON class to behave just like an STL container. In fact, it satisfies the [**ReversibleContainer**](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) requirement.
|
||||
We designed the JSON class to behave just like an STL container. In fact, it satisfies the [**ReversibleContainer**](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer) requirement.
|
||||
|
||||
```cpp
|
||||
// create an array using push_back
|
||||
@ -352,7 +407,7 @@ o.erase("foo");
|
||||
|
||||
### Conversion from STL containers
|
||||
|
||||
Any sequence container (`std::array`, `std::vector`, `std::deque`, `std::forward_list`, `std::list`) whose values can be used to construct JSON types (e.g., integers, floating point numbers, Booleans, string types, or again STL containers described in this section) can be used to create a JSON array. The same holds for similar associative containers (`std::set`, `std::multiset`, `std::unordered_set`, `std::unordered_multiset`), but in these cases the order of the elements of the array depends on how the elements are ordered in the respective STL container.
|
||||
Any sequence container (`std::array`, `std::vector`, `std::deque`, `std::forward_list`, `std::list`) whose values can be used to construct JSON values (e.g., integers, floating point numbers, Booleans, string types, or again STL containers described in this section) can be used to create a JSON array. The same holds for similar associative containers (`std::set`, `std::multiset`, `std::unordered_set`, `std::unordered_multiset`), but in these cases the order of the elements of the array depends on how the elements are ordered in the respective STL container.
|
||||
|
||||
```cpp
|
||||
std::vector<int> c_vector {1, 2, 3, 4};
|
||||
@ -392,7 +447,7 @@ json j_umset(c_umset); // both entries for "one" are used
|
||||
// maybe ["one", "two", "one", "four"]
|
||||
```
|
||||
|
||||
Likewise, any associative key-value containers (`std::map`, `std::multimap`, `std::unordered_map`, `std::unordered_multimap`) whose keys can construct an `std::string` and whose values can be used to construct JSON types (see examples above) can be used to 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 values (see examples above) can be used 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<std::string, int> c_map { {"one", 1}, {"two", 2}, {"three", 3} };
|
||||
@ -450,6 +505,37 @@ json::diff(j_result, j_original);
|
||||
// ]
|
||||
```
|
||||
|
||||
### JSON Merge Patch
|
||||
|
||||
The library supports **JSON Merge Patch** ([RFC 7386](https://tools.ietf.org/html/rfc7386)) as a patch format. Instead of using JSON Pointer (see above) to specify values to be manipulated, it describes the changes using a syntax that closely mimics the document being modified.
|
||||
|
||||
```cpp
|
||||
// a JSON value
|
||||
json j_document = R"({
|
||||
"a": "b",
|
||||
"c": {
|
||||
"d": "e",
|
||||
"f": "g"
|
||||
}
|
||||
})"_json;
|
||||
|
||||
// a patch
|
||||
json j_patch = R"({
|
||||
"a":"z",
|
||||
"c": {
|
||||
"f": null
|
||||
}
|
||||
})"_json;
|
||||
|
||||
// apply the patch
|
||||
j_original.merge_patch(j_patch);
|
||||
// {
|
||||
// "a": "z",
|
||||
// "c": {
|
||||
// "d": "e"
|
||||
// }
|
||||
// }
|
||||
```
|
||||
|
||||
### Implicit conversions
|
||||
|
||||
@ -484,9 +570,17 @@ int vi = jn.get<int>();
|
||||
// etc.
|
||||
```
|
||||
|
||||
Note that `char` types are not automatically converted to JSON strings, but to integer numbers. A conversion to a string must be specified explicitly:
|
||||
|
||||
```cpp
|
||||
char ch = 'A'; // ASCII value 65
|
||||
json j_default = ch; // stores integer number 65
|
||||
json j_string = std::string(1, ch); // stores string "A"
|
||||
```
|
||||
|
||||
### Arbitrary types conversions
|
||||
|
||||
Every type can be serialized in JSON, not just STL-containers and scalar types. Usually, you would do something along those lines:
|
||||
Every type can be serialized in JSON, not just STL containers and scalar types. Usually, you would do something along those lines:
|
||||
|
||||
```cpp
|
||||
namespace ns {
|
||||
@ -561,7 +655,8 @@ Likewise, when calling `get<your_type>()`, the `from_json` method will be called
|
||||
Some important things:
|
||||
|
||||
* Those methods **MUST** be in your type's namespace (which can be the global namespace), or the library will not be able to locate them (in this example, they are in namespace `ns`, where `person` is defined).
|
||||
* When using `get<your_type>()`, `your_type` **MUST** be [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible). (There is a way to bypass this requirement described later.)
|
||||
* Those methods **MUST** be available (e.g., properly headers must be included) everywhere you use the implicit conversions. Look at [issue 1108](https://github.com/nlohmann/json/issues/1108) for errors that may occur otherwise.
|
||||
* When using `get<your_type>()`, `your_type` **MUST** be [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). (There is a way to bypass this requirement described later.)
|
||||
* In function `from_json`, use function [`at()`](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_a93403e803947b86f4da2d1fb3345cf2c.html#a93403e803947b86f4da2d1fb3345cf2c) to access the object values rather than `operator[]`. In case a key does not exist, `at` throws an exception that you can handle, whereas `operator[]` exhibits undefined behavior.
|
||||
* In case your type contains several `operator=` definitions, code like `your_variable = your_json;` [may not compile](https://github.com/nlohmann/json/issues/667). You need to write `your_variable = your_json.get<decltype your_variable>();` instead.
|
||||
* You do not need to add serializers or deserializers for STL types like `std::vector`: the library already implements these.
|
||||
@ -573,7 +668,7 @@ Some important things:
|
||||
This requires a bit more advanced technique. But first, let's see how this conversion mechanism works:
|
||||
|
||||
The library uses **JSON Serializers** to convert types to json.
|
||||
The default serializer for `nlohmann::json` is `nlohmann::adl_serializer` (ADL means [Argument-Dependent Lookup](http://en.cppreference.com/w/cpp/language/adl)).
|
||||
The default serializer for `nlohmann::json` is `nlohmann::adl_serializer` (ADL means [Argument-Dependent Lookup](https://en.cppreference.com/w/cpp/language/adl)).
|
||||
|
||||
It is implemented like this (simplified):
|
||||
|
||||
@ -590,7 +685,7 @@ struct adl_serializer {
|
||||
};
|
||||
```
|
||||
|
||||
This serializer works fine when you have control over the type's namespace. However, what about `boost::optional`, or `std::filesystem::path` (C++17)? Hijacking the `boost` namespace is pretty bad, and it's illegal to add something other than template specializations to `std`...
|
||||
This serializer works fine when you have control over the type's namespace. However, what about `boost::optional` or `std::filesystem::path` (C++17)? Hijacking the `boost` namespace is pretty bad, and it's illegal to add something other than template specializations to `std`...
|
||||
|
||||
To solve this, you need to add a specialization of `adl_serializer` to the `nlohmann` namespace, here's an example:
|
||||
|
||||
@ -612,7 +707,7 @@ namespace nlohmann {
|
||||
if (j.is_null()) {
|
||||
opt = boost::none;
|
||||
} else {
|
||||
opt = j.get<T>(); // same as above, but with
|
||||
opt = j.get<T>(); // same as above, but with
|
||||
// adl_serializer<T>::from_json
|
||||
}
|
||||
}
|
||||
@ -622,7 +717,7 @@ namespace nlohmann {
|
||||
|
||||
#### How can I use `get()` for non-default constructible/non-copyable types?
|
||||
|
||||
There is a way, if your type is [MoveConstructible](http://en.cppreference.com/w/cpp/concept/MoveConstructible). You will need to specialize the `adl_serializer` as well, but with a special `from_json` overload:
|
||||
There is a way, if your type is [MoveConstructible](https://en.cppreference.com/w/cpp/named_req/MoveConstructible). You will need to specialize the `adl_serializer` as well, but with a special `from_json` overload:
|
||||
|
||||
```cpp
|
||||
struct move_only_type {
|
||||
@ -630,7 +725,7 @@ struct move_only_type {
|
||||
move_only_type(int ii): i(ii) {}
|
||||
move_only_type(const move_only_type&) = delete;
|
||||
move_only_type(move_only_type&&) = default;
|
||||
|
||||
|
||||
int i;
|
||||
};
|
||||
|
||||
@ -642,7 +737,7 @@ namespace nlohmann {
|
||||
static move_only_type from_json(const json& j) {
|
||||
return {j.get<int>()};
|
||||
}
|
||||
|
||||
|
||||
// Here's the catch! You must provide a to_json method! Otherwise you
|
||||
// will not be able to convert move_only_type to json, since you fully
|
||||
// specialized adl_serializer on that type
|
||||
@ -659,9 +754,9 @@ Yes. You might want to take a look at [`unit-udt.cpp`](https://github.com/nlohma
|
||||
|
||||
If you write your own serializer, you'll need to do a few things:
|
||||
|
||||
* use a different `basic_json` alias than `nlohmann::json` (the last template parameter of `basic_json` is the `JSONSerializer`)
|
||||
* use your `basic_json` alias (or a template parameter) in all your `to_json`/`from_json` methods
|
||||
* use `nlohmann::to_json` and `nlohmann::from_json` when you need ADL
|
||||
- use a different `basic_json` alias than `nlohmann::json` (the last template parameter of `basic_json` is the `JSONSerializer`)
|
||||
- use your `basic_json` alias (or a template parameter) in all your `to_json`/`from_json` methods
|
||||
- use `nlohmann::to_json` and `nlohmann::from_json` when you need ADL
|
||||
|
||||
Here is an example, without simplifications, that only accepts types with a size <= 32, and uses ADL.
|
||||
|
||||
@ -677,7 +772,7 @@ struct less_than_32_serializer {
|
||||
// this is where the magic happens
|
||||
to_json(j, value);
|
||||
}
|
||||
|
||||
|
||||
template <typename BasicJsonType>
|
||||
static void from_json(const BasicJsonType& j, T& value) {
|
||||
// same thing here
|
||||
@ -699,7 +794,7 @@ struct bad_serializer
|
||||
// if BasicJsonType::json_serializer == bad_serializer ... oops!
|
||||
j = value;
|
||||
}
|
||||
|
||||
|
||||
template <typename BasicJsonType>
|
||||
static void to_json(const BasicJsonType& j, T& value) {
|
||||
// this calls BasicJsonType::json_serializer<T>::from_json(j, value);
|
||||
@ -709,9 +804,9 @@ struct bad_serializer
|
||||
};
|
||||
```
|
||||
|
||||
### Binary formats (CBOR and MessagePack)
|
||||
### Binary formats (CBOR, MessagePack, and UBJSON)
|
||||
|
||||
Though JSON is a ubiquitous data format, it is not a very compact format suitable for data exchange, for instance over a network. Hence, the library supports [CBOR](http://cbor.io) (Concise Binary Object Representation) and [MessagePack](http://msgpack.org) to efficiently encode JSON values to byte vectors and to decode such vectors.
|
||||
Though JSON is a ubiquitous data format, it is not a very compact format suitable for data exchange, for instance over a network. Hence, the library supports [CBOR](http://cbor.io) (Concise Binary Object Representation), [MessagePack](http://msgpack.org), and [UBJSON](http://ubjson.org) (Universal Binary JSON Specification) to efficiently encode JSON values to byte vectors and to decode such vectors.
|
||||
|
||||
```cpp
|
||||
// create a JSON value
|
||||
@ -720,7 +815,7 @@ json j = R"({"compact": true, "schema": 0})"_json;
|
||||
// serialize to CBOR
|
||||
std::vector<std::uint8_t> v_cbor = json::to_cbor(j);
|
||||
|
||||
// 0xa2, 0x67, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0xf5, 0x66, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x00
|
||||
// 0xA2, 0x67, 0x63, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0xF5, 0x66, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x00
|
||||
|
||||
// roundtrip
|
||||
json j_from_cbor = json::from_cbor(v_cbor);
|
||||
@ -728,19 +823,27 @@ json j_from_cbor = json::from_cbor(v_cbor);
|
||||
// serialize to MessagePack
|
||||
std::vector<std::uint8_t> v_msgpack = json::to_msgpack(j);
|
||||
|
||||
// 0x82, 0xa7, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0xc3, 0xa6, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x00
|
||||
// 0x82, 0xA7, 0x63, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0xC3, 0xA6, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x00
|
||||
|
||||
// roundtrip
|
||||
json j_from_msgpack = json::from_msgpack(v_msgpack);
|
||||
|
||||
// serialize to UBJSON
|
||||
std::vector<std::uint8_t> v_ubjson = json::to_ubjson(j);
|
||||
|
||||
// 0x7B, 0x69, 0x07, 0x63, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0x54, 0x69, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x69, 0x00, 0x7D
|
||||
|
||||
// roundtrip
|
||||
json j_from_ubjson = json::from_ubjson(v_ubjson);
|
||||
```
|
||||
|
||||
|
||||
## Supported compilers
|
||||
|
||||
Though it's 2017 already, the support for C++11 is still a bit sparse. Currently, the following compilers are known to work:
|
||||
Though it's 2018 already, the support for C++11 is still a bit sparse. Currently, the following compilers are known to work:
|
||||
|
||||
- GCC 4.9 - 7.2 (and possibly later)
|
||||
- Clang 3.4 - 5.0 (and possibly later)
|
||||
- GCC 4.9 - 8.2 (and possibly later)
|
||||
- Clang 3.4 - 6.1 (and possibly later)
|
||||
- Intel C++ Compiler 17.0.2 (and possibly later)
|
||||
- Microsoft Visual C++ 2015 / Build Tools 14.0.25123.0 (and possibly later)
|
||||
- Microsoft Visual C++ 2017 / Build Tools 15.5.180.51428 (and possibly later)
|
||||
@ -751,43 +854,49 @@ Please note:
|
||||
|
||||
- GCC 4.8 does not work because of two bugs ([55817](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55817) and [57824](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=57824)) in the C++11 support. Note there is a [pull request](https://github.com/nlohmann/json/pull/212) to fix some of the issues.
|
||||
- Android defaults to using very old compilers and C++ libraries. To fix this, add the following to your `Application.mk`. This will switch to the LLVM C++ library, the Clang compiler, and enable C++11 and other features disabled by default.
|
||||
|
||||
|
||||
```
|
||||
APP_STL := c++_shared
|
||||
NDK_TOOLCHAIN_VERSION := clang3.6
|
||||
APP_CPPFLAGS += -frtti -fexceptions
|
||||
```
|
||||
|
||||
|
||||
The code compiles successfully with [Android NDK](https://developer.android.com/ndk/index.html?hl=ml), Revision 9 - 11 (and possibly later) and [CrystaX's Android NDK](https://www.crystax.net/en/android/ndk) version 10.
|
||||
|
||||
- For GCC running on MinGW or Android SDK, the error `'to_string' is not a member of 'std'` (or similarly, for `strtod`) may occur. Note this is not an issue with the code, but rather with the compiler itself. On Android, see above to build with a newer environment. For MinGW, please refer to [this site](http://tehsausage.com/mingw-to-string) and [this discussion](https://github.com/nlohmann/json/issues/136) for information on how to fix this bug. For Android NDK using `APP_STL := gnustl_static`, please refer to [this discussion](https://github.com/nlohmann/json/issues/219).
|
||||
|
||||
- Unsupported versions of GCC and Clang are rejected by `#error` directives. This can be switched off by defining `JSON_SKIP_UNSUPPORTED_COMPILER_CHECK`. Note that you can expect no support in this case.
|
||||
|
||||
The following compilers are currently used in continuous integration at [Travis](https://travis-ci.org/nlohmann/json) and [AppVeyor](https://ci.appveyor.com/project/nlohmann/json):
|
||||
|
||||
| Compiler | Operating System | Version String |
|
||||
|-----------------|------------------------------|----------------|
|
||||
| GCC 4.9.4 | Ubuntu 14.04.5 LTS | g++-4.9 (Ubuntu 4.9.4-2ubuntu1~14.04.1) 4.9.4 |
|
||||
| GCC 5.4.1 | Ubuntu 14.04.5 LTS | g++-5 (Ubuntu 5.4.1-2ubuntu1~14.04) 5.4.1 20160904 |
|
||||
| GCC 6.3.0 | Ubuntu 14.04.5 LTS | g++-6 (Ubuntu/Linaro 6.3.0-18ubuntu2~14.04) 6.3.0 20170519 |
|
||||
| GCC 7.1.0 | Ubuntu 14.04.5 LTS | g++-7 (Ubuntu 7.1.0-5ubuntu2~14.04) 7.1.0
|
||||
| Clang 3.5.0 | Ubuntu 14.04.5 LTS | clang version 3.5.0-4ubuntu2~trusty2 (tags/RELEASE_350/final) |
|
||||
| Clang 3.6.2 | Ubuntu 14.04.5 LTS | clang version 3.6.2-svn240577-1~exp1 (branches/release_36) |
|
||||
| Clang 3.7.1 | Ubuntu 14.04.5 LTS | clang version 3.7.1-svn253571-1~exp1 (branches/release_37) |
|
||||
| Clang 3.8.0 | Ubuntu 14.04.5 LTS | clang version 3.8.0-2ubuntu3~trusty5 (tags/RELEASE_380/final) |
|
||||
| Clang 3.9.1 | Ubuntu 14.04.5 LTS | clang version 3.9.1-4ubuntu3~14.04.2 (tags/RELEASE_391/rc2) |
|
||||
| Clang 4.0.1 | Ubuntu 14.04.5 LTS | clang version 4.0.1-svn305264-1~exp1 (branches/release_40) |
|
||||
| Clang 5.0.0 | Ubuntu 14.04.5 LTS | clang version 5.0.0-svn310902-1~exp1 (branches/release_50) |
|
||||
| Clang Xcode 6.4 | Darwin Kernel Version 14.3.0 (OSX 10.10.3) | Apple LLVM version 6.1.0 (clang-602.0.53) (based on LLVM 3.6.0svn) |
|
||||
| Clang Xcode 7.3 | Darwin Kernel Version 15.0.0 (OSX 10.10.5) | Apple LLVM version 7.3.0 (clang-703.0.29) |
|
||||
| Clang Xcode 8.0 | Darwin Kernel Version 15.6.0 | Apple LLVM version 8.0.0 (clang-800.0.38) |
|
||||
| Clang Xcode 8.1 | Darwin Kernel Version 16.1.0 (macOS 10.12.1) | Apple LLVM version 8.0.0 (clang-800.0.42.1) |
|
||||
| Clang Xcode 8.2 | Darwin Kernel Version 16.1.0 (macOS 10.12.1) | Apple LLVM version 8.0.0 (clang-800.0.42.1) |
|
||||
| Clang Xcode 8.3 | Darwin Kernel Version 16.5.0 (macOS 10.12.4) | Apple LLVM version 8.1.0 (clang-802.0.38) |
|
||||
| Clang Xcode 9.0 | Darwin Kernel Version 16.7.0 (macOS 10.12.6) | Apple LLVM version 9.0.0 (clang-900.0.37) |
|
||||
| Clang Xcode 9.1 | Darwin Kernel Version 16.7.0 (macOS 10.12.6) | Apple LLVM version 9.0.0 (clang-900.0.38) |
|
||||
| Clang Xcode 9.2 | Darwin Kernel Version 16.7.0 (macOS 10.12.6) | Apple LLVM version 8.1.0 (clang-900.0.39.2) |
|
||||
| Visual Studio 14 2015 | Windows Server 2012 R2 (x64) | Microsoft (R) Build Engine version 14.0.25420.1, MSVC 19.0.24215.1 |
|
||||
| Visual Studio 2017 | Windows Server 2016 | Microsoft (R) Build Engine version 15.5.180.51428, MSVC 19.12.25830.2 |
|
||||
| GCC 4.9.4 | Ubuntu 14.04.1 LTS | g++-4.9 (Ubuntu 4.9.4-2ubuntu1~14.04.1) 4.9.4 |
|
||||
| GCC 5.5.0 | Ubuntu 14.04.1 LTS | g++-5 (Ubuntu 5.5.0-12ubuntu1~14.04) 5.5.0 20171010 |
|
||||
| GCC 6.4.0 | Ubuntu 14.04.1 LTS | g++-6 (Ubuntu 6.4.0-17ubuntu1~14.04) 6.4.0 20180424 |
|
||||
| GCC 7.3.0 | Ubuntu 14.04.1 LTS | g++-7 (Ubuntu 7.3.0-21ubuntu1~14.04) 7.3.0 |
|
||||
| GCC 7.3.0 | Windows Server 2012 R2 (x64) | g++ (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 7.3.0 |
|
||||
| GCC 8.1.0 | Ubuntu 14.04.1 LTS | g++-8 (Ubuntu 8.1.0-5ubuntu1~14.04) 8.1.0 |
|
||||
| Clang 3.5.0 | Ubuntu 14.04.1 LTS | clang version 3.5.0-4ubuntu2~trusty2 (tags/RELEASE_350/final) (based on LLVM 3.5.0) |
|
||||
| Clang 3.6.2 | Ubuntu 14.04.1 LTS | clang version 3.6.2-svn240577-1~exp1 (branches/release_36) (based on LLVM 3.6.2) |
|
||||
| Clang 3.7.1 | Ubuntu 14.04.1 LTS | clang version 3.7.1-svn253571-1~exp1 (branches/release_37) (based on LLVM 3.7.1) |
|
||||
| Clang 3.8.0 | Ubuntu 14.04.1 LTS | clang version 3.8.0-2ubuntu3~trusty5 (tags/RELEASE_380/final) |
|
||||
| Clang 3.9.1 | Ubuntu 14.04.1 LTS | clang version 3.9.1-4ubuntu3~14.04.3 (tags/RELEASE_391/rc2) |
|
||||
| Clang 4.0.1 | Ubuntu 14.04.1 LTS | clang version 4.0.1-svn305264-1~exp1 (branches/release_40) |
|
||||
| Clang 5.0.2 | Ubuntu 14.04.1 LTS | clang version 5.0.2-svn328729-1~exp1~20180509123505.100 (branches/release_50) |
|
||||
| Clang 6.0.1 | Ubuntu 14.04.1 LTS | clang version 6.0.1-svn334776-1~exp1~20180726133705.85 (branches/release_60) |
|
||||
| Clang Xcode 6.4 | OSX 10.10.5 | Apple LLVM version 6.1.0 (clang-602.0.53) (based on LLVM 3.6.0svn) |
|
||||
| Clang Xcode 7.3 | OSX 10.11.6 | Apple LLVM version 7.3.0 (clang-703.0.31) |
|
||||
| Clang Xcode 8.0 | OSX 10.11.6 | Apple LLVM version 8.0.0 (clang-800.0.38) |
|
||||
| Clang Xcode 8.1 | OSX 10.12.6 | Apple LLVM version 8.0.0 (clang-800.0.42.1) |
|
||||
| Clang Xcode 8.2 | OSX 10.12.6 | Apple LLVM version 8.0.0 (clang-800.0.42.1) |
|
||||
| Clang Xcode 8.3 | OSX 10.11.6 | Apple LLVM version 8.1.0 (clang-802.0.38) |
|
||||
| Clang Xcode 9.0 | OSX 10.12.6 | Apple LLVM version 9.0.0 (clang-900.0.37) |
|
||||
| Clang Xcode 9.1 | OSX 10.12.6 | Apple LLVM version 9.0.0 (clang-900.0.38) |
|
||||
| Clang Xcode 9.2 | OSX 10.13.3 | Apple LLVM version 9.1.0 (clang-902.0.39.1) |
|
||||
| Clang Xcode 9.3 | OSX 10.13.3 | Apple LLVM version 9.1.0 (clang-902.0.39.2) |
|
||||
| Visual Studio 14 2015 | Windows Server 2012 R2 (x64) | Microsoft (R) Build Engine version 14.0.25420.1, MSVC 19.0.24215.1 |
|
||||
| Visual Studio 2017 | Windows Server 2016 | Microsoft (R) Build Engine version 15.7.180.61344, MSVC 19.14.26433.0 |
|
||||
|
||||
## License
|
||||
|
||||
@ -795,7 +904,7 @@ The following compilers are currently used in continuous integration at [Travis]
|
||||
|
||||
The class is licensed under the [MIT License](http://opensource.org/licenses/MIT):
|
||||
|
||||
Copyright © 2013-2017 [Niels Lohmann](http://nlohmann.me)
|
||||
Copyright © 2013-2018 [Niels Lohmann](http://nlohmann.me)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
@ -807,12 +916,17 @@ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR I
|
||||
|
||||
The class contains the UTF-8 Decoder from Bjoern Hoehrmann which is licensed under the [MIT License](http://opensource.org/licenses/MIT) (see above). Copyright © 2008-2009 [Björn Hoehrmann](http://bjoern.hoehrmann.de/) <bjoern@hoehrmann.de>
|
||||
|
||||
The class contains a slightly modified version of the Grisu2 algorithm from Florian Loitsch which is licensed under the [MIT License](http://opensource.org/licenses/MIT) (see above). Copyright © 2009 [Florian Loitsch](http://florian.loitsch.com/)
|
||||
|
||||
## Contact
|
||||
|
||||
If you have questions regarding the library, I would like to invite you to [open an issue at Github](https://github.com/nlohmann/json/issues/new). Please describe your request, problem, or question as detailed as possible, and also mention the version of the library you are using as well as the version of your compiler and operating system. Opening an issue at Github allows other users and contributors to this library to collaborate. For instance, I have little experience with MSVC, and most issues in this regard have been solved by a growing community. If you have a look at the [closed issues](https://github.com/nlohmann/json/issues?q=is%3Aissue+is%3Aclosed), you will see that we react quite timely in most cases.
|
||||
If you have questions regarding the library, I would like to invite you to [open an issue at GitHub](https://github.com/nlohmann/json/issues/new). Please describe your request, problem, or question as detailed as possible, and also mention the version of the library you are using as well as the version of your compiler and operating system. Opening an issue at GitHub allows other users and contributors to this library to collaborate. For instance, I have little experience with MSVC, and most issues in this regard have been solved by a growing community. If you have a look at the [closed issues](https://github.com/nlohmann/json/issues?q=is%3Aissue+is%3Aclosed), you will see that we react quite timely in most cases.
|
||||
|
||||
Only if your request would contain confidential information, please [send me an email](mailto:mail@nlohmann.me). For encrypted messages, please use [this key](https://keybase.io/nlohmann/pgp_keys.asc).
|
||||
|
||||
## Security
|
||||
|
||||
[Commits by Niels Lohmann](https://github.com/nlohmann/json/commits) and [releases](https://github.com/nlohmann/json/releases) are signed with this [PGP Key](https://keybase.io/nlohmann/pgp_keys.asc?fingerprint=797167ae41c0a6d9232e48457f3cea63ae251b69).
|
||||
|
||||
## Thanks
|
||||
|
||||
@ -847,7 +961,7 @@ I deeply appreciate the help of the following people.
|
||||
- [Corbin Hughes](https://github.com/nibroc) fixed some typos in the contribution guidelines.
|
||||
- [twelsby](https://github.com/twelsby) fixed the array subscript operator, an issue that failed the MSVC build, and floating-point parsing/dumping. He further added support for unsigned integer numbers and implemented better roundtrip support for parsed numbers.
|
||||
- [Volker Diels-Grabsch](https://github.com/vog) fixed a link in the README file.
|
||||
- [msm-](https://github.com/msm-) added support for american fuzzy lop.
|
||||
- [msm-](https://github.com/msm-) added support for American Fuzzy Lop.
|
||||
- [Annihil](https://github.com/Annihil) fixed an example in the README file.
|
||||
- [Themercee](https://github.com/Themercee) noted a wrong URL in the README file.
|
||||
- [Lv Zheng](https://github.com/lv-zheng) fixed a namespace issue with `int64_t` and `uint64_t`.
|
||||
@ -860,14 +974,14 @@ I deeply appreciate the help of the following people.
|
||||
- [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). He also implemented the magic behind the serialization/deserialization of user-defined types.
|
||||
- [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). He also implemented the magic behind the serialization/deserialization of user-defined types and split the single header file into smaller chunks.
|
||||
- [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 and added Visual Studio 17 to the build matrix.
|
||||
- [Denis Andrejew](https://github.com/seeekr) fixed a grammar issue in the README file.
|
||||
- [Pierre-Antoine Lacaze](https://github.com/palacaze) found a subtle bug in the `dump()` function.
|
||||
- [TurpentineDistillery](https://github.com/TurpentineDistillery) pointed to [`std::locale::classic()`](http://en.cppreference.com/w/cpp/locale/locale/classic) to avoid too much locale joggling, found some nice performance improvements in the parser, improved the benchmarking code, and realized locale-independent number parsing and printing.
|
||||
- [TurpentineDistillery](https://github.com/TurpentineDistillery) pointed to [`std::locale::classic()`](https://en.cppreference.com/w/cpp/locale/locale/classic) to avoid too much locale joggling, found some nice performance improvements in the parser, improved the benchmarking code, and realized locale-independent number parsing and printing.
|
||||
- [cgzones](https://github.com/cgzones) had an idea how to fix the Coverity scan.
|
||||
- [Jared Grubb](https://github.com/jaredgrubb) silenced a nasty documentation warning.
|
||||
- [Yixin Zhang](https://github.com/qwename) fixed an integer overflow check.
|
||||
@ -893,7 +1007,7 @@ I deeply appreciate the help of the following people.
|
||||
- [Vincent Thiery](https://github.com/vthiery) maintains a package for the Conan package manager.
|
||||
- [Steffen](https://github.com/koemeet) fixed a potential issue with MSVC and `std::min`.
|
||||
- [Mike Tzou](https://github.com/Chocobo1) fixed some typos.
|
||||
- [amrcode](https://github.com/amrcode) noted a missleading documentation about comparison of floats.
|
||||
- [amrcode](https://github.com/amrcode) noted a misleading documentation about comparison of floats.
|
||||
- [Oleg Endo](https://github.com/olegendo) reduced the memory consumption by replacing `<iostream>` with `<iosfwd>`.
|
||||
- [dan-42](https://github.com/dan-42) cleaned up the CMake files to simplify including/reusing of the library.
|
||||
- [Nikita Ofitserov](https://github.com/himikof) allowed for moving values from initializer lists.
|
||||
@ -910,25 +1024,50 @@ I deeply appreciate the help of the following people.
|
||||
- [Nate Vargas](https://github.com/eld00d) added a Doxygen tag file.
|
||||
- [pvleuven](https://github.com/pvleuven) helped fixing a warning in ICC.
|
||||
- [Pavel](https://github.com/crea7or) helped fixing some warnings in MSVC.
|
||||
- [Jamie Seward](https://github.com/jseward) avoided unneccessary string copies in `find()` and `count()`.
|
||||
- [Jamie Seward](https://github.com/jseward) avoided unnecessary string copies in `find()` and `count()`.
|
||||
- [Mitja](https://github.com/Itja) fixed some typos.
|
||||
- [Jorrit Wronski](https://github.com/jowr) updated the Hunter package links.
|
||||
- [Matthias Möller](https://github.com/TinyTinni) added a `.natvis` for the MSVC debug view.
|
||||
- [bogemic](https://github.com/bogemic) fixed some C++17 deprecation warnings.
|
||||
- [Eren Okka](https://github.com/erengy) fixed some MSVC warnings.
|
||||
|
||||
- [abolz](https://github.com/abolz) integrated the Grisu2 algorithm for proper floating-point formatting, allowing more roundtrip checks to succeed.
|
||||
- [Vadim Evard](https://github.com/Pipeliner) fixed a Markdown issue in the README.
|
||||
- [zerodefect](https://github.com/zerodefect) fixed a compiler warning.
|
||||
- [Kert](https://github.com/kaidokert) allowed to template the string type in the serialization and added the possibility to override the exceptional behavior.
|
||||
- [mark-99](https://github.com/mark-99) helped fixing an ICC error.
|
||||
- [Patrik Huber](https://github.com/patrikhuber) fixed links in the README file.
|
||||
- [johnfb](https://github.com/johnfb) found a bug in the implementation of CBOR's indefinite length strings.
|
||||
- [Paul Fultz II](https://github.com/pfultz2) added a note on the cget package manager.
|
||||
- [Wilson Lin](https://github.com/wla80) made the integration section of the README more concise.
|
||||
- [RalfBielig](https://github.com/ralfbielig) detected and fixed a memory leak in the parser callback.
|
||||
- [agrianius](https://github.com/agrianius) allowed to dump JSON to an alternative string type.
|
||||
- [Kevin Tonon](https://github.com/ktonon) overworked the C++11 compiler checks in CMake.
|
||||
- [Axel Huebl](https://github.com/ax3l) simplified a CMake check and added support for the [Spack package manager](https://spack.io).
|
||||
- [Carlos O'Ryan](https://github.com/coryan) fixed a typo.
|
||||
- [James Upjohn](https://github.com/jammehcow) fixed a version number in the compilers section.
|
||||
- [Chuck Atkins](https://github.com/chuckatkins) adjusted the CMake files to the CMake packaging guidelines
|
||||
- [Jan Schöppach](https://github.com/dns13) fixed a typo.
|
||||
- [martin-mfg](https://github.com/martin-mfg) fixed a typo.
|
||||
- [Matthias Möller](https://github.com/TinyTinni) removed the dependency from `std::stringstream`.
|
||||
- [agrianius](https://github.com/agrianius) added code to use alternative string implementations.
|
||||
- [Daniel599](https://github.com/Daniel599) allowed to use more algorithms with the `items()` function.
|
||||
- [Julius Rakow](https://github.com/jrakow) fixed the Meson include directory and fixed the links to [cppreference.com](cppreference.com).
|
||||
- [Sonu Lohani](https://github.com/sonulohani) fixed the compilation with MSVC 2015 in debug mode.
|
||||
- [grembo](https://github.com/grembo) fixed the test suite and re-enabled several test cases.
|
||||
- [Hyeon Kim](https://github.com/simnalamburt) introduced the macro `JSON_INTERNAL_CATCH` to control the exception handling inside the library.
|
||||
- [thyu](https://github.com/thyu) fixed a compiler warning.
|
||||
|
||||
Thanks a lot for helping out! Please [let me know](mailto:mail@nlohmann.me) if I forgot someone.
|
||||
|
||||
|
||||
## Used third-party tools
|
||||
|
||||
The library itself contains of a single header file licensed under the MIT license. However, it is built, tested, documented, and whatnot using a lot of third-party tools and services. Thanks a lot!
|
||||
The library itself consists of a single header file licensed under the MIT license. However, it is built, tested, documented, and whatnot using a lot of third-party tools and services. Thanks a lot!
|
||||
|
||||
- [**amalgamate.py - Amalgamate C source and header files**](https://github.com/edlund/amalgamate) to create a single header file
|
||||
- [**American fuzzy lop**](http://lcamtuf.coredump.cx/afl/) for fuzz testing
|
||||
- [**AppVeyor**](https://www.appveyor.com) for [continuous integration](https://ci.appveyor.com/project/nlohmann/json) on Windows
|
||||
- [**Artistic Style**](http://astyle.sourceforge.net) for automatic source code identation
|
||||
- [**benchpress**](https://github.com/sbs-ableton/benchpress) to benchmark the code
|
||||
- [**Catch**](https://github.com/philsquared/Catch) for the unit tests
|
||||
- [**Clang**](http://clang.llvm.org) for compilation with code sanitizers
|
||||
- [**Cmake**](https://cmake.org) for build automation
|
||||
@ -936,17 +1075,17 @@ The library itself contains of a single header file licensed under the MIT licen
|
||||
- [**Coveralls**](https://coveralls.io) to measure [code coverage](https://coveralls.io/github/nlohmann/json)
|
||||
- [**Coverity Scan**](https://scan.coverity.com) for [static analysis](https://scan.coverity.com/projects/nlohmann-json)
|
||||
- [**cppcheck**](http://cppcheck.sourceforge.net) for static analysis
|
||||
- [**cxxopts**](https://github.com/jarro2783/cxxopts) to let benchpress parse command-line parameters
|
||||
- [**Doxygen**](http://www.stack.nl/~dimitri/doxygen/) to generate [documentation](https://nlohmann.github.io/json/)
|
||||
- [**git-update-ghpages**](https://github.com/rstacruz/git-update-ghpages) to upload the documentation to gh-pages
|
||||
- [**Github Changelog Generator**](https://github.com/skywinder/github-changelog-generator) to generate the [ChangeLog](https://github.com/nlohmann/json/blob/develop/ChangeLog.md)
|
||||
- [**GitHub Changelog Generator**](https://github.com/skywinder/github-changelog-generator) to generate the [ChangeLog](https://github.com/nlohmann/json/blob/develop/ChangeLog.md)
|
||||
- [**Google Benchmark**](https://github.com/google/benchmark) to implement the benchmarks
|
||||
- [**libFuzzer**](http://llvm.org/docs/LibFuzzer.html) to implement fuzz testing for OSS-Fuzz
|
||||
- [**OSS-Fuzz**](https://github.com/google/oss-fuzz) for continuous fuzz testing of the library
|
||||
- [**OSS-Fuzz**](https://github.com/google/oss-fuzz) for continuous fuzz testing of the library ([project repository](https://github.com/google/oss-fuzz/tree/master/projects/json))
|
||||
- [**Probot**](https://probot.github.io) for automating maintainer tasks such as closing stale issues, requesting missing information, or detecting toxic comments.
|
||||
- [**send_to_wandbox**](https://github.com/nlohmann/json/blob/develop/doc/scripts/send_to_wandbox.py) to send code examples to [Wandbox](http://melpon.org/wandbox)
|
||||
- [**Travis**](https://travis-ci.org) for [continuous integration](https://travis-ci.org/nlohmann/json) on Linux and macOS
|
||||
- [**Valgrind**](http://valgrind.org) to check for correct memory management
|
||||
- [**Wandbox**](http://melpon.org/wandbox) for [online examples](https://wandbox.org/permlink/Op57X0V7fTf2tdwl)
|
||||
- [**Wandbox**](http://melpon.org/wandbox) for [online examples](https://wandbox.org/permlink/TarF5pPn9NtHQjhf)
|
||||
|
||||
|
||||
## Projects using JSON for Modern C++
|
||||
@ -956,7 +1095,7 @@ The library is currently used in Apple macOS Sierra and iOS 10. I am not sure wh
|
||||
|
||||
## Notes
|
||||
|
||||
- The code contains numerous debug **assertions** which can be switched off by defining the preprocessor macro `NDEBUG`, see the [documentation of `assert`](http://en.cppreference.com/w/cpp/error/assert). In particular, note [`operator[]`](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_a2e26bd0b0168abb61f67ad5bcd5b9fa1.html#a2e26bd0b0168abb61f67ad5bcd5b9fa1) implements **unchecked access** for const objects: If the given key is not present, the behavior is undefined (think of a dereferenced null pointer) and yields an [assertion failure](https://github.com/nlohmann/json/issues/289) if assertions are switched on. If you are not sure whether an element in an object exists, use checked access with the [`at()` function](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_a674de1ee73e6bf4843fc5dc1351fb726.html#a674de1ee73e6bf4843fc5dc1351fb726).
|
||||
- The code contains numerous debug **assertions** which can be switched off by defining the preprocessor macro `NDEBUG`, see the [documentation of `assert`](https://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).
|
||||
|
8868
ext/json/json.hpp
8868
ext/json/json.hpp
File diff suppressed because it is too large
Load Diff
2538
ext/librabbitmq/centos_x64/include/amqp.h
Normal file
2538
ext/librabbitmq/centos_x64/include/amqp.h
Normal file
File diff suppressed because it is too large
Load Diff
1144
ext/librabbitmq/centos_x64/include/amqp_framing.h
Normal file
1144
ext/librabbitmq/centos_x64/include/amqp_framing.h
Normal file
File diff suppressed because it is too large
Load Diff
68
ext/librabbitmq/centos_x64/include/amqp_tcp_socket.h
Normal file
68
ext/librabbitmq/centos_x64/include/amqp_tcp_socket.h
Normal file
@ -0,0 +1,68 @@
|
||||
/** \file */
|
||||
/*
|
||||
* Portions created by Alan Antonuk are Copyright (c) 2013-2014 Alan Antonuk.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Portions created by Michael Steinert are Copyright (c) 2012-2013 Michael
|
||||
* Steinert. All Rights Reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A TCP socket connection.
|
||||
*/
|
||||
|
||||
#ifndef AMQP_TCP_SOCKET_H
|
||||
#define AMQP_TCP_SOCKET_H
|
||||
|
||||
#include <amqp.h>
|
||||
|
||||
AMQP_BEGIN_DECLS
|
||||
|
||||
/**
|
||||
* Create a new TCP socket.
|
||||
*
|
||||
* Call amqp_connection_close() to release socket resources.
|
||||
*
|
||||
* \return A new socket object or NULL if an error occurred.
|
||||
*
|
||||
* \since v0.4.0
|
||||
*/
|
||||
AMQP_PUBLIC_FUNCTION
|
||||
amqp_socket_t *AMQP_CALL amqp_tcp_socket_new(amqp_connection_state_t state);
|
||||
|
||||
/**
|
||||
* Assign an open file descriptor to a socket object.
|
||||
*
|
||||
* This function must not be used in conjunction with amqp_socket_open(), i.e.
|
||||
* the socket connection should already be open(2) when this function is
|
||||
* called.
|
||||
*
|
||||
* \param [in,out] self A TCP socket object.
|
||||
* \param [in] sockfd An open socket descriptor.
|
||||
*
|
||||
* \since v0.4.0
|
||||
*/
|
||||
AMQP_PUBLIC_FUNCTION
|
||||
void AMQP_CALL amqp_tcp_socket_set_sockfd(amqp_socket_t *self, int sockfd);
|
||||
|
||||
AMQP_END_DECLS
|
||||
|
||||
#endif /* AMQP_TCP_SOCKET_H */
|
BIN
ext/librabbitmq/centos_x64/lib/librabbitmq.a
Normal file
BIN
ext/librabbitmq/centos_x64/lib/librabbitmq.a
Normal file
Binary file not shown.
@ -1,11 +0,0 @@
|
||||
sudo: required
|
||||
dist: trusty
|
||||
|
||||
python:
|
||||
- "3.4.3"
|
||||
|
||||
addons:
|
||||
rethinkdb: "2.3"
|
||||
|
||||
script:
|
||||
- make test
|
@ -1,16 +0,0 @@
|
||||
RethinkDB Language Drivers
|
||||
|
||||
Copyright 2010-2012 RethinkDB
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this product except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
@ -1,126 +0,0 @@
|
||||
# Customisable build settings
|
||||
|
||||
CXX ?= clang++
|
||||
CXXFLAGS ?=
|
||||
INCLUDE_PYTHON_DOCS ?= no
|
||||
DEBUG ?= no
|
||||
PYTHON ?= python3
|
||||
|
||||
# Required build settings
|
||||
|
||||
ifneq (no,$(DEBUG))
|
||||
CXXFLAGS += -ggdb
|
||||
else
|
||||
CXXFLAGS += -O3 # -flto
|
||||
endif
|
||||
|
||||
CXXFLAGS += -std=c++11 -I'build/gen' -Wall -pthread -fPIC
|
||||
|
||||
prefix ?= /usr
|
||||
DESTDIR ?=
|
||||
|
||||
.DELETE_ON_ERROR:
|
||||
SHELL := /bin/bash
|
||||
|
||||
modules := connection datum json term cursor types utils
|
||||
headers := utils error exceptions types datum connection cursor term
|
||||
|
||||
o_files := $(patsubst %, build/obj/%.o, $(modules))
|
||||
d_files := $(patsubst %, build/dep/%.d, $(modules))
|
||||
|
||||
skip_tests := regression/1133 regression/767 regression/1005 # python-only
|
||||
skip_tests += arity # arity errors are compile-time
|
||||
skip_tests += geo # geo types not implemented yet
|
||||
skip_tests += limits # possibly broken tests: https://github.com/rethinkdb/rethinkdb/issues/5940
|
||||
|
||||
upstream_tests := \
|
||||
$(filter-out %.rb.%, \
|
||||
$(filter-out $(patsubst %,test/upstream/%%, $(skip_tests)), \
|
||||
$(filter test/upstream/$(test_filter)%, \
|
||||
$(shell find test/upstream -name '*.yaml' | egrep -v '.(rb|js).yaml$$'))))
|
||||
upstream_tests_cc := $(patsubst %.yaml, build/tests/%.cc, $(upstream_tests))
|
||||
upstream_tests_o := $(patsubst %.cc, %.o, $(upstream_tests_cc))
|
||||
|
||||
.PRECIOUS: $(upstream_tests_cc) $(upstream_tests_o)
|
||||
|
||||
default: build/librethinkdb++.a build/include/rethinkdb.h build/librethinkdb++.so
|
||||
|
||||
all: default build/test
|
||||
|
||||
build/librethinkdb++.a: $(o_files)
|
||||
ar rcs $@ $^
|
||||
|
||||
build/librethinkdb++.so: $(o_files)
|
||||
$(CXX) -o $@ $(CXXFLAGS) -shared $^
|
||||
|
||||
build/obj/%.o: src/%.cc build/gen/protocol_defs.h
|
||||
@mkdir -p $(dir $@)
|
||||
@mkdir -p $(dir build/dep/$*.d)
|
||||
$(CXX) -o $@ $(CXXFLAGS) -c $< -MP -MQ $@ -MD -MF build/dep/$*.d
|
||||
|
||||
build/gen/protocol_defs.h: reql/ql2.proto reql/gen.py | build/gen/.
|
||||
$(PYTHON) reql/gen.py $< > $@
|
||||
|
||||
clean:
|
||||
rm -rf build
|
||||
|
||||
ifneq (no,$(INCLUDE_PYTHON_DOCS))
|
||||
build/include/rethinkdb.h: build/rethinkdb.nodocs.h reql/add_docs.py reql/python_docs.txt | build/include/.
|
||||
$(PYTHON) reql/add_docs.py reql/python_docs.txt < $< > $@
|
||||
else
|
||||
build/include/rethinkdb.h: build/rethinkdb.nodocs.h | build/include/.
|
||||
cp $< $@
|
||||
endif
|
||||
|
||||
build/rethinkdb.nodocs.h: build/gen/protocol_defs.h $(patsubst %, src/%.h, $(headers))
|
||||
( echo "// Auto-generated file, built from $^"; \
|
||||
echo '#pragma once'; \
|
||||
cat $^ | \
|
||||
grep -v '^#pragma once' | \
|
||||
grep -v '^#include "'; \
|
||||
) > $@
|
||||
|
||||
build/tests/%.cc: %.yaml test/yaml_to_cxx.py
|
||||
@mkdir -p $(dir $@)
|
||||
$(PYTHON) test/yaml_to_cxx.py $< > $@
|
||||
|
||||
build/tests/upstream_tests.cc: $(upstream_tests) test/gen_index_cxx.py FORCE | build/tests/.
|
||||
@echo '$(PYTHON) test/gen_index_cxx.py $(wordlist 1,5,$(upstream_tests)) ... > $@'
|
||||
@$(PYTHON) test/gen_index_cxx.py $(upstream_tests) > $@
|
||||
|
||||
build/tests/%.o: build/tests/%.cc build/include/rethinkdb.h test/testlib.h | build/tests/.
|
||||
$(CXX) -o $@ $(CXXFLAGS) -isystem build/include -I test -c $< -Wno-unused-variable
|
||||
|
||||
build/tests/%.o: test/%.cc test/testlib.h build/include/rethinkdb.h | build/tests/.
|
||||
$(CXX) -o $@ $(CXXFLAGS) -isystem build/include -I test -c $<
|
||||
|
||||
build/test: build/tests/testlib.o build/tests/test.o build/tests/upstream_tests.o $(upstream_tests_o) build/librethinkdb++.a
|
||||
@echo $(CXX) -o $@ $(CXXFLAGS) $(wordlist 1,5,$^) ...
|
||||
@$(CXX) -o $@ $(CXXFLAGS) build/librethinkdb++.a $^
|
||||
|
||||
.PHONY: test
|
||||
test: build/test
|
||||
build/test
|
||||
|
||||
build/bench: build/tests/bench.o build/librethinkdb++.a
|
||||
@$(CXX) -o $@ $(CXXFLAGS) -isystem build/include build/librethinkdb++.a $^
|
||||
|
||||
.PHONY: bench
|
||||
bench: build/bench
|
||||
build/bench
|
||||
|
||||
.PHONY: install
|
||||
install: build/librethinkdb++.a build/include/rethinkdb.h build/librethinkdb++.so
|
||||
install -m755 -d $(DESTDIR)$(prefix)/lib
|
||||
install -m755 -d $(DESTDIR)$(prefix)/include
|
||||
install -m644 build/librethinkdb++.a $(DESTDIR)$(prefix)/lib/librethinkdb++.a
|
||||
install -m644 build/librethinkdb++.so $(DESTDIR)$(prefix)/lib/librethinkdb++.so
|
||||
install -m644 build/include/rethinkdb.h $(DESTDIR)$(prefix)/include/rethinkdb.h
|
||||
|
||||
%/.:
|
||||
mkdir -p $*
|
||||
|
||||
.PHONY: FORCE
|
||||
FORCE:
|
||||
|
||||
-include $(d_files)
|
@ -1,72 +0,0 @@
|
||||
# RethinkDB driver for C++
|
||||
|
||||
This driver is compatible with RethinkDB 2.0. It is based on the
|
||||
official RethinkDB Python driver.
|
||||
|
||||
* [RethinkDB server](http://rethinkdb.com/)
|
||||
* [RethinkDB API docs](http://rethinkdb.com/api/python/)
|
||||
|
||||
## Example
|
||||
|
||||
```
|
||||
#include <memory>
|
||||
#include <cstdio>
|
||||
#include <rethinkdb.h>
|
||||
|
||||
namespace R = RethinkDB;
|
||||
|
||||
int main() {
|
||||
std::unique_ptr<R::Connection> conn = R::connect("localhost", 28015);
|
||||
R::Cursor cursor = R::table("users").filter(R::row["age"] > 14).run(*conn);
|
||||
for (R::Datum& user : cursor) {
|
||||
printf("%s\n", user.as_json().c_str());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
Requires a modern C++ compiler. to build and install, run:
|
||||
|
||||
```
|
||||
make
|
||||
make install
|
||||
```
|
||||
|
||||
Will build `include/rethinkdb.h`, `librethinkdb++.a` and
|
||||
`librethinkdb++.so` into the `build/` directory.
|
||||
|
||||
To include documentation from the Python driver in the header file,
|
||||
pass the following argument to make.
|
||||
|
||||
```
|
||||
make INCLUDE_PYTHON_DOCS=yes
|
||||
```
|
||||
|
||||
To build in debug mode:
|
||||
|
||||
```
|
||||
make DEBUG=yes
|
||||
```
|
||||
|
||||
To install to a specific location:
|
||||
|
||||
```
|
||||
make install prefix=/usr/local DESTDIR=
|
||||
```
|
||||
|
||||
## Status
|
||||
|
||||
Still in early stages of development.
|
||||
|
||||
## Tests
|
||||
|
||||
This driver is tested against the upstream ReQL tests from the
|
||||
RethinkDB repo, which are programmatically translated from Python to
|
||||
C++. As of 34dc13c, all tests pass:
|
||||
|
||||
```
|
||||
$ make test
|
||||
...
|
||||
SUCCESS: 2053 tests passed
|
||||
```
|
@ -1,80 +0,0 @@
|
||||
from sys import stdin, stderr, stdout, argv
|
||||
from re import match, sub
|
||||
|
||||
docs = {}
|
||||
|
||||
for line in open(argv[1]):
|
||||
res = match('^\t\(([^,]*), (.*)\),$', line)
|
||||
if res:
|
||||
fullname = res.group(1)
|
||||
docs[fullname.split('.')[-1]] = eval(res.group(2)).decode('utf-8')
|
||||
|
||||
translate_name = {
|
||||
'name': None,
|
||||
'delete_': 'delete',
|
||||
'union_': 'union',
|
||||
'operator[]': '__getitem__',
|
||||
'operator+': '__add__',
|
||||
'operator-': '__sub__',
|
||||
'operator*': '__mul__',
|
||||
'operator/': '__div__',
|
||||
'operator%': '__mod__',
|
||||
'operator&&': 'and_',
|
||||
'operator||': 'or_',
|
||||
'operator==': '__eq__',
|
||||
'operator!=': '__ne__',
|
||||
'operator>': '__gt__',
|
||||
'operator>=': '__ge__',
|
||||
'operator<': '__lt__',
|
||||
'operator<=': '__le__',
|
||||
'operator!': 'not_',
|
||||
'default_': 'default',
|
||||
'array': None,
|
||||
'desc': None,
|
||||
'asc': None,
|
||||
'maxval': None,
|
||||
'minval': None,
|
||||
'january': None,
|
||||
'february': None,
|
||||
'march': None,
|
||||
'april': None,
|
||||
'may': None,
|
||||
'june': None,
|
||||
'july': None,
|
||||
'august': None,
|
||||
'september': None,
|
||||
'october': None,
|
||||
'november': None,
|
||||
'december': None,
|
||||
'monday': None,
|
||||
'tuesday': None,
|
||||
'wednesday': None,
|
||||
'thursday': None,
|
||||
'friday': None,
|
||||
'saturday': None,
|
||||
'sunday': None,
|
||||
}
|
||||
|
||||
def print_docs(name, line):
|
||||
py_name = translate_name.get(name, name)
|
||||
if py_name in docs:
|
||||
indent = match("^( *)", line).group(1)
|
||||
stdout.write('\n')
|
||||
# TODO: convert the examples to C++
|
||||
for line in docs[py_name].split('\n'):
|
||||
stdout.write(indent + "// " + line + '\n')
|
||||
elif py_name:
|
||||
stderr.write('Warning: no docs for ' + py_name + ': ' + line)
|
||||
|
||||
stdout.write('// Contains documentation copied as-is from the Python driver')
|
||||
|
||||
for line in stdin:
|
||||
res = match("^ *CO?[0-9_]+\(([^,)]+)|extern Query (\w+)|^ *// *(\$)doc\((\w+)\) *$", line)
|
||||
if res:
|
||||
name = res.group(1) or res.group(2) or res.group(4)
|
||||
print_docs(name, line)
|
||||
if not res.group(3):
|
||||
stdout.write(line)
|
||||
else:
|
||||
stdout.write(line)
|
||||
|
@ -1,33 +0,0 @@
|
||||
from sys import argv
|
||||
from re import sub, finditer, VERBOSE
|
||||
|
||||
def gen(defs):
|
||||
indent = 0
|
||||
enum = False
|
||||
def p(s): print(" " * (indent * 4) + s)
|
||||
for item in finditer("""
|
||||
(?P<type> message|enum) \\s+ (?P<name> \\w+) \\s* \\{ |
|
||||
(?P<var> \\w+) \\s* = \\s* (?P<val> \\w+) \\s* ; |
|
||||
\\}
|
||||
""", defs, flags=VERBOSE):
|
||||
if item.group(0) == "}":
|
||||
indent = indent - 1
|
||||
p("};" if enum else "}")
|
||||
enum = False;
|
||||
elif item.group('type') == 'enum':
|
||||
p("enum class %s {" % item.group('name'))
|
||||
indent = indent + 1
|
||||
enum = True
|
||||
elif item.group('type') == 'message':
|
||||
p("namespace %s {" % item.group('name'))
|
||||
indent = indent + 1
|
||||
enum = False
|
||||
else:
|
||||
if enum:
|
||||
p("%s = %s," % (item.group('var'), item.group('val')))
|
||||
|
||||
print("// Auto-generated by reql/gen.py")
|
||||
print("#pragma once")
|
||||
print("namespace RethinkDB { namespace Protocol {")
|
||||
gen(sub("//.*", "", open(argv[1]).read()))
|
||||
print("} }")
|
File diff suppressed because one or more lines are too long
@ -1,843 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// THE HIGH-LEVEL VIEW //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Process: When you first open a connection, send the magic number
|
||||
// for the version of the protobuf you're targeting (in the [Version]
|
||||
// enum). This should **NOT** be sent as a protobuf; just send the
|
||||
// little-endian 32-bit integer over the wire raw. This number should
|
||||
// only be sent once per connection.
|
||||
|
||||
// The magic number shall be followed by an authorization key. The
|
||||
// first 4 bytes are the length of the key to be sent as a little-endian
|
||||
// 32-bit integer, followed by the key string. Even if there is no key,
|
||||
// an empty string should be sent (length 0 and no data).
|
||||
|
||||
// Following the authorization key, the client shall send a magic number
|
||||
// for the communication protocol they want to use (in the [Protocol]
|
||||
// enum). This shall be a little-endian 32-bit integer.
|
||||
|
||||
// The server will then respond with a NULL-terminated string response.
|
||||
// "SUCCESS" indicates that the connection has been accepted. Any other
|
||||
// response indicates an error, and the response string should describe
|
||||
// the error.
|
||||
|
||||
// Next, for each query you want to send, construct a [Query] protobuf
|
||||
// and serialize it to a binary blob. Send the blob's size to the
|
||||
// server encoded as a little-endian 32-bit integer, followed by the
|
||||
// blob itself. You will recieve a [Response] protobuf back preceded
|
||||
// by its own size, once again encoded as a little-endian 32-bit
|
||||
// integer. You can see an example exchange below in **EXAMPLE**.
|
||||
|
||||
// A query consists of a [Term] to evaluate and a unique-per-connection
|
||||
// [token].
|
||||
|
||||
// Tokens are used for two things:
|
||||
// * Keeping track of which responses correspond to which queries.
|
||||
// * Batched queries. Some queries return lots of results, so we send back
|
||||
// batches of <1000, and you need to send a [CONTINUE] query with the same
|
||||
// token to get more results from the original query.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
message VersionDummy { // We need to wrap it like this for some
|
||||
// non-conforming protobuf libraries
|
||||
// This enum contains the magic numbers for your version. See **THE HIGH-LEVEL
|
||||
// VIEW** for what to do with it.
|
||||
enum Version {
|
||||
V0_1 = 0x3f61ba36;
|
||||
V0_2 = 0x723081e1; // Authorization key during handshake
|
||||
V0_3 = 0x5f75e83e; // Authorization key and protocol during handshake
|
||||
V0_4 = 0x400c2d20; // Queries execute in parallel
|
||||
V1_0 = 0x34c2bdc3; // Users and permissions
|
||||
}
|
||||
|
||||
// The protocol to use after the handshake, specified in V0_3
|
||||
enum Protocol {
|
||||
PROTOBUF = 0x271ffc41;
|
||||
JSON = 0x7e6970c7;
|
||||
}
|
||||
}
|
||||
|
||||
// You send one of:
|
||||
// * A [START] query with a [Term] to evaluate and a unique-per-connection token.
|
||||
// * A [CONTINUE] query with the same token as a [START] query that returned
|
||||
// [SUCCESS_PARTIAL] in its [Response].
|
||||
// * A [STOP] query with the same token as a [START] query that you want to stop.
|
||||
// * A [NOREPLY_WAIT] query with a unique per-connection token. The server answers
|
||||
// with a [WAIT_COMPLETE] [Response].
|
||||
// * A [SERVER_INFO] query. The server answers with a [SERVER_INFO] [Response].
|
||||
message Query {
|
||||
enum QueryType {
|
||||
START = 1; // Start a new query.
|
||||
CONTINUE = 2; // Continue a query that returned [SUCCESS_PARTIAL]
|
||||
// (see [Response]).
|
||||
STOP = 3; // Stop a query partway through executing.
|
||||
NOREPLY_WAIT = 4; // Wait for noreply operations to finish.
|
||||
SERVER_INFO = 5; // Get server information.
|
||||
}
|
||||
optional QueryType type = 1;
|
||||
// A [Term] is how we represent the operations we want a query to perform.
|
||||
optional Term query = 2; // only present when [type] = [START]
|
||||
optional int64 token = 3;
|
||||
// This flag is ignored on the server. `noreply` should be added
|
||||
// to `global_optargs` instead (the key "noreply" should map to
|
||||
// either true or false).
|
||||
optional bool OBSOLETE_noreply = 4 [default = false];
|
||||
|
||||
// If this is set to [true], then [Datum] values will sometimes be
|
||||
// of [DatumType] [R_JSON] (see below). This can provide enormous
|
||||
// speedups in languages with poor protobuf libraries.
|
||||
optional bool accepts_r_json = 5 [default = false];
|
||||
|
||||
message AssocPair {
|
||||
optional string key = 1;
|
||||
optional Term val = 2;
|
||||
}
|
||||
repeated AssocPair global_optargs = 6;
|
||||
}
|
||||
|
||||
// A backtrace frame (see `backtrace` in Response below)
|
||||
message Frame {
|
||||
enum FrameType {
|
||||
POS = 1; // Error occurred in a positional argument.
|
||||
OPT = 2; // Error occurred in an optional argument.
|
||||
}
|
||||
optional FrameType type = 1;
|
||||
optional int64 pos = 2; // The index of the positional argument.
|
||||
optional string opt = 3; // The name of the optional argument.
|
||||
}
|
||||
message Backtrace {
|
||||
repeated Frame frames = 1;
|
||||
}
|
||||
|
||||
// You get back a response with the same [token] as your query.
|
||||
message Response {
|
||||
enum ResponseType {
|
||||
// These response types indicate success.
|
||||
SUCCESS_ATOM = 1; // Query returned a single RQL datatype.
|
||||
SUCCESS_SEQUENCE = 2; // Query returned a sequence of RQL datatypes.
|
||||
SUCCESS_PARTIAL = 3; // Query returned a partial sequence of RQL
|
||||
// datatypes. If you send a [CONTINUE] query with
|
||||
// the same token as this response, you will get
|
||||
// more of the sequence. Keep sending [CONTINUE]
|
||||
// queries until you get back [SUCCESS_SEQUENCE].
|
||||
WAIT_COMPLETE = 4; // A [NOREPLY_WAIT] query completed.
|
||||
SERVER_INFO = 5; // The data for a [SERVER_INFO] request. This is
|
||||
// the same as `SUCCESS_ATOM` except that there will
|
||||
// never be profiling data.
|
||||
|
||||
// These response types indicate failure.
|
||||
CLIENT_ERROR = 16; // Means the client is buggy. An example is if the
|
||||
// client sends a malformed protobuf, or tries to
|
||||
// send [CONTINUE] for an unknown token.
|
||||
COMPILE_ERROR = 17; // Means the query failed during parsing or type
|
||||
// checking. For example, if you pass too many
|
||||
// arguments to a function.
|
||||
RUNTIME_ERROR = 18; // Means the query failed at runtime. An example is
|
||||
// if you add together two values from a table, but
|
||||
// they turn out at runtime to be booleans rather
|
||||
// than numbers.
|
||||
}
|
||||
optional ResponseType type = 1;
|
||||
|
||||
// If `ResponseType` is `RUNTIME_ERROR`, this may be filled in with more
|
||||
// information about the error.
|
||||
enum ErrorType {
|
||||
INTERNAL = 1000000;
|
||||
RESOURCE_LIMIT = 2000000;
|
||||
QUERY_LOGIC = 3000000;
|
||||
NON_EXISTENCE = 3100000;
|
||||
OP_FAILED = 4100000;
|
||||
OP_INDETERMINATE = 4200000;
|
||||
USER = 5000000;
|
||||
PERMISSION_ERROR = 6000000;
|
||||
}
|
||||
optional ErrorType error_type = 7;
|
||||
|
||||
// ResponseNotes are used to provide information about the query
|
||||
// response that may be useful for people writing drivers or ORMs.
|
||||
// Currently all the notes we send indicate that a stream has certain
|
||||
// special properties.
|
||||
enum ResponseNote {
|
||||
// The stream is a changefeed stream (e.g. `r.table('test').changes()`).
|
||||
SEQUENCE_FEED = 1;
|
||||
// The stream is a point changefeed stream
|
||||
// (e.g. `r.table('test').get(0).changes()`).
|
||||
ATOM_FEED = 2;
|
||||
// The stream is an order_by_limit changefeed stream
|
||||
// (e.g. `r.table('test').order_by(index: 'id').limit(5).changes()`).
|
||||
ORDER_BY_LIMIT_FEED = 3;
|
||||
// The stream is a union of multiple changefeed types that can't be
|
||||
// collapsed to a single type
|
||||
// (e.g. `r.table('test').changes().union(r.table('test').get(0).changes())`).
|
||||
UNIONED_FEED = 4;
|
||||
// The stream is a changefeed stream and includes notes on what state
|
||||
// the changefeed stream is in (e.g. objects of the form `{state:
|
||||
// 'initializing'}`).
|
||||
INCLUDES_STATES = 5;
|
||||
}
|
||||
repeated ResponseNote notes = 6;
|
||||
|
||||
optional int64 token = 2; // Indicates what [Query] this response corresponds to.
|
||||
|
||||
// [response] contains 1 RQL datum if [type] is [SUCCESS_ATOM] or
|
||||
// [SERVER_INFO]. [response] contains many RQL data if [type] is
|
||||
// [SUCCESS_SEQUENCE] or [SUCCESS_PARTIAL]. [response] contains 1
|
||||
// error message (of type [R_STR]) in all other cases.
|
||||
repeated Datum response = 3;
|
||||
|
||||
// If [type] is [CLIENT_ERROR], [TYPE_ERROR], or [RUNTIME_ERROR], then a
|
||||
// backtrace will be provided. The backtrace says where in the query the
|
||||
// error occurred. Ideally this information will be presented to the user as
|
||||
// a pretty-printed version of their query with the erroneous section
|
||||
// underlined. A backtrace is a series of 0 or more [Frame]s, each of which
|
||||
// specifies either the index of a positional argument or the name of an
|
||||
// optional argument. (Those words will make more sense if you look at the
|
||||
// [Term] message below.)
|
||||
optional Backtrace backtrace = 4; // Contains n [Frame]s when you get back an error.
|
||||
|
||||
// If the [global_optargs] in the [Query] that this [Response] is a
|
||||
// response to contains a key "profile" which maps to a static value of
|
||||
// true then [profile] will contain a [Datum] which provides profiling
|
||||
// information about the execution of the query. This field should be
|
||||
// returned to the user along with the result that would normally be
|
||||
// returned (a datum or a cursor). In official drivers this is accomplished
|
||||
// by putting them inside of an object with "value" mapping to the return
|
||||
// value and "profile" mapping to the profile object.
|
||||
optional Datum profile = 5;
|
||||
}
|
||||
|
||||
// A [Datum] is a chunk of data that can be serialized to disk or returned to
|
||||
// the user in a Response. Currently we only support JSON types, but we may
|
||||
// support other types in the future (e.g., a date type or an integer type).
|
||||
message Datum {
|
||||
enum DatumType {
|
||||
R_NULL = 1;
|
||||
R_BOOL = 2;
|
||||
R_NUM = 3; // a double
|
||||
R_STR = 4;
|
||||
R_ARRAY = 5;
|
||||
R_OBJECT = 6;
|
||||
// This [DatumType] will only be used if [accepts_r_json] is
|
||||
// set to [true] in [Query]. [r_str] will be filled with a
|
||||
// JSON encoding of the [Datum].
|
||||
R_JSON = 7; // uses r_str
|
||||
}
|
||||
optional DatumType type = 1;
|
||||
optional bool r_bool = 2;
|
||||
optional double r_num = 3;
|
||||
optional string r_str = 4;
|
||||
|
||||
repeated Datum r_array = 5;
|
||||
message AssocPair {
|
||||
optional string key = 1;
|
||||
optional Datum val = 2;
|
||||
}
|
||||
repeated AssocPair r_object = 6;
|
||||
}
|
||||
|
||||
// A [Term] is either a piece of data (see **Datum** above), or an operator and
|
||||
// its operands. If you have a [Datum], it's stored in the member [datum]. If
|
||||
// you have an operator, its positional arguments are stored in [args] and its
|
||||
// optional arguments are stored in [optargs].
|
||||
//
|
||||
// A note about type signatures:
|
||||
// We use the following notation to denote types:
|
||||
// arg1_type, arg2_type, argrest_type... -> result_type
|
||||
// So, for example, if we have a function `avg` that takes any number of
|
||||
// arguments and averages them, we might write:
|
||||
// NUMBER... -> NUMBER
|
||||
// Or if we had a function that took one number modulo another:
|
||||
// NUMBER, NUMBER -> NUMBER
|
||||
// Or a function that takes a table and a primary key of any Datum type, then
|
||||
// retrieves the entry with that primary key:
|
||||
// Table, DATUM -> OBJECT
|
||||
// Some arguments must be provided as literal values (and not the results of sub
|
||||
// terms). These are marked with a `!`.
|
||||
// Optional arguments are specified within curly braces as argname `:` value
|
||||
// type (e.x `{noreply:BOOL}`)
|
||||
// Many RQL operations are polymorphic. For these, alterantive type signatures
|
||||
// are separated by `|`.
|
||||
//
|
||||
// The RQL type hierarchy is as follows:
|
||||
// Top
|
||||
// DATUM
|
||||
// NULL
|
||||
// BOOL
|
||||
// NUMBER
|
||||
// STRING
|
||||
// OBJECT
|
||||
// SingleSelection
|
||||
// ARRAY
|
||||
// Sequence
|
||||
// ARRAY
|
||||
// Stream
|
||||
// StreamSelection
|
||||
// Table
|
||||
// Database
|
||||
// Function
|
||||
// Ordering - used only by ORDER_BY
|
||||
// Pathspec -- an object, string, or array that specifies a path
|
||||
// Error
|
||||
message Term {
|
||||
enum TermType {
|
||||
// A RQL datum, stored in `datum` below.
|
||||
DATUM = 1;
|
||||
|
||||
MAKE_ARRAY = 2; // DATUM... -> ARRAY
|
||||
// Evaluate the terms in [optargs] and make an object
|
||||
MAKE_OBJ = 3; // {...} -> OBJECT
|
||||
|
||||
// * Compound types
|
||||
|
||||
// Takes an integer representing a variable and returns the value stored
|
||||
// in that variable. It's the responsibility of the client to translate
|
||||
// from their local representation of a variable to a unique _non-negative_
|
||||
// integer for that variable. (We do it this way instead of letting
|
||||
// clients provide variable names as strings to discourage
|
||||
// variable-capturing client libraries, and because it's more efficient
|
||||
// on the wire.)
|
||||
VAR = 10; // !NUMBER -> DATUM
|
||||
// Takes some javascript code and executes it.
|
||||
JAVASCRIPT = 11; // STRING {timeout: !NUMBER} -> DATUM |
|
||||
// STRING {timeout: !NUMBER} -> Function(*)
|
||||
UUID = 169; // () -> DATUM
|
||||
|
||||
// Takes an HTTP URL and gets it. If the get succeeds and
|
||||
// returns valid JSON, it is converted into a DATUM
|
||||
HTTP = 153; // STRING {data: OBJECT | STRING,
|
||||
// timeout: !NUMBER,
|
||||
// method: STRING,
|
||||
// params: OBJECT,
|
||||
// header: OBJECT | ARRAY,
|
||||
// attempts: NUMBER,
|
||||
// redirects: NUMBER,
|
||||
// verify: BOOL,
|
||||
// page: FUNC | STRING,
|
||||
// page_limit: NUMBER,
|
||||
// auth: OBJECT,
|
||||
// result_format: STRING,
|
||||
// } -> STRING | STREAM
|
||||
|
||||
// Takes a string and throws an error with that message.
|
||||
// Inside of a `default` block, you can omit the first
|
||||
// argument to rethrow whatever error you catch (this is most
|
||||
// useful as an argument to the `default` filter optarg).
|
||||
ERROR = 12; // STRING -> Error | -> Error
|
||||
// Takes nothing and returns a reference to the implicit variable.
|
||||
IMPLICIT_VAR = 13; // -> DATUM
|
||||
|
||||
// * Data Operators
|
||||
// Returns a reference to a database.
|
||||
DB = 14; // STRING -> Database
|
||||
// Returns a reference to a table.
|
||||
TABLE = 15; // Database, STRING, {read_mode:STRING, identifier_format:STRING} -> Table
|
||||
// STRING, {read_mode:STRING, identifier_format:STRING} -> Table
|
||||
// Gets a single element from a table by its primary or a secondary key.
|
||||
GET = 16; // Table, STRING -> SingleSelection | Table, NUMBER -> SingleSelection |
|
||||
// Table, STRING -> NULL | Table, NUMBER -> NULL |
|
||||
GET_ALL = 78; // Table, DATUM..., {index:!STRING} => ARRAY
|
||||
|
||||
// Simple DATUM Ops
|
||||
EQ = 17; // DATUM... -> BOOL
|
||||
NE = 18; // DATUM... -> BOOL
|
||||
LT = 19; // DATUM... -> BOOL
|
||||
LE = 20; // DATUM... -> BOOL
|
||||
GT = 21; // DATUM... -> BOOL
|
||||
GE = 22; // DATUM... -> BOOL
|
||||
NOT = 23; // BOOL -> BOOL
|
||||
// ADD can either add two numbers or concatenate two arrays.
|
||||
ADD = 24; // NUMBER... -> NUMBER | STRING... -> STRING
|
||||
SUB = 25; // NUMBER... -> NUMBER
|
||||
MUL = 26; // NUMBER... -> NUMBER
|
||||
DIV = 27; // NUMBER... -> NUMBER
|
||||
MOD = 28; // NUMBER, NUMBER -> NUMBER
|
||||
|
||||
FLOOR = 183; // NUMBER -> NUMBER
|
||||
CEIL = 184; // NUMBER -> NUMBER
|
||||
ROUND = 185; // NUMBER -> NUMBER
|
||||
|
||||
// DATUM Array Ops
|
||||
// Append a single element to the end of an array (like `snoc`).
|
||||
APPEND = 29; // ARRAY, DATUM -> ARRAY
|
||||
// Prepend a single element to the end of an array (like `cons`).
|
||||
PREPEND = 80; // ARRAY, DATUM -> ARRAY
|
||||
//Remove the elements of one array from another array.
|
||||
DIFFERENCE = 95; // ARRAY, ARRAY -> ARRAY
|
||||
|
||||
// DATUM Set Ops
|
||||
// Set ops work on arrays. They don't use actual sets and thus have
|
||||
// performance characteristics you would expect from arrays rather than
|
||||
// from sets. All set operations have the post condition that they
|
||||
// array they return contains no duplicate values.
|
||||
SET_INSERT = 88; // ARRAY, DATUM -> ARRAY
|
||||
SET_INTERSECTION = 89; // ARRAY, ARRAY -> ARRAY
|
||||
SET_UNION = 90; // ARRAY, ARRAY -> ARRAY
|
||||
SET_DIFFERENCE = 91; // ARRAY, ARRAY -> ARRAY
|
||||
|
||||
SLICE = 30; // Sequence, NUMBER, NUMBER -> Sequence
|
||||
SKIP = 70; // Sequence, NUMBER -> Sequence
|
||||
LIMIT = 71; // Sequence, NUMBER -> Sequence
|
||||
OFFSETS_OF = 87; // Sequence, DATUM -> Sequence | Sequence, Function(1) -> Sequence
|
||||
CONTAINS = 93; // Sequence, (DATUM | Function(1))... -> BOOL
|
||||
|
||||
// Stream/Object Ops
|
||||
// Get a particular field from an object, or map that over a
|
||||
// sequence.
|
||||
GET_FIELD = 31; // OBJECT, STRING -> DATUM
|
||||
// | Sequence, STRING -> Sequence
|
||||
// Return an array containing the keys of the object.
|
||||
KEYS = 94; // OBJECT -> ARRAY
|
||||
// Return an array containing the values of the object.
|
||||
VALUES = 186; // OBJECT -> ARRAY
|
||||
// Creates an object
|
||||
OBJECT = 143; // STRING, DATUM, ... -> OBJECT
|
||||
// Check whether an object contains all the specified fields,
|
||||
// or filters a sequence so that all objects inside of it
|
||||
// contain all the specified fields.
|
||||
HAS_FIELDS = 32; // OBJECT, Pathspec... -> BOOL
|
||||
// x.with_fields(...) <=> x.has_fields(...).pluck(...)
|
||||
WITH_FIELDS = 96; // Sequence, Pathspec... -> Sequence
|
||||
// Get a subset of an object by selecting some attributes to preserve,
|
||||
// or map that over a sequence. (Both pick and pluck, polymorphic.)
|
||||
PLUCK = 33; // Sequence, Pathspec... -> Sequence | OBJECT, Pathspec... -> OBJECT
|
||||
// Get a subset of an object by selecting some attributes to discard, or
|
||||
// map that over a sequence. (Both unpick and without, polymorphic.)
|
||||
WITHOUT = 34; // Sequence, Pathspec... -> Sequence | OBJECT, Pathspec... -> OBJECT
|
||||
// Merge objects (right-preferential)
|
||||
MERGE = 35; // OBJECT... -> OBJECT | Sequence -> Sequence
|
||||
|
||||
// Sequence Ops
|
||||
// Get all elements of a sequence between two values.
|
||||
// Half-open by default, but the openness of either side can be
|
||||
// changed by passing 'closed' or 'open for `right_bound` or
|
||||
// `left_bound`.
|
||||
BETWEEN_DEPRECATED = 36; // Deprecated version of between, which allows `null` to specify unboundedness
|
||||
// With the newer version, clients should use `r.minval` and `r.maxval` for unboundedness
|
||||
BETWEEN = 182; // StreamSelection, DATUM, DATUM, {index:!STRING, right_bound:STRING, left_bound:STRING} -> StreamSelection
|
||||
REDUCE = 37; // Sequence, Function(2) -> DATUM
|
||||
MAP = 38; // Sequence, Function(1) -> Sequence
|
||||
// The arity of the function should be
|
||||
// Sequence..., Function(sizeof...(Sequence)) -> Sequence
|
||||
|
||||
FOLD = 187; // Sequence, Datum, Function(2), {Function(3), Function(1)
|
||||
|
||||
// Filter a sequence with either a function or a shortcut
|
||||
// object (see API docs for details). The body of FILTER is
|
||||
// wrapped in an implicit `.default(false)`, and you can
|
||||
// change the default value by specifying the `default`
|
||||
// optarg. If you make the default `r.error`, all errors
|
||||
// caught by `default` will be rethrown as if the `default`
|
||||
// did not exist.
|
||||
FILTER = 39; // Sequence, Function(1), {default:DATUM} -> Sequence |
|
||||
// Sequence, OBJECT, {default:DATUM} -> Sequence
|
||||
// Map a function over a sequence and then concatenate the results together.
|
||||
CONCAT_MAP = 40; // Sequence, Function(1) -> Sequence
|
||||
// Order a sequence based on one or more attributes.
|
||||
ORDER_BY = 41; // Sequence, (!STRING | Ordering)..., {index: (!STRING | Ordering)} -> Sequence
|
||||
// Get all distinct elements of a sequence (like `uniq`).
|
||||
DISTINCT = 42; // Sequence -> Sequence
|
||||
// Count the number of elements in a sequence, or only the elements that match
|
||||
// a given filter.
|
||||
COUNT = 43; // Sequence -> NUMBER | Sequence, DATUM -> NUMBER | Sequence, Function(1) -> NUMBER
|
||||
IS_EMPTY = 86; // Sequence -> BOOL
|
||||
// Take the union of multiple sequences (preserves duplicate elements! (use distinct)).
|
||||
UNION = 44; // Sequence... -> Sequence
|
||||
// Get the Nth element of a sequence.
|
||||
NTH = 45; // Sequence, NUMBER -> DATUM
|
||||
// do NTH or GET_FIELD depending on target object
|
||||
BRACKET = 170; // Sequence | OBJECT, NUMBER | STRING -> DATUM
|
||||
// OBSOLETE_GROUPED_MAPREDUCE = 46;
|
||||
// OBSOLETE_GROUPBY = 47;
|
||||
|
||||
INNER_JOIN = 48; // Sequence, Sequence, Function(2) -> Sequence
|
||||
OUTER_JOIN = 49; // Sequence, Sequence, Function(2) -> Sequence
|
||||
// An inner-join that does an equality comparison on two attributes.
|
||||
EQ_JOIN = 50; // Sequence, !STRING, Sequence, {index:!STRING} -> Sequence
|
||||
ZIP = 72; // Sequence -> Sequence
|
||||
RANGE = 173; // -> Sequence [0, +inf)
|
||||
// NUMBER -> Sequence [0, a)
|
||||
// NUMBER, NUMBER -> Sequence [a, b)
|
||||
|
||||
// Array Ops
|
||||
// Insert an element in to an array at a given index.
|
||||
INSERT_AT = 82; // ARRAY, NUMBER, DATUM -> ARRAY
|
||||
// Remove an element at a given index from an array.
|
||||
DELETE_AT = 83; // ARRAY, NUMBER -> ARRAY |
|
||||
// ARRAY, NUMBER, NUMBER -> ARRAY
|
||||
// Change the element at a given index of an array.
|
||||
CHANGE_AT = 84; // ARRAY, NUMBER, DATUM -> ARRAY
|
||||
// Splice one array in to another array.
|
||||
SPLICE_AT = 85; // ARRAY, NUMBER, ARRAY -> ARRAY
|
||||
|
||||
// * Type Ops
|
||||
// Coerces a datum to a named type (e.g. "bool").
|
||||
// If you previously used `stream_to_array`, you should use this instead
|
||||
// with the type "array".
|
||||
COERCE_TO = 51; // Top, STRING -> Top
|
||||
// Returns the named type of a datum (e.g. TYPE_OF(true) = "BOOL")
|
||||
TYPE_OF = 52; // Top -> STRING
|
||||
|
||||
// * Write Ops (the OBJECTs contain data about number of errors etc.)
|
||||
// Updates all the rows in a selection. Calls its Function with the row
|
||||
// to be updated, and then merges the result of that call.
|
||||
UPDATE = 53; // StreamSelection, Function(1), {non_atomic:BOOL, durability:STRING, return_changes:BOOL} -> OBJECT |
|
||||
// SingleSelection, Function(1), {non_atomic:BOOL, durability:STRING, return_changes:BOOL} -> OBJECT |
|
||||
// StreamSelection, OBJECT, {non_atomic:BOOL, durability:STRING, return_changes:BOOL} -> OBJECT |
|
||||
// SingleSelection, OBJECT, {non_atomic:BOOL, durability:STRING, return_changes:BOOL} -> OBJECT
|
||||
// Deletes all the rows in a selection.
|
||||
DELETE = 54; // StreamSelection, {durability:STRING, return_changes:BOOL} -> OBJECT | SingleSelection -> OBJECT
|
||||
// Replaces all the rows in a selection. Calls its Function with the row
|
||||
// to be replaced, and then discards it and stores the result of that
|
||||
// call.
|
||||
REPLACE = 55; // StreamSelection, Function(1), {non_atomic:BOOL, durability:STRING, return_changes:BOOL} -> OBJECT | SingleSelection, Function(1), {non_atomic:BOOL, durability:STRING, return_changes:BOOL} -> OBJECT
|
||||
// Inserts into a table. If `conflict` is replace, overwrites
|
||||
// entries with the same primary key. If `conflict` is
|
||||
// update, does an update on the entry. If `conflict` is
|
||||
// error, or is omitted, conflicts will trigger an error.
|
||||
INSERT = 56; // Table, OBJECT, {conflict:STRING, durability:STRING, return_changes:BOOL} -> OBJECT | Table, Sequence, {conflict:STRING, durability:STRING, return_changes:BOOL} -> OBJECT
|
||||
|
||||
// * Administrative OPs
|
||||
// Creates a database with a particular name.
|
||||
DB_CREATE = 57; // STRING -> OBJECT
|
||||
// Drops a database with a particular name.
|
||||
DB_DROP = 58; // STRING -> OBJECT
|
||||
// Lists all the databases by name. (Takes no arguments)
|
||||
DB_LIST = 59; // -> ARRAY
|
||||
// Creates a table with a particular name in a particular
|
||||
// database. (You may omit the first argument to use the
|
||||
// default database.)
|
||||
TABLE_CREATE = 60; // Database, STRING, {primary_key:STRING, shards:NUMBER, replicas:NUMBER, primary_replica_tag:STRING} -> OBJECT
|
||||
// Database, STRING, {primary_key:STRING, shards:NUMBER, replicas:OBJECT, primary_replica_tag:STRING} -> OBJECT
|
||||
// STRING, {primary_key:STRING, shards:NUMBER, replicas:NUMBER, primary_replica_tag:STRING} -> OBJECT
|
||||
// STRING, {primary_key:STRING, shards:NUMBER, replicas:OBJECT, primary_replica_tag:STRING} -> OBJECT
|
||||
// Drops a table with a particular name from a particular
|
||||
// database. (You may omit the first argument to use the
|
||||
// default database.)
|
||||
TABLE_DROP = 61; // Database, STRING -> OBJECT
|
||||
// STRING -> OBJECT
|
||||
// Lists all the tables in a particular database. (You may
|
||||
// omit the first argument to use the default database.)
|
||||
TABLE_LIST = 62; // Database -> ARRAY
|
||||
// -> ARRAY
|
||||
// Returns the row in the `rethinkdb.table_config` or `rethinkdb.db_config` table
|
||||
// that corresponds to the given database or table.
|
||||
CONFIG = 174; // Database -> SingleSelection
|
||||
// Table -> SingleSelection
|
||||
// Returns the row in the `rethinkdb.table_status` table that corresponds to the
|
||||
// given table.
|
||||
STATUS = 175; // Table -> SingleSelection
|
||||
// Called on a table, waits for that table to be ready for read/write operations.
|
||||
// Called on a database, waits for all of the tables in the database to be ready.
|
||||
// Returns the corresponding row or rows from the `rethinkdb.table_status` table.
|
||||
WAIT = 177; // Table -> OBJECT
|
||||
// Database -> OBJECT
|
||||
// Generates a new config for the given table, or all tables in the given database
|
||||
// The `shards` and `replicas` arguments are required. If `emergency_repair` is
|
||||
// specified, it will enter a completely different mode of repairing a table
|
||||
// which has lost half or more of its replicas.
|
||||
RECONFIGURE = 176; // Database|Table, {shards:NUMBER, replicas:NUMBER [,
|
||||
// dry_run:BOOLEAN]
|
||||
// } -> OBJECT
|
||||
// Database|Table, {shards:NUMBER, replicas:OBJECT [,
|
||||
// primary_replica_tag:STRING,
|
||||
// nonvoting_replica_tags:ARRAY,
|
||||
// dry_run:BOOLEAN]
|
||||
// } -> OBJECT
|
||||
// Table, {emergency_repair:STRING, dry_run:BOOLEAN} -> OBJECT
|
||||
// Balances the table's shards but leaves everything else the same. Can also be
|
||||
// applied to an entire database at once.
|
||||
REBALANCE = 179; // Table -> OBJECT
|
||||
// Database -> OBJECT
|
||||
|
||||
// Ensures that previously issued soft-durability writes are complete and
|
||||
// written to disk.
|
||||
SYNC = 138; // Table -> OBJECT
|
||||
|
||||
// Set global, database, or table-specific permissions
|
||||
GRANT = 188; // -> OBJECT
|
||||
// Database -> OBJECT
|
||||
// Table -> OBJECT
|
||||
|
||||
// * Secondary indexes OPs
|
||||
// Creates a new secondary index with a particular name and definition.
|
||||
INDEX_CREATE = 75; // Table, STRING, Function(1), {multi:BOOL} -> OBJECT
|
||||
// Drops a secondary index with a particular name from the specified table.
|
||||
INDEX_DROP = 76; // Table, STRING -> OBJECT
|
||||
// Lists all secondary indexes on a particular table.
|
||||
INDEX_LIST = 77; // Table -> ARRAY
|
||||
// Gets information about whether or not a set of indexes are ready to
|
||||
// be accessed. Returns a list of objects that look like this:
|
||||
// {index:STRING, ready:BOOL[, progress:NUMBER]}
|
||||
INDEX_STATUS = 139; // Table, STRING... -> ARRAY
|
||||
// Blocks until a set of indexes are ready to be accessed. Returns the
|
||||
// same values INDEX_STATUS.
|
||||
INDEX_WAIT = 140; // Table, STRING... -> ARRAY
|
||||
// Renames the given index to a new name
|
||||
INDEX_RENAME = 156; // Table, STRING, STRING, {overwrite:BOOL} -> OBJECT
|
||||
|
||||
// * Control Operators
|
||||
// Calls a function on data
|
||||
FUNCALL = 64; // Function(*), DATUM... -> DATUM
|
||||
// Executes its first argument, and returns its second argument if it
|
||||
// got [true] or its third argument if it got [false] (like an `if`
|
||||
// statement).
|
||||
BRANCH = 65; // BOOL, Top, Top -> Top
|
||||
// Returns true if any of its arguments returns true (short-circuits).
|
||||
OR = 66; // BOOL... -> BOOL
|
||||
// Returns true if all of its arguments return true (short-circuits).
|
||||
AND = 67; // BOOL... -> BOOL
|
||||
// Calls its Function with each entry in the sequence
|
||||
// and executes the array of terms that Function returns.
|
||||
FOR_EACH = 68; // Sequence, Function(1) -> OBJECT
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////// Special Terms
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// An anonymous function. Takes an array of numbers representing
|
||||
// variables (see [VAR] above), and a [Term] to execute with those in
|
||||
// scope. Returns a function that may be passed an array of arguments,
|
||||
// then executes the Term with those bound to the variable names. The
|
||||
// user will never construct this directly. We use it internally for
|
||||
// things like `map` which take a function. The "arity" of a [Function] is
|
||||
// the number of arguments it takes.
|
||||
// For example, here's what `_X_.map{|x| x+2}` turns into:
|
||||
// Term {
|
||||
// type = MAP;
|
||||
// args = [_X_,
|
||||
// Term {
|
||||
// type = Function;
|
||||
// args = [Term {
|
||||
// type = DATUM;
|
||||
// datum = Datum {
|
||||
// type = R_ARRAY;
|
||||
// r_array = [Datum { type = R_NUM; r_num = 1; }];
|
||||
// };
|
||||
// },
|
||||
// Term {
|
||||
// type = ADD;
|
||||
// args = [Term {
|
||||
// type = VAR;
|
||||
// args = [Term {
|
||||
// type = DATUM;
|
||||
// datum = Datum { type = R_NUM;
|
||||
// r_num = 1};
|
||||
// }];
|
||||
// },
|
||||
// Term {
|
||||
// type = DATUM;
|
||||
// datum = Datum { type = R_NUM; r_num = 2; };
|
||||
// }];
|
||||
// }];
|
||||
// }];
|
||||
FUNC = 69; // ARRAY, Top -> ARRAY -> Top
|
||||
|
||||
// Indicates to ORDER_BY that this attribute is to be sorted in ascending order.
|
||||
ASC = 73; // !STRING -> Ordering
|
||||
// Indicates to ORDER_BY that this attribute is to be sorted in descending order.
|
||||
DESC = 74; // !STRING -> Ordering
|
||||
|
||||
// Gets info about anything. INFO is most commonly called on tables.
|
||||
INFO = 79; // Top -> OBJECT
|
||||
|
||||
// `a.match(b)` returns a match object if the string `a`
|
||||
// matches the regular expression `b`.
|
||||
MATCH = 97; // STRING, STRING -> DATUM
|
||||
|
||||
// Change the case of a string.
|
||||
UPCASE = 141; // STRING -> STRING
|
||||
DOWNCASE = 142; // STRING -> STRING
|
||||
|
||||
// Select a number of elements from sequence with uniform distribution.
|
||||
SAMPLE = 81; // Sequence, NUMBER -> Sequence
|
||||
|
||||
// Evaluates its first argument. If that argument returns
|
||||
// NULL or throws an error related to the absence of an
|
||||
// expected value (for instance, accessing a non-existent
|
||||
// field or adding NULL to an integer), DEFAULT will either
|
||||
// return its second argument or execute it if it's a
|
||||
// function. If the second argument is a function, it will be
|
||||
// passed either the text of the error or NULL as its
|
||||
// argument.
|
||||
DEFAULT = 92; // Top, Top -> Top
|
||||
|
||||
// Parses its first argument as a json string and returns it as a
|
||||
// datum.
|
||||
JSON = 98; // STRING -> DATUM
|
||||
// Returns the datum as a JSON string.
|
||||
// N.B.: we would really prefer this be named TO_JSON and that exists as
|
||||
// an alias in Python and JavaScript drivers; however it conflicts with the
|
||||
// standard `to_json` method defined by Ruby's standard json library.
|
||||
TO_JSON_STRING = 172; // DATUM -> STRING
|
||||
|
||||
// Parses its first arguments as an ISO 8601 time and returns it as a
|
||||
// datum.
|
||||
ISO8601 = 99; // STRING -> PSEUDOTYPE(TIME)
|
||||
// Prints a time as an ISO 8601 time.
|
||||
TO_ISO8601 = 100; // PSEUDOTYPE(TIME) -> STRING
|
||||
|
||||
// Returns a time given seconds since epoch in UTC.
|
||||
EPOCH_TIME = 101; // NUMBER -> PSEUDOTYPE(TIME)
|
||||
// Returns seconds since epoch in UTC given a time.
|
||||
TO_EPOCH_TIME = 102; // PSEUDOTYPE(TIME) -> NUMBER
|
||||
|
||||
// The time the query was received by the server.
|
||||
NOW = 103; // -> PSEUDOTYPE(TIME)
|
||||
// Puts a time into an ISO 8601 timezone.
|
||||
IN_TIMEZONE = 104; // PSEUDOTYPE(TIME), STRING -> PSEUDOTYPE(TIME)
|
||||
// a.during(b, c) returns whether a is in the range [b, c)
|
||||
DURING = 105; // PSEUDOTYPE(TIME), PSEUDOTYPE(TIME), PSEUDOTYPE(TIME) -> BOOL
|
||||
// Retrieves the date portion of a time.
|
||||
DATE = 106; // PSEUDOTYPE(TIME) -> PSEUDOTYPE(TIME)
|
||||
// x.time_of_day == x.date - x
|
||||
TIME_OF_DAY = 126; // PSEUDOTYPE(TIME) -> NUMBER
|
||||
// Returns the timezone of a time.
|
||||
TIMEZONE = 127; // PSEUDOTYPE(TIME) -> STRING
|
||||
|
||||
// These access the various components of a time.
|
||||
YEAR = 128; // PSEUDOTYPE(TIME) -> NUMBER
|
||||
MONTH = 129; // PSEUDOTYPE(TIME) -> NUMBER
|
||||
DAY = 130; // PSEUDOTYPE(TIME) -> NUMBER
|
||||
DAY_OF_WEEK = 131; // PSEUDOTYPE(TIME) -> NUMBER
|
||||
DAY_OF_YEAR = 132; // PSEUDOTYPE(TIME) -> NUMBER
|
||||
HOURS = 133; // PSEUDOTYPE(TIME) -> NUMBER
|
||||
MINUTES = 134; // PSEUDOTYPE(TIME) -> NUMBER
|
||||
SECONDS = 135; // PSEUDOTYPE(TIME) -> NUMBER
|
||||
|
||||
// Construct a time from a date and optional timezone or a
|
||||
// date+time and optional timezone.
|
||||
TIME = 136; // NUMBER, NUMBER, NUMBER, STRING -> PSEUDOTYPE(TIME) |
|
||||
// NUMBER, NUMBER, NUMBER, NUMBER, NUMBER, NUMBER, STRING -> PSEUDOTYPE(TIME) |
|
||||
|
||||
// Constants for ISO 8601 days of the week.
|
||||
MONDAY = 107; // -> 1
|
||||
TUESDAY = 108; // -> 2
|
||||
WEDNESDAY = 109; // -> 3
|
||||
THURSDAY = 110; // -> 4
|
||||
FRIDAY = 111; // -> 5
|
||||
SATURDAY = 112; // -> 6
|
||||
SUNDAY = 113; // -> 7
|
||||
|
||||
// Constants for ISO 8601 months.
|
||||
JANUARY = 114; // -> 1
|
||||
FEBRUARY = 115; // -> 2
|
||||
MARCH = 116; // -> 3
|
||||
APRIL = 117; // -> 4
|
||||
MAY = 118; // -> 5
|
||||
JUNE = 119; // -> 6
|
||||
JULY = 120; // -> 7
|
||||
AUGUST = 121; // -> 8
|
||||
SEPTEMBER = 122; // -> 9
|
||||
OCTOBER = 123; // -> 10
|
||||
NOVEMBER = 124; // -> 11
|
||||
DECEMBER = 125; // -> 12
|
||||
|
||||
// Indicates to MERGE to replace, or remove in case of an empty literal, the
|
||||
// other object rather than merge it.
|
||||
LITERAL = 137; // -> Merging
|
||||
// JSON -> Merging
|
||||
|
||||
// SEQUENCE, STRING -> GROUPED_SEQUENCE | SEQUENCE, FUNCTION -> GROUPED_SEQUENCE
|
||||
GROUP = 144;
|
||||
SUM = 145;
|
||||
AVG = 146;
|
||||
MIN = 147;
|
||||
MAX = 148;
|
||||
|
||||
// `str.split()` splits on whitespace
|
||||
// `str.split(" ")` splits on spaces only
|
||||
// `str.split(" ", 5)` splits on spaces with at most 5 results
|
||||
// `str.split(nil, 5)` splits on whitespace with at most 5 results
|
||||
SPLIT = 149; // STRING -> ARRAY | STRING, STRING -> ARRAY | STRING, STRING, NUMBER -> ARRAY | STRING, NULL, NUMBER -> ARRAY
|
||||
|
||||
UNGROUP = 150; // GROUPED_DATA -> ARRAY
|
||||
|
||||
// Takes a range of numbers and returns a random number within the range
|
||||
RANDOM = 151; // NUMBER, NUMBER {float:BOOL} -> DATUM
|
||||
|
||||
CHANGES = 152; // TABLE -> STREAM
|
||||
ARGS = 154; // ARRAY -> SPECIAL (used to splice arguments)
|
||||
|
||||
// BINARY is client-only at the moment, it is not supported on the server
|
||||
BINARY = 155; // STRING -> PSEUDOTYPE(BINARY)
|
||||
|
||||
GEOJSON = 157; // OBJECT -> PSEUDOTYPE(GEOMETRY)
|
||||
TO_GEOJSON = 158; // PSEUDOTYPE(GEOMETRY) -> OBJECT
|
||||
POINT = 159; // NUMBER, NUMBER -> PSEUDOTYPE(GEOMETRY)
|
||||
LINE = 160; // (ARRAY | PSEUDOTYPE(GEOMETRY))... -> PSEUDOTYPE(GEOMETRY)
|
||||
POLYGON = 161; // (ARRAY | PSEUDOTYPE(GEOMETRY))... -> PSEUDOTYPE(GEOMETRY)
|
||||
DISTANCE = 162; // PSEUDOTYPE(GEOMETRY), PSEUDOTYPE(GEOMETRY) {geo_system:STRING, unit:STRING} -> NUMBER
|
||||
INTERSECTS = 163; // PSEUDOTYPE(GEOMETRY), PSEUDOTYPE(GEOMETRY) -> BOOL
|
||||
INCLUDES = 164; // PSEUDOTYPE(GEOMETRY), PSEUDOTYPE(GEOMETRY) -> BOOL
|
||||
CIRCLE = 165; // PSEUDOTYPE(GEOMETRY), NUMBER {num_vertices:NUMBER, geo_system:STRING, unit:STRING, fill:BOOL} -> PSEUDOTYPE(GEOMETRY)
|
||||
GET_INTERSECTING = 166; // TABLE, PSEUDOTYPE(GEOMETRY) {index:!STRING} -> StreamSelection
|
||||
FILL = 167; // PSEUDOTYPE(GEOMETRY) -> PSEUDOTYPE(GEOMETRY)
|
||||
GET_NEAREST = 168; // TABLE, PSEUDOTYPE(GEOMETRY) {index:!STRING, max_results:NUM, max_dist:NUM, geo_system:STRING, unit:STRING} -> ARRAY
|
||||
POLYGON_SUB = 171; // PSEUDOTYPE(GEOMETRY), PSEUDOTYPE(GEOMETRY) -> PSEUDOTYPE(GEOMETRY)
|
||||
|
||||
// Constants for specifying key ranges
|
||||
MINVAL = 180;
|
||||
MAXVAL = 181;
|
||||
}
|
||||
optional TermType type = 1;
|
||||
|
||||
// This is only used when type is DATUM.
|
||||
optional Datum datum = 2;
|
||||
|
||||
repeated Term args = 3; // Holds the positional arguments of the query.
|
||||
message AssocPair {
|
||||
optional string key = 1;
|
||||
optional Term val = 2;
|
||||
}
|
||||
repeated AssocPair optargs = 4; // Holds the optional arguments of the query.
|
||||
// (Note that the order of the optional arguments doesn't matter; think of a
|
||||
// Hash.)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// EXAMPLE //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// ```ruby
|
||||
// r.table('tbl', {:read_mode => 'outdated'}).insert([{:id => 0}, {:id => 1}])
|
||||
// ```
|
||||
// Would turn into:
|
||||
// Term {
|
||||
// type = INSERT;
|
||||
// args = [Term {
|
||||
// type = TABLE;
|
||||
// args = [Term {
|
||||
// type = DATUM;
|
||||
// datum = Datum { type = R_STR; r_str = "tbl"; };
|
||||
// }];
|
||||
// optargs = [["read_mode",
|
||||
// Term {
|
||||
// type = DATUM;
|
||||
// datum = Datum { type = R_STR; r_bool = "outdated"; };
|
||||
// }]];
|
||||
// },
|
||||
// Term {
|
||||
// type = MAKE_ARRAY;
|
||||
// args = [Term {
|
||||
// type = DATUM;
|
||||
// datum = Datum { type = R_OBJECT; r_object = [["id", 0]]; };
|
||||
// },
|
||||
// Term {
|
||||
// type = DATUM;
|
||||
// datum = Datum { type = R_OBJECT; r_object = [["id", 1]]; };
|
||||
// }];
|
||||
// }]
|
||||
// }
|
||||
// And the server would reply:
|
||||
// Response {
|
||||
// type = SUCCESS_ATOM;
|
||||
// token = 1;
|
||||
// response = [Datum { type = R_OBJECT; r_object = [["inserted", 2]]; }];
|
||||
// }
|
||||
// Or, if there were an error:
|
||||
// Response {
|
||||
// type = RUNTIME_ERROR;
|
||||
// token = 1;
|
||||
// response = [Datum { type = R_STR; r_str = "The table `tbl` doesn't exist!"; }];
|
||||
// backtrace = [Frame { type = POS; pos = 0; }, Frame { type = POS; pos = 0; }];
|
||||
// }
|
@ -1,434 +0,0 @@
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/select.h>
|
||||
|
||||
#include <netdb.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <cinttypes>
|
||||
#include <memory>
|
||||
|
||||
#include "connection.h"
|
||||
#include "connection_p.h"
|
||||
#include "json_p.h"
|
||||
#include "exceptions.h"
|
||||
#include "term.h"
|
||||
#include "cursor_p.h"
|
||||
|
||||
#include "rapidjson-config.h"
|
||||
#include "rapidjson/rapidjson.h"
|
||||
#include "rapidjson/encodedstream.h"
|
||||
#include "rapidjson/document.h"
|
||||
|
||||
namespace RethinkDB {
|
||||
|
||||
using QueryType = Protocol::Query::QueryType;
|
||||
|
||||
// constants
|
||||
const int debug_net = 0;
|
||||
const uint32_t version_magic =
|
||||
static_cast<uint32_t>(Protocol::VersionDummy::Version::V0_4);
|
||||
const uint32_t json_magic =
|
||||
static_cast<uint32_t>(Protocol::VersionDummy::Protocol::JSON);
|
||||
|
||||
std::unique_ptr<Connection> connect(std::string host, int port, std::string auth_key) {
|
||||
struct addrinfo hints;
|
||||
memset(&hints, 0, sizeof hints);
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
char port_str[16];
|
||||
snprintf(port_str, 16, "%d", port);
|
||||
struct addrinfo *servinfo;
|
||||
int ret = getaddrinfo(host.c_str(), port_str, &hints, &servinfo);
|
||||
if (ret) throw Error("getaddrinfo: %s\n", gai_strerror(ret));
|
||||
|
||||
struct addrinfo *p;
|
||||
Error error;
|
||||
int sockfd;
|
||||
for (p = servinfo; p != NULL; p = p->ai_next) {
|
||||
sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
|
||||
if (sockfd == -1) {
|
||||
error = Error::from_errno("socket");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
|
||||
::close(sockfd);
|
||||
error = Error::from_errno("connect");
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (p == NULL) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
freeaddrinfo(servinfo);
|
||||
|
||||
std::unique_ptr<ConnectionPrivate> conn_private(new ConnectionPrivate(sockfd));
|
||||
WriteLock writer(conn_private.get());
|
||||
{
|
||||
size_t size = auth_key.size();
|
||||
char buf[12 + size];
|
||||
memcpy(buf, &version_magic, 4);
|
||||
uint32_t n = size;
|
||||
memcpy(buf + 4, &n, 4);
|
||||
memcpy(buf + 8, auth_key.data(), size);
|
||||
memcpy(buf + 8 + size, &json_magic, 4);
|
||||
writer.send(buf, sizeof buf);
|
||||
}
|
||||
|
||||
ReadLock reader(conn_private.get());
|
||||
{
|
||||
const size_t max_response_length = 1024;
|
||||
char buf[max_response_length + 1];
|
||||
size_t len = reader.recv_cstring(buf, max_response_length);
|
||||
if (len == max_response_length || strcmp(buf, "SUCCESS")) {
|
||||
buf[len] = 0;
|
||||
::close(sockfd);
|
||||
throw Error("Server rejected connection with message: %s", buf);
|
||||
}
|
||||
}
|
||||
|
||||
return std::unique_ptr<Connection>(new Connection(conn_private.release()));
|
||||
}
|
||||
|
||||
Connection::Connection(ConnectionPrivate *dd) : d(dd) { }
|
||||
Connection::~Connection() {
|
||||
// close();
|
||||
if (d->guarded_sockfd >= 0)
|
||||
::close(d->guarded_sockfd);
|
||||
}
|
||||
|
||||
size_t ReadLock::recv_some(char* buf, size_t size, double wait) {
|
||||
if (wait != FOREVER) {
|
||||
while (true) {
|
||||
fd_set readfds;
|
||||
struct timeval tv;
|
||||
|
||||
FD_ZERO(&readfds);
|
||||
FD_SET(conn->guarded_sockfd, &readfds);
|
||||
|
||||
tv.tv_sec = (int)wait;
|
||||
tv.tv_usec = (int)((wait - (int)wait) / MICROSECOND);
|
||||
int rv = select(conn->guarded_sockfd + 1, &readfds, NULL, NULL, &tv);
|
||||
if (rv == -1) {
|
||||
throw Error::from_errno("select");
|
||||
} else if (rv == 0) {
|
||||
throw TimeoutException();
|
||||
}
|
||||
|
||||
if (FD_ISSET(conn->guarded_sockfd, &readfds)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t numbytes = ::recv(conn->guarded_sockfd, buf, size, 0);
|
||||
if (numbytes <= 0) throw Error::from_errno("recv");
|
||||
if (debug_net > 1) {
|
||||
fprintf(stderr, "<< %s\n", write_datum(std::string(buf, numbytes)).c_str());
|
||||
}
|
||||
|
||||
return numbytes;
|
||||
}
|
||||
|
||||
void ReadLock::recv(char* buf, size_t size, double wait) {
|
||||
while (size) {
|
||||
size_t numbytes = recv_some(buf, size, wait);
|
||||
|
||||
buf += numbytes;
|
||||
size -= numbytes;
|
||||
}
|
||||
}
|
||||
|
||||
size_t ReadLock::recv_cstring(char* buf, size_t max_size){
|
||||
size_t size = 0;
|
||||
for (; size < max_size; size++) {
|
||||
recv(buf, 1, FOREVER);
|
||||
if (*buf == 0) {
|
||||
break;
|
||||
}
|
||||
buf++;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
void WriteLock::send(const char* buf, size_t size) {
|
||||
while (size) {
|
||||
ssize_t numbytes = ::write(conn->guarded_sockfd, buf, size);
|
||||
if (numbytes == -1) throw Error::from_errno("write");
|
||||
if (debug_net > 1) {
|
||||
fprintf(stderr, ">> %s\n", write_datum(std::string(buf, numbytes)).c_str());
|
||||
}
|
||||
|
||||
buf += numbytes;
|
||||
size -= numbytes;
|
||||
}
|
||||
}
|
||||
|
||||
void WriteLock::send(const std::string data) {
|
||||
send(data.data(), data.size());
|
||||
}
|
||||
|
||||
std::string ReadLock::recv(size_t size) {
|
||||
char buf[size];
|
||||
recv(buf, size, FOREVER);
|
||||
return buf;
|
||||
}
|
||||
|
||||
void Connection::close() {
|
||||
CacheLock guard(d.get());
|
||||
for (auto& it : d->guarded_cache) {
|
||||
stop_query(it.first);
|
||||
}
|
||||
|
||||
int ret = ::close(d->guarded_sockfd);
|
||||
if (ret == -1) {
|
||||
throw Error::from_errno("close");
|
||||
}
|
||||
d->guarded_sockfd = -1;
|
||||
}
|
||||
|
||||
Response ConnectionPrivate::wait_for_response(uint64_t token_want, double wait) {
|
||||
CacheLock guard(this);
|
||||
ConnectionPrivate::TokenCache& cache = guarded_cache[token_want];
|
||||
|
||||
while (true) {
|
||||
if (!cache.responses.empty()) {
|
||||
Response response(std::move(cache.responses.front()));
|
||||
cache.responses.pop();
|
||||
if (cache.closed && cache.responses.empty()) {
|
||||
guarded_cache.erase(token_want);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
if (cache.closed) {
|
||||
throw Error("Trying to read from a closed token");
|
||||
}
|
||||
|
||||
if (guarded_loop_active) {
|
||||
cache.cond.wait(guard.inner_lock);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ReadLock reader(this);
|
||||
return reader.read_loop(token_want, std::move(guard), wait);
|
||||
}
|
||||
|
||||
Response ReadLock::read_loop(uint64_t token_want, CacheLock&& guard, double wait) {
|
||||
if (!guard.inner_lock) {
|
||||
guard.lock();
|
||||
}
|
||||
if (conn->guarded_loop_active) {
|
||||
throw Error("Cannot run more than one read loop on the same connection");
|
||||
}
|
||||
conn->guarded_loop_active = true;
|
||||
guard.unlock();
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
char buf[12];
|
||||
bzero(buf, sizeof(buf));
|
||||
recv(buf, 12, wait);
|
||||
uint64_t token_got;
|
||||
memcpy(&token_got, buf, 8);
|
||||
uint32_t length;
|
||||
memcpy(&length, buf + 8, 4);
|
||||
|
||||
std::unique_ptr<char[]> bufmem(new char[length + 1]);
|
||||
char *buffer = bufmem.get();
|
||||
bzero(buffer, length + 1);
|
||||
recv(buffer, length, wait);
|
||||
buffer[length] = '\0';
|
||||
|
||||
rapidjson::Document json;
|
||||
json.ParseInsitu(buffer);
|
||||
if (json.HasParseError()) {
|
||||
fprintf(stderr, "json parse error, code: %d, position: %d\n",
|
||||
(int)json.GetParseError(), (int)json.GetErrorOffset());
|
||||
} else if (json.IsNull()) {
|
||||
fprintf(stderr, "null value, read: %s\n", buffer);
|
||||
}
|
||||
|
||||
Datum datum = read_datum(json);
|
||||
if (debug_net > 0) {
|
||||
fprintf(stderr, "[%" PRIu64 "] << %s\n", token_got, write_datum(datum).c_str());
|
||||
}
|
||||
|
||||
Response response(std::move(datum));
|
||||
|
||||
if (token_got == token_want) {
|
||||
guard.lock();
|
||||
if (response.type != Protocol::Response::ResponseType::SUCCESS_PARTIAL) {
|
||||
auto it = conn->guarded_cache.find(token_got);
|
||||
if (it != conn->guarded_cache.end()) {
|
||||
it->second.closed = true;
|
||||
it->second.cond.notify_all();
|
||||
}
|
||||
conn->guarded_cache.erase(it);
|
||||
}
|
||||
conn->guarded_loop_active = false;
|
||||
for (auto& it : conn->guarded_cache) {
|
||||
it.second.cond.notify_all();
|
||||
}
|
||||
return response;
|
||||
} else {
|
||||
guard.lock();
|
||||
auto it = conn->guarded_cache.find(token_got);
|
||||
if (it == conn->guarded_cache.end()) {
|
||||
// drop the response
|
||||
} else if (!it->second.closed) {
|
||||
it->second.responses.emplace(std::move(response));
|
||||
if (response.type != Protocol::Response::ResponseType::SUCCESS_PARTIAL) {
|
||||
it->second.closed = true;
|
||||
}
|
||||
}
|
||||
it->second.cond.notify_all();
|
||||
guard.unlock();
|
||||
}
|
||||
}
|
||||
} catch (const TimeoutException &e) {
|
||||
if (!guard.inner_lock){
|
||||
guard.lock();
|
||||
}
|
||||
conn->guarded_loop_active = false;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionPrivate::run_query(Query query, bool no_reply) {
|
||||
WriteLock writer(this);
|
||||
writer.send(query.serialize());
|
||||
}
|
||||
|
||||
Cursor Connection::start_query(Term *term, OptArgs&& opts) {
|
||||
bool no_reply = false;
|
||||
auto it = opts.find("noreply");
|
||||
if (it != opts.end()) {
|
||||
no_reply = *(it->second.datum.get_boolean());
|
||||
}
|
||||
|
||||
uint64_t token = d->new_token();
|
||||
{
|
||||
CacheLock guard(d.get());
|
||||
d->guarded_cache[token];
|
||||
}
|
||||
|
||||
d->run_query(Query{QueryType::START, token, term->datum, std::move(opts)});
|
||||
if (no_reply) {
|
||||
return Cursor(new CursorPrivate(token, this, Nil()));
|
||||
}
|
||||
|
||||
Cursor cursor(new CursorPrivate(token, this));
|
||||
Response response = d->wait_for_response(token, FOREVER);
|
||||
cursor.d->add_response(std::move(response));
|
||||
return cursor;
|
||||
}
|
||||
|
||||
void Connection::stop_query(uint64_t token) {
|
||||
const auto& it = d->guarded_cache.find(token);
|
||||
if (it != d->guarded_cache.end() && !it->second.closed) {
|
||||
d->run_query(Query{QueryType::STOP, token}, true);
|
||||
}
|
||||
}
|
||||
|
||||
void Connection::continue_query(uint64_t token) {
|
||||
d->run_query(Query{QueryType::CONTINUE, token}, true);
|
||||
}
|
||||
|
||||
Error Response::as_error() {
|
||||
std::string repr;
|
||||
if (result.size() == 1) {
|
||||
std::string* string = result[0].get_string();
|
||||
if (string) {
|
||||
repr = *string;
|
||||
} else {
|
||||
repr = write_datum(result[0]);
|
||||
}
|
||||
} else {
|
||||
repr = write_datum(Datum(result));
|
||||
}
|
||||
std::string err;
|
||||
using RT = Protocol::Response::ResponseType;
|
||||
using ET = Protocol::Response::ErrorType;
|
||||
switch (type) {
|
||||
case RT::SUCCESS_SEQUENCE: err = "unexpected response: SUCCESS_SEQUENCE"; break;
|
||||
case RT::SUCCESS_PARTIAL: err = "unexpected response: SUCCESS_PARTIAL"; break;
|
||||
case RT::SUCCESS_ATOM: err = "unexpected response: SUCCESS_ATOM"; break;
|
||||
case RT::WAIT_COMPLETE: err = "unexpected response: WAIT_COMPLETE"; break;
|
||||
case RT::SERVER_INFO: err = "unexpected response: SERVER_INFO"; break;
|
||||
case RT::CLIENT_ERROR: err = "ReqlDriverError"; break;
|
||||
case RT::COMPILE_ERROR: err = "ReqlCompileError"; break;
|
||||
case RT::RUNTIME_ERROR:
|
||||
switch (error_type) {
|
||||
case ET::INTERNAL: err = "ReqlInternalError"; break;
|
||||
case ET::RESOURCE_LIMIT: err = "ReqlResourceLimitError"; break;
|
||||
case ET::QUERY_LOGIC: err = "ReqlQueryLogicError"; break;
|
||||
case ET::NON_EXISTENCE: err = "ReqlNonExistenceError"; break;
|
||||
case ET::OP_FAILED: err = "ReqlOpFailedError"; break;
|
||||
case ET::OP_INDETERMINATE: err = "ReqlOpIndeterminateError"; break;
|
||||
case ET::USER: err = "ReqlUserError"; break;
|
||||
case ET::PERMISSION_ERROR: err = "ReqlPermissionError"; break;
|
||||
default: err = "ReqlRuntimeError"; break;
|
||||
}
|
||||
}
|
||||
throw Error("%s: %s", err.c_str(), repr.c_str());
|
||||
}
|
||||
|
||||
Protocol::Response::ResponseType response_type(double t) {
|
||||
int n = static_cast<int>(t);
|
||||
using RT = Protocol::Response::ResponseType;
|
||||
switch (n) {
|
||||
case static_cast<int>(RT::SUCCESS_ATOM):
|
||||
return RT::SUCCESS_ATOM;
|
||||
case static_cast<int>(RT::SUCCESS_SEQUENCE):
|
||||
return RT::SUCCESS_SEQUENCE;
|
||||
case static_cast<int>(RT::SUCCESS_PARTIAL):
|
||||
return RT::SUCCESS_PARTIAL;
|
||||
case static_cast<int>(RT::WAIT_COMPLETE):
|
||||
return RT::WAIT_COMPLETE;
|
||||
case static_cast<int>(RT::CLIENT_ERROR):
|
||||
return RT::CLIENT_ERROR;
|
||||
case static_cast<int>(RT::COMPILE_ERROR):
|
||||
return RT::COMPILE_ERROR;
|
||||
case static_cast<int>(RT::RUNTIME_ERROR):
|
||||
return RT::RUNTIME_ERROR;
|
||||
default:
|
||||
throw Error("Unknown response type");
|
||||
}
|
||||
}
|
||||
|
||||
Protocol::Response::ErrorType runtime_error_type(double t) {
|
||||
int n = static_cast<int>(t);
|
||||
using ET = Protocol::Response::ErrorType;
|
||||
switch (n) {
|
||||
case static_cast<int>(ET::INTERNAL):
|
||||
return ET::INTERNAL;
|
||||
case static_cast<int>(ET::RESOURCE_LIMIT):
|
||||
return ET::RESOURCE_LIMIT;
|
||||
case static_cast<int>(ET::QUERY_LOGIC):
|
||||
return ET::QUERY_LOGIC;
|
||||
case static_cast<int>(ET::NON_EXISTENCE):
|
||||
return ET::NON_EXISTENCE;
|
||||
case static_cast<int>(ET::OP_FAILED):
|
||||
return ET::OP_FAILED;
|
||||
case static_cast<int>(ET::OP_INDETERMINATE):
|
||||
return ET::OP_INDETERMINATE;
|
||||
case static_cast<int>(ET::USER):
|
||||
return ET::USER;
|
||||
default:
|
||||
throw Error("Unknown error type");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <condition_variable>
|
||||
|
||||
#include "protocol_defs.h"
|
||||
#include "datum.h"
|
||||
#include "error.h"
|
||||
|
||||
#define FOREVER (-1)
|
||||
#define SECOND 1
|
||||
#define MICROSECOND 0.000001
|
||||
|
||||
namespace RethinkDB {
|
||||
|
||||
class Term;
|
||||
using OptArgs = std::map<std::string, Term>;
|
||||
|
||||
// A connection to a RethinkDB server
|
||||
// It contains:
|
||||
// * A socket
|
||||
// * Read and write locks
|
||||
// * A cache of responses that have not been read by the corresponding Cursor
|
||||
class ConnectionPrivate;
|
||||
class Connection {
|
||||
public:
|
||||
Connection() = delete;
|
||||
Connection(const Connection&) noexcept = delete;
|
||||
Connection(Connection&&) noexcept = delete;
|
||||
Connection& operator=(Connection&&) noexcept = delete;
|
||||
Connection& operator=(const Connection&) noexcept = delete;
|
||||
~Connection();
|
||||
|
||||
void close();
|
||||
|
||||
private:
|
||||
explicit Connection(ConnectionPrivate *dd);
|
||||
std::unique_ptr<ConnectionPrivate> d;
|
||||
|
||||
Cursor start_query(Term *term, OptArgs&& args);
|
||||
void stop_query(uint64_t);
|
||||
void continue_query(uint64_t);
|
||||
|
||||
friend class Cursor;
|
||||
friend class CursorPrivate;
|
||||
friend class Token;
|
||||
friend class Term;
|
||||
friend std::unique_ptr<Connection>
|
||||
connect(std::string host, int port, std::string auth_key);
|
||||
|
||||
};
|
||||
|
||||
// $doc(connect)
|
||||
std::unique_ptr<Connection> connect(std::string host = "localhost", int port = 28015, std::string auth_key = "");
|
||||
|
||||
}
|
@ -1,133 +0,0 @@
|
||||
#ifndef CONNECTION_P_H
|
||||
#define CONNECTION_P_H
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "connection.h"
|
||||
#include "term.h"
|
||||
#include "json_p.h"
|
||||
|
||||
namespace RethinkDB {
|
||||
|
||||
extern const int debug_net;
|
||||
|
||||
struct Query {
|
||||
Protocol::Query::QueryType type;
|
||||
uint64_t token;
|
||||
Datum term;
|
||||
OptArgs optArgs;
|
||||
|
||||
std::string serialize() {
|
||||
Array query_arr{static_cast<double>(type)};
|
||||
if (term.is_valid()) query_arr.emplace_back(term);
|
||||
if (!optArgs.empty())
|
||||
query_arr.emplace_back(Term(std::move(optArgs)).datum);
|
||||
|
||||
std::string query_str = write_datum(query_arr);
|
||||
if (debug_net > 0) {
|
||||
fprintf(stderr, "[%" PRIu64 "] >> %s\n", token, query_str.c_str());
|
||||
}
|
||||
|
||||
char header[12];
|
||||
memcpy(header, &token, 8);
|
||||
uint32_t size = query_str.size();
|
||||
memcpy(header + 8, &size, 4);
|
||||
query_str.insert(0, header, 12);
|
||||
return query_str;
|
||||
}
|
||||
};
|
||||
|
||||
// Used internally to convert a raw response type into an enum
|
||||
Protocol::Response::ResponseType response_type(double t);
|
||||
Protocol::Response::ErrorType runtime_error_type(double t);
|
||||
|
||||
// Contains a response from the server. Use the Cursor class to interact with these responses
|
||||
class Response {
|
||||
public:
|
||||
Response() = delete;
|
||||
explicit Response(Datum&& datum) :
|
||||
type(response_type(std::move(datum).extract_field("t").extract_number())),
|
||||
error_type(datum.get_field("e") ?
|
||||
runtime_error_type(std::move(datum).extract_field("e").extract_number()) :
|
||||
Protocol::Response::ErrorType(0)),
|
||||
result(std::move(datum).extract_field("r").extract_array()) { }
|
||||
Error as_error();
|
||||
Protocol::Response::ResponseType type;
|
||||
Protocol::Response::ErrorType error_type;
|
||||
Array result;
|
||||
};
|
||||
|
||||
class Token;
|
||||
class ConnectionPrivate {
|
||||
public:
|
||||
ConnectionPrivate(int sockfd)
|
||||
: guarded_next_token(1), guarded_sockfd(sockfd), guarded_loop_active(false)
|
||||
{ }
|
||||
|
||||
void run_query(Query query, bool no_reply = false);
|
||||
|
||||
Response wait_for_response(uint64_t, double);
|
||||
uint64_t new_token() {
|
||||
return guarded_next_token++;
|
||||
}
|
||||
|
||||
std::mutex read_lock;
|
||||
std::mutex write_lock;
|
||||
std::mutex cache_lock;
|
||||
|
||||
struct TokenCache {
|
||||
bool closed = false;
|
||||
std::condition_variable cond;
|
||||
std::queue<Response> responses;
|
||||
};
|
||||
|
||||
std::map<uint64_t, TokenCache> guarded_cache;
|
||||
uint64_t guarded_next_token;
|
||||
int guarded_sockfd;
|
||||
bool guarded_loop_active;
|
||||
};
|
||||
|
||||
class CacheLock {
|
||||
public:
|
||||
CacheLock(ConnectionPrivate* conn) : inner_lock(conn->cache_lock) { }
|
||||
|
||||
void lock() {
|
||||
inner_lock.lock();
|
||||
}
|
||||
|
||||
void unlock() {
|
||||
inner_lock.unlock();
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> inner_lock;
|
||||
};
|
||||
|
||||
class ReadLock {
|
||||
public:
|
||||
ReadLock(ConnectionPrivate* conn_) : lock(conn_->read_lock), conn(conn_) { }
|
||||
|
||||
size_t recv_some(char*, size_t, double wait);
|
||||
void recv(char*, size_t, double wait);
|
||||
std::string recv(size_t);
|
||||
size_t recv_cstring(char*, size_t);
|
||||
|
||||
Response read_loop(uint64_t, CacheLock&&, double);
|
||||
|
||||
std::lock_guard<std::mutex> lock;
|
||||
ConnectionPrivate* conn;
|
||||
};
|
||||
|
||||
class WriteLock {
|
||||
public:
|
||||
WriteLock(ConnectionPrivate* conn_) : lock(conn_->write_lock), conn(conn_) { }
|
||||
|
||||
void send(const char*, size_t);
|
||||
void send(std::string);
|
||||
|
||||
std::lock_guard<std::mutex> lock;
|
||||
ConnectionPrivate* conn;
|
||||
};
|
||||
|
||||
} // namespace RethinkDB
|
||||
|
||||
#endif // CONNECTION_P_H
|
@ -1,223 +0,0 @@
|
||||
#include "cursor.h"
|
||||
#include "cursor_p.h"
|
||||
#include "exceptions.h"
|
||||
|
||||
namespace RethinkDB {
|
||||
|
||||
// for type completion, in order to forward declare with unique_ptr
|
||||
Cursor::Cursor(Cursor&&) = default;
|
||||
Cursor& Cursor::operator=(Cursor&&) = default;
|
||||
|
||||
CursorPrivate::CursorPrivate(uint64_t token_, Connection *conn_)
|
||||
: single(false), no_more(false), index(0),
|
||||
token(token_), conn(conn_)
|
||||
{ }
|
||||
|
||||
CursorPrivate::CursorPrivate(uint64_t token_, Connection *conn_, Datum&& datum)
|
||||
: single(true), no_more(true), index(0), buffer(Array{std::move(datum)}),
|
||||
token(token_), conn(conn_)
|
||||
{ }
|
||||
|
||||
Cursor::Cursor(CursorPrivate *dd) : d(dd) {}
|
||||
|
||||
Cursor::~Cursor() {
|
||||
try {
|
||||
if (d && d->conn) {
|
||||
close();
|
||||
}
|
||||
} catch ( ... ) {}
|
||||
}
|
||||
|
||||
Datum& Cursor::next(double wait) const {
|
||||
if (!has_next(wait)) {
|
||||
throw Error("next: No more data");
|
||||
}
|
||||
|
||||
return d->buffer[d->index++];
|
||||
}
|
||||
|
||||
Datum& Cursor::peek(double wait) const {
|
||||
if (!has_next(wait)) {
|
||||
throw Error("next: No more data");
|
||||
}
|
||||
|
||||
return d->buffer[d->index];
|
||||
}
|
||||
|
||||
void Cursor::each(std::function<void(Datum&&)> f, double wait) const {
|
||||
while (has_next(wait)) {
|
||||
f(std::move(d->buffer[d->index++]));
|
||||
}
|
||||
}
|
||||
|
||||
void CursorPrivate::convert_single() const {
|
||||
if (index != 0) {
|
||||
throw Error("Cursor: already consumed");
|
||||
}
|
||||
|
||||
if (buffer.size() != 1) {
|
||||
throw Error("Cursor: invalid response from server");
|
||||
}
|
||||
|
||||
if (!buffer[0].is_array()) {
|
||||
throw Error("Cursor: not an array");
|
||||
}
|
||||
|
||||
buffer.swap(buffer[0].extract_array());
|
||||
single = false;
|
||||
}
|
||||
|
||||
void CursorPrivate::clear_and_read_all() const {
|
||||
if (single) {
|
||||
convert_single();
|
||||
}
|
||||
if (index != 0) {
|
||||
buffer.erase(buffer.begin(), buffer.begin() + index);
|
||||
index = 0;
|
||||
}
|
||||
while (!no_more) {
|
||||
add_response(conn->d->wait_for_response(token, FOREVER));
|
||||
}
|
||||
}
|
||||
|
||||
Array&& Cursor::to_array() && {
|
||||
d->clear_and_read_all();
|
||||
return std::move(d->buffer);
|
||||
}
|
||||
|
||||
Array Cursor::to_array() const & {
|
||||
d->clear_and_read_all();
|
||||
return d->buffer;
|
||||
}
|
||||
|
||||
Datum Cursor::to_datum() const & {
|
||||
if (d->single) {
|
||||
if (d->index != 0) {
|
||||
throw Error("to_datum: already consumed");
|
||||
}
|
||||
return d->buffer[0];
|
||||
}
|
||||
|
||||
d->clear_and_read_all();
|
||||
return d->buffer;
|
||||
}
|
||||
|
||||
Datum Cursor::to_datum() && {
|
||||
Datum ret((Nil()));
|
||||
if (d->single) {
|
||||
if (d->index != 0) {
|
||||
throw Error("to_datum: already consumed");
|
||||
}
|
||||
ret = std::move(d->buffer[0]);
|
||||
} else {
|
||||
d->clear_and_read_all();
|
||||
ret = std::move(d->buffer);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Cursor::close() const {
|
||||
d->conn->stop_query(d->token);
|
||||
d->no_more = true;
|
||||
}
|
||||
|
||||
bool Cursor::has_next(double wait) const {
|
||||
if (d->single) {
|
||||
d->convert_single();
|
||||
}
|
||||
|
||||
while (true) {
|
||||
if (d->index >= d->buffer.size()) {
|
||||
if (d->no_more) {
|
||||
return false;
|
||||
}
|
||||
d->add_response(d->conn->d->wait_for_response(d->token, wait));
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Cursor::is_single() const {
|
||||
return d->single;
|
||||
}
|
||||
|
||||
void CursorPrivate::add_results(Array&& results) const {
|
||||
if (index >= buffer.size()) {
|
||||
buffer = std::move(results);
|
||||
index = 0;
|
||||
} else {
|
||||
for (auto& it : results) {
|
||||
buffer.emplace_back(std::move(it));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CursorPrivate::add_response(Response&& response) const {
|
||||
using RT = Protocol::Response::ResponseType;
|
||||
switch (response.type) {
|
||||
case RT::SUCCESS_SEQUENCE:
|
||||
add_results(std::move(response.result));
|
||||
no_more = true;
|
||||
break;
|
||||
case RT::SUCCESS_PARTIAL:
|
||||
conn->continue_query(token);
|
||||
add_results(std::move(response.result));
|
||||
break;
|
||||
case RT::SUCCESS_ATOM:
|
||||
add_results(std::move(response.result));
|
||||
single = true;
|
||||
no_more = true;
|
||||
break;
|
||||
case RT::SERVER_INFO:
|
||||
add_results(std::move(response.result));
|
||||
single = true;
|
||||
no_more = true;
|
||||
break;
|
||||
case RT::WAIT_COMPLETE:
|
||||
case RT::CLIENT_ERROR:
|
||||
case RT::COMPILE_ERROR:
|
||||
case RT::RUNTIME_ERROR:
|
||||
no_more = true;
|
||||
throw response.as_error();
|
||||
}
|
||||
}
|
||||
|
||||
Cursor::iterator Cursor::begin() {
|
||||
return iterator(this);
|
||||
}
|
||||
|
||||
Cursor::iterator Cursor::end() {
|
||||
return iterator(nullptr);
|
||||
}
|
||||
|
||||
Cursor::iterator::iterator(Cursor* cursor_) : cursor(cursor_) {}
|
||||
|
||||
Cursor::iterator& Cursor::iterator::operator++ () {
|
||||
if (cursor == nullptr) {
|
||||
throw Error("incrementing an exhausted Cursor iterator");
|
||||
}
|
||||
|
||||
cursor->next();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Datum& Cursor::iterator::operator* () {
|
||||
if (cursor == nullptr) {
|
||||
throw Error("reading from empty Cursor iterator");
|
||||
}
|
||||
|
||||
return cursor->peek();
|
||||
}
|
||||
|
||||
bool Cursor::iterator::operator!= (const Cursor::iterator& other) const {
|
||||
if (cursor == other.cursor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !((cursor == nullptr && !other.cursor->has_next()) ||
|
||||
(other.cursor == nullptr && !cursor->has_next()));
|
||||
}
|
||||
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "connection.h"
|
||||
|
||||
namespace RethinkDB {
|
||||
|
||||
// The response from the server, as returned by run.
|
||||
// The response is either a single datum or a stream:
|
||||
// * If it is a stream, the cursor represents each element of the stream.
|
||||
// - Batches are fetched from the server as needed.
|
||||
// * If it is a single datum, is_single() returns true.
|
||||
// - If it is an array, the cursor represents each element of that array
|
||||
// - Otherwise, to_datum() returns the datum and iteration throws an exception.
|
||||
// The cursor can only be iterated over once, it discards data that has already been read.
|
||||
class CursorPrivate;
|
||||
class Cursor {
|
||||
public:
|
||||
Cursor() = delete;
|
||||
~Cursor();
|
||||
|
||||
Cursor(Cursor&&); // movable
|
||||
Cursor& operator=(Cursor&&);
|
||||
Cursor(const Cursor&) = delete; // not copyable
|
||||
Cursor& operator=(const Cursor&) = delete;
|
||||
|
||||
// Returned by begin() and end()
|
||||
class iterator {
|
||||
public:
|
||||
iterator(Cursor*);
|
||||
iterator& operator++ ();
|
||||
Datum& operator* ();
|
||||
bool operator!= (const iterator&) const;
|
||||
|
||||
private:
|
||||
Cursor *cursor;
|
||||
};
|
||||
|
||||
// Consume the next element
|
||||
Datum& next(double wait = FOREVER) const;
|
||||
|
||||
// Peek at the next element
|
||||
Datum& peek(double wait = FOREVER) const;
|
||||
|
||||
// Call f on every element of the Cursor
|
||||
void each(std::function<void(Datum&&)> f, double wait = FOREVER) const;
|
||||
|
||||
// Consume and return all elements
|
||||
Array&& to_array() &&;
|
||||
|
||||
// If is_single(), returns the single datum. Otherwise returns to_array().
|
||||
Datum to_datum() &&;
|
||||
Datum to_datum() const &;
|
||||
|
||||
// Efficiently consume and return all elements
|
||||
Array to_array() const &;
|
||||
|
||||
// Close the cursor
|
||||
void close() const;
|
||||
|
||||
// Returns false if there are no more elements
|
||||
bool has_next(double wait = FOREVER) const;
|
||||
|
||||
// Returns false if the cursor is a stream
|
||||
bool is_single() const;
|
||||
|
||||
iterator begin();
|
||||
iterator end();
|
||||
|
||||
private:
|
||||
explicit Cursor(CursorPrivate *dd);
|
||||
std::unique_ptr<CursorPrivate> d;
|
||||
|
||||
friend class Connection;
|
||||
};
|
||||
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
#ifndef CURSOR_P_H
|
||||
#define CURSOR_P_H
|
||||
|
||||
#include "connection_p.h"
|
||||
|
||||
namespace RethinkDB {
|
||||
|
||||
class CursorPrivate {
|
||||
public:
|
||||
CursorPrivate(uint64_t token, Connection *conn);
|
||||
CursorPrivate(uint64_t token, Connection *conn, Datum&&);
|
||||
|
||||
void add_response(Response&&) const;
|
||||
void add_results(Array&&) const;
|
||||
void clear_and_read_all() const;
|
||||
void convert_single() const;
|
||||
|
||||
mutable bool single = false;
|
||||
mutable bool no_more = false;
|
||||
mutable size_t index = 0;
|
||||
mutable Array buffer;
|
||||
|
||||
uint64_t token;
|
||||
Connection *conn;
|
||||
};
|
||||
|
||||
} // namespace RethinkDB
|
||||
|
||||
#endif // CURSOR_P_H
|
@ -1,449 +0,0 @@
|
||||
#include <float.h>
|
||||
#include <cmath>
|
||||
|
||||
#include "datum.h"
|
||||
#include "json_p.h"
|
||||
#include "utils.h"
|
||||
#include "cursor.h"
|
||||
|
||||
#include "rapidjson-config.h"
|
||||
#include "rapidjson/prettywriter.h"
|
||||
#include "rapidjson/stringbuffer.h"
|
||||
|
||||
namespace RethinkDB {
|
||||
|
||||
using TT = Protocol::Term::TermType;
|
||||
|
||||
bool Datum::is_nil() const {
|
||||
return type == Type::NIL;
|
||||
}
|
||||
|
||||
bool Datum::is_boolean() const {
|
||||
return type == Type::BOOLEAN;
|
||||
}
|
||||
|
||||
bool Datum::is_number() const {
|
||||
return type == Type::NUMBER;
|
||||
}
|
||||
|
||||
bool Datum::is_string() const {
|
||||
return type == Type::STRING;
|
||||
}
|
||||
|
||||
bool Datum::is_object() const {
|
||||
return type == Type::OBJECT;
|
||||
}
|
||||
|
||||
bool Datum::is_array() const {
|
||||
return type == Type::ARRAY;
|
||||
}
|
||||
|
||||
bool Datum::is_binary() const {
|
||||
return type == Type::BINARY;
|
||||
}
|
||||
|
||||
bool Datum::is_time() const {
|
||||
return type == Type::TIME;
|
||||
}
|
||||
|
||||
bool* Datum::get_boolean() {
|
||||
if (type == Type::BOOLEAN) {
|
||||
return &value.boolean;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const bool* Datum::get_boolean() const {
|
||||
if (type == Type::BOOLEAN) {
|
||||
return &value.boolean;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
double* Datum::get_number() {
|
||||
if (type == Type::NUMBER) {
|
||||
return &value.number;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const double* Datum::get_number() const {
|
||||
if (type == Type::NUMBER) {
|
||||
return &value.number;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
std::string* Datum::get_string() {
|
||||
if (type == Type::STRING) {
|
||||
return &value.string;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string* Datum::get_string() const {
|
||||
if (type == Type::STRING) {
|
||||
return &value.string;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
Datum* Datum::get_field(std::string key) {
|
||||
if (type != Type::OBJECT) {
|
||||
return NULL;
|
||||
}
|
||||
auto it = value.object.find(key);
|
||||
if (it == value.object.end()) {
|
||||
return NULL;
|
||||
}
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
const Datum* Datum::get_field(std::string key) const {
|
||||
if (type != Type::OBJECT) {
|
||||
return NULL;
|
||||
}
|
||||
auto it = value.object.find(key);
|
||||
if (it == value.object.end()) {
|
||||
return NULL;
|
||||
}
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
Datum* Datum::get_nth(size_t i) {
|
||||
if (type != Type::ARRAY) {
|
||||
return NULL;
|
||||
}
|
||||
if (i >= value.array.size()) {
|
||||
return NULL;
|
||||
}
|
||||
return &value.array[i];
|
||||
}
|
||||
|
||||
const Datum* Datum::get_nth(size_t i) const {
|
||||
if (type != Type::ARRAY) {
|
||||
return NULL;
|
||||
}
|
||||
if (i >= value.array.size()) {
|
||||
return NULL;
|
||||
}
|
||||
return &value.array[i];
|
||||
}
|
||||
|
||||
Object* Datum::get_object() {
|
||||
if (type == Type::OBJECT) {
|
||||
return &value.object;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const Object* Datum::get_object() const {
|
||||
if (type == Type::OBJECT) {
|
||||
return &value.object;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
Array* Datum::get_array() {
|
||||
if (type == Type::ARRAY) {
|
||||
return &value.array;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const Array* Datum::get_array() const {
|
||||
if (type == Type::ARRAY) {
|
||||
return &value.array;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
Binary* Datum::get_binary() {
|
||||
if (type == Type::BINARY) {
|
||||
return &value.binary;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const Binary* Datum::get_binary() const {
|
||||
if (type == Type::BINARY) {
|
||||
return &value.binary;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
Time* Datum::get_time() {
|
||||
if (type == Type::TIME) {
|
||||
return &value.time;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const Time* Datum::get_time() const {
|
||||
if (type == Type::TIME) {
|
||||
return &value.time;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool& Datum::extract_boolean() {
|
||||
if (type != Type::BOOLEAN) {
|
||||
throw Error("extract_bool: Not a boolean");
|
||||
}
|
||||
return value.boolean;
|
||||
}
|
||||
|
||||
double& Datum::extract_number() {
|
||||
if (type != Type::NUMBER) {
|
||||
throw Error("extract_number: Not a number: %s", write_datum(*this).c_str());
|
||||
}
|
||||
return value.number;
|
||||
}
|
||||
|
||||
std::string& Datum::extract_string() {
|
||||
if (type != Type::STRING) {
|
||||
throw Error("extract_string: Not a string");
|
||||
}
|
||||
return value.string;
|
||||
}
|
||||
|
||||
Object& Datum::extract_object() {
|
||||
if (type != Type::OBJECT) {
|
||||
throw Error("extract_object: Not an object");
|
||||
}
|
||||
return value.object;
|
||||
}
|
||||
|
||||
Datum& Datum::extract_field(std::string key) {
|
||||
if (type != Type::OBJECT) {
|
||||
throw Error("extract_field: Not an object");
|
||||
}
|
||||
auto it = value.object.find(key);
|
||||
if (it == value.object.end()) {
|
||||
throw Error("extract_field: No such key in object");
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
Datum& Datum::extract_nth(size_t i) {
|
||||
if (type != Type::ARRAY) {
|
||||
throw Error("extract_nth: Not an array");
|
||||
}
|
||||
if (i >= value.array.size()) {
|
||||
throw Error("extract_nth: index too large");
|
||||
}
|
||||
return value.array[i];
|
||||
}
|
||||
|
||||
Array& Datum::extract_array() {
|
||||
if (type != Type::ARRAY) {
|
||||
throw Error("get_array: Not an array");
|
||||
}
|
||||
return value.array;
|
||||
}
|
||||
|
||||
Binary& Datum::extract_binary() {
|
||||
if (type != Type::BINARY) {
|
||||
throw Error("get_binary: Not a binary");
|
||||
}
|
||||
return value.binary;
|
||||
}
|
||||
|
||||
Time& Datum::extract_time() {
|
||||
if (type != Type::TIME) {
|
||||
throw Error("get_time: Not a time");
|
||||
}
|
||||
return value.time;
|
||||
}
|
||||
|
||||
int Datum::compare(const Datum& other) const {
|
||||
#define COMPARE(a, b) do { \
|
||||
if (a < b) { return -1; } \
|
||||
if (a > b) { return 1; } } while(0)
|
||||
#define COMPARE_OTHER(x) COMPARE(x, other.x)
|
||||
|
||||
COMPARE_OTHER(type);
|
||||
int c;
|
||||
switch (type) {
|
||||
case Type::NIL: case Type::INVALID: break;
|
||||
case Type::BOOLEAN: COMPARE_OTHER(value.boolean); break;
|
||||
case Type::NUMBER: COMPARE_OTHER(value.number); break;
|
||||
case Type::STRING:
|
||||
c = value.string.compare(other.value.string);
|
||||
COMPARE(c, 0);
|
||||
break;
|
||||
case Type::BINARY:
|
||||
c = value.binary.data.compare(other.value.binary.data);
|
||||
COMPARE(c, 0);
|
||||
break;
|
||||
case Type::TIME:
|
||||
COMPARE(value.time.epoch_time, other.value.time.epoch_time);
|
||||
COMPARE(value.time.utc_offset, other.value.time.utc_offset);
|
||||
break;
|
||||
case Type::ARRAY:
|
||||
COMPARE_OTHER(value.array.size());
|
||||
for (size_t i = 0; i < value.array.size(); i++) {
|
||||
c = value.array[i].compare(other.value.array[i]);
|
||||
COMPARE(c, 0);
|
||||
}
|
||||
break;
|
||||
case Type::OBJECT:
|
||||
COMPARE_OTHER(value.object.size());
|
||||
for (Object::const_iterator l = value.object.begin(),
|
||||
r = other.value.object.begin();
|
||||
l != value.object.end();
|
||||
++l, ++r) {
|
||||
COMPARE(l->first, r->first);
|
||||
c = l->second.compare(r->second);
|
||||
COMPARE(c, 0);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw Error("cannot compare invalid datum");
|
||||
}
|
||||
return 0;
|
||||
#undef COMPARE_OTHER
|
||||
#undef COMPARE
|
||||
}
|
||||
|
||||
bool Datum::operator== (const Datum& other) const {
|
||||
return compare(other) == 0;
|
||||
}
|
||||
|
||||
Datum Datum::from_raw() const {
|
||||
do {
|
||||
const Datum* type_field = get_field("$reql_type$");
|
||||
if (!type_field) break;
|
||||
const std::string* type = type_field->get_string();
|
||||
if (!type) break;;
|
||||
if (!strcmp(type->c_str(), "BINARY")) {
|
||||
const Datum* data_field = get_field("data");
|
||||
if (!data_field) break;
|
||||
const std::string* encoded_data = data_field->get_string();
|
||||
if (!encoded_data) break;
|
||||
Binary binary("");
|
||||
if (base64_decode(*encoded_data, binary.data)) {
|
||||
return binary;
|
||||
}
|
||||
} else if (!strcmp(type->c_str(), "TIME")) {
|
||||
const Datum* epoch_field = get_field("epoch_time");
|
||||
if (!epoch_field) break;
|
||||
const Datum* tz_field = get_field("timezone");
|
||||
if (!tz_field) break;
|
||||
const double* epoch_time = epoch_field->get_number();
|
||||
if (!epoch_time) break;
|
||||
const std::string* tz = tz_field->get_string();
|
||||
if (!tz) break;
|
||||
double offset;
|
||||
if (!Time::parse_utc_offset(*tz, &offset)) break;
|
||||
return Time(*epoch_time, offset);
|
||||
}
|
||||
} while (0);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Datum Datum::to_raw() const {
|
||||
if (type == Type::BINARY) {
|
||||
return Object{
|
||||
{"$reql_type$", "BINARY"},
|
||||
{"data", base64_encode(value.binary.data)}};
|
||||
} else if (type == Type::TIME) {
|
||||
return Object{
|
||||
{"$reql_type$", "TIME"},
|
||||
{"epoch_time", value.time.epoch_time},
|
||||
{"timezone", Time::utc_offset_string(value.time.utc_offset)}};
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Datum::Datum(Cursor&& cursor) : Datum(cursor.to_datum()) { }
|
||||
Datum::Datum(const Cursor& cursor) : Datum(cursor.to_datum()) { }
|
||||
|
||||
static const double max_dbl_int = 0x1LL << DBL_MANT_DIG;
|
||||
static const double min_dbl_int = max_dbl_int * -1;
|
||||
bool number_as_integer(double d, int64_t *i_out) {
|
||||
static_assert(DBL_MANT_DIG == 53, "Doubles are wrong size.");
|
||||
|
||||
if (min_dbl_int <= d && d <= max_dbl_int) {
|
||||
int64_t i = d;
|
||||
if (static_cast<double>(i) == d) {
|
||||
*i_out = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template void Datum::write_json(
|
||||
rapidjson::Writer<rapidjson::StringBuffer> *writer) const;
|
||||
template void Datum::write_json(
|
||||
rapidjson::PrettyWriter<rapidjson::StringBuffer> *writer) const;
|
||||
|
||||
template <class json_writer_t>
|
||||
void Datum::write_json(json_writer_t *writer) const {
|
||||
switch (type) {
|
||||
case Type::NIL: writer->Null(); break;
|
||||
case Type::BOOLEAN: writer->Bool(value.boolean); break;
|
||||
case Type::NUMBER: {
|
||||
const double d = value.number;
|
||||
// Always print -0.0 as a double since integers cannot represent -0.
|
||||
// Otherwise check if the number is an integer and print it as such.
|
||||
int64_t i;
|
||||
if (!(d == 0.0 && std::signbit(d)) && number_as_integer(d, &i)) {
|
||||
writer->Int64(i);
|
||||
} else {
|
||||
writer->Double(d);
|
||||
}
|
||||
} break;
|
||||
case Type::STRING: writer->String(value.string.data(), value.string.size()); break;
|
||||
case Type::ARRAY: {
|
||||
writer->StartArray();
|
||||
for (auto it : value.array) {
|
||||
it.write_json(writer);
|
||||
}
|
||||
writer->EndArray();
|
||||
} break;
|
||||
case Type::OBJECT: {
|
||||
writer->StartObject();
|
||||
for (auto it : value.object) {
|
||||
writer->Key(it.first.data(), it.first.size());
|
||||
it.second.write_json(writer);
|
||||
}
|
||||
writer->EndObject();
|
||||
} break;
|
||||
|
||||
case Type::BINARY:
|
||||
case Type::TIME:
|
||||
to_raw().write_json(writer);
|
||||
break;
|
||||
default:
|
||||
throw Error("cannot write invalid datum");
|
||||
}
|
||||
}
|
||||
|
||||
std::string Datum::as_json() const {
|
||||
rapidjson::StringBuffer buffer;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
||||
write_json(&writer);
|
||||
return std::string(buffer.GetString(), buffer.GetSize());
|
||||
}
|
||||
|
||||
Datum Datum::from_json(const std::string& json) {
|
||||
return read_datum(json);
|
||||
}
|
||||
|
||||
} // namespace RethinkDB
|
@ -1,287 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <functional>
|
||||
|
||||
#include "protocol_defs.h"
|
||||
#include "error.h"
|
||||
#include "types.h"
|
||||
|
||||
namespace RethinkDB {
|
||||
|
||||
class Cursor;
|
||||
|
||||
// The type of data stored in a RethinkDB database.
|
||||
// The following JSON types are represented in a Datum as
|
||||
// * null -> Nil
|
||||
// * boolean -> bool
|
||||
// * number -> double
|
||||
// * unicode strings -> std::string
|
||||
// * array -> Array (aka std::vector<Datum>
|
||||
// * object -> Object (aka std::map<std::string, Datum>>
|
||||
// Datums can also contain one of the following extra types
|
||||
// * binary strings -> Binary
|
||||
// * timestamps -> Time
|
||||
// * points. lines and polygons -> not implemented
|
||||
class Datum {
|
||||
public:
|
||||
Datum() : type(Type::INVALID), value() {}
|
||||
Datum(Nil) : type(Type::NIL), value() { }
|
||||
Datum(bool boolean_) : type(Type::BOOLEAN), value(boolean_) { }
|
||||
Datum(double number_) : type(Type::NUMBER), value(number_) { }
|
||||
Datum(const std::string& string_) : type(Type::STRING), value(string_) { }
|
||||
Datum(std::string&& string_) : type(Type::STRING), value(std::move(string_)) { }
|
||||
Datum(const Array& array_) : type(Type::ARRAY), value(array_) { }
|
||||
Datum(Array&& array_) : type(Type::ARRAY), value(std::move(array_)) { }
|
||||
Datum(const Binary& binary) : type(Type::BINARY), value(binary) { }
|
||||
Datum(Binary&& binary) : type(Type::BINARY), value(std::move(binary)) { }
|
||||
Datum(const Time time) : type(Type::TIME), value(time) { }
|
||||
Datum(const Object& object_) : type(Type::OBJECT), value(object_) { }
|
||||
Datum(Object&& object_) : type(Type::OBJECT), value(std::move(object_)) { }
|
||||
Datum(const Datum& other) : type(other.type), value(other.type, other.value) { }
|
||||
Datum(Datum&& other) : type(other.type), value(other.type, std::move(other.value)) { }
|
||||
|
||||
Datum& operator=(const Datum& other) {
|
||||
value.destroy(type);
|
||||
type = other.type;
|
||||
value.set(type, other.value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Datum& operator=(Datum&& other) {
|
||||
value.destroy(type);
|
||||
type = other.type;
|
||||
value.set(type, std::move(other.value));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Datum(unsigned short number_) : Datum(static_cast<double>(number_)) { }
|
||||
Datum(signed short number_) : Datum(static_cast<double>(number_)) { }
|
||||
Datum(unsigned int number_) : Datum(static_cast<double>(number_)) { }
|
||||
Datum(signed int number_) : Datum(static_cast<double>(number_)) { }
|
||||
Datum(unsigned long number_) : Datum(static_cast<double>(number_)) { }
|
||||
Datum(signed long number_) : Datum(static_cast<double>(number_)) { }
|
||||
Datum(unsigned long long number_) : Datum(static_cast<double>(number_)) { }
|
||||
Datum(signed long long number_) : Datum(static_cast<double>(number_)) { }
|
||||
|
||||
Datum(Protocol::Term::TermType type) : Datum(static_cast<double>(type)) { }
|
||||
Datum(const char* string) : Datum(static_cast<std::string>(string)) { }
|
||||
|
||||
// Cursors are implicitly converted into datums
|
||||
Datum(Cursor&&);
|
||||
Datum(const Cursor&);
|
||||
|
||||
template <class T>
|
||||
Datum(const std::map<std::string, T>& map) : type(Type::OBJECT), value(Object()) {
|
||||
for (const auto& it : map) {
|
||||
value.object.emplace(it.left, Datum(it.right));
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Datum(std::map<std::string, T>&& map) : type(Type::OBJECT), value(Object()) {
|
||||
for (auto& it : map) {
|
||||
value.object.emplace(it.first, Datum(std::move(it.second)));
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Datum(const std::vector<T>& vec) : type(Type::ARRAY), value(Array()) {
|
||||
for (const auto& it : vec) {
|
||||
value.array.emplace_back(it);
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Datum(std::vector<T>&& vec) : type(Type::ARRAY), value(Array()) {
|
||||
for (auto& it : vec) {
|
||||
value.array.emplace_back(std::move(it));
|
||||
}
|
||||
}
|
||||
|
||||
~Datum() {
|
||||
value.destroy(type);
|
||||
}
|
||||
|
||||
// Apply a visitor
|
||||
template <class R, class F, class ...A>
|
||||
R apply(F f, A&& ...args) const & {
|
||||
switch (type) {
|
||||
case Type::NIL: return f(Nil(), std::forward<A>(args)...); break;
|
||||
case Type::BOOLEAN: return f(value.boolean, std::forward<A>(args)...); break;
|
||||
case Type::NUMBER: return f(value.number, std::forward<A>(args)...); break;
|
||||
case Type::STRING: return f(value.string, std::forward<A>(args)...); break;
|
||||
case Type::OBJECT: return f(value.object, std::forward<A>(args)...); break;
|
||||
case Type::ARRAY: return f(value.array, std::forward<A>(args)...); break;
|
||||
case Type::BINARY: return f(value.binary, std::forward<A>(args)...); break;
|
||||
case Type::TIME: return f(value.time, std::forward<A>(args)...); break;
|
||||
default:
|
||||
throw Error("internal error: no such datum type %d", static_cast<int>(type));
|
||||
}
|
||||
}
|
||||
|
||||
template <class R, class F, class ...A>
|
||||
R apply(F f, A&& ...args) && {
|
||||
switch (type) {
|
||||
case Type::NIL: return f(Nil(), std::forward<A>(args)...); break;
|
||||
case Type::BOOLEAN: return f(std::move(value.boolean), std::forward<A>(args)...); break;
|
||||
case Type::NUMBER: return f(std::move(value.number), std::forward<A>(args)...); break;
|
||||
case Type::STRING: return f(std::move(value.string), std::forward<A>(args)...); break;
|
||||
case Type::OBJECT: return f(std::move(value.object), std::forward<A>(args)...); break;
|
||||
case Type::ARRAY: return f(std::move(value.array), std::forward<A>(args)...); break;
|
||||
case Type::BINARY: return f(std::move(value.binary), std::forward<A>(args)...); break;
|
||||
case Type::TIME: return f(std::move(value.time), std::forward<A>(args)...); break;
|
||||
default:
|
||||
throw Error("internal error: no such datum type %d", static_cast<int>(type));
|
||||
}
|
||||
}
|
||||
|
||||
bool is_nil() const;
|
||||
bool is_boolean() const;
|
||||
bool is_number() const;
|
||||
bool is_string() const;
|
||||
bool is_object() const;
|
||||
bool is_array() const;
|
||||
bool is_binary() const;
|
||||
bool is_time() const;
|
||||
|
||||
// get_* returns nullptr if the datum has a different type
|
||||
|
||||
bool* get_boolean();
|
||||
const bool* get_boolean() const;
|
||||
double* get_number();
|
||||
const double* get_number() const;
|
||||
std::string* get_string();
|
||||
const std::string* get_string() const;
|
||||
Object* get_object();
|
||||
const Object* get_object() const;
|
||||
Datum* get_field(std::string);
|
||||
const Datum* get_field(std::string) const;
|
||||
Array* get_array();
|
||||
const Array* get_array() const;
|
||||
Datum* get_nth(size_t);
|
||||
const Datum* get_nth(size_t) const;
|
||||
Binary* get_binary();
|
||||
const Binary* get_binary() const;
|
||||
Time* get_time();
|
||||
const Time* get_time() const;
|
||||
|
||||
// extract_* throws an exception if the types don't match
|
||||
|
||||
bool& extract_boolean();
|
||||
double& extract_number();
|
||||
std::string& extract_string();
|
||||
Object& extract_object();
|
||||
Datum& extract_field(std::string);
|
||||
Array& extract_array();
|
||||
Datum& extract_nth(size_t);
|
||||
Binary& extract_binary();
|
||||
Time& extract_time();
|
||||
|
||||
// negative, zero or positive if this datum is smaller, identical or larger than the other one, respectively
|
||||
// This is meant to match the results of RethinkDB's comparison operators
|
||||
int compare(const Datum&) const;
|
||||
|
||||
// Deep equality
|
||||
bool operator== (const Datum&) const;
|
||||
|
||||
// Recusively replace non-JSON types into objects that represent them
|
||||
Datum to_raw() const;
|
||||
|
||||
// Recursively replace objects with a $reql_type$ field into the datum they represent
|
||||
Datum from_raw() const;
|
||||
|
||||
template <class json_writer_t> void write_json(json_writer_t *writer) const;
|
||||
|
||||
std::string as_json() const;
|
||||
static Datum from_json(const std::string&);
|
||||
|
||||
bool is_valid() const { return type != Type::INVALID; }
|
||||
|
||||
private:
|
||||
enum class Type {
|
||||
INVALID, // default constructed
|
||||
ARRAY, BOOLEAN, NIL, NUMBER, OBJECT, BINARY, STRING, TIME
|
||||
// POINT, LINE, POLYGON
|
||||
};
|
||||
Type type;
|
||||
|
||||
union datum_value {
|
||||
bool boolean;
|
||||
double number;
|
||||
std::string string;
|
||||
Object object;
|
||||
Array array;
|
||||
Binary binary;
|
||||
Time time;
|
||||
|
||||
datum_value() { }
|
||||
datum_value(bool boolean_) : boolean(boolean_) { }
|
||||
datum_value(double number_) : number(number_) { }
|
||||
datum_value(const std::string& string_) : string(string_) { }
|
||||
datum_value(std::string&& string_) : string(std::move(string_)) { }
|
||||
datum_value(const Object& object_) : object(object_) { }
|
||||
datum_value(Object&& object_) : object(std::move(object_)) { }
|
||||
datum_value(const Array& array_) : array(array_) { }
|
||||
datum_value(Array&& array_) : array(std::move(array_)) { }
|
||||
datum_value(const Binary& binary_) : binary(binary_) { }
|
||||
datum_value(Binary&& binary_) : binary(std::move(binary_)) { }
|
||||
datum_value(Time time) : time(std::move(time)) { }
|
||||
|
||||
datum_value(Type type, const datum_value& other){
|
||||
set(type, other);
|
||||
}
|
||||
|
||||
datum_value(Type type, datum_value&& other){
|
||||
set(type, std::move(other));
|
||||
}
|
||||
|
||||
void set(Type type, datum_value&& other) {
|
||||
switch(type){
|
||||
case Type::NIL: case Type::INVALID: break;
|
||||
case Type::BOOLEAN: new (this) bool(other.boolean); break;
|
||||
case Type::NUMBER: new (this) double(other.number); break;
|
||||
case Type::STRING: new (this) std::string(std::move(other.string)); break;
|
||||
case Type::OBJECT: new (this) Object(std::move(other.object)); break;
|
||||
case Type::ARRAY: new (this) Array(std::move(other.array)); break;
|
||||
case Type::BINARY: new (this) Binary(std::move(other.binary)); break;
|
||||
case Type::TIME: new (this) Time(std::move(other.time)); break;
|
||||
}
|
||||
}
|
||||
|
||||
void set(Type type, const datum_value& other) {
|
||||
switch(type){
|
||||
case Type::NIL: case Type::INVALID: break;
|
||||
case Type::BOOLEAN: new (this) bool(other.boolean); break;
|
||||
case Type::NUMBER: new (this) double(other.number); break;
|
||||
case Type::STRING: new (this) std::string(other.string); break;
|
||||
case Type::OBJECT: new (this) Object(other.object); break;
|
||||
case Type::ARRAY: new (this) Array(other.array); break;
|
||||
case Type::BINARY: new (this) Binary(other.binary); break;
|
||||
case Type::TIME: new (this) Time(other.time); break;
|
||||
}
|
||||
}
|
||||
|
||||
void destroy(Type type) {
|
||||
switch(type){
|
||||
case Type::INVALID: break;
|
||||
case Type::NIL: break;
|
||||
case Type::BOOLEAN: break;
|
||||
case Type::NUMBER: break;
|
||||
case Type::STRING: { typedef std::string str; string.~str(); } break;
|
||||
case Type::OBJECT: object.~Object(); break;
|
||||
case Type::ARRAY: array.~Array(); break;
|
||||
case Type::BINARY: binary.~Binary(); break;
|
||||
case Type::TIME: time.~Time(); break;
|
||||
}
|
||||
}
|
||||
|
||||
~datum_value() { }
|
||||
};
|
||||
|
||||
datum_value value;
|
||||
};
|
||||
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <cerrno>
|
||||
|
||||
namespace RethinkDB {
|
||||
|
||||
// All errors thrown by the server have this type
|
||||
struct Error {
|
||||
template <class ...T>
|
||||
explicit Error(const char* format_, T... A) {
|
||||
format(format_, A...);
|
||||
}
|
||||
|
||||
Error() = default;
|
||||
Error(Error&&) = default;
|
||||
Error(const Error&) = default;
|
||||
|
||||
Error& operator= (Error&& other) {
|
||||
message = std::move(other.message);
|
||||
return *this;
|
||||
}
|
||||
|
||||
static Error from_errno(const char* str){
|
||||
return Error("%s: %s", str, strerror(errno));
|
||||
}
|
||||
|
||||
// The error message
|
||||
std::string message;
|
||||
|
||||
private:
|
||||
const size_t max_message_size = 2048;
|
||||
|
||||
void format(const char* format_, ...) {
|
||||
va_list args;
|
||||
va_start(args, format_);
|
||||
char message_[max_message_size];
|
||||
vsnprintf(message_, max_message_size, format_, args);
|
||||
va_end(args);
|
||||
message = message_;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
#ifndef EXCEPTIONS_H
|
||||
#define EXCEPTIONS_H
|
||||
|
||||
namespace RethinkDB {
|
||||
|
||||
class TimeoutException : public std::exception {
|
||||
public:
|
||||
const char *what() const throw () { return "operation timed out"; }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // EXCEPTIONS_H
|
@ -1,62 +0,0 @@
|
||||
#include "json_p.h"
|
||||
#include "error.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include "rapidjson-config.h"
|
||||
#include "rapidjson/document.h"
|
||||
#include "rapidjson/stringbuffer.h"
|
||||
#include "rapidjson/writer.h"
|
||||
#include "rapidjson/prettywriter.h"
|
||||
|
||||
namespace RethinkDB {
|
||||
|
||||
Datum read_datum(const std::string& json) {
|
||||
rapidjson::Document document;
|
||||
document.Parse(json);
|
||||
return read_datum(document);
|
||||
}
|
||||
|
||||
Datum read_datum(const rapidjson::Value &json) {
|
||||
switch(json.GetType()) {
|
||||
case rapidjson::kNullType: return Nil();
|
||||
case rapidjson::kFalseType: return false;
|
||||
case rapidjson::kTrueType: return true;
|
||||
case rapidjson::kNumberType: return json.GetDouble();
|
||||
case rapidjson::kStringType:
|
||||
return std::string(json.GetString(), json.GetStringLength());
|
||||
|
||||
case rapidjson::kObjectType: {
|
||||
Object result;
|
||||
for (rapidjson::Value::ConstMemberIterator it = json.MemberBegin();
|
||||
it != json.MemberEnd(); ++it) {
|
||||
result.insert(std::make_pair(std::string(it->name.GetString(),
|
||||
it->name.GetStringLength()),
|
||||
read_datum(it->value)));
|
||||
}
|
||||
|
||||
if (result.count("$reql_type$"))
|
||||
return Datum(std::move(result)).from_raw();
|
||||
return std::move(result);
|
||||
} break;
|
||||
case rapidjson::kArrayType: {
|
||||
Array result;
|
||||
result.reserve(json.Size());
|
||||
for (rapidjson::Value::ConstValueIterator it = json.Begin();
|
||||
it != json.End(); ++it) {
|
||||
result.push_back(read_datum(*it));
|
||||
}
|
||||
return std::move(result);
|
||||
} break;
|
||||
default:
|
||||
throw Error("invalid rapidjson value");
|
||||
}
|
||||
}
|
||||
|
||||
std::string write_datum(const Datum& datum) {
|
||||
rapidjson::StringBuffer buffer;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
||||
datum.write_json(&writer);
|
||||
return std::string(buffer.GetString(), buffer.GetSize());
|
||||
}
|
||||
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "datum.h"
|
||||
|
||||
namespace rapidjson {
|
||||
class CrtAllocator;
|
||||
template<typename> struct UTF8;
|
||||
template <typename, typename> class GenericValue;
|
||||
template <typename> class MemoryPoolAllocator;
|
||||
typedef GenericValue<UTF8<char>, MemoryPoolAllocator<CrtAllocator> > Value;
|
||||
}
|
||||
|
||||
namespace RethinkDB {
|
||||
|
||||
Datum read_datum(const std::string&);
|
||||
Datum read_datum(const rapidjson::Value &json);
|
||||
std::string write_datum(const Datum&);
|
||||
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#define RAPIDJSON_HAS_STDSTRING 1
|
||||
#define RAPIDJSON_HAS_CXX11_RVALUE_REFS 1
|
||||
#define RAPIDJSON_HAS_CXX11_NOEXCEPT 1
|
||||
#define RAPIDJSON_HAS_CXX11_TYPETRAITS 1
|
||||
#define RAPIDJSON_HAS_CXX11_RANGE_FOR 1
|
||||
#define RAPIDJSON_PARSE_DEFAULT_FLAGS kParseFullPrecisionFlag
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user