Compare commits

...

34 Commits

Author SHA1 Message Date
ee73ac1a74 update makefile for multi-arch controller-v2 docker build
Some checks failed
/ build_macos (push) Has been cancelled
/ build_windows (push) Has been cancelled
/ build_ubuntu (push) Has been cancelled
2025-06-20 14:49:40 -07:00
169250d484 update makefile for multi-arch controller-v2 docker build 2025-06-20 14:48:18 -07:00
4fea4fc76f debug output 2025-06-20 14:39:12 -07:00
6fa849c956 dump member save output only on error
Some checks failed
/ build_macos (push) Has been cancelled
/ build_windows (push) Has been cancelled
/ build_ubuntu (push) Has been cancelled
2025-05-21 15:52:16 -07:00
3cef1b0842 error output on exceptions in commit thread
Some checks failed
/ build_macos (push) Has been cancelled
/ build_windows (push) Has been cancelled
/ build_ubuntu (push) Has been cancelled
2025-05-21 08:40:11 -07:00
08b1ceb9a1 "relayed" as ip addr for unknown IP in last_seen 2025-05-21 07:24:35 -07:00
af0a9118c1 another query update
Some checks failed
/ build_macos (push) Has been cancelled
/ build_windows (push) Has been cancelled
/ build_ubuntu (push) Has been cancelled
2025-05-19 11:07:18 -07:00
568da750bb another query fix 2025-05-19 10:50:37 -07:00
d5f6f6b98e fix sql statement 2025-05-19 10:37:52 -07:00
10ac0c2e1a remove last_authorized_credential field.
Some checks failed
/ build_macos (push) Has been cancelled
/ build_windows (push) Has been cancelled
/ build_ubuntu (push) Has been cancelled
Think that came from when we were trying to have the controller use the same table as CV2.  It's not used or needed by the controller itself
2025-05-19 09:52:27 -07:00
1ffadf31de Get node OS/Arch info into the CV2 db
Some checks failed
/ build_macos (push) Has been cancelled
/ build_windows (push) Has been cancelled
/ build_ubuntu (push) Has been cancelled
2025-05-14 15:06:24 -07:00
224ee88b91 cleanup some debug statements
Some checks failed
/ build_macos (push) Has been cancelled
/ build_windows (push) Has been cancelled
/ build_ubuntu (push) Has been cancelled
2025-05-13 13:36:39 -07:00
b42067e654 account for SSL db connection 2025-05-13 13:31:49 -07:00
c9e08c951a add postgres tag to go install migrate 2025-05-13 13:27:25 -07:00
42f760d72f file:// url 2025-05-13 13:21:57 -07:00
c68acebe31 Add db migrations to CV2 db 2025-05-13 13:11:27 -07:00
4b7c5159a3 "" instead of nullptr
Some checks failed
/ build_macos (push) Has been cancelled
/ build_windows (push) Has been cancelled
/ build_ubuntu (push) Has been cancelled
2025-05-08 12:24:57 -07:00
4a8daf43e4 more query fixes 2025-05-08 10:33:15 -07:00
182148a26c query fix 2025-05-08 10:10:20 -07:00
af715ca0ff query & null fix 2025-05-08 09:22:28 -07:00
990ecb5eb1 another query fix
Some checks failed
/ build_macos (push) Has been cancelled
/ build_windows (push) Has been cancelled
/ build_ubuntu (push) Has been cancelled
2025-05-07 12:12:32 -07:00
7c88b3f124 fix a couple of queries 2025-05-07 11:57:28 -07:00
af3f8b00a4 fix cv2 connection
Some checks failed
/ build_macos (push) Has been cancelled
/ build_windows (push) Has been cancelled
/ build_ubuntu (push) Has been cancelled
2025-05-01 15:59:07 -07:00
db06c98608 startup script 2025-05-01 14:17:49 -07:00
332a7d1488 more fun with the startup script 2025-05-01 14:12:30 -07:00
48f9f7de14 Allow setting cv2: for the db url 2025-05-01 13:58:54 -07:00
35d37559c8 v2controller docker target on mac 2025-05-01 12:57:54 -07:00
1a6c0a5f12 reflect schema changes 2025-05-01 12:26:43 -07:00
2c8f032a76 CV2 controller. Should have everything but SSO now
Some checks failed
/ build_macos (push) Has been cancelled
/ build_windows (push) Has been cancelled
/ build_ubuntu (push) Has been cancelled
2025-04-10 13:32:52 -07:00
fb7b07fc42 make sure the pg connstring is correct for cv2:// instances 2025-04-03 14:48:13 -07:00
1d2130610c use the CV2 db instance if the path prefix is cv2:// 2025-04-03 14:38:58 -07:00
0b04f772ef make things compile 2025-04-03 14:26:09 -07:00
5c73fe9304 build fix 2025-04-03 10:45:06 -07:00
2af105000f WIP: refactoring for CV2 db integration 2025-04-03 10:27:42 -07:00
27 changed files with 3412 additions and 2059 deletions

1851
controller/CV1.cpp Normal file

File diff suppressed because it is too large Load Diff

144
controller/CV1.hpp Normal file
View File

@ -0,0 +1,144 @@
/*
* 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 "DB.hpp"
#ifdef ZT_CONTROLLER_USE_LIBPQ
#ifndef ZT_CONTROLLER_CV1_HPP
#define ZT_CONTROLLER_CV1_HPP
#define ZT_CENTRAL_CONTROLLER_COMMIT_THREADS 4
#include "ConnectionPool.hpp"
#include <pqxx/pqxx>
#include <memory>
#include <redis++/redis++.h>
#include "../node/Metrics.hpp"
#include "PostgreSQL.hpp"
namespace smeeclient {
struct SmeeClient;
}
namespace ZeroTier {
struct RedisConfig;
/**
* 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 that we might change it at any time.
*/
class CV1 : public DB
{
public:
CV1(const Identity &myId, const char *path, int listenPort, RedisConfig *rc);
virtual ~CV1();
virtual bool waitForReady();
virtual bool isReady();
virtual bool save(nlohmann::json &record,bool notifyListeners);
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);
virtual void nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress, const char *osArch);
virtual AuthInfo getSSOAuthInfo(const nlohmann::json &member, const std::string &redirectURL);
virtual bool ready() {
return _ready == 2;
}
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); }
};
virtual void _memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool notifyListeners) {
DB::_memberChanged(old, memberConfig, notifyListeners);
}
virtual void _networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool notifyListeners) {
DB::_networkChanged(old, networkConfig, notifyListeners);
}
private:
void initializeNetworks();
void initializeMembers();
void heartbeat();
void membersDbWatcher();
void _membersWatcher_Postgres();
void networksDbWatcher();
void _networksWatcher_Postgres();
void _membersWatcher_Redis();
void _networksWatcher_Redis();
void commitThread();
void onlineNotificationThread();
void onlineNotification_Postgres();
void onlineNotification_Redis();
uint64_t _doRedisUpdate(sw::redis::Transaction &tx, std::string &controllerId,
std::unordered_map< std::pair<uint64_t,uint64_t>,NodeOnlineRecord,_PairHasher > &lastOnline);
void configureSmee();
void notifyNewMember(const std::string &networkID, const std::string &memberID);
enum OverrideMode {
ALLOW_PGBOUNCER_OVERRIDE = 0,
NO_OVERRIDE = 1
};
std::shared_ptr<ConnectionPool<PostgresConnection> > _pool;
const Identity _myId;
const Address _myAddress;
std::string _myAddressStr;
std::string _connString;
BlockingQueue< std::pair<nlohmann::json,bool> > _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>,NodeOnlineRecord,_PairHasher > _lastOnline;
mutable std::mutex _lastOnline_l;
mutable std::mutex _readyLock;
std::atomic<int> _ready, _connected, _run;
mutable volatile bool _waitNoticePrinted;
int _listenPort;
uint8_t _ssoPsk[48];
RedisConfig *_rc;
std::shared_ptr<sw::redis::Redis> _redis;
std::shared_ptr<sw::redis::RedisCluster> _cluster;
bool _redisMemberStatus;
smeeclient::SmeeClient *_smee;
};
} // namespace ZeroTier
#endif // ZT_CONTROLLER_CV1_HPP
#endif // ZT_CONTROLLER_USE_LIBPQ

1047
controller/CV2.cpp Normal file

File diff suppressed because it is too large Load Diff

113
controller/CV2.hpp Normal file
View File

@ -0,0 +1,113 @@
/*
* Copyright (c)2025 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 "DB.hpp"
#ifdef ZT_CONTROLLER_USE_LIBPQ
#ifndef ZT_CONTROLLER_CV2_HPP
#define ZT_CONTROLLER_CV2_HPP
#define ZT_CENTRAL_CONTROLLER_COMMIT_THREADS 4
#include "ConnectionPool.hpp"
#include <pqxx/pqxx>
#include <memory>
#include <redis++/redis++.h>
#include "../node/Metrics.hpp"
#include "PostgreSQL.hpp"
namespace ZeroTier {
class CV2 : public DB
{
public:
CV2(const Identity &myId, const char *path, int listenPort);
virtual ~CV2();
virtual bool waitForReady();
virtual bool isReady();
virtual bool save(nlohmann::json &record,bool notifyListeners);
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);
virtual void nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress, const char *osArch);
virtual AuthInfo getSSOAuthInfo(const nlohmann::json &member, const std::string &redirectURL);
virtual bool ready() {
return _ready == 2;
}
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); }
};
virtual void _memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool notifyListeners) {
DB::_memberChanged(old, memberConfig, notifyListeners);
}
virtual void _networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool notifyListeners) {
DB::_networkChanged(old, networkConfig, notifyListeners);
}
private:
void initializeNetworks();
void initializeMembers();
void heartbeat();
void membersDbWatcher();
void networksDbWatcher();
void commitThread();
void onlineNotificationThread();
// void notifyNewMember(const std::string &networkID, const std::string &memberID);
enum OverrideMode {
ALLOW_PGBOUNCER_OVERRIDE = 0,
NO_OVERRIDE = 1
};
std::shared_ptr<ConnectionPool<PostgresConnection> > _pool;
const Identity _myId;
const Address _myAddress;
std::string _myAddressStr;
std::string _connString;
BlockingQueue< std::pair<nlohmann::json,bool> > _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>,NodeOnlineRecord,_PairHasher > _lastOnline;
mutable std::mutex _lastOnline_l;
mutable std::mutex _readyLock;
std::atomic<int> _ready, _connected, _run;
mutable volatile bool _waitNoticePrinted;
int _listenPort;
uint8_t _ssoPsk[48];
};
} // namespace Zerotier
#endif // ZT_CONTROLLER_CV2_HPP
#endif // ZT_CONTROLLER_USE_LIBPQ

62
controller/CtlUtil.cpp Normal file
View File

@ -0,0 +1,62 @@
#include "CtlUtil.hpp"
#ifdef ZT_CONTROLLER_USE_LIBPQ
#include <sstream>
#include <iomanip>
namespace ZeroTier {
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;
}
std::vector<std::string> split(std::string str, char delim){
std::istringstream iss(str);
std::vector<std::string> tokens;
std::string item;
while(std::getline(iss, item, delim)) {
tokens.push_back(item);
}
return tokens;
}
std::string url_encode(const std::string &value) {
std::ostringstream escaped;
escaped.fill('0');
escaped << std::hex;
for (std::string::const_iterator i = value.begin(), n = value.end(); i != n; ++i) {
std::string::value_type c = (*i);
// Keep alphanumeric and other accepted characters intact
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
escaped << c;
continue;
}
// Any other characters are percent-encoded
escaped << std::uppercase;
escaped << '%' << std::setw(2) << int((unsigned char) c);
escaped << std::nouppercase;
}
return escaped.str();
}
} // namespace ZeroTier
#endif

16
controller/CtlUtil.hpp Normal file
View File

@ -0,0 +1,16 @@
#ifndef ZT_CTLUTIL_HPP
#define ZT_CTLUTIL_HPP
#include <vector>
#include <string>
namespace ZeroTier {
const char *_timestr();
std::vector<std::string> split(std::string str, char delim);
std::string url_encode(const std::string &value);
}
#endif // namespace ZeroTier

View File

@ -75,6 +75,10 @@ public:
*/
class DB
{
#ifdef ZT_CONTROLLER_USE_LIBPQ
friend class MemberNotificationReceiver;
friend class NetworkNotificationReceiver;
#endif
public:
class ChangeListener
{
@ -137,6 +141,7 @@ public:
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;
virtual void nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress, const char *osArch) = 0;
virtual AuthInfo getSSOAuthInfo(const nlohmann::json &member, const std::string &redirectURL) { return AuthInfo(); }

View File

@ -206,14 +206,18 @@ void DBMirrorSet::eraseMember(const uint64_t networkId,const uint64_t memberId)
}
}
void DBMirrorSet::nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress)
{
void DBMirrorSet::nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress, const char *osArch) {
std::shared_lock<std::shared_mutex> l(_dbs_l);
for(auto d=_dbs.begin();d!=_dbs.end();++d) {
(*d)->nodeIsOnline(networkId,memberId,physicalAddress);
}
}
void DBMirrorSet::nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress)
{
this->nodeIsOnline(networkId,memberId,physicalAddress,"unknown/unknown");
}
void DBMirrorSet::onNetworkUpdate(const void *db,uint64_t networkId,const nlohmann::json &network)
{
nlohmann::json record(network);

View File

@ -44,7 +44,8 @@ public:
bool save(nlohmann::json &record,bool notifyListeners);
void eraseNetwork(const uint64_t networkId);
void eraseMember(const uint64_t networkId,const uint64_t memberId);
void nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress);
virtual void nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress);
virtual void nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress, const char *osArch);
// These are called by various DB instances when changes occur.
virtual void onNetworkUpdate(const void *db,uint64_t networkId,const nlohmann::json &network);

View File

@ -40,7 +40,8 @@
#include "LFDB.hpp"
#include "FileDB.hpp"
#ifdef ZT_CONTROLLER_USE_LIBPQ
#include "PostgreSQL.hpp"
#include "CV1.hpp"
#include "CV2.hpp"
#endif
#include "../node/Node.hpp"
@ -534,8 +535,13 @@ void EmbeddedNetworkController::init(const Identity &signingId,Sender *sender)
#ifdef ZT_CONTROLLER_USE_LIBPQ
if ((_path.length() > 9)&&(_path.substr(0,9) == "postgres:")) {
_db.addDB(std::shared_ptr<DB>(new PostgreSQL(_signingId,_path.substr(9).c_str(), _listenPort, _rc)));
fprintf(stderr, "CV1\n");
_db.addDB(std::shared_ptr<DB>(new CV1(_signingId,_path.substr(9).c_str(), _listenPort, _rc)));
} else if ((_path.length() > 4)&&(_path.substr(0,4) == "cv2:")) {
fprintf(stderr, "CV2\n");
_db.addDB(std::shared_ptr<DB>(new CV2(_signingId,_path.substr(4).c_str(),_listenPort)));
} else {
fprintf(stderr, "FileDB\n");
#endif
_db.addDB(std::shared_ptr<DB>(new FileDB(_path.c_str())));
#ifdef ZT_CONTROLLER_USE_LIBPQ

View File

@ -155,8 +155,7 @@ void FileDB::eraseMember(const uint64_t networkId,const uint64_t memberId)
this->_online[networkId].erase(memberId);
}
void FileDB::nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress)
{
void FileDB::nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress, const char *osArch) {
char mid[32],atmp[64];
OSUtils::ztsnprintf(mid,sizeof(mid),"%.10llx",(unsigned long long)memberId);
physicalAddress.toString(atmp);
@ -164,4 +163,9 @@ void FileDB::nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const
this->_online[networkId][memberId][OSUtils::now()] = physicalAddress;
}
void FileDB::nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress)
{
this->nodeIsOnline(networkId,memberId,physicalAddress,"unknown/unknown");
}
} // namespace ZeroTier

View File

@ -31,7 +31,8 @@ public:
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);
virtual void nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress, const char *osArch);
protected:
std::string _path;
std::string _networksPath;

View File

@ -388,7 +388,7 @@ 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)
void LFDB::nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress,const char *osArch)
{
std::lock_guard<std::mutex> l(_state_l);
auto nw = _state.find(networkId);
@ -403,4 +403,9 @@ void LFDB::nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const I
}
}
void LFDB::nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress)
{
this->nodeIsOnline(networkId,memberId,physicalAddress,"unknown/unknown");
}
} // namespace ZeroTier

View File

@ -47,7 +47,8 @@ public:
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);
virtual void nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress, const char *osArch);
protected:
const Identity _myId;

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
/*
* Copyright (c)2019 ZeroTier, Inc.
* Copyright (c)2025 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.
@ -11,35 +11,22 @@
*/
/****/
#include "DB.hpp"
#ifdef ZT_CONTROLLER_USE_LIBPQ
#ifndef ZT_CONTROLLER_LIBPQ_HPP
#define ZT_CONTROLLER_LIBPQ_HPP
#define ZT_CENTRAL_CONTROLLER_COMMIT_THREADS 4
#ifndef ZT_CONTROLLER_POSTGRESQL_HPP
#define ZT_CONTROLLER_POSTGRESQL_HPP
#include "DB.hpp"
#include "ConnectionPool.hpp"
#include <pqxx/pqxx>
#include <memory>
#include <redis++/redis++.h>
#include "../node/Metrics.hpp"
namespace ZeroTier {
extern "C" {
typedef struct pg_conn PGconn;
}
namespace smeeclient {
struct SmeeClient;
}
namespace ZeroTier {
struct RedisConfig;
class PostgresConnection : public Connection {
public:
@ -68,129 +55,38 @@ private:
std::string m_connString;
};
class PostgreSQL;
class MemberNotificationReceiver : public pqxx::notification_receiver {
public:
MemberNotificationReceiver(PostgreSQL *p, pqxx::connection &c, const std::string &channel);
MemberNotificationReceiver(DB *p, pqxx::connection &c, const std::string &channel);
virtual ~MemberNotificationReceiver() {
fprintf(stderr, "MemberNotificationReceiver destroyed\n");
}
virtual void operator() (const std::string &payload, int backendPid);
private:
PostgreSQL *_psql;
DB *_psql;
};
class NetworkNotificationReceiver : public pqxx::notification_receiver {
public:
NetworkNotificationReceiver(PostgreSQL *p, pqxx::connection &c, const std::string &channel);
NetworkNotificationReceiver(DB *p, pqxx::connection &c, const std::string &channel);
virtual ~NetworkNotificationReceiver() {
fprintf(stderr, "NetworkNotificationReceiver destroyed\n");
};
virtual void operator() (const std::string &payload, int packend_pid);
private:
PostgreSQL *_psql;
DB *_psql;
};
/**
* 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 that we might change it at any time.
*/
class PostgreSQL : public DB
{
friend class MemberNotificationReceiver;
friend class NetworkNotificationReceiver;
public:
PostgreSQL(const Identity &myId, const char *path, int listenPort, RedisConfig *rc);
virtual ~PostgreSQL();
virtual bool waitForReady();
virtual bool isReady();
virtual bool save(nlohmann::json &record,bool notifyListeners);
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);
virtual AuthInfo getSSOAuthInfo(const nlohmann::json &member, const std::string &redirectURL);
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); }
};
virtual void _memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool notifyListeners) {
DB::_memberChanged(old, memberConfig, notifyListeners);
}
virtual void _networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool notifyListeners) {
DB::_networkChanged(old, networkConfig, notifyListeners);
}
private:
void initializeNetworks();
void initializeMembers();
void heartbeat();
void membersDbWatcher();
void _membersWatcher_Postgres();
void networksDbWatcher();
void _networksWatcher_Postgres();
void _membersWatcher_Redis();
void _networksWatcher_Redis();
void commitThread();
void onlineNotificationThread();
void onlineNotification_Postgres();
void onlineNotification_Redis();
uint64_t _doRedisUpdate(sw::redis::Transaction &tx, std::string &controllerId,
std::unordered_map< std::pair<uint64_t,uint64_t>,std::pair<int64_t,InetAddress>,_PairHasher > &lastOnline);
void configureSmee();
void notifyNewMember(const std::string &networkID, const std::string &memberID);
enum OverrideMode {
ALLOW_PGBOUNCER_OVERRIDE = 0,
NO_OVERRIDE = 1
};
std::shared_ptr<ConnectionPool<PostgresConnection> > _pool;
const Identity _myId;
const Address _myAddress;
std::string _myAddressStr;
std::string _connString;
BlockingQueue< std::pair<nlohmann::json,bool> > _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;
uint8_t _ssoPsk[48];
RedisConfig *_rc;
std::shared_ptr<sw::redis::Redis> _redis;
std::shared_ptr<sw::redis::RedisCluster> _cluster;
bool _redisMemberStatus;
smeeclient::SmeeClient *_smee;
struct NodeOnlineRecord {
uint64_t lastSeen;
InetAddress physicalAddress;
std::string osArch;
};
} // namespace ZeroTier
#endif // ZT_CONTROLLER_LIBPQ_HPP
#endif // ZT_CONTROLLER_POSTGRESQL_HPP
#endif // ZT_CONTROLLER_USE_LIBPQ
#endif // ZT_CONTROLLER_USE_LIBPQ

View File

@ -1,11 +1,16 @@
# Dockerfile for ZeroTier Central Controllers
FROM registry.zerotier.com/zerotier/ctlbuild:latest as builder
MAINTAINER Adam Ierymekno <adam.ierymenko@zerotier.com>, Grant Limberg <grant.limberg@zerotier.com>
FROM registry.zerotier.com/zerotier/ctlbuild:2025-05-13-01 AS builder
ADD . /ZeroTierOne
RUN export PATH=$PATH:~/.cargo/bin && cd ZeroTierOne && make clean && make central-controller -j8
FROM registry.zerotier.com/zerotier/ctlrun:latest
FROM golang:bookworm AS go_base
RUN go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
FROM registry.zerotier.com/zerotier/ctlrun:2025-05-13-01
COPY --from=builder /ZeroTierOne/zerotier-one /usr/local/bin/zerotier-one
COPY --from=go_base /go/bin/migrate /usr/local/bin/migrate
COPY ext/central-controller-docker/migrations /migrations
RUN chmod a+x /usr/local/bin/zerotier-one
RUN echo "/usr/local/lib64" > /etc/ld.so.conf.d/usr-local-lib64.conf && ldconfig

View File

@ -1,8 +1,5 @@
# Dockerfile for building ZeroTier Central Controllers
FROM ubuntu:jammy as builder
MAINTAINER Adam Ierymekno <adam.ierymenko@zerotier.com>, Grant Limberg <grant.limberg@zerotier.com>
ARG git_branch=master
FROM debian:bookworm
RUN apt update && apt upgrade -y
RUN apt -y install \

View File

@ -1,15 +1,17 @@
FROM ubuntu:jammy
FROM debian:bookworm
RUN apt update && apt upgrade -y
RUN apt -y install \
netcat \
netcat-traditional \
postgresql-client \
postgresql-client-common \
libjemalloc2 \
libpq5 \
curl \
binutils \
linux-tools-gke \
perf-tools-unstable \
google-perftools
google-perftools \
gnupg

View File

@ -1,9 +1,5 @@
#!/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
@ -24,6 +20,9 @@ if [ -z "$ZT_DB_PASSWORD" ]; then
echo '*** FAILED: ZT_DB_PASSWORD environment variable not defined'
exit 1
fi
if [ -z "$ZT_DB_TYPE" ]; then
ZT_DB_TYPE="postgres"
fi
REDIS=""
if [ "$ZT_USE_REDIS" == "true" ]; then
@ -56,10 +55,14 @@ 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
if [ -f "$ZT_IDENTITY_PATH/authtoken.secret" ]; then
ln -s $ZT_IDENTITY_PATH/authtoken.secret authtoken.secret
if [ -d "$ZT_IDENTITY_PATH" ]; then
echo '*** Using existing ZT identity from path $ZT_IDENTITY_PATH'
ln -s $ZT_IDENTITY_PATH/identity.public identity.public
ln -s $ZT_IDENTITY_PATH/identity.secret identity.secret
if [ -f "$ZT_IDENTITY_PATH/authtoken.secret" ]; then
ln -s $ZT_IDENTITY_PATH/authtoken.secret authtoken.secret
fi
fi
popd
@ -70,7 +73,7 @@ APP_NAME="controller-$(cat /var/lib/zerotier-one/identity.public | cut -d ':' -f
echo "{
\"settings\": {
\"controllerDbPath\": \"postgres:host=${ZT_DB_HOST} port=${ZT_DB_PORT} dbname=${ZT_DB_NAME} user=${ZT_DB_USER} password=${ZT_DB_PASSWORD} application_name=${APP_NAME} sslmode=prefer sslcert=${DB_CLIENT_CERT} sslkey=${DB_CLIENT_KEY} sslrootcert=${DB_SERVER_CA}\",
\"controllerDbPath\": \"${ZT_DB_TYPE}:host=${ZT_DB_HOST} port=${ZT_DB_PORT} dbname=${ZT_DB_NAME} user=${ZT_DB_USER} password=${ZT_DB_PASSWORD} application_name=${APP_NAME} sslmode=prefer sslcert=${DB_CLIENT_CERT} sslkey=${DB_CLIENT_KEY} sslrootcert=${DB_SERVER_CA}\",
\"portMappingEnabled\": true,
\"softwareUpdate\": \"disable\",
\"interfacePrefixBlacklist\": [
@ -100,6 +103,15 @@ else
done
fi
if [ "$ZT_DB_TYPE" == "cv2" ]; then
echo "Migrating database (if needed)..."
if [ -n "$DB_SERVER_CA" ]; then
/usr/local/bin/migrate -source file:///migrations -database "postgres://$ZT_DB_USER:$ZT_DB_PASSWORD@$ZT_DB_HOST:$ZT_DB_PORT/$ZT_DB_NAME?x-migrations-table=controller_migrations&sslmode=verify-full&sslrootcert=$DB_SERVER_CA&sslcert=$DB_CLIENT_CERT&sslkey=$DB_CLIENT_KEY" up
else
/usr/local/bin/migrate -source file:///migrations -database "postgres://$ZT_DB_USER:$ZT_DB_PASSWORD@$ZT_DB_HOST:$ZT_DB_PORT/$ZT_DB_NAME?x-migrations-table=controller_migrations&sslmode=disable" up
fi
fi
if [ -n "$ZT_TEMPORAL_HOST" ] && [ -n "$ZT_TEMPORAL_PORT" ]; then
echo "waiting for temporal..."
while ! nc -z ${ZT_TEMPORAL_HOST} ${ZT_TEMPORAL_PORT}; do

View File

@ -0,0 +1,3 @@
DROP TABLE IF EXISTS network_memberships_ctl;
DROP TABLE IF EXISTS networks_ctl;
DROP TABLE IF EXISTS controllers_ctl;

View File

@ -0,0 +1,47 @@
-- inits controller db schema
CREATE TABLE IF NOT EXISTS controllers_ctl (
id text NOT NULL PRIMARY KEY,
hostname text,
last_heartbeat timestamp with time zone,
public_identity text NOT NULL,
version text
);
CREATE TABLE IF NOT EXISTS networks_ctl (
id character varying(22) NOT NULL PRIMARY KEY,
name text NOT NULL,
configuration jsonb DEFAULT '{}'::jsonb NOT NULL,
controller_id text REFERENCES controllers_ctl(id),
revision integer DEFAULT 0 NOT NULL,
last_modified timestamp with time zone DEFAULT now(),
creation_time timestamp with time zone DEFAULT now()
);
CREATE TABLE IF NOT EXISTS network_memberships_ctl (
device_id character varying(22) NOT NULL,
network_id character varying(22) NOT NULL REFERENCES networks_ctl(id),
authorized boolean,
active_bridge boolean,
ip_assignments text[],
no_auto_assign_ips boolean,
sso_exempt boolean,
authentication_expiry_time timestamp with time zone,
capabilities jsonb,
creation_time timestamp with time zone DEFAULT now(),
last_modified timestamp with time zone DEFAULT now(),
identity text DEFAULT ''::text,
last_authorized_credential text,
last_authorized_time timestamp with time zone,
last_deauthorized_time timestamp with time zone,
last_seen jsonb DEFAULT '{}'::jsonb NOT NULL, -- in the context of the network
remote_trace_level integer DEFAULT 0 NOT NULL,
remote_trace_target text DEFAULT ''::text NOT NULL,
revision integer DEFAULT 0 NOT NULL,
tags jsonb,
version_major integer DEFAULT 0 NOT NULL,
version_minor integer DEFAULT 0 NOT NULL,
version_revision integer DEFAULT 0 NOT NULL,
version_protocol integer DEFAULT 0 NOT NULL,
PRIMARY KEY (device_id, network_id)
);

View File

@ -0,0 +1,3 @@
ALTER TABLE network_memberships_ctl
DROP COLUMN os,
DROP COLUMN arch;

View File

@ -0,0 +1,3 @@
ALTER TABLE network_memberships_ctl
ADD COLUMN os TEXT NOT NULL DEFAULT 'unknown',
ADD COLUMN arch TEXT NOT NULL DEFAULT 'unknown';

View File

@ -431,6 +431,10 @@ central-controller-docker: _buildx FORCE
docker buildx build --platform linux/amd64,linux/arm64 --no-cache -t registry.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=`git name-rev --name-only HEAD` . --push
@echo Image: registry.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP}
centralv2-controller-docker: _buildx FORCE
docker buildx build --platform linux/amd64,linux/arm64 --no-cache -t us-central1-docker.pkg.dev/zerotier-421eb9/docker-images/ztcentral-controller:$(shell git rev-parse --short HEAD) -f ext/central-controller-docker/Dockerfile --build-arg git_branch=`git name-rev --name-only HEAD` . --push
@echo Image: us-central1-docker.pkg.dev/zerotier-421eb9/docker-images/ztcentral-controller:$(shell git rev-parse --short HEAD)
debug: FORCE
make ZT_DEBUG=1 one
make ZT_DEBUG=1 selftest

View File

@ -57,9 +57,9 @@ ONE_OBJS+=ext/libnatpmp/natpmp.o ext/libnatpmp/getgateway.o ext/miniupnpc/connec
ifeq ($(ZT_CONTROLLER),1)
MACOS_VERSION_MIN=10.15
override CXXFLAGS=$(CFLAGS) -std=c++17 -stdlib=libc++
LIBS+=-L/usr/local/opt/libpqxx/lib -L/usr/local/opt/libpq/lib -L/usr/local/opt/openssl/lib/ -lpqxx -lpq -lssl -lcrypto -lgssapi_krb5 ext/redis-plus-plus-1.1.1/install/macos/lib/libredis++.a ext/hiredis-0.14.1/lib/macos/libhiredis.a
LIBS+=-L/opt/homebrew/lib -L/usr/local/opt/libpqxx/lib -L/usr/local/opt/libpq/lib -L/usr/local/opt/openssl/lib/ -lpqxx -lpq -lssl -lcrypto -lgssapi_krb5 ext/redis-plus-plus-1.1.1/install/macos/lib/libredis++.a ext/hiredis-0.14.1/lib/macos/libhiredis.a rustybits/target/libsmeeclient.a
DEFS+=-DZT_CONTROLLER_USE_LIBPQ -DZT_CONTROLLER_USE_REDIS -DZT_CONTROLLER
INCLUDES+=-I/usr/local/opt/libpq/include -I/usr/local/opt/libpqxx/include -Iext/hiredis-0.14.1/include/ -Iext/redis-plus-plus-1.1.1/install/macos/include/sw/
INCLUDES+=-I/opt/homebrew/include -I/opt/homebrew/opt/libpq/include -I/usr/local/opt/libpq/include -I/usr/local/opt/libpqxx/include -Iext/hiredis-0.14.1/include/ -Iext/redis-plus-plus-1.1.1/install/macos/include/sw/ -Irustybits/target/
else
MACOS_VERSION_MIN=10.13
endif
@ -115,7 +115,11 @@ mac-agent: FORCE
osdep/MacDNSHelper.o: osdep/MacDNSHelper.mm
$(CXX) $(CXXFLAGS) -c osdep/MacDNSHelper.mm -o osdep/MacDNSHelper.o
ifeq ($(ZT_CONTROLLER),1)
one: zeroidc smeeclient $(CORE_OBJS) $(ONE_OBJS) one.o mac-agent
else
one: zeroidc $(CORE_OBJS) $(ONE_OBJS) one.o mac-agent
endif
$(CXX) $(CXXFLAGS) -o zerotier-one $(CORE_OBJS) $(ONE_OBJS) one.o $(LIBS) rustybits/target/libzeroidc.a
# $(STRIP) zerotier-one
ln -sf zerotier-one zerotier-idtool
@ -126,6 +130,15 @@ zerotier-one: one
zeroidc: rustybits/target/libzeroidc.a
ifeq ($(ZT_CONTROLLER),1)
smeeclient: rustybits/target/libsmeeclient.a
rustybits/target/libsmeeclient.a: FORCE
cd rustybits && MACOSX_DEPLOYMENT_TARGET=$(MACOS_VERSION_MIN) cargo build -p smeeclient --target=x86_64-apple-darwin $(EXTRA_CARGO_FLAGS)
cd rustybits && MACOSX_DEPLOYMENT_TARGET=$(MACOS_VERSION_MIN) cargo build -p smeeclient --target=aarch64-apple-darwin $(EXTRA_CARGO_FLAGS)
cd rustybits && lipo -create target/x86_64-apple-darwin/$(RUST_VARIANT)/libsmeeclient.a target/aarch64-apple-darwin/$(RUST_VARIANT)/libsmeeclient.a -output target/libsmeeclient.a
endif
rustybits/target/libzeroidc.a: FORCE
cd rustybits && MACOSX_DEPLOYMENT_TARGET=$(MACOS_VERSION_MIN) cargo build -p zeroidc --target=x86_64-apple-darwin $(EXTRA_CARGO_FLAGS)
cd rustybits && MACOSX_DEPLOYMENT_TARGET=$(MACOS_VERSION_MIN) cargo build -p zeroidc --target=aarch64-apple-darwin $(EXTRA_CARGO_FLAGS)
@ -195,6 +208,10 @@ central-controller-docker: _buildx FORCE
docker buildx build --platform linux/arm64,linux/amd64 --no-cache -t registry.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=$(shell git name-rev --name-only HEAD) . --push
@echo Image: registry.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP}
centralv2-controller-docker: _buildx FORCE
docker buildx build --platform linux/amd64,linux/arm64 --no-cache -t us-central1-docker.pkg.dev/zerotier-d648c7/central-v2/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=`git name-rev --name-only HEAD` . --push
@echo Image: us-central1-docker.pkg.dev/zerotier-d648c7/central-v2/ztcentral-controller:${TIMESTAMP}
docker-release: _buildx
docker buildx build --platform linux/386,linux/amd64,linux/arm/v7,linux/arm64,linux/mips64le,linux/ppc64le,linux/s390x -t zerotier/zerotier:${RELEASE_DOCKER_TAG} -t zerotier/zerotier:latest --build-arg VERSION=${RELEASE_VERSION} -f Dockerfile.release . --push

View File

@ -39,7 +39,10 @@ ONE_OBJS=\
controller/DB.o \
controller/FileDB.o \
controller/LFDB.o \
controller/CtlUtil.o \
controller/PostgreSQL.o \
controller/CV1.o \
controller/CV2.o \
osdep/EthernetTap.o \
osdep/ManagedRoute.o \
osdep/Http.o \