From 7637ef10d7e746991282960e77c948e4bc77de9e Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Fri, 25 Aug 2023 09:51:33 -0700 Subject: [PATCH] Fix primary port binding issue in 1.12 (#2107) * Add test for primary port bindings to validator - See #2105 * Add delay to binding test * Remove TCP binding logic from Binder to fix #2105 * add second control plane socket for ipv6 * fix controller network post endpoint * exit if we can't bind at least one of IPV4 or IPV6 for control plane port --------- Co-authored-by: Grant Limberg --- .github/workflows/validate-linux.sh | 42 ++++- controller/EmbeddedNetworkController.cpp | 74 +++++++-- controller/EmbeddedNetworkController.hpp | 1 + osdep/Binder.hpp | 20 +-- service/OneService.cpp | 203 +++++++++++++++++------ 5 files changed, 249 insertions(+), 91 deletions(-) diff --git a/.github/workflows/validate-linux.sh b/.github/workflows/validate-linux.sh index 61670d670..a661f6f6c 100755 --- a/.github/workflows/validate-linux.sh +++ b/.github/workflows/validate-linux.sh @@ -20,6 +20,9 @@ mkdir $TEST_DIR_PREFIX # How long we will wait for ZT to come online before considering it a failure MAX_WAIT_SECS=30 +ZT_PORT_NODE_1=9996 +ZT_PORT_NODE_2=9997 + ################################################################################ # Multi-node connectivity and performance test # ################################################################################ @@ -99,14 +102,19 @@ test() { --xml=yes \ --xml-file=$FILENAME_MEMORY_LOG \ --leak-check=full \ - ./zerotier-one node1 -p9996 -U >>node_1.log 2>&1 & + ./zerotier-one node1 -p$ZT_PORT_NODE_1 -U >>node_1.log 2>&1 & # Second instance, not run in memory profiler # Don't set up internet access until _after_ zerotier is running # This has been a source of stuckness in the past. $NS2 ip addr del 192.168.1.2/24 dev veth3 - $NS2 sudo ./zerotier-one node2 -U -p9997 >>node_2.log 2>&1 & - sleep 1; + $NS2 sudo ./zerotier-one node2 -U -p$ZT_PORT_NODE_2 >>node_2.log 2>&1 & + + sleep 10; # New HTTP control plane is a bit sluggish, so we delay here + + check_bind_to_correct_ports $ZT_PORT_NODE_1 + check_bind_to_correct_ports $ZT_PORT_NODE_2 + $NS2 ip addr add 192.168.1.2/24 dev veth3 $NS2 ip route add default via 192.168.1.1 @@ -458,4 +466,32 @@ check_exit_on_invalid_identity() { fi } +################################################################################ +# Check that we're binding to the primary port for TCP/TCP6/UDP # +################################################################################ + +check_bind_to_correct_ports() { + PORT_NUMBER=$1 + echo "Checking bound ports:" + sudo netstat -anp | grep "$PORT_NUMBER" | grep "zerotier" + if [[ $(sudo netstat -anp | grep "$PORT_NUMBER" | grep "zerotier" | grep "tcp") ]]; + then + : + else + exit_test_and_generate_report $TEST_FAIL "ZeroTier did not bind to tcp/$1" + fi + if [[ $(sudo netstat -anp | grep "$PORT_NUMBER" | grep "zerotier" | grep "tcp6") ]]; + then + : + else + exit_test_and_generate_report $TEST_FAIL "ZeroTier did not bind to tcp6/$1" + fi + if [[ $(sudo netstat -anp | grep "$PORT_NUMBER" | grep "zerotier" | grep "udp") ]]; + then + : + else + exit_test_and_generate_report $TEST_FAIL "ZeroTier did not bind to udp/$1" + fi +} + test "$@" diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp index b60c375c4..ac1947ce4 100644 --- a/controller/EmbeddedNetworkController.cpp +++ b/controller/EmbeddedNetworkController.cpp @@ -863,9 +863,17 @@ std::string EmbeddedNetworkController::networkUpdateFromPostData(uint64_t networ void EmbeddedNetworkController::configureHTTPControlPlane( httplib::Server &s, + httplib::Server &sv6, const std::function setContent) { - s.Get("/controller/network", [&, setContent](const httplib::Request &req, httplib::Response &res) { + // Control plane Endpoints + std::string networkListPath = "/controller/network"; + std::string networkPath = "/controller/network/([0-9a-fA-F]{16})"; + std::string oldAndBustedNetworkCreatePath = "/controller/network/([0-9a-fA-F]{10})______"; + std::string memberListPath = "/controller/network/([0-9a-fA-F]{16})/member"; + std::string memberPath = "/controller/network/([0-9a-fA-F]{16})/member/([0-9a-fA-F]{10})"; + + auto networkListGet = [&, setContent](const httplib::Request &req, httplib::Response &res) { std::set networkIds; _db.networks(networkIds); char tmp[64]; @@ -877,9 +885,11 @@ void EmbeddedNetworkController::configureHTTPControlPlane( } setContent(req, res, out.dump()); - }); + }; + s.Get(networkListPath, networkListGet); + sv6.Get(networkListPath, networkListGet); - s.Get("/controller/network/([0-9a-fA-F]{16})", [&, setContent](const httplib::Request &req, httplib::Response &res) { + auto networkGet = [&, setContent](const httplib::Request &req, httplib::Response &res) { auto networkID = req.matches[1]; uint64_t nwid = Utils::hexStrToU64(networkID.str().c_str()); json network; @@ -889,7 +899,9 @@ void EmbeddedNetworkController::configureHTTPControlPlane( } setContent(req, res, network.dump()); - }); + }; + s.Get(networkPath, networkGet); + sv6.Get(networkPath, networkGet); auto createNewNetwork = [&, setContent](const httplib::Request &req, httplib::Response &res) { fprintf(stderr, "creating new network (new style)\n"); @@ -912,8 +924,10 @@ void EmbeddedNetworkController::configureHTTPControlPlane( setContent(req, res, networkUpdateFromPostData(nwid, req.body)); }; - s.Put("/controller/network", createNewNetwork); - s.Post("/controller/network", createNewNetwork); + s.Put(networkListPath, createNewNetwork); + s.Post(networkListPath, createNewNetwork); + sv6.Put(networkListPath, createNewNetwork); + sv6.Post(networkListPath, createNewNetwork); auto createNewNetworkOldAndBusted = [&, setContent](const httplib::Request &req, httplib::Response &res) { auto inID = req.matches[1].str(); @@ -941,10 +955,24 @@ void EmbeddedNetworkController::configureHTTPControlPlane( } setContent(req, res, networkUpdateFromPostData(nwid, req.body)); }; - s.Put("/controller/network/([0-9a-fA-F]{10})______", createNewNetworkOldAndBusted); - s.Post("/controller/network/([0-9a-fA-F]{10})______", createNewNetworkOldAndBusted); + s.Put(oldAndBustedNetworkCreatePath, createNewNetworkOldAndBusted); + s.Post(oldAndBustedNetworkCreatePath, createNewNetworkOldAndBusted); + sv6.Put(oldAndBustedNetworkCreatePath, createNewNetworkOldAndBusted); + sv6.Post(oldAndBustedNetworkCreatePath, createNewNetworkOldAndBusted); - s.Delete("/controller/network/([0-9a-fA-F]{16})", [&, setContent](const httplib::Request &req, httplib::Response &res) { + auto networkPost = [&, setContent](const httplib::Request &req, httplib::Response &res) { + auto networkID = req.matches[1].str(); + uint64_t nwid = Utils::hexStrToU64(networkID.c_str()); + + res.status = 200; + setContent(req, res, networkUpdateFromPostData(nwid, req.body)); + }; + s.Put(networkPath, networkPost); + s.Post(networkPath, networkPost); + sv6.Put(networkPath, networkPost); + sv6.Post(networkPath, networkPost); + + auto networkDelete = [&, setContent](const httplib::Request &req, httplib::Response &res) { auto networkID = req.matches[1].str(); uint64_t nwid = Utils::hexStrToU64(networkID.c_str()); @@ -956,9 +984,11 @@ void EmbeddedNetworkController::configureHTTPControlPlane( _db.eraseNetwork(nwid); setContent(req, res, network.dump()); - }); + }; + s.Delete(networkPath, networkDelete); + sv6.Delete(networkPath, networkDelete); - s.Get("/controller/network/([0-9a-fA-F]{16})/member", [&, setContent](const httplib::Request &req, httplib::Response &res) { + auto memberListGet = [&, setContent](const httplib::Request &req, httplib::Response &res) { auto networkID = req.matches[1]; uint64_t nwid = Utils::hexStrToU64(networkID.str().c_str()); json network; @@ -982,9 +1012,11 @@ void EmbeddedNetworkController::configureHTTPControlPlane( } setContent(req, res, out.dump()); - }); + }; + s.Get(memberListPath, memberListGet); + sv6.Get(memberListPath, memberListGet); - s.Get("/controller/network/([0-9a-fA-F]{16})/member/([0-9a-fA-F]{10})", [&, setContent](const httplib::Request &req, httplib::Response &res) { + auto memberGet = [&, setContent](const httplib::Request &req, httplib::Response &res) { auto networkID = req.matches[1]; auto memberID = req.matches[2]; uint64_t nwid = Utils::hexStrToU64(networkID.str().c_str()); @@ -997,7 +1029,9 @@ void EmbeddedNetworkController::configureHTTPControlPlane( } setContent(req, res, member.dump()); - }); + }; + s.Get(memberPath, memberGet); + sv6.Get(memberPath, memberGet); auto memberPost = [&, setContent](const httplib::Request &req, httplib::Response &res) { auto networkID = req.matches[1].str(); @@ -1102,10 +1136,12 @@ void EmbeddedNetworkController::configureHTTPControlPlane( setContent(req, res, member.dump()); }; - s.Put("/controller/network/([0-9a-fA-F]{16})/member/([0-9a-fA-F]{10})", memberPost); - s.Post("/controller/network/([0-9a-fA-F]{16})/member/([0-9a-fA-F]{10})", memberPost); + s.Put(memberPath, memberPost); + s.Post(memberPath, memberPost); + sv6.Put(memberPath, memberPost); + sv6.Post(memberPath, memberPost); - s.Delete("/controller/network/([0-9a-fA-F]{16})/member/([0-9a-fA-F]{10})", [&, setContent](const httplib::Request &req, httplib::Response &res) { + auto memberDelete = [&, setContent](const httplib::Request &req, httplib::Response &res) { auto networkID = req.matches[1].str(); auto memberID = req.matches[2].str(); @@ -1126,7 +1162,9 @@ void EmbeddedNetworkController::configureHTTPControlPlane( _db.eraseMember(nwid, address); setContent(req, res, member.dump()); - }); + }; + s.Delete(memberPath, memberDelete); + sv6.Delete(memberPath, memberDelete); } void EmbeddedNetworkController::handleRemoteTrace(const ZT_RemoteTrace &rt) diff --git a/controller/EmbeddedNetworkController.hpp b/controller/EmbeddedNetworkController.hpp index ef369be39..4ea00b65a 100644 --- a/controller/EmbeddedNetworkController.hpp +++ b/controller/EmbeddedNetworkController.hpp @@ -70,6 +70,7 @@ public: void configureHTTPControlPlane( httplib::Server &s, + httplib::Server &sV6, const std::function); void handleRemoteTrace(const ZT_RemoteTrace &rt); diff --git a/osdep/Binder.hpp b/osdep/Binder.hpp index fe08f5195..614c6e725 100644 --- a/osdep/Binder.hpp +++ b/osdep/Binder.hpp @@ -87,11 +87,10 @@ namespace ZeroTier { class Binder { private: struct _Binding { - _Binding() : udpSock((PhySocket*)0), tcpListenSock((PhySocket*)0) + _Binding() : udpSock((PhySocket*)0) { } PhySocket* udpSock; - PhySocket* tcpListenSock; InetAddress address; char ifname[256] = {}; }; @@ -111,7 +110,6 @@ class Binder { Mutex::Lock _l(_lock); for (unsigned int b = 0, c = _bindingCount; b < c; ++b) { phy.close(_bindings[b].udpSock, false); - phy.close(_bindings[b].tcpListenSock, false); } _bindingCount = 0; } @@ -133,7 +131,7 @@ class Binder { template void refresh(Phy& phy, unsigned int* ports, unsigned int portCount, const std::vector explicitBind, INTERFACE_CHECKER& ifChecker) { std::map localIfAddrs; - PhySocket *udps, *tcps; + PhySocket *udps; Mutex::Lock _l(_lock); bool interfacesEnumerated = true; @@ -419,11 +417,8 @@ class Binder { } else { PhySocket* const udps = _bindings[b].udpSock; - PhySocket* const tcps = _bindings[b].tcpListenSock; _bindings[b].udpSock = (PhySocket*)0; - _bindings[b].tcpListenSock = (PhySocket*)0; phy.close(udps, false); - phy.close(tcps, false); } } @@ -437,24 +432,20 @@ class Binder { } if (bi == _bindingCount) { udps = phy.udpBind(reinterpret_cast(&(ii->first)), (void*)0, ZT_UDP_DESIRED_BUF_SIZE); - tcps = phy.tcpListen(reinterpret_cast(&(ii->first)), (void*)0); - if ((udps) && (tcps)) { + if (udps) { #ifdef __LINUX__ // Bind Linux sockets to their device so routes that we manage do not override physical routes (wish all platforms had this!) if (ii->second.length() > 0) { char tmp[256]; Utils::scopy(tmp, sizeof(tmp), ii->second.c_str()); int fd = (int)Phy::getDescriptor(udps); - if (fd >= 0) - setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, tmp, strlen(tmp)); - fd = (int)Phy::getDescriptor(tcps); - if (fd >= 0) + if (fd >= 0) { setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, tmp, strlen(tmp)); + } } #endif // __LINUX__ if (_bindingCount < ZT_BINDER_MAX_BINDINGS) { _bindings[_bindingCount].udpSock = udps; - _bindings[_bindingCount].tcpListenSock = tcps; _bindings[_bindingCount].address = ii->first; memcpy(_bindings[_bindingCount].ifname, (char*)ii->second.c_str(), (int)ii->second.length()); ++_bindingCount; @@ -462,7 +453,6 @@ class Binder { } else { phy.close(udps, false); - phy.close(tcps, false); } } } diff --git a/service/OneService.cpp b/service/OneService.cpp index b5e5c1924..dfa5df056 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -786,8 +786,11 @@ public: bool _updateAutoApply; httplib::Server _controlPlane; + httplib::Server _controlPlaneV6; std::thread _serverThread; + std::thread _serverThreadV6; bool _serverThreadRunning; + bool _serverThreadRunningV6; bool _allowTcpFallbackRelay; bool _forceTcpRelay; @@ -888,8 +891,11 @@ public: ,_updater((SoftwareUpdater *)0) ,_updateAutoApply(false) ,_controlPlane() + ,_controlPlaneV6() ,_serverThread() + ,_serverThreadV6() ,_serverThreadRunning(false) + ,_serverThreadRunningV6(false) ,_forceTcpRelay(false) ,_primaryPort(port) ,_udpPortPickerCounter(0) @@ -944,6 +950,10 @@ public: if (_serverThreadRunning) { _serverThread.join(); } + _controlPlaneV6.stop(); + if (_serverThreadRunningV6) { + _serverThreadV6.join(); + } #ifdef ZT_USE_MINIUPNPC delete _portMapper; @@ -1527,6 +1537,22 @@ public: // Internal HTTP Control Plane void startHTTPControlPlane() { + // control plane endpoints + std::string bondShowPath = "/bond/show/([0-9a-fA-F]{10})"; + std::string bondRotatePath = "/bond/rotate/([0-9a-fA-F]{10})"; + std::string setBondMtuPath = "/bond/setmtu/([0-9]{3,5})/([a-zA-Z0-9_]{1,16})/([0-9a-fA-F\\.\\:]{1,39})"; + std::string configPath = "/config"; + std::string configPostPath = "/config/settings"; + std::string healthPath = "/health"; + std::string moonListPath = "/moon"; + std::string moonPath = "/moon/([0-9a-fA-F]{10})"; + std::string networkListPath = "/network"; + std::string networkPath = "/network/([0-9a-fA-F]{16})"; + std::string peerListPath = "/peer"; + std::string peerPath = "/peer/([0-9a-fA-F]{10})"; + std::string statusPath = "/status"; + std::string metricsPath = "/metrics"; + std::vector noAuthEndpoints { "/sso", "/health" }; auto setContent = [=] (const httplib::Request &req, httplib::Response &res, std::string content) { @@ -1625,8 +1651,7 @@ public: }; - - _controlPlane.Get("/bond/show/([0-9a-fA-F]{10})", [&, setContent](const httplib::Request &req, httplib::Response &res) { + auto bondShow = [&, setContent](const httplib::Request &req, httplib::Response &res) { if (!_node->bondController()->inUse()) { setContent(req, res, ""); res.status = 400; @@ -1652,7 +1677,9 @@ public: } } _node->freeQueryResult((void *)pl); - }); + }; + _controlPlane.Get(bondShowPath, bondShow); + _controlPlaneV6.Get(bondShowPath, bondShow); auto bondRotate = [&, setContent](const httplib::Request &req, httplib::Response &res) { if (!_node->bondController()->inUse()) { @@ -1675,8 +1702,10 @@ public: } setContent(req, res, "{}"); }; - _controlPlane.Post("/bond/rotate/([0-9a-fA-F]{10})", bondRotate); - _controlPlane.Put("/bond/rotate/([0-9a-fA-F]{10})", bondRotate); + _controlPlane.Post(bondRotatePath, bondRotate); + _controlPlane.Put(bondRotatePath, bondRotate); + _controlPlaneV6.Post(bondRotatePath, bondRotate); + _controlPlaneV6.Put(bondRotatePath, bondRotate); auto setMtu = [&, setContent](const httplib::Request &req, httplib::Response &res) { if (!_node->bondController()->inUse()) { @@ -1688,10 +1717,12 @@ public: res.status = _node->bondController()->setAllMtuByTuple(mtu, req.matches[2].str().c_str(), req.matches[3].str().c_str()) ? 200 : 400; setContent(req, res, "{}"); }; - _controlPlane.Post("/bond/setmtu/([0-9]{3,5})/([a-zA-Z0-9_]{1,16})/([0-9a-fA-F\\.\\:]{1,39})", setMtu); - _controlPlane.Put("/bond/setmtu/([0-9]{3,5})/([a-zA-Z0-9_]{1,16})/([0-9a-fA-F\\.\\:]{1,39})", setMtu); + _controlPlane.Post(setBondMtuPath, setMtu); + _controlPlane.Put(setBondMtuPath, setMtu); + _controlPlaneV6.Post(setBondMtuPath, setMtu); + _controlPlaneV6.Put(setBondMtuPath, setMtu); - _controlPlane.Get("/config", [&, setContent](const httplib::Request &req, httplib::Response &res) { + auto getConfig = [&, setContent](const httplib::Request &req, httplib::Response &res) { std::string config; { Mutex::Lock lc(_localConfig_m); @@ -1701,7 +1732,9 @@ public: config = "{}"; } setContent(req, res, config); - }); + }; + _controlPlane.Get(configPath, getConfig); + _controlPlaneV6.Get(configPath, getConfig); auto configPost = [&, setContent](const httplib::Request &req, httplib::Response &res) { json j(OSUtils::jsonParse(req.body)); @@ -1718,10 +1751,12 @@ public: } setContent(req, res, "{}"); }; - _controlPlane.Post("/config/settings", configPost); - _controlPlane.Put("/config/settings", configPost); + _controlPlane.Post(configPostPath, configPost); + _controlPlane.Put(configPostPath, configPost); + _controlPlaneV6.Post(configPostPath, configPost); + _controlPlaneV6.Put(configPostPath, configPost); - _controlPlane.Get("/health", [&, setContent](const httplib::Request &req, httplib::Response &res) { + auto healthGet = [&, setContent](const httplib::Request &req, httplib::Response &res) { json out = json::object(); char tmp[256]; @@ -1739,9 +1774,11 @@ public: out["clock"] = OSUtils::now(); setContent(req, res, out.dump()); - }); + }; + _controlPlane.Get(healthPath, healthGet); + _controlPlaneV6.Get(healthPath, healthGet); - _controlPlane.Get("/moon", [&, setContent](const httplib::Request &req, httplib::Response &res) { + auto moonListGet = [&, setContent](const httplib::Request &req, httplib::Response &res) { std::vector moons(_node->moons()); auto out = json::array(); @@ -1751,9 +1788,11 @@ public: out.push_back(mj); } setContent(req, res, out.dump()); - }); + }; + _controlPlane.Get(moonListPath, moonListGet); + _controlPlaneV6.Get(moonListPath, moonListGet); - _controlPlane.Get("/moon/([0-9a-fA-F]{10})", [&, setContent](const httplib::Request &req, httplib::Response &res){ + auto moonGet = [&, setContent](const httplib::Request &req, httplib::Response &res){ std::vector moons(_node->moons()); auto input = req.matches[1]; auto out = json::object(); @@ -1765,7 +1804,9 @@ public: } } setContent(req, res, out.dump()); - }); + }; + _controlPlane.Get(moonPath, moonGet); + _controlPlaneV6.Get(moonPath, moonGet); auto moonPost = [&, setContent](const httplib::Request &req, httplib::Response &res) { auto input = req.matches[1]; @@ -1804,19 +1845,22 @@ public: } setContent(req, res, out.dump()); }; - _controlPlane.Post("/moon/([0-9a-fA-F]{10})", moonPost); - _controlPlane.Put("/moon/([0-9a-fA-F]{10})", moonPost); + _controlPlane.Post(moonPath, moonPost); + _controlPlane.Put(moonPath, moonPost); + _controlPlaneV6.Post(moonPath, moonPost); + _controlPlaneV6.Put(moonPath, moonPost); - _controlPlane.Delete("/moon/([0-9a-fA-F]{10})", [&, setContent](const httplib::Request &req, httplib::Response &res) { + auto moonDelete = [&, setContent](const httplib::Request &req, httplib::Response &res) { auto input = req.matches[1]; uint64_t id = Utils::hexStrToU64(input.str().c_str()); auto out = json::object(); _node->deorbit((void*)0,id); out["result"] = true; setContent(req, res, out.dump()); - }); + }; + _controlPlane.Delete(moonPath, moonDelete); - _controlPlane.Get("/network", [&, setContent](const httplib::Request &req, httplib::Response &res) { + auto networkListGet = [&, setContent](const httplib::Request &req, httplib::Response &res) { Mutex::Lock _l(_nets_m); auto out = json::array(); @@ -1827,9 +1871,11 @@ public: out.push_back(nj); } setContent(req, res, out.dump()); - }); + }; + _controlPlane.Get(networkListPath, networkListGet); + _controlPlaneV6.Get(networkListPath, networkListGet); - _controlPlane.Get("/network/([0-9a-fA-F]{16})", [&, setContent](const httplib::Request &req, httplib::Response &res) { + auto networkGet = [&, setContent](const httplib::Request &req, httplib::Response &res) { Mutex::Lock _l(_nets_m); auto input = req.matches[1]; @@ -1843,7 +1889,9 @@ public: } setContent(req, res, ""); res.status = 404; - }); + }; + _controlPlane.Get(networkPath, networkGet); + _controlPlaneV6.Get(networkPath, networkGet); auto networkPost = [&, setContent](const httplib::Request &req, httplib::Response &res) { auto input = req.matches[1]; @@ -1897,10 +1945,12 @@ public: setContent(req, res, out.dump()); #endif }; - _controlPlane.Post("/network/([0-9a-fA-F]{16})", networkPost); - _controlPlane.Put("/network/([0-9a-fA-F]){16}", networkPost); + _controlPlane.Post(networkPath, networkPost); + _controlPlane.Put(networkPath, networkPost); + _controlPlaneV6.Post(networkPath, networkPost); + _controlPlaneV6.Put(networkPath, networkPost); - _controlPlane.Delete("/network/([0-9a-fA-F]{16})", [&, setContent](const httplib::Request &req, httplib::Response &res) { + auto networkDelete = [&, setContent](const httplib::Request &req, httplib::Response &res) { auto input = req.matches[1]; auto out = json::object(); ZT_VirtualNetworkList *nws = _node->networks(); @@ -1913,9 +1963,11 @@ public: } _node->freeQueryResult((void*)nws); setContent(req, res, out.dump()); - }); + }; + _controlPlane.Delete(networkPath, networkDelete); + _controlPlaneV6.Delete(networkPath, networkDelete); - _controlPlane.Get("/peer", [&, setContent](const httplib::Request &req, httplib::Response &res) { + auto peerListGet = [&, setContent](const httplib::Request &req, httplib::Response &res) { ZT_PeerList *pl = _node->peers(); auto out = nlohmann::json::array(); @@ -1931,9 +1983,11 @@ public: } _node->freeQueryResult((void*)pl); setContent(req, res, out.dump()); - }); + }; + _controlPlane.Get(peerListPath, peerListGet); + _controlPlaneV6.Get(peerListPath, peerListGet); - _controlPlane.Get("/peer/([0-9a-fA-F]{10})", [&, setContent](const httplib::Request &req, httplib::Response &res) { + auto peerGet = [&, setContent](const httplib::Request &req, httplib::Response &res) { ZT_PeerList *pl = _node->peers(); auto input = req.matches[1]; @@ -1951,9 +2005,11 @@ public: } _node->freeQueryResult((void*)pl); setContent(req, res, out.dump()); - }); + }; + _controlPlane.Get(peerPath, peerGet); + _controlPlaneV6.Get(peerPath, peerGet); - _controlPlane.Get("/status", [&, setContent](const httplib::Request &req, httplib::Response &res) { + auto statusGet = [&, setContent](const httplib::Request &req, httplib::Response &res) { ZT_NodeStatus status; _node->status(&status); @@ -2016,10 +2072,13 @@ public: out["planetWorldTimestamp"] = planet.timestamp(); setContent(req, res, out.dump()); - }); + }; + _controlPlane.Get(statusPath, statusGet); + _controlPlaneV6.Get(statusPath, statusGet); #if ZT_SSO_ENABLED - _controlPlane.Get("/sso", [this](const httplib::Request &req, httplib::Response &res) { + std::string ssoPath = "/sso"; + auto ssoGet = [this](const httplib::Request &req, httplib::Response &res) { std::string htmlTemplatePath = _homePath + ZT_PATH_SEPARATOR + "sso-auth.template.html"; std::string htmlTemplate; if (!OSUtils::readFile(htmlTemplatePath.c_str(), htmlTemplate)) { @@ -2085,10 +2144,11 @@ public: zeroidc::free_cstr(ret); } - }); + }; + _controlPlane.Get(ssoPath, ssoGet); + _controlPlaneV6.Get(ssoPath, ssoGet); #endif - - _controlPlane.Get("/metrics", [this](const httplib::Request &req, httplib::Response &res) { + auto metricsGet = [this](const httplib::Request &req, httplib::Response &res) { std::string statspath = _homePath + ZT_PATH_SEPARATOR + "metrics.prom"; std::string metrics; if (OSUtils::readFile(statspath.c_str(), metrics)) { @@ -2097,9 +2157,11 @@ public: res.set_content("{}", "application/json"); res.status = 500; } - }); + }; + _controlPlane.Get(metricsPath, metricsGet); + _controlPlaneV6.Get(metricsPath, metricsGet); - _controlPlane.set_exception_handler([&, setContent](const httplib::Request &req, httplib::Response &res, std::exception_ptr ep) { + auto exceptionHandler = [&, setContent](const httplib::Request &req, httplib::Response &res, std::exception_ptr ep) { char buf[1024]; auto fmt = "{\"error\": %d, \"description\": \"%s\"}"; try { @@ -2111,39 +2173,70 @@ public: } setContent(req, res, buf); res.status = 500; - }); + }; + _controlPlane.set_exception_handler(exceptionHandler); + _controlPlaneV6.set_exception_handler(exceptionHandler); if (_controller) { - _controller->configureHTTPControlPlane(_controlPlane, setContent); + _controller->configureHTTPControlPlane(_controlPlane, _controlPlaneV6, setContent); } _controlPlane.set_pre_routing_handler(authCheck); + _controlPlaneV6.set_pre_routing_handler(authCheck); #if ZT_DEBUG==1 _controlPlane.set_logger([](const httplib::Request &req, const httplib::Response &res) { fprintf(stderr, "%s", http_log(req, res).c_str()); }); + _controlPlaneV6.set_logger([](const httplib::Request &req, const httplib::Response &res) { + fprintf(stderr, "%s", http_log(req, res).c_str()); + }); #endif if (_primaryPort==0) { fprintf(stderr, "unable to determine local control port"); exit(-1); } - if(!_controlPlane.bind_to_port("0.0.0.0", _primaryPort)) { - fprintf(stderr, "Error binding control plane to port %d\n", _primaryPort); - exit(-1); + bool v4controlPlaneBound = false; + _controlPlane.set_address_family(AF_INET); + if(_controlPlane.bind_to_port("0.0.0.0", _primaryPort)) { + _serverThread = std::thread([&] { + _serverThreadRunning = true; + fprintf(stderr, "Starting Control Plane...\n"); + if(!_controlPlane.listen_after_bind()) { + fprintf(stderr, "Error on listen_after_bind()\n"); + } + fprintf(stderr, "Control Plane Stopped\n"); + _serverThreadRunning = false; + }); + v4controlPlaneBound = true; + } else { + fprintf(stderr, "Error binding control plane to 0.0.0.0:%d\n", _primaryPort); + v4controlPlaneBound = false; } - _serverThread = std::thread([&] { - _serverThreadRunning = true; - fprintf(stderr, "Starting Control Plane...\n"); - if(!_controlPlane.listen_after_bind()) { - fprintf(stderr, "Error on listen_after_bind()\n"); - } - fprintf(stderr, "Control Plane Stopped\n"); - _serverThreadRunning = false; - }); + bool v6controlPlaneBound = false; + _controlPlaneV6.set_address_family(AF_INET6); + if(_controlPlaneV6.bind_to_port("::", _primaryPort)) { + _serverThreadV6 = std::thread([&] { + _serverThreadRunningV6 = true; + fprintf(stderr, "Starting V6 Control Plane...\n"); + if(!_controlPlaneV6.listen_after_bind()) { + fprintf(stderr, "Error on V6 listen_after_bind()\n"); + } + fprintf(stderr, "V6 Control Plane Stopped\n"); + _serverThreadRunningV6 = false; + }); + v6controlPlaneBound = true; + } else { + fprintf(stderr, "Error binding control plane to [::]:%d\n", _primaryPort); + v6controlPlaneBound = false; + } + if (!v4controlPlaneBound && !v6controlPlaneBound) { + fprintf(stderr, "ERROR: Could not bind control plane. Exiting...\n"); + exit(-1); + } } // Must be called after _localConfig is read or modified