First Commit
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright (c)2019 ZeroTier, Inc.
|
||||
*
|
||||
* Use of this software is governed by the Business Source License included
|
||||
* in the LICENSE.TXT file in the project's root directory.
|
||||
*
|
||||
* Change Date: 2026-01-01
|
||||
*
|
||||
* On the date above, in accordance with the Business Source License, use
|
||||
* of this software will be governed by version 2.0 of the Apache License.
|
||||
*/
|
||||
/****/
|
||||
|
||||
#ifndef ZT_ONESERVICE_HPP
|
||||
#define ZT_ONESERVICE_HPP
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
#ifdef ZT_SDK
|
||||
class VirtualTap;
|
||||
// Use the virtual libzt endpoint instead of a tun/tap port driver
|
||||
namespace ZeroTier { typedef VirtualTap EthernetTap; }
|
||||
#endif
|
||||
|
||||
// Forward declaration so we can avoid dragging everything in
|
||||
struct InetAddress;
|
||||
class Node;
|
||||
|
||||
/**
|
||||
* Local service for ZeroTier One as system VPN/NFV provider
|
||||
*/
|
||||
class OneService
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Returned by node main if/when it terminates
|
||||
*/
|
||||
enum ReasonForTermination
|
||||
{
|
||||
/**
|
||||
* Instance is still running
|
||||
*/
|
||||
ONE_STILL_RUNNING = 0,
|
||||
|
||||
/**
|
||||
* Normal shutdown
|
||||
*/
|
||||
ONE_NORMAL_TERMINATION = 1,
|
||||
|
||||
/**
|
||||
* A serious unrecoverable error has occurred
|
||||
*/
|
||||
ONE_UNRECOVERABLE_ERROR = 2,
|
||||
|
||||
/**
|
||||
* Your identity has collided with another
|
||||
*/
|
||||
ONE_IDENTITY_COLLISION = 3
|
||||
};
|
||||
|
||||
/**
|
||||
* Local settings for each network
|
||||
*/
|
||||
struct NetworkSettings
|
||||
{
|
||||
/**
|
||||
* Allow this network to configure IP addresses and routes?
|
||||
*/
|
||||
bool allowManaged;
|
||||
|
||||
/**
|
||||
* Whitelist of addresses that can be configured by this network.
|
||||
* If empty and allowManaged is true, allow all private/pseudoprivate addresses.
|
||||
*/
|
||||
std::vector<InetAddress> allowManagedWhitelist;
|
||||
|
||||
/**
|
||||
* Allow configuration of IPs and routes within global (Internet) IP space?
|
||||
*/
|
||||
bool allowGlobal;
|
||||
|
||||
/**
|
||||
* Allow overriding of system default routes for "full tunnel" operation?
|
||||
*/
|
||||
bool allowDefault;
|
||||
|
||||
/**
|
||||
* Allow configuration of DNS for the network
|
||||
*/
|
||||
bool allowDNS;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return Platform default home path or empty string if this platform doesn't have one
|
||||
*/
|
||||
static std::string platformDefaultHomePath();
|
||||
|
||||
/**
|
||||
* Create a new instance of the service
|
||||
*
|
||||
* Once created, you must call the run() method to actually start
|
||||
* processing.
|
||||
*
|
||||
* The port is saved to a file in the home path called zerotier-one.port,
|
||||
* which is used by the CLI and can be used to see which port was chosen if
|
||||
* 0 (random port) is picked.
|
||||
*
|
||||
* @param hp Home path
|
||||
* @param port TCP and UDP port for packets and HTTP control (if 0, pick random port)
|
||||
*/
|
||||
static OneService *newInstance(const char *hp,unsigned int port);
|
||||
|
||||
virtual ~OneService();
|
||||
|
||||
/**
|
||||
* Execute the service main I/O loop until terminated
|
||||
*
|
||||
* The terminate() method may be called from a signal handler or another
|
||||
* thread to terminate execution. Otherwise this will not return unless
|
||||
* another condition terminates execution such as a fatal error.
|
||||
*/
|
||||
virtual ReasonForTermination run() = 0;
|
||||
|
||||
/**
|
||||
* @return Reason for terminating or ONE_STILL_RUNNING if running
|
||||
*/
|
||||
virtual ReasonForTermination reasonForTermination() const = 0;
|
||||
|
||||
/**
|
||||
* @return Fatal error message or empty string if none
|
||||
*/
|
||||
virtual std::string fatalErrorMessage() const = 0;
|
||||
|
||||
/**
|
||||
* @return System device name corresponding with a given ZeroTier network ID or empty string if not opened yet or network ID not found
|
||||
*/
|
||||
virtual std::string portDeviceName(uint64_t nwid) const = 0;
|
||||
|
||||
#ifdef ZT_SDK
|
||||
/**
|
||||
* Whether we allow access to the service via local HTTP requests (disabled by default in libzt)
|
||||
*/
|
||||
bool allowHttpBackplaneManagement = false;
|
||||
/**
|
||||
* @return Reference to the Node
|
||||
*/
|
||||
virtual Node * getNode() = 0;
|
||||
/**
|
||||
* Fills out a structure with network-specific route information
|
||||
*/
|
||||
virtual void getRoutes(uint64_t nwid, void *routeArray, unsigned int *numRoutes) = 0;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Terminate background service (can be called from other threads)
|
||||
*/
|
||||
virtual void terminate() = 0;
|
||||
|
||||
/**
|
||||
* Get local settings for a network
|
||||
*
|
||||
* @param nwid Network ID
|
||||
* @param settings Buffer to fill with local network settings
|
||||
* @return True if network was found and settings is filled
|
||||
*/
|
||||
virtual bool getNetworkSettings(const uint64_t nwid,NetworkSettings &settings) const = 0;
|
||||
|
||||
/**
|
||||
* Set local settings for a network
|
||||
*
|
||||
* @param nwid Network ID
|
||||
* @param settings New network local settings
|
||||
* @return True if network was found and setting modified
|
||||
*/
|
||||
virtual bool setNetworkSettings(const uint64_t nwid,const NetworkSettings &settings) = 0;
|
||||
|
||||
/**
|
||||
* @return True if service is still running
|
||||
*/
|
||||
inline bool isRunning() const { return (this->reasonForTermination() == ONE_STILL_RUNNING); }
|
||||
|
||||
protected:
|
||||
OneService() {}
|
||||
|
||||
private:
|
||||
OneService(const OneService &one) {}
|
||||
inline OneService &operator=(const OneService &one) { return *this; }
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,192 @@
|
||||
ZeroTier One Network Virtualization Service
|
||||
======
|
||||
|
||||
This is the actual implementation of ZeroTier One, a service providing connectivity to ZeroTier virtual networks for desktops, laptops, servers, VMs, etc. (Mobile versions for iOS and Android have their own implementations in native Java and Objective C that leverage only the ZeroTier core engine.)
|
||||
|
||||
### Local Configuration File
|
||||
|
||||
A file called `local.conf` in the ZeroTier [home](https://github.com/zerotier/ZeroTierOne/blob/6faca86bb424d0b9643b6efa50571f73310d8276/README.md) folder contains configuration options that apply to the local node. (It does not exist unless you create it). It can be used to set up trusted paths, blacklist physical paths, set up physical path hints for certain nodes, and define trusted upstream devices (federated roots). In a large deployment it can be deployed using a tool like Puppet, Chef, SaltStack, etc. to set a uniform configuration across systems.
|
||||
|
||||
It's a JSON format file that can also be edited and rewritten by ZeroTier One itself, so ensure that proper JSON formatting is used. To validate your config, paste it into a website like [jsonlint.com](https://jsonlint.com), or use a tool like `jq`.
|
||||
|
||||
Check the output of `zerotier-cli info -j` to see if your configuration is being loaded.
|
||||
|
||||
Settings available in `local.conf` (this is not valid JSON, and JSON does not allow comments):
|
||||
|
||||
```javascript
|
||||
{
|
||||
"physical": { /* Settings that apply to physical L2/L3 network paths. */
|
||||
"NETWORK/bits": { /* Network e.g. 10.0.0.0/24 or fd00::/32 */
|
||||
"blacklist": true|false, /* If true, blacklist this path for all ZeroTier traffic */
|
||||
"trustedPathId": 0|!0, /* If present and nonzero, define this as a trusted path (see below) */
|
||||
"mtu": 0|!0 /* if present and non-zero, set UDP maximum payload MTU for this path */
|
||||
} /* ,... additional networks */
|
||||
},
|
||||
"virtual": { /* Settings applied to ZeroTier virtual network devices (VL1) */
|
||||
"##########": { /* 10-digit ZeroTier address */
|
||||
"try": [ "IP/port"/*,...*/ ], /* Hints on where to reach this peer if no upstreams/roots are online */
|
||||
"blacklist": [ "NETWORK/bits"/*,...*/ ] /* Blacklist a physical path for only this peer. */
|
||||
}
|
||||
},
|
||||
"settings": { /* Other global settings */
|
||||
"primaryPort": 1-65535, /* If set, override default port of 9993 and any command line port */
|
||||
"secondaryPort": 1-65535, /* If set, override default random secondary port */
|
||||
"tertiaryPort": 1-65535, /* If set, override default random tertiary port */
|
||||
"portMappingEnabled": true|false, /* If true (the default), try to use uPnP or NAT-PMP to map ports */
|
||||
"allowSecondaryPort": true|false /* false will also disable secondary port */
|
||||
"softwareUpdate": "apply"|"download"|"disable", /* Automatically apply updates, just download, or disable built-in software updates */
|
||||
"softwareUpdateChannel": "release"|"beta", /* Software update channel */
|
||||
"softwareUpdateDist": true|false, /* If true, distribute software updates (only really useful to ZeroTier, Inc. itself, default is false) */
|
||||
"interfacePrefixBlacklist": [ "XXX",... ], /* Array of interface name prefixes (e.g. eth for eth#) to blacklist for ZT traffic */
|
||||
"allowManagementFrom": [ "NETWORK/bits", ...] |null, /* If non-NULL, allow JSON/HTTP management from this IP network. Default is 127.0.0.1 only. */
|
||||
"bind": [ "ip",... ], /* If present and non-null, bind to these IPs instead of to each interface (wildcard IP allowed) */
|
||||
"allowTcpFallbackRelay": true|false, /* Allow or disallow establishment of TCP relay connections (true by default) */
|
||||
"multipathMode": 0|1|2 /* multipath mode: none (0), random (1), proportional (2) */
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* **trustedPathId**: A trusted path is a physical network over which encryption and authentication are not required. This provides a performance boost but sacrifices all ZeroTier's security features when communicating over this path. Only use this if you know what you are doing and really need the performance! To set up a trusted path, all devices using it *MUST* have the *same trusted path ID* for the same network. Trusted path IDs are arbitrary positive non-zero integers. For example a group of devices on a LAN with IPs in 10.0.0.0/24 could use it as a fast trusted path if they all had the same trusted path ID of "25" defined for that network.
|
||||
|
||||
An example `local.conf`:
|
||||
|
||||
```javascript
|
||||
{
|
||||
"physical": {
|
||||
"10.0.0.0/24": {
|
||||
"blacklist": true
|
||||
},
|
||||
"10.10.10.0/24": {
|
||||
"trustedPathId": 101010024
|
||||
},
|
||||
},
|
||||
"virtual": {
|
||||
"feedbeef12": {
|
||||
"role": "UPSTREAM",
|
||||
"try": [ "10.10.20.1/9993" ],
|
||||
"blacklist": [ "192.168.0.0/24" ]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"softwareUpdate": "apply",
|
||||
"softwareUpdateChannel": "release"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Network Virtualization Service API
|
||||
|
||||
The JSON API supports GET, POST/PUT, and DELETE. PUT is treated as a synonym for POST. Other methods including HEAD are not supported.
|
||||
|
||||
Values POSTed to the JSON API are *extremely* type sensitive. Things *must* be of the indicated type, otherwise they will be ignored or will generate an error. Anything quoted is a string so booleans and integers must lack quotes. Booleans must be *true* or *false* and nothing else. Integers cannot contain decimal points or they are floats (and vice versa). If something seems to be getting ignored or set to a strange value, or if you receive errors, check the type of all JSON fields you are submitting against the types listed below. Unrecognized fields in JSON objects are also ignored.
|
||||
|
||||
API requests must be authenticated via an authentication token. ZeroTier One saves this token in the *authtoken.secret* file in its working directory. This token may be supplied via the *auth* URL parameter (e.g. '?auth=...') or via the *X-ZT1-Auth* HTTP request header. Static UI pages are the only thing the server will allow without authentication.
|
||||
|
||||
A *jsonp* URL argument may be supplied to request JSONP encapsulation. A JSONP response is sent as a script with its JSON response payload wrapped in a call to the function name supplied as the argument to *jsonp*.
|
||||
|
||||
#### /status
|
||||
|
||||
* Purpose: Get running node status and addressing info
|
||||
* Methods: GET
|
||||
* Returns: { object }
|
||||
|
||||
| Field | Type | Description | Writable |
|
||||
| --------------------- | ------------- | ------------------------------------------------- | -------- |
|
||||
| address | string | 10-digit hex ZeroTier address of this node | no |
|
||||
| publicIdentity | string | This node's ZeroTier identity.public | no |
|
||||
| worldId | integer | ZeroTier world ID (never changes except for test) | no |
|
||||
| worldTimestamp | integer | Timestamp of most recent world definition | no |
|
||||
| online | boolean | If true at least one upstream peer is reachable | no |
|
||||
| tcpFallbackActive | boolean | If true we are using slow TCP fallback | no |
|
||||
| relayPolicy | string | Relay policy: ALWAYS, TRUSTED, or NEVER | no |
|
||||
| versionMajor | integer | Software major version | no |
|
||||
| versionMinor | integer | Software minor version | no |
|
||||
| versionRev | integer | Software revision | no |
|
||||
| version | string | major.minor.revision | no |
|
||||
| clock | integer | Current system clock at node (ms since epoch) | no |
|
||||
|
||||
#### /network
|
||||
|
||||
* Purpose: Get all network memberships
|
||||
* Methods: GET
|
||||
* Returns: [ {object}, ... ]
|
||||
|
||||
Getting /network returns an array of all networks that this node has joined. See below for network object format.
|
||||
|
||||
#### /network/\<network ID\>
|
||||
|
||||
* Purpose: Get, join, or leave a network
|
||||
* Methods: GET, POST, DELETE
|
||||
* Returns: { object }
|
||||
|
||||
To join a network, POST to it. Since networks have no mandatory writable parameters, POST data is optional and may be omitted. Example: POST to /network/8056c2e21c000001 to join the public "Earth" network. To leave a network, DELETE it e.g. DELETE /network/8056c2e21c000001.
|
||||
|
||||
Most network settings are not writable, as they are defined by the network controller.
|
||||
|
||||
| Field | Type | Description | Writable |
|
||||
| --------------------- | ------------- | ------------------------------------------------- | -------- |
|
||||
| id | string | 16-digit hex network ID | no |
|
||||
| nwid | string | 16-digit hex network ID (legacy field) | no |
|
||||
| mac | string | MAC address of network device for this network | no |
|
||||
| name | string | Short name of this network (from controller) | no |
|
||||
| status | string | Network status (OK, ACCESS_DENIED, etc.) | no |
|
||||
| type | string | Network type (PUBLIC or PRIVATE) | no |
|
||||
| mtu | integer | Ethernet MTU | no |
|
||||
| dhcp | boolean | If true, DHCP should be used to get IP info | no |
|
||||
| bridge | boolean | If true, this device can bridge others | no |
|
||||
| broadcastEnabled | boolean | If true ff:ff:ff:ff:ff:ff broadcasts work | no |
|
||||
| portError | integer | Error code returned by underlying tap driver | no |
|
||||
| netconfRevision | integer | Network configuration revision ID | no |
|
||||
| assignedAddresses | [string] | Array of ZeroTier-assigned IP addresses (/bits) | no |
|
||||
| routes | [object] | Array of ZeroTier-assigned routes (see below) | no |
|
||||
| portDeviceName | string | Name of virtual network device (if any) | no |
|
||||
| allowManaged | boolean | Allow IP and route management | yes |
|
||||
| allowGlobal | boolean | Allow IPs and routes that overlap with global IPs | yes |
|
||||
| allowDefault | boolean | Allow overriding of system default route | yes |
|
||||
| allowDNS | boolean | Allow configuration of DNS on network | yes |
|
||||
|
||||
Route objects:
|
||||
|
||||
| Field | Type | Description | Writable |
|
||||
| --------------------- | ------------- | ------------------------------------------------- | -------- |
|
||||
| target | string | Target network / netmask bits | no |
|
||||
| via | string | Gateway IP address (next hop) or null for LAN | no |
|
||||
| flags | integer | Flags, currently always 0 | no |
|
||||
| metric | integer | Route metric (not currently used) | no |
|
||||
|
||||
#### /peer
|
||||
|
||||
* Purpose: Get all peers
|
||||
* Methods: GET
|
||||
* Returns: [ {object}, ... ]
|
||||
|
||||
Getting /peer returns an array of peer objects for all current peers. See below for peer object format.
|
||||
|
||||
#### /peer/\<address\>
|
||||
|
||||
* Purpose: Get or set information about a peer
|
||||
* Methods: GET, POST
|
||||
* Returns: { object }
|
||||
|
||||
| Field | Type | Description | Writable |
|
||||
| --------------------- | ------------- | ------------------------------------------------- | -------- |
|
||||
| address | string | 10-digit hex ZeroTier address of peer | no |
|
||||
| versionMajor | integer | Major version of remote (if known) | no |
|
||||
| versionMinor | integer | Minor version of remote (if known) | no |
|
||||
| versionRev | integer | Software revision of remote (if known) | no |
|
||||
| version | string | major.minor.revision | no |
|
||||
| latency | integer | Latency in milliseconds if known | no |
|
||||
| role | string | LEAF, UPSTREAM, ROOT or PLANET | no |
|
||||
| paths | [object] | Currently active physical paths (see below) | no |
|
||||
|
||||
Path objects:
|
||||
|
||||
| Field | Type | Description | Writable |
|
||||
| --------------------- | ------------- | ------------------------------------------------- | -------- |
|
||||
| address | string | Physical socket address e.g. IP/port | no |
|
||||
| lastSend | integer | Time of last send through this path | no |
|
||||
| lastReceive | integer | Time of last receive through this path | no |
|
||||
| active | boolean | Is this path in use? | no |
|
||||
| expired | boolean | Is this path expired? | no |
|
||||
| preferred | boolean | Is this a current preferred path? | no |
|
||||
| trustedPathId | integer | If nonzero this is a trusted path (unencrypted) | no |
|
||||
@@ -0,0 +1,426 @@
|
||||
/*
|
||||
* Copyright (c)2019 ZeroTier, Inc.
|
||||
*
|
||||
* Use of this software is governed by the Business Source License included
|
||||
* in the LICENSE.TXT file in the project's root directory.
|
||||
*
|
||||
* Change Date: 2026-01-01
|
||||
*
|
||||
* On the date above, in accordance with the Business Source License, use
|
||||
* of this software will be governed by version 2.0 of the Apache License.
|
||||
*/
|
||||
/****/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "../node/Constants.hpp"
|
||||
#include "../version.h"
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
#include <winsock2.h>
|
||||
#include <windows.h>
|
||||
#include <shlobj.h>
|
||||
#include <netioapi.h>
|
||||
#include <iphlpapi.h>
|
||||
#else
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <ifaddrs.h>
|
||||
#endif
|
||||
|
||||
#include "SoftwareUpdater.hpp"
|
||||
|
||||
#include "../node/Utils.hpp"
|
||||
#include "../node/SHA512.hpp"
|
||||
#include "../node/Buffer.hpp"
|
||||
#include "../node/Node.hpp"
|
||||
|
||||
#include "../osdep/OSUtils.hpp"
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
static int _compareVersion(unsigned int maj1,unsigned int min1,unsigned int rev1,unsigned int b1,unsigned int maj2,unsigned int min2,unsigned int rev2,unsigned int b2)
|
||||
{
|
||||
if (maj1 > maj2) {
|
||||
return 1;
|
||||
} else if (maj1 < maj2) {
|
||||
return -1;
|
||||
} else {
|
||||
if (min1 > min2) {
|
||||
return 1;
|
||||
} else if (min1 < min2) {
|
||||
return -1;
|
||||
} else {
|
||||
if (rev1 > rev2) {
|
||||
return 1;
|
||||
} else if (rev1 < rev2) {
|
||||
return -1;
|
||||
} else {
|
||||
if (b1 > b2) {
|
||||
return 1;
|
||||
} else if (b1 < b2) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SoftwareUpdater::SoftwareUpdater(Node &node,const std::string &homePath) :
|
||||
_node(node),
|
||||
_lastCheckTime(0),
|
||||
_homePath(homePath),
|
||||
_channel(ZT_SOFTWARE_UPDATE_DEFAULT_CHANNEL),
|
||||
_distLog((FILE *)0),
|
||||
_latestValid(false),
|
||||
_downloadLength(0)
|
||||
{
|
||||
OSUtils::rm((_homePath + ZT_PATH_SEPARATOR_S ZT_SOFTWARE_UPDATE_BIN_FILENAME).c_str());
|
||||
}
|
||||
|
||||
SoftwareUpdater::~SoftwareUpdater()
|
||||
{
|
||||
if (_distLog)
|
||||
fclose(_distLog);
|
||||
}
|
||||
|
||||
void SoftwareUpdater::setUpdateDistribution(bool distribute)
|
||||
{
|
||||
_dist.clear();
|
||||
if (distribute) {
|
||||
_distLog = fopen((_homePath + ZT_PATH_SEPARATOR_S "update-dist.log").c_str(),"a");
|
||||
|
||||
const std::string udd(_homePath + ZT_PATH_SEPARATOR_S "update-dist.d");
|
||||
const std::vector<std::string> ud(OSUtils::listDirectory(udd.c_str()));
|
||||
for(std::vector<std::string>::const_iterator u(ud.begin());u!=ud.end();++u) {
|
||||
// Each update has a companion .json file describing it. Other files are ignored.
|
||||
if ((u->length() > 5)&&(u->substr(u->length() - 5,5) == ".json")) {
|
||||
|
||||
std::string buf;
|
||||
if (OSUtils::readFile((udd + ZT_PATH_SEPARATOR_S + *u).c_str(),buf)) {
|
||||
try {
|
||||
_D d;
|
||||
d.meta = OSUtils::jsonParse(buf); // throws on invalid JSON
|
||||
|
||||
// If update meta is called e.g. foo.exe.json, then foo.exe is the update itself
|
||||
const std::string binPath(udd + ZT_PATH_SEPARATOR_S + u->substr(0,u->length() - 5));
|
||||
const std::string metaHash(OSUtils::jsonBinFromHex(d.meta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_HASH]));
|
||||
if ((metaHash.length() == 64)&&(OSUtils::readFile(binPath.c_str(),d.bin))) {
|
||||
std::array<uint8_t,64> sha512;
|
||||
SHA512(sha512.data(),d.bin.data(),(unsigned int)d.bin.length());
|
||||
if (!memcmp(sha512.data(),metaHash.data(),64)) { // double check that hash in JSON is correct
|
||||
d.meta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_SIZE] = d.bin.length(); // override with correct value -- setting this in meta json is optional
|
||||
std::array<uint8_t,16> shakey;
|
||||
memcpy(shakey.data(),sha512.data(),16);
|
||||
_dist[shakey] = d;
|
||||
if (_distLog) {
|
||||
fprintf(_distLog,".......... INIT: DISTRIBUTING %s (%u bytes)" ZT_EOL_S,binPath.c_str(),(unsigned int)d.bin.length());
|
||||
fflush(_distLog);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch ( ... ) {} // ignore bad meta JSON, etc.
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (_distLog) {
|
||||
fclose(_distLog);
|
||||
_distLog = (FILE *)0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SoftwareUpdater::handleSoftwareUpdateUserMessage(uint64_t origin,const void *data,unsigned int len)
|
||||
{
|
||||
if (!len) return;
|
||||
const MessageVerb v = (MessageVerb)reinterpret_cast<const uint8_t *>(data)[0];
|
||||
try {
|
||||
switch(v) {
|
||||
|
||||
case VERB_GET_LATEST:
|
||||
case VERB_LATEST: {
|
||||
nlohmann::json req = OSUtils::jsonParse(std::string(reinterpret_cast<const char *>(data) + 1,len - 1)); // throws on invalid JSON
|
||||
if (req.is_object()) {
|
||||
const unsigned int rvMaj = (unsigned int)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_VERSION_MAJOR],0);
|
||||
const unsigned int rvMin = (unsigned int)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_VERSION_MINOR],0);
|
||||
const unsigned int rvRev = (unsigned int)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_VERSION_REVISION],0);
|
||||
const unsigned int rvBld = (unsigned int)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_VERSION_BUILD],0);
|
||||
const unsigned int rvPlatform = (unsigned int)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_PLATFORM],0);
|
||||
const unsigned int rvArch = (unsigned int)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_ARCHITECTURE],0);
|
||||
const unsigned int rvVendor = (unsigned int)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_VENDOR],0);
|
||||
const std::string rvChannel(OSUtils::jsonString(req[ZT_SOFTWARE_UPDATE_JSON_CHANNEL],""));
|
||||
|
||||
if (v == VERB_GET_LATEST) {
|
||||
|
||||
if (!_dist.empty()) {
|
||||
const nlohmann::json *latest = (const nlohmann::json *)0;
|
||||
const std::string expectedSigner = OSUtils::jsonString(req[ZT_SOFTWARE_UPDATE_JSON_EXPECT_SIGNED_BY],"");
|
||||
unsigned int bestVMaj = rvMaj;
|
||||
unsigned int bestVMin = rvMin;
|
||||
unsigned int bestVRev = rvRev;
|
||||
unsigned int bestVBld = rvBld;
|
||||
for(std::map< std::array<uint8_t,16>,_D >::const_iterator d(_dist.begin());d!=_dist.end();++d) {
|
||||
// The arch field in update description .json files can be an array for e.g. multi-arch update files
|
||||
const nlohmann::json &dvArch2 = d->second.meta[ZT_SOFTWARE_UPDATE_JSON_ARCHITECTURE];
|
||||
std::vector<unsigned int> dvArch;
|
||||
if (dvArch2.is_array()) {
|
||||
for(unsigned long i=0;i<dvArch2.size();++i)
|
||||
dvArch.push_back((unsigned int)OSUtils::jsonInt(dvArch2[i],0));
|
||||
} else {
|
||||
dvArch.push_back((unsigned int)OSUtils::jsonInt(dvArch2,0));
|
||||
}
|
||||
|
||||
if ((OSUtils::jsonInt(d->second.meta[ZT_SOFTWARE_UPDATE_JSON_PLATFORM],0) == rvPlatform)&&
|
||||
(std::find(dvArch.begin(),dvArch.end(),rvArch) != dvArch.end())&&
|
||||
(OSUtils::jsonInt(d->second.meta[ZT_SOFTWARE_UPDATE_JSON_VENDOR],0) == rvVendor)&&
|
||||
(OSUtils::jsonString(d->second.meta[ZT_SOFTWARE_UPDATE_JSON_CHANNEL],"") == rvChannel)&&
|
||||
(OSUtils::jsonString(d->second.meta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_SIGNED_BY],"") == expectedSigner)) {
|
||||
const unsigned int dvMaj = (unsigned int)OSUtils::jsonInt(d->second.meta[ZT_SOFTWARE_UPDATE_JSON_VERSION_MAJOR],0);
|
||||
const unsigned int dvMin = (unsigned int)OSUtils::jsonInt(d->second.meta[ZT_SOFTWARE_UPDATE_JSON_VERSION_MINOR],0);
|
||||
const unsigned int dvRev = (unsigned int)OSUtils::jsonInt(d->second.meta[ZT_SOFTWARE_UPDATE_JSON_VERSION_REVISION],0);
|
||||
const unsigned int dvBld = (unsigned int)OSUtils::jsonInt(d->second.meta[ZT_SOFTWARE_UPDATE_JSON_VERSION_BUILD],0);
|
||||
if (_compareVersion(dvMaj,dvMin,dvRev,dvBld,bestVMaj,bestVMin,bestVRev,bestVBld) > 0) {
|
||||
latest = &(d->second.meta);
|
||||
bestVMaj = dvMaj;
|
||||
bestVMin = dvMin;
|
||||
bestVRev = dvRev;
|
||||
bestVBld = dvBld;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (latest) {
|
||||
std::string lj;
|
||||
lj.push_back((char)VERB_LATEST);
|
||||
lj.append(OSUtils::jsonDump(*latest));
|
||||
_node.sendUserMessage((void *)0,origin,ZT_SOFTWARE_UPDATE_USER_MESSAGE_TYPE,lj.data(),(unsigned int)lj.length());
|
||||
if (_distLog) {
|
||||
fprintf(_distLog,"%.10llx GET_LATEST %u.%u.%u_%u platform %u arch %u vendor %u channel %s -> LATEST %u.%u.%u_%u" ZT_EOL_S,(unsigned long long)origin,rvMaj,rvMin,rvRev,rvBld,rvPlatform,rvArch,rvVendor,rvChannel.c_str(),bestVMaj,bestVMin,bestVRev,bestVBld);
|
||||
fflush(_distLog);
|
||||
}
|
||||
}
|
||||
} // else no reply, since we have nothing to distribute
|
||||
|
||||
} else { // VERB_LATEST
|
||||
|
||||
if ((origin == ZT_SOFTWARE_UPDATE_SERVICE)&&
|
||||
(_compareVersion(rvMaj,rvMin,rvRev,rvBld,ZEROTIER_ONE_VERSION_MAJOR,ZEROTIER_ONE_VERSION_MINOR,ZEROTIER_ONE_VERSION_REVISION,ZEROTIER_ONE_VERSION_BUILD) > 0)&&
|
||||
(OSUtils::jsonString(req[ZT_SOFTWARE_UPDATE_JSON_UPDATE_SIGNED_BY],"") == ZT_SOFTWARE_UPDATE_SIGNING_AUTHORITY)) {
|
||||
const unsigned long len = (unsigned long)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_UPDATE_SIZE],0);
|
||||
const std::string hash = OSUtils::jsonBinFromHex(req[ZT_SOFTWARE_UPDATE_JSON_UPDATE_HASH]);
|
||||
if ((len <= ZT_SOFTWARE_UPDATE_MAX_SIZE)&&(hash.length() >= 16)) {
|
||||
if (_latestMeta != req) {
|
||||
_latestMeta = req;
|
||||
_latestValid = false;
|
||||
OSUtils::rm((_homePath + ZT_PATH_SEPARATOR_S ZT_SOFTWARE_UPDATE_BIN_FILENAME).c_str());
|
||||
_download = std::string();
|
||||
memcpy(_downloadHashPrefix.data(),hash.data(),16);
|
||||
_downloadLength = len;
|
||||
}
|
||||
|
||||
if ((_downloadLength > 0)&&(_download.length() < _downloadLength)) {
|
||||
Buffer<128> gd;
|
||||
gd.append((uint8_t)VERB_GET_DATA);
|
||||
gd.append(_downloadHashPrefix.data(),16);
|
||||
gd.append((uint32_t)_download.length());
|
||||
_node.sendUserMessage((void *)0,ZT_SOFTWARE_UPDATE_SERVICE,ZT_SOFTWARE_UPDATE_USER_MESSAGE_TYPE,gd.data(),gd.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} break;
|
||||
|
||||
case VERB_GET_DATA:
|
||||
if ((len >= 21)&&(!_dist.empty())) {
|
||||
unsigned long idx = (unsigned long)*(reinterpret_cast<const uint8_t *>(data) + 17) << 24;
|
||||
idx |= (unsigned long)*(reinterpret_cast<const uint8_t *>(data) + 18) << 16;
|
||||
idx |= (unsigned long)*(reinterpret_cast<const uint8_t *>(data) + 19) << 8;
|
||||
idx |= (unsigned long)*(reinterpret_cast<const uint8_t *>(data) + 20);
|
||||
std::array<uint8_t,16> shakey;
|
||||
memcpy(shakey.data(),reinterpret_cast<const uint8_t *>(data) + 1,16);
|
||||
std::map< std::array<uint8_t,16>,_D >::iterator d(_dist.find(shakey));
|
||||
if ((d != _dist.end())&&(idx < (unsigned long)d->second.bin.length())) {
|
||||
Buffer<ZT_SOFTWARE_UPDATE_CHUNK_SIZE + 128> buf;
|
||||
buf.append((uint8_t)VERB_DATA);
|
||||
buf.append(reinterpret_cast<const uint8_t *>(data) + 1,16);
|
||||
buf.append((uint32_t)idx);
|
||||
buf.append(d->second.bin.data() + idx,std::min((unsigned long)ZT_SOFTWARE_UPDATE_CHUNK_SIZE,(unsigned long)(d->second.bin.length() - idx)));
|
||||
_node.sendUserMessage((void *)0,origin,ZT_SOFTWARE_UPDATE_USER_MESSAGE_TYPE,buf.data(),buf.size());
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case VERB_DATA:
|
||||
if ((len >= 21)&&(_downloadLength > 0)&&(!memcmp(_downloadHashPrefix.data(),reinterpret_cast<const uint8_t *>(data) + 1,16))) {
|
||||
unsigned long idx = (unsigned long)*(reinterpret_cast<const uint8_t *>(data) + 17) << 24;
|
||||
idx |= (unsigned long)*(reinterpret_cast<const uint8_t *>(data) + 18) << 16;
|
||||
idx |= (unsigned long)*(reinterpret_cast<const uint8_t *>(data) + 19) << 8;
|
||||
idx |= (unsigned long)*(reinterpret_cast<const uint8_t *>(data) + 20);
|
||||
if (idx == (unsigned long)_download.length()) {
|
||||
_download.append(reinterpret_cast<const char *>(data) + 21,len - 21);
|
||||
if (_download.length() < _downloadLength) {
|
||||
Buffer<128> gd;
|
||||
gd.append((uint8_t)VERB_GET_DATA);
|
||||
gd.append(_downloadHashPrefix.data(),16);
|
||||
gd.append((uint32_t)_download.length());
|
||||
_node.sendUserMessage((void *)0,ZT_SOFTWARE_UPDATE_SERVICE,ZT_SOFTWARE_UPDATE_USER_MESSAGE_TYPE,gd.data(),gd.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (_distLog) {
|
||||
fprintf(_distLog,"%.10llx WARNING: bad update message verb==%u length==%u (unrecognized verb)" ZT_EOL_S,(unsigned long long)origin,(unsigned int)v,len);
|
||||
fflush(_distLog);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} catch ( ... ) {
|
||||
if (_distLog) {
|
||||
fprintf(_distLog,"%.10llx WARNING: bad update message verb==%u length==%u (unexpected exception, likely invalid JSON)" ZT_EOL_S,(unsigned long long)origin,(unsigned int)v,len);
|
||||
fflush(_distLog);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SoftwareUpdater::check(const int64_t now)
|
||||
{
|
||||
if ((now - _lastCheckTime) >= ZT_SOFTWARE_UPDATE_CHECK_PERIOD) {
|
||||
_lastCheckTime = now;
|
||||
char tmp[512];
|
||||
const unsigned int len = OSUtils::ztsnprintf(tmp,sizeof(tmp),
|
||||
"%c{\"" ZT_SOFTWARE_UPDATE_JSON_VERSION_MAJOR "\":%d,"
|
||||
"\"" ZT_SOFTWARE_UPDATE_JSON_VERSION_MINOR "\":%d,"
|
||||
"\"" ZT_SOFTWARE_UPDATE_JSON_VERSION_REVISION "\":%d,"
|
||||
"\"" ZT_SOFTWARE_UPDATE_JSON_VERSION_BUILD "\":%d,"
|
||||
"\"" ZT_SOFTWARE_UPDATE_JSON_EXPECT_SIGNED_BY "\":\"%s\","
|
||||
"\"" ZT_SOFTWARE_UPDATE_JSON_PLATFORM "\":%d,"
|
||||
"\"" ZT_SOFTWARE_UPDATE_JSON_ARCHITECTURE "\":%d,"
|
||||
"\"" ZT_SOFTWARE_UPDATE_JSON_VENDOR "\":%d,"
|
||||
"\"" ZT_SOFTWARE_UPDATE_JSON_CHANNEL "\":\"%s\"}",
|
||||
(char)VERB_GET_LATEST,
|
||||
ZEROTIER_ONE_VERSION_MAJOR,
|
||||
ZEROTIER_ONE_VERSION_MINOR,
|
||||
ZEROTIER_ONE_VERSION_REVISION,
|
||||
ZEROTIER_ONE_VERSION_BUILD,
|
||||
ZT_SOFTWARE_UPDATE_SIGNING_AUTHORITY,
|
||||
ZT_BUILD_PLATFORM,
|
||||
ZT_BUILD_ARCHITECTURE,
|
||||
(int)ZT_VENDOR_ZEROTIER,
|
||||
_channel.c_str());
|
||||
_node.sendUserMessage((void *)0,ZT_SOFTWARE_UPDATE_SERVICE,ZT_SOFTWARE_UPDATE_USER_MESSAGE_TYPE,tmp,len);
|
||||
}
|
||||
|
||||
if (_latestValid)
|
||||
return true;
|
||||
|
||||
if (_downloadLength > 0) {
|
||||
if (_download.length() >= _downloadLength) {
|
||||
// This is the very important security validation part that makes sure
|
||||
// this software update doesn't have cooties.
|
||||
|
||||
const std::string binPath(_homePath + ZT_PATH_SEPARATOR_S ZT_SOFTWARE_UPDATE_BIN_FILENAME);
|
||||
try {
|
||||
// (1) Check the hash itself to make sure the image is basically okay
|
||||
uint8_t sha512[64];
|
||||
SHA512(sha512,_download.data(),(unsigned int)_download.length());
|
||||
char hexbuf[(64 * 2) + 2];
|
||||
if (OSUtils::jsonString(_latestMeta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_HASH],"") == Utils::hex(sha512,64,hexbuf)) {
|
||||
// (2) Check signature by signing authority
|
||||
const std::string sig(OSUtils::jsonBinFromHex(_latestMeta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_SIGNATURE]));
|
||||
if (Identity(ZT_SOFTWARE_UPDATE_SIGNING_AUTHORITY).verify(_download.data(),(unsigned int)_download.length(),sig.data(),(unsigned int)sig.length())) {
|
||||
// (3) Try to save file, and if so we are good.
|
||||
OSUtils::rm(binPath.c_str());
|
||||
if (OSUtils::writeFile(binPath.c_str(),_download)) {
|
||||
OSUtils::lockDownFile(binPath.c_str(),false);
|
||||
_latestValid = true;
|
||||
_download = std::string();
|
||||
_downloadLength = 0;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch ( ... ) {} // any exception equals verification failure
|
||||
|
||||
// If we get here, checks failed.
|
||||
OSUtils::rm(binPath.c_str());
|
||||
_latestMeta = nlohmann::json();
|
||||
_latestValid = false;
|
||||
_download = std::string();
|
||||
_downloadLength = 0;
|
||||
} else {
|
||||
Buffer<128> gd;
|
||||
gd.append((uint8_t)VERB_GET_DATA);
|
||||
gd.append(_downloadHashPrefix.data(),16);
|
||||
gd.append((uint32_t)_download.length());
|
||||
_node.sendUserMessage((void *)0,ZT_SOFTWARE_UPDATE_SERVICE,ZT_SOFTWARE_UPDATE_USER_MESSAGE_TYPE,gd.data(),gd.size());
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SoftwareUpdater::apply()
|
||||
{
|
||||
std::string updatePath(_homePath + ZT_PATH_SEPARATOR_S ZT_SOFTWARE_UPDATE_BIN_FILENAME);
|
||||
if ((_latestMeta.is_object())&&(_latestValid)&&(OSUtils::fileExists(updatePath.c_str(),false))) {
|
||||
#ifdef __WINDOWS__
|
||||
std::string cmdArgs(OSUtils::jsonString(_latestMeta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_EXEC_ARGS],""));
|
||||
if (cmdArgs.length() > 0) {
|
||||
updatePath.push_back(' ');
|
||||
updatePath.append(cmdArgs);
|
||||
}
|
||||
STARTUPINFOA si;
|
||||
PROCESS_INFORMATION pi;
|
||||
memset(&si,0,sizeof(si));
|
||||
memset(&pi,0,sizeof(pi));
|
||||
CreateProcessA(NULL,const_cast<LPSTR>(updatePath.c_str()),NULL,NULL,FALSE,CREATE_NO_WINDOW|CREATE_NEW_PROCESS_GROUP,NULL,NULL,&si,&pi);
|
||||
// Windows doesn't exit here -- updater will stop the service during update, etc. -- but we do want to stop multiple runs from happening
|
||||
_latestMeta = nlohmann::json();
|
||||
_latestValid = false;
|
||||
#else
|
||||
char *argv[256];
|
||||
unsigned long ac = 0;
|
||||
argv[ac++] = const_cast<char *>(updatePath.c_str());
|
||||
const std::vector<std::string> argsSplit(OSUtils::split(OSUtils::jsonString(_latestMeta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_EXEC_ARGS],"").c_str()," ","\\","\""));
|
||||
for(std::vector<std::string>::const_iterator a(argsSplit.begin());a!=argsSplit.end();++a) {
|
||||
argv[ac] = const_cast<char *>(a->c_str());
|
||||
if (++ac == 255) break;
|
||||
}
|
||||
argv[ac] = (char *)0;
|
||||
chmod(updatePath.c_str(),0700);
|
||||
|
||||
// Close all open file descriptors except stdout/stderr/etc.
|
||||
int minMyFd = STDIN_FILENO;
|
||||
if (STDOUT_FILENO > minMyFd) minMyFd = STDOUT_FILENO;
|
||||
if (STDERR_FILENO > minMyFd) minMyFd = STDERR_FILENO;
|
||||
++minMyFd;
|
||||
#ifdef _SC_OPEN_MAX
|
||||
int maxMyFd = (int)sysconf(_SC_OPEN_MAX);
|
||||
if (maxMyFd <= minMyFd)
|
||||
maxMyFd = 65536;
|
||||
#else
|
||||
int maxMyFd = 65536;
|
||||
#endif
|
||||
while (minMyFd < maxMyFd)
|
||||
close(minMyFd++);
|
||||
|
||||
execv(updatePath.c_str(),argv);
|
||||
fprintf(stderr,"FATAL: unable to execute software update binary at %s\n",updatePath.c_str());
|
||||
exit(1);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ZeroTier
|
||||
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* Copyright (c)2019 ZeroTier, Inc.
|
||||
*
|
||||
* Use of this software is governed by the Business Source License included
|
||||
* in the LICENSE.TXT file in the project's root directory.
|
||||
*
|
||||
* Change Date: 2026-01-01
|
||||
*
|
||||
* On the date above, in accordance with the Business Source License, use
|
||||
* of this software will be governed by version 2.0 of the Apache License.
|
||||
*/
|
||||
/****/
|
||||
|
||||
#ifndef ZT_SOFTWAREUPDATER_HPP
|
||||
#define ZT_SOFTWAREUPDATER_HPP
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <array>
|
||||
|
||||
#include "../include/ZeroTierOne.h"
|
||||
|
||||
#include "../node/Identity.hpp"
|
||||
#include "../node/Packet.hpp"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
/**
|
||||
* VERB_USER_MESSAGE type ID for software update messages
|
||||
*/
|
||||
#define ZT_SOFTWARE_UPDATE_USER_MESSAGE_TYPE 100
|
||||
|
||||
/**
|
||||
* ZeroTier address of node that provides software updates
|
||||
*/
|
||||
#define ZT_SOFTWARE_UPDATE_SERVICE 0xb1d366e81fULL
|
||||
|
||||
/**
|
||||
* ZeroTier identity that must be used to sign software updates
|
||||
*
|
||||
* df24360f3e - update-signing-key-0010 generated Fri Jan 13th, 2017 at 4:05pm PST
|
||||
*/
|
||||
#define ZT_SOFTWARE_UPDATE_SIGNING_AUTHORITY "df24360f3e:0:06072642959c8dfb68312904d74d90197c8a7692697caa1b3fd769eca714f4370fab462fcee6ebcb5fffb63bc5af81f28a2514b2cd68daabb42f7352c06f21db"
|
||||
|
||||
/**
|
||||
* Chunk size for in-band downloads (can be changed, designed to always fit in one UDP packet easily)
|
||||
*/
|
||||
#define ZT_SOFTWARE_UPDATE_CHUNK_SIZE (ZT_PROTO_MAX_PACKET_LENGTH - 128)
|
||||
|
||||
/**
|
||||
* Sanity limit for the size of an update binary image
|
||||
*/
|
||||
#define ZT_SOFTWARE_UPDATE_MAX_SIZE (1024 * 1024 * 256)
|
||||
|
||||
/**
|
||||
* How often (ms) do we check?
|
||||
*/
|
||||
#define ZT_SOFTWARE_UPDATE_CHECK_PERIOD (60 * 10 * 1000)
|
||||
|
||||
/**
|
||||
* Default update channel
|
||||
*/
|
||||
#define ZT_SOFTWARE_UPDATE_DEFAULT_CHANNEL "release"
|
||||
|
||||
/**
|
||||
* Filename for latest update's binary image
|
||||
*/
|
||||
#define ZT_SOFTWARE_UPDATE_BIN_FILENAME "latest-update.exe"
|
||||
|
||||
#define ZT_SOFTWARE_UPDATE_JSON_VERSION_MAJOR "vMajor"
|
||||
#define ZT_SOFTWARE_UPDATE_JSON_VERSION_MINOR "vMinor"
|
||||
#define ZT_SOFTWARE_UPDATE_JSON_VERSION_REVISION "vRev"
|
||||
#define ZT_SOFTWARE_UPDATE_JSON_VERSION_BUILD "vBuild"
|
||||
#define ZT_SOFTWARE_UPDATE_JSON_PLATFORM "platform"
|
||||
#define ZT_SOFTWARE_UPDATE_JSON_ARCHITECTURE "arch"
|
||||
#define ZT_SOFTWARE_UPDATE_JSON_VENDOR "vendor"
|
||||
#define ZT_SOFTWARE_UPDATE_JSON_CHANNEL "channel"
|
||||
#define ZT_SOFTWARE_UPDATE_JSON_EXPECT_SIGNED_BY "expectedSigner"
|
||||
#define ZT_SOFTWARE_UPDATE_JSON_UPDATE_SIGNED_BY "signer"
|
||||
#define ZT_SOFTWARE_UPDATE_JSON_UPDATE_SIGNATURE "signature"
|
||||
#define ZT_SOFTWARE_UPDATE_JSON_UPDATE_HASH "hash"
|
||||
#define ZT_SOFTWARE_UPDATE_JSON_UPDATE_SIZE "size"
|
||||
#define ZT_SOFTWARE_UPDATE_JSON_UPDATE_EXEC_ARGS "execArgs"
|
||||
#define ZT_SOFTWARE_UPDATE_JSON_UPDATE_URL "url"
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
class Node;
|
||||
|
||||
/**
|
||||
* This class handles retrieving and executing updates, or serving them
|
||||
*/
|
||||
class SoftwareUpdater
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Each message begins with an 8-bit message verb
|
||||
*/
|
||||
enum MessageVerb
|
||||
{
|
||||
/**
|
||||
* Payload: JSON containing current system platform, version, etc.
|
||||
*/
|
||||
VERB_GET_LATEST = 1,
|
||||
|
||||
/**
|
||||
* Payload: JSON describing latest update for this target. (No response is sent if there is none.)
|
||||
*/
|
||||
VERB_LATEST = 2,
|
||||
|
||||
/**
|
||||
* Payload:
|
||||
* <[16] first 128 bits of hash of data object>
|
||||
* <[4] 32-bit index of chunk to get>
|
||||
*/
|
||||
VERB_GET_DATA = 3,
|
||||
|
||||
/**
|
||||
* Payload:
|
||||
* <[16] first 128 bits of hash of data object>
|
||||
* <[4] 32-bit index of chunk>
|
||||
* <[...] chunk data>
|
||||
*/
|
||||
VERB_DATA = 4
|
||||
};
|
||||
|
||||
SoftwareUpdater(Node &node,const std::string &homePath);
|
||||
~SoftwareUpdater();
|
||||
|
||||
/**
|
||||
* Set whether or not we will distribute updates
|
||||
*
|
||||
* @param distribute If true, scan update-dist.d now and distribute updates found there -- if false, clear and stop distributing
|
||||
*/
|
||||
void setUpdateDistribution(bool distribute);
|
||||
|
||||
/**
|
||||
* Handle a software update user message
|
||||
*
|
||||
* @param origin ZeroTier address of message origin
|
||||
* @param data Message payload
|
||||
* @param len Length of message
|
||||
*/
|
||||
void handleSoftwareUpdateUserMessage(uint64_t origin,const void *data,unsigned int len);
|
||||
|
||||
/**
|
||||
* Check for updates and do other update-related housekeeping
|
||||
*
|
||||
* It should be called about every 10 seconds.
|
||||
*
|
||||
* @return True if we've downloaded and verified an update
|
||||
*/
|
||||
bool check(const int64_t now);
|
||||
|
||||
/**
|
||||
* @return Meta-data for downloaded update or NULL if none
|
||||
*/
|
||||
inline const nlohmann::json &pending() const { return _latestMeta; }
|
||||
|
||||
/**
|
||||
* Apply any ready update now
|
||||
*
|
||||
* Depending on the platform this function may never return and may forcibly
|
||||
* exit the process. It does nothing if no update is ready.
|
||||
*/
|
||||
void apply();
|
||||
|
||||
/**
|
||||
* Set software update channel
|
||||
*
|
||||
* @param channel 'release', 'beta', etc.
|
||||
*/
|
||||
inline void setChannel(const std::string &channel) { _channel = channel; }
|
||||
|
||||
private:
|
||||
Node &_node;
|
||||
uint64_t _lastCheckTime;
|
||||
std::string _homePath;
|
||||
std::string _channel;
|
||||
FILE *_distLog;
|
||||
|
||||
// Offered software updates if we are an update host (we have update-dist.d and update hosting is enabled)
|
||||
struct _D
|
||||
{
|
||||
nlohmann::json meta;
|
||||
std::string bin;
|
||||
};
|
||||
std::map< std::array<uint8_t,16>,_D > _dist; // key is first 16 bytes of hash
|
||||
|
||||
nlohmann::json _latestMeta;
|
||||
bool _latestValid;
|
||||
|
||||
std::string _download;
|
||||
std::array<uint8_t,16> _downloadHashPrefix;
|
||||
unsigned long _downloadLength;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user