diff options
author | Adam Ierymenko <adam.ierymenko@gmail.com> | 2015-03-31 15:23:14 -0700 |
---|---|---|
committer | Adam Ierymenko <adam.ierymenko@gmail.com> | 2015-03-31 15:23:14 -0700 |
commit | 647ce82b86a56c45f07cd69d5cabedd083179365 (patch) | |
tree | 9874c3c4b1c240b53cf8249f7c55ce11ec7994e2 /osdep | |
parent | e61a40a956dfa538be8c8fec1448a9e51acd02e5 (diff) | |
download | infinitytier-647ce82b86a56c45f07cd69d5cabedd083179365.tar.gz infinitytier-647ce82b86a56c45f07cd69d5cabedd083179365.zip |
Move more stuff into osdep/ -- node/ will not use threads directly.
Diffstat (limited to 'osdep')
-rw-r--r-- | osdep/BSDEthernetTap.hpp | 2 | ||||
-rw-r--r-- | osdep/BSDEthernetTapFactory.hpp | 2 | ||||
-rw-r--r-- | osdep/EthernetTap.hpp | 179 | ||||
-rw-r--r-- | osdep/EthernetTapFactory.hpp | 98 | ||||
-rw-r--r-- | osdep/HttpClient.cpp | 591 | ||||
-rw-r--r-- | osdep/HttpClient.hpp | 111 | ||||
-rw-r--r-- | osdep/SoftwareUpdater.cpp | 280 | ||||
-rw-r--r-- | osdep/SoftwareUpdater.hpp | 166 | ||||
-rw-r--r-- | osdep/Thread.hpp | 203 |
9 files changed, 1630 insertions, 2 deletions
diff --git a/osdep/BSDEthernetTap.hpp b/osdep/BSDEthernetTap.hpp index 582292a1..453edea1 100644 --- a/osdep/BSDEthernetTap.hpp +++ b/osdep/BSDEthernetTap.hpp @@ -33,7 +33,7 @@ #include <stdexcept> -#include "../node/EthernetTap.hpp" +#include "EthernetTap.hpp" #include "../node/Thread.hpp" namespace ZeroTier { diff --git a/osdep/BSDEthernetTapFactory.hpp b/osdep/BSDEthernetTapFactory.hpp index 63e77339..5c681fb4 100644 --- a/osdep/BSDEthernetTapFactory.hpp +++ b/osdep/BSDEthernetTapFactory.hpp @@ -31,7 +31,7 @@ #include <vector> #include <string> -#include "../node/EthernetTapFactory.hpp" +#include "EthernetTapFactory.hpp" #include "../node/Mutex.hpp" namespace ZeroTier { diff --git a/osdep/EthernetTap.hpp b/osdep/EthernetTap.hpp new file mode 100644 index 00000000..f2675e11 --- /dev/null +++ b/osdep/EthernetTap.hpp @@ -0,0 +1,179 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef ZT_ETHERNETTAP_HPP +#define ZT_ETHERNETTAP_HPP + +#include <stdio.h> +#include <stdlib.h> + +#include <string> +#include <set> + +#include "../node/Constants.hpp" +#include "../node/MAC.hpp" +#include "../node/InetAddress.hpp" +#include "../node/Buffer.hpp" +#include "MulticastGroup.hpp" +#include "NonCopyable.hpp" + +namespace ZeroTier { + +/** + * Base class for Ethernet tap device implementations + */ +class EthernetTap : NonCopyable +{ +protected: + EthernetTap(const char *cn,const MAC &m,unsigned int mt,unsigned int met) : + _implName(cn), + _mac(m), + _mtu(mt), + _metric(met) {} + +public: + virtual ~EthernetTap() {} + + /** + * @return Implementation class name (e.g. UnixEthernetTap) + */ + inline const char *implementationName() const { return _implName; } + + /** + * Sets whether device is 'up' + * + * This may do nothing on some platforms. + * + * @param en Is device enabled? + */ + virtual void setEnabled(bool en) = 0; + + /** + * @return Is device 'up'? + */ + virtual bool enabled() const = 0; + + /** + * @return MAC address of this interface + */ + inline const MAC &mac() const throw() { return _mac; } + + /** + * @return MTU of this interface + */ + inline unsigned int mtu() const throw() { return _mtu; } + + /** + * @return Interface metric + */ + inline unsigned int metric() const throw() { return _metric; } + + /** + * Add an IP to this interface + * + * @param ip IP and netmask (netmask stored in port field) + * @return True if IP added successfully + */ + virtual bool addIP(const InetAddress &ip) = 0; + + /** + * Remove an IP from this interface + * + * Link-local IP addresses may not be able to be removed, depending on platform and type. + * + * @param ip IP and netmask (netmask stored in port field) + * @return True if IP removed successfully + */ + virtual bool removeIP(const InetAddress &ip) = 0; + + /** + * @return All IP addresses (V4 and V6) assigned to this interface (including link-local) + */ + virtual std::set<InetAddress> ips() const = 0; + + /** + * Put a frame, making it available to the OS for processing + * + * @param from MAC address from which frame originated + * @param to MAC address of destination (typically MAC of tap itself) + * @param etherType Ethernet protocol ID + * @param data Frame payload + * @param len Length of frame + */ + virtual void put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len) = 0; + + /** + * @return OS-specific device or connection name (e.g. zt0, tap0, etc.) + */ + virtual std::string deviceName() const = 0; + + /** + * Change this device's user-visible name (if supported) + * + * @param friendlyName New name + */ + virtual void setFriendlyName(const char *friendlyName) = 0; + + /** + * Fill or modify a set to contain multicast groups for this device + * + * This populates a set or, if already populated, modifies it to contain + * only multicast groups in which this device is interested. + * + * This neither includes nor removes the broadcast (ff:ff:ff:ff:ff:ff / 0) + * group. + * + * @param groups Set to modify in place + * @return True if set was changed since last call + */ + virtual bool updateMulticastGroups(std::set<MulticastGroup> &groups) = 0; + + /** + * Inject a packet as if it was sent by the host, if supported + * + * This is for testing and is typically not supported by real TAP devices. + * It's implemented by TestEthernetTap in testnet. + * + * @param from Source MAC + * @param to Destination MAC + * @param etherType Ethernet frame type + * @param data Packet data + * @param len Packet length + * @return False if not supported or packet too large + */ + virtual bool injectPacketFromHost(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len) = 0; + +protected: + const char *_implName; + MAC _mac; + unsigned int _mtu; + unsigned int _metric; +}; + +} // namespace ZeroTier + +#endif diff --git a/osdep/EthernetTapFactory.hpp b/osdep/EthernetTapFactory.hpp new file mode 100644 index 00000000..4acb2369 --- /dev/null +++ b/osdep/EthernetTapFactory.hpp @@ -0,0 +1,98 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef ZT_ETHERNETTAPFACTORY_HPP +#define ZT_ETHERNETTAPFACTORY_HPP + +#include <stdint.h> + +#include "MAC.hpp" +#include "NonCopyable.hpp" +#include "Buffer.hpp" + +namespace ZeroTier { + +class EthernetTap; + +/** + * Ethernet tap factory + * + * This serves up tap implementations for a given platform. It should never be + * deleted until the Node using it is shut down, since doing so may invalidate + * any tap devices it manages. + * + * Using a factory pattern will faciliatate packaging ZeroTier as a library, + * as well as moving toward a design that makes unit testing the entire app + * quite a bit easier. + */ +class EthernetTapFactory : NonCopyable +{ +public: + EthernetTapFactory() {} + virtual ~EthernetTapFactory() {} + + /** + * Create / open an Ethernet tap device + * + * On some platforms (Windows) this can be a time-consuming operation. + * + * Note that close() must be used. Do not just delete the tap instance, + * since this may leave orphaned resources or cause other problems. + * + * @param mac MAC address + * @param mtu Device MTU + * @param metric Interface metric (higher = lower priority, may not be supported on all OSes) + * @param nwid ZeroTier network ID + * @param desiredDevice Desired system device name or NULL for no preference + * @param friendlyName Friendly name of this interface or NULL for none (not used on all platforms) + * @param handler Function to call when packets are received + * @param arg First argument to provide to handler + * @return EthernetTap instance + * @throws std::runtime_error Unable to initialize tap device + */ + virtual EthernetTap *open( + const MAC &mac, + unsigned int mtu, + unsigned int metric, + uint64_t nwid, + const char *desiredDevice, + const char *friendlyName, + void (*handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &), + void *arg) = 0; + + /** + * Close an ethernet tap device and delete/free the tap object + * + * @param tap Tap instance + * @param destroyPersistentDevices If true, destroy persistent device (on platforms where applicable) + */ + virtual void close(EthernetTap *tap,bool destroyPersistentDevices) = 0; +}; + +} // namespace ZeroTier + +#endif diff --git a/osdep/HttpClient.cpp b/osdep/HttpClient.cpp new file mode 100644 index 00000000..6b96960b --- /dev/null +++ b/osdep/HttpClient.cpp @@ -0,0 +1,591 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include "Constants.hpp" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifdef __WINDOWS__ +#include <WinSock2.h> +#include <Windows.h> +#include <winhttp.h> +#include <locale> +#include <codecvt> +#endif // __WINDOWS__ + +#ifdef __UNIX_LIKE__ +#include <unistd.h> +#include <signal.h> +#include <fcntl.h> +#include <sys/select.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/wait.h> +#endif // __UNIX_LIKE__ + +#include <vector> +#include <utility> +#include <algorithm> + +#include "HttpClient.hpp" +#include "Thread.hpp" +#include "Utils.hpp" +#include "NonCopyable.hpp" +#include "Defaults.hpp" + +namespace ZeroTier { + +#ifdef __UNIX_LIKE__ + +// The *nix implementation calls 'curl' externally rather than linking to it. +// This makes it an optional dependency that can be avoided in tiny systems +// provided you don't want to have automatic software updates... or want to +// do them via another method. + +#ifdef __APPLE__ +// TODO: get proxy configuration +#endif + +// Paths where "curl" may be found on the system +#define NUM_CURL_PATHS 6 +static const char *CURL_PATHS[NUM_CURL_PATHS] = { "/usr/bin/curl","/bin/curl","/usr/local/bin/curl","/usr/sbin/curl","/sbin/curl","/usr/libexec/curl" }; + +// Maximum message length +#define CURL_MAX_MESSAGE_LENGTH (1024 * 1024 * 64) + +// Internal private thread class that performs request, notifies handler, +// and then commits suicide by deleting itself. +class HttpClient_Private_Request : NonCopyable +{ +public: + HttpClient_Private_Request(HttpClient *parent,const char *method,const std::string &url,const std::map<std::string,std::string> &headers,unsigned int timeout,void (*handler)(void *,int,const std::string &,const std::string &),void *arg) : + _url(url), + _headers(headers), + _timeout(timeout), + _handler(handler), + _arg(arg), + _parent(parent), + _pid(0), + _cancelled(false) + { + _myThread = Thread::start(this); + } + + ~HttpClient_Private_Request() + { + Mutex::Lock _l(_parent->_requests_m); + _parent->_requests.erase((HttpClient::Request)this); + } + + void threadMain() + { + char *curlArgs[1024]; + char buf[16384]; + fd_set readfds,writefds,errfds; + struct timeval tv; + + std::string curlPath; + for(int i=0;i<NUM_CURL_PATHS;++i) { + if (Utils::fileExists(CURL_PATHS[i])) { + curlPath = CURL_PATHS[i]; + break; + } + } + + if (!curlPath.length()) { + _doH(_arg,-1,_url,"unable to locate 'curl' binary in /usr/bin, /bin, /usr/local/bin, /usr/sbin, or /sbin"); + delete this; + return; + } + if (!_url.length()) { + _doH(_arg,-1,_url,"cannot fetch empty URL"); + delete this; + return; + } + + curlArgs[0] = const_cast <char *>(curlPath.c_str()); + curlArgs[1] = const_cast <char *>("-D"); + curlArgs[2] = const_cast <char *>("-"); // append headers before output + int argPtr = 3; + std::vector<std::string> headerArgs; + for(std::map<std::string,std::string>::const_iterator h(_headers.begin());h!=_headers.end();++h) { + headerArgs.push_back(h->first); + headerArgs.back().append(": "); + headerArgs.back().append(h->second); + } + for(std::vector<std::string>::iterator h(headerArgs.begin());h!=headerArgs.end();++h) { + if (argPtr >= (1024 - 4)) // leave room for terminating NULL and URL + break; + curlArgs[argPtr++] = const_cast <char *>("-H"); + curlArgs[argPtr++] = const_cast <char *>(h->c_str()); + } + curlArgs[argPtr++] = const_cast <char *>(_url.c_str()); + curlArgs[argPtr] = (char *)0; + + if (_cancelled) { + delete this; + return; + } + + int curlStdout[2]; + int curlStderr[2]; + ::pipe(curlStdout); + ::pipe(curlStderr); + + _pid = (long)vfork(); + if (_pid < 0) { + // fork() failed + ::close(curlStdout[0]); + ::close(curlStdout[1]); + ::close(curlStderr[0]); + ::close(curlStderr[1]); + _doH(_arg,-1,_url,"unable to fork()"); + delete this; + return; + } else if (_pid > 0) { + // fork() succeeded, in parent process + ::close(curlStdout[1]); + ::close(curlStderr[1]); + fcntl(curlStdout[0],F_SETFL,O_NONBLOCK); + fcntl(curlStderr[0],F_SETFL,O_NONBLOCK); + + int exitCode = -1; + unsigned long long timesOutAt = Utils::now() + ((unsigned long long)_timeout * 1000ULL); + bool timedOut = false; + bool tooLong = false; + + while (!_cancelled) { + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_ZERO(&errfds); + FD_SET(curlStdout[0],&readfds); + FD_SET(curlStderr[0],&readfds); + FD_SET(curlStdout[0],&errfds); + FD_SET(curlStderr[0],&errfds); + tv.tv_sec = 1; + tv.tv_usec = 0; + select(std::max(curlStdout[0],curlStderr[0])+1,&readfds,&writefds,&errfds,&tv); + + if (FD_ISSET(curlStdout[0],&readfds)) { + int n = (int)::read(curlStdout[0],buf,sizeof(buf)); + if (n > 0) { + _body.append(buf,n); + // Reset timeout when data is read... + timesOutAt = Utils::now() + ((unsigned long long)_timeout * 1000ULL); + } else if (n < 0) + break; + if (_body.length() > CURL_MAX_MESSAGE_LENGTH) { + tooLong = true; + break; + } + } + + if (FD_ISSET(curlStderr[0],&readfds)) + ::read(curlStderr[0],buf,sizeof(buf)); + + if (FD_ISSET(curlStdout[0],&errfds)||FD_ISSET(curlStderr[0],&errfds)) + break; + + if (Utils::now() >= timesOutAt) { + timedOut = true; + break; + } + + if (waitpid(_pid,&exitCode,WNOHANG) > 0) { + for(;;) { + // Drain output... + int n = (int)::read(curlStdout[0],buf,sizeof(buf)); + if (n <= 0) + break; + else { + _body.append(buf,n); + if (_body.length() > CURL_MAX_MESSAGE_LENGTH) { + tooLong = true; + break; + } + } + } + _pid = 0; + break; + } + } + + if (_pid > 0) { + ::kill(_pid,SIGKILL); + waitpid(_pid,&exitCode,0); + } + _pid = 0; + + ::close(curlStdout[0]); + ::close(curlStderr[0]); + + if (timedOut) + _doH(_arg,-1,_url,"connection timed out"); + else if (tooLong) + _doH(_arg,-1,_url,"response too long"); + else if (exitCode) + _doH(_arg,-1,_url,"connection failed (curl returned non-zero exit code)"); + else { + unsigned long idx = 0; + + // Grab status line and headers, which will prefix output on + // success and will end with an empty line. + std::vector<std::string> headers; + headers.push_back(std::string()); + while (idx < _body.length()) { + char c = _body[idx++]; + if (c == '\n') { + if (!headers.back().length()) { + headers.pop_back(); + break; + } else headers.push_back(std::string()); + } else if (c != '\r') + headers.back().push_back(c); + } + if (headers.empty()||(!headers.front().length())) { + _doH(_arg,-1,_url,"HTTP response empty"); + delete this; + return; + } + + // Parse first line -- HTTP status code and response + size_t scPos = headers.front().find(' '); + if (scPos == std::string::npos) { + _doH(_arg,-1,_url,"invalid HTTP response (no status line)"); + delete this; + return; + } + ++scPos; + unsigned int rcode = Utils::strToUInt(headers.front().substr(scPos,3).c_str()); + if ((!rcode)||(rcode > 999)) { + _doH(_arg,-1,_url,"invalid HTTP response (invalid response code)"); + delete this; + return; + } + + // Serve up the resulting data to the handler + if (rcode == 200) + _doH(_arg,rcode,_url,_body.substr(idx)); + else if ((scPos + 4) < headers.front().length()) + _doH(_arg,rcode,_url,headers.front().substr(scPos+4)); + else _doH(_arg,rcode,_url,"(no status message from server)"); + } + + delete this; + return; + } else { + // fork() succeeded, in child process + ::dup2(curlStdout[1],STDOUT_FILENO); + ::close(curlStdout[1]); + ::dup2(curlStderr[1],STDERR_FILENO); + ::close(curlStderr[1]); + ::execv(curlPath.c_str(),curlArgs); + ::exit(-1); // only reached if execv() fails + } + } + + inline void cancel() + { + { + Mutex::Lock _l(_cancelled_m); + _cancelled = true; + if (_pid > 0) + ::kill(_pid,SIGKILL); + } + Thread::join(_myThread); + } + +private: + inline void _doH(void *arg,int code,const std::string &url,const std::string &body) + { + Mutex::Lock _l(_cancelled_m); + try { + if ((!_cancelled)&&(_handler)) + _handler(arg,code,url,body); + } catch ( ... ) {} + } + + const std::string _url; + std::string _body; + std::map<std::string,std::string> _headers; + unsigned int _timeout; + void (*_handler)(void *,int,const std::string &,const std::string &); + void *_arg; + HttpClient *_parent; + long _pid; + volatile bool _cancelled; + Mutex _cancelled_m; + Thread _myThread; +}; + +#endif // __UNIX_LIKE__ + +#ifdef __WINDOWS__ + +#define WIN_MAX_MESSAGE_LENGTH (1024 * 1024 * 64) + +// Internal private thread class that performs request, notifies handler, +// and then commits suicide by deleting itself. +class HttpClient_Private_Request : NonCopyable +{ +public: + HttpClient_Private_Request(HttpClient *parent,const char *method,const std::string &url,const std::map<std::string,std::string> &headers,unsigned int timeout,void (*handler)(void *,int,const std::string &,const std::string &),void *arg) : + _url(url), + _headers(headers), + _timeout(timeout), + _handler(handler), + _arg(arg), + _parent(parent), + _hRequest((HINTERNET)0) + { + _myThread = Thread::start(this); + } + + ~HttpClient_Private_Request() + { + Mutex::Lock _l(_parent->_requests_m); + _parent->_requests.erase((HttpClient::Request)this); + } + + void threadMain() + { + HINTERNET hSession = (HINTERNET)0; + HINTERNET hConnect = (HINTERNET)0; + HINTERNET hRequest = (HINTERNET)0; + + try { + hSession = WinHttpOpen(L"ZeroTier One HttpClient/1.0 (WinHttp)",WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,WINHTTP_NO_PROXY_NAME,WINHTTP_NO_PROXY_BYPASS,0); + if (!hSession) { + _handler(_arg,-1,_url,"WinHttpOpen() failed"); + goto closeAndReturnFromHttp; + } + int timeoutMs = (int)_timeout * 1000; + WinHttpSetTimeouts(hSession,timeoutMs,timeoutMs,timeoutMs,timeoutMs); + + std::wstring_convert< std::codecvt_utf8<wchar_t> > wcconv; + std::wstring wurl(wcconv.from_bytes(_url)); + + URL_COMPONENTS uc; + memset(&uc,0,sizeof(uc)); + uc.dwStructSize = sizeof(uc); + uc.dwSchemeLength = -1; + uc.dwHostNameLength = -1; + uc.dwUrlPathLength = -1; + uc.dwExtraInfoLength = -1; + if (!WinHttpCrackUrl(wurl.c_str(),(DWORD)wurl.length(),0,&uc)) { + _handler(_arg,-1,_url,"unable to parse URL: WinHttpCrackUrl() failed"); + goto closeAndReturnFromHttp; + } + if ((!uc.lpszHostName)||(!uc.lpszUrlPath)||(!uc.lpszScheme)||(uc.dwHostNameLength <= 0)||(uc.dwUrlPathLength <= 0)||(uc.dwSchemeLength <= 0)) { + _handler(_arg,-1,_url,"unable to parse URL: missing scheme, host name, or path"); + goto closeAndReturnFromHttp; + } + std::wstring urlScheme(uc.lpszScheme,uc.dwSchemeLength); + std::wstring urlHostName(uc.lpszHostName,uc.dwHostNameLength); + std::wstring urlPath(uc.lpszUrlPath,uc.dwUrlPathLength); + if ((uc.lpszExtraInfo)&&(uc.dwExtraInfoLength > 0)) + urlPath.append(uc.lpszExtraInfo,uc.dwExtraInfoLength); + + if (urlScheme != L"http") { + _handler(_arg,-1,_url,"only 'http' scheme is supported"); + goto closeAndReturnFromHttp; + } + + hConnect = WinHttpConnect(hSession,urlHostName.c_str(),((uc.nPort > 0) ? uc.nPort : 80),0); + if (!hConnect) { + _handler(_arg,-1,_url,"connection failed"); + goto closeAndReturnFromHttp; + } + + { + Mutex::Lock _rl(_hRequest_m); + _hRequest = WinHttpOpenRequest(hConnect,L"GET",urlPath.c_str(),NULL,WINHTTP_NO_REFERER,WINHTTP_DEFAULT_ACCEPT_TYPES,0); + if (!_hRequest) { + _handler(_arg,-1,_url,"error sending request (1)"); + goto closeAndReturnFromHttp; + } + if (!WinHttpSendRequest(_hRequest,WINHTTP_NO_ADDITIONAL_HEADERS,0,WINHTTP_NO_REQUEST_DATA,0,0,0)) { + _handler(_arg,-1,_url,"error sending request (2)"); + goto closeAndReturnFromHttp; + } + hRequest = _hRequest; + } + + if (WinHttpReceiveResponse(hRequest,NULL)) { + DWORD dwStatusCode = 0; + DWORD dwTmp = sizeof(dwStatusCode); + WinHttpQueryHeaders(hRequest,WINHTTP_QUERY_STATUS_CODE| WINHTTP_QUERY_FLAG_NUMBER,NULL,&dwStatusCode,&dwTmp,NULL); + + DWORD dwSize; + do { + dwSize = 0; + if (!WinHttpQueryDataAvailable(hRequest,&dwSize)) { + _handler(_arg,-1,_url,"receive error (1)"); + goto closeAndReturnFromHttp; + } + + { + Mutex::Lock _rl(_hRequest_m); + if (!_hRequest) { + _handler(_arg,-1,_url,"request cancelled"); + goto closeAndReturnFromHttp; + } + } + + char *outBuffer = new char[dwSize]; + DWORD dwRead = 0; + if (!WinHttpReadData(hRequest,(LPVOID)outBuffer,dwSize,&dwRead)) { + _handler(_arg,-1,_url,"receive error (2)"); + goto closeAndReturnFromHttp; + } + + { + Mutex::Lock _rl(_hRequest_m); + if (!_hRequest) { + _handler(_arg,-1,_url,"request cancelled"); + goto closeAndReturnFromHttp; + } + + _body.append(outBuffer,dwRead); + delete [] outBuffer; + if (_body.length() > WIN_MAX_MESSAGE_LENGTH) { + _handler(_arg,-1,_url,"result too large"); + goto closeAndReturnFromHttp; + } + } + } while ((dwSize > 0)&&(_hRequest)); + + { + Mutex::Lock _rl(_hRequest_m); + if (!_hRequest) { + _handler(_arg,-1,_url,"request cancelled"); + goto closeAndReturnFromHttp; + } + + _handler(_arg,dwStatusCode,_url,_body); + } + } else { + _handler(_arg,-1,_url,"receive response failed"); + } + } catch ( ... ) { + _handler(_arg,-1,_url,"unexpected exception"); + } + +closeAndReturnFromHttp: + { + Mutex::Lock _rl(_hRequest_m); + if (_hRequest) { + WinHttpCloseHandle(_hRequest); + _hRequest = (HINTERNET)0; + } + } + if (hConnect) + WinHttpCloseHandle(hConnect); + if (hSession) + WinHttpCloseHandle(hSession); + delete this; + return; + } + + inline void cancel() + { + Mutex::Lock _rl(_hRequest_m); + if (_hRequest) { + WinHttpCloseHandle(_hRequest); + _hRequest = (HINTERNET)0; + } + } + + const std::string _url; + std::string _body; + std::map<std::string,std::string> _headers; + unsigned int _timeout; + void (*_handler)(void *,int,const std::string &,const std::string &); + void *_arg; + HttpClient *_parent; + HINTERNET _hRequest; + Mutex _hRequest_m; + Thread _myThread; +}; + +#endif // __WINDOWS__ + +const std::map<std::string,std::string> HttpClient::NO_HEADERS; + +HttpClient::HttpClient() +{ +} + +HttpClient::~HttpClient() +{ + std::set<Request> reqs; + { + Mutex::Lock _l(_requests_m); + reqs = _requests; + } + + for(std::set<Request>::iterator r(reqs.begin());r!=reqs.end();++r) + this->cancel(*r); + + for(;;) { + _requests_m.lock(); + if (_requests.empty()) { + _requests_m.unlock(); + break; + } else { + _requests_m.unlock(); + Thread::sleep(250); + } + } +} + +void HttpClient::cancel(HttpClient::Request req) +{ + Mutex::Lock _l(_requests_m); + if (_requests.count(req) == 0) + return; + ((HttpClient_Private_Request *)req)->cancel(); +} + +HttpClient::Request HttpClient::_do( + const char *method, + const std::string &url, + const std::map<std::string,std::string> &headers, + unsigned int timeout, + void (*handler)(void *,int,const std::string &,const std::string &), + void *arg) +{ + HttpClient::Request r = (HttpClient::Request)(new HttpClient_Private_Request(this,method,url,headers,timeout,handler,arg)); + Mutex::Lock _l(_requests_m); + _requests.insert(r); + return r; +} + +} // namespace ZeroTier diff --git a/osdep/HttpClient.hpp b/osdep/HttpClient.hpp new file mode 100644 index 00000000..925ce76e --- /dev/null +++ b/osdep/HttpClient.hpp @@ -0,0 +1,111 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef ZT_HTTPCLIENT_HPP +#define ZT_HTTPCLIENT_HPP + +#include <string> +#include <map> +#include <set> + +#include "Constants.hpp" +#include "Mutex.hpp" + +namespace ZeroTier { + +class HttpClient_Private_Request; + +/** + * HTTP client that does queries in the background + * + * The handler method takes the following arguments: an arbitrary pointer, an + * HTTP response code, the URL queried, whether or not the message body was + * stored on disk, and the message body. + * + * If stored on disk, the body string contains the path and the file must be + * moved or deleted by the receiver when it's done. If an error occurs, the + * response code will be negative and the body will be the error message. + * + * All headers in the returned headers map will have their header names + * converted to lower case, e.g. "content-type". + * + * Currently only the "http" transport is guaranteed to be supported on all + * platforms. + */ +class HttpClient +{ +public: + friend class HttpClient_Private_Request; + typedef void * Request; + + HttpClient(); + ~HttpClient(); + + /** + * Empty map for convenience use + */ + static const std::map<std::string,std::string> NO_HEADERS; + + /** + * Request a URL using the GET method + */ + inline Request GET( + const std::string &url, + const std::map<std::string,std::string> &headers, + unsigned int timeout, + void (*handler)(void *,int,const std::string &,const std::string &), + void *arg) + { + return _do("GET",url,headers,timeout,handler,arg); + } + + /** + * Cancel a request + * + * If the request is not active, this does nothing. This may take some time + * depending on HTTP implementation. It may also not kill instantly, but + * it will prevent the handler function from ever being called and cause the + * request to die silently when complete. + */ + void cancel(Request req); + +private: + Request _do( + const char *method, + const std::string &url, + const std::map<std::string,std::string> &headers, + unsigned int timeout, + void (*handler)(void *,int,const std::string &,const std::string &), + void *arg); + + std::set<Request> _requests; + Mutex _requests_m; +}; + +} // namespace ZeroTier + +#endif diff --git a/osdep/SoftwareUpdater.cpp b/osdep/SoftwareUpdater.cpp new file mode 100644 index 00000000..345fc28e --- /dev/null +++ b/osdep/SoftwareUpdater.cpp @@ -0,0 +1,280 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <stdexcept> + +#include "../version.h" + +#include "Constants.hpp" +#include "SoftwareUpdater.hpp" +#include "Dictionary.hpp" +#include "C25519.hpp" +#include "Identity.hpp" +#include "Logger.hpp" +#include "RuntimeEnvironment.hpp" +#include "Thread.hpp" +#include "Node.hpp" +#include "Utils.hpp" +#include "HttpClient.hpp" + +#ifdef __UNIX_LIKE__ +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#endif + +namespace ZeroTier { + +SoftwareUpdater::SoftwareUpdater(const RuntimeEnvironment *renv) : + RR(renv), + _myVersion(packVersion(ZEROTIER_ONE_VERSION_MAJOR,ZEROTIER_ONE_VERSION_MINOR,ZEROTIER_ONE_VERSION_REVISION)), + _lastUpdateAttempt(0), + _status(UPDATE_STATUS_IDLE), + _die(false), + _lock() +{ +} + +SoftwareUpdater::~SoftwareUpdater() +{ + _die = true; + for(;;) { + _lock.lock(); + bool ip = (_status != UPDATE_STATUS_IDLE); + _lock.unlock(); + if (ip) + Thread::sleep(500); + else break; + } +} + +void SoftwareUpdater::cleanOldUpdates() +{ + std::string updatesDir(RR->homePath + ZT_PATH_SEPARATOR_S + "updates.d"); + std::map<std::string,bool> dl(Utils::listDirectory(updatesDir.c_str())); + for(std::map<std::string,bool>::iterator i(dl.begin());i!=dl.end();++i) { + if (!i->second) + Utils::rm((updatesDir + ZT_PATH_SEPARATOR_S + i->first).c_str()); + } +} + +void SoftwareUpdater::sawRemoteVersion(unsigned int vmaj,unsigned int vmin,unsigned int rev) +{ + const uint64_t tmp = packVersion(vmaj,vmin,rev); + if (tmp > _myVersion) { + Mutex::Lock _l(_lock); + if ((_status == UPDATE_STATUS_IDLE)&&(!_die)&&(ZT_DEFAULTS.updateLatestNfoURL.length())) { + const uint64_t now = Utils::now(); + if ((now - _lastUpdateAttempt) >= ZT_UPDATE_MIN_INTERVAL) { + _lastUpdateAttempt = now; + _status = UPDATE_STATUS_GETTING_NFO; + RR->http->GET(ZT_DEFAULTS.updateLatestNfoURL,HttpClient::NO_HEADERS,ZT_UPDATE_HTTP_TIMEOUT,&_cbHandleGetLatestVersionInfo,this); + } + } + } +} + +void SoftwareUpdater::checkNow() +{ + Mutex::Lock _l(_lock); + if (_status == UPDATE_STATUS_IDLE) { + _lastUpdateAttempt = Utils::now(); + _status = UPDATE_STATUS_GETTING_NFO; + RR->http->GET(ZT_DEFAULTS.updateLatestNfoURL,HttpClient::NO_HEADERS,ZT_UPDATE_HTTP_TIMEOUT,&_cbHandleGetLatestVersionInfo,this); + } +} + +const char *SoftwareUpdater::parseNfo( + const char *nfoText, + unsigned int &vMajor, + unsigned int &vMinor, + unsigned int &vRevision, + Address &signedBy, + std::string &signature, + std::string &url) +{ + try { + Dictionary nfo(nfoText); + + vMajor = Utils::strToUInt(nfo.get("vMajor").c_str()); + vMinor = Utils::strToUInt(nfo.get("vMinor").c_str()); + vRevision = Utils::strToUInt(nfo.get("vRevision").c_str()); + signedBy = nfo.get("signedBy"); + signature = Utils::unhex(nfo.get("ed25519")); + url = nfo.get("url"); + + if (signature.length() != ZT_C25519_SIGNATURE_LEN) + return "bad ed25519 signature, invalid length"; + if ((url.length() <= 7)||(url.substr(0,7) != "http://")) + return "invalid URL, must begin with http://"; + + return (const char *)0; + } catch ( ... ) { + return "invalid NFO file format or one or more required fields missing"; + } +} + +bool SoftwareUpdater::validateUpdate( + const void *data, + unsigned int len, + const Address &signedBy, + const std::string &signature) +{ + std::map< Address,Identity >::const_iterator updateAuthority = ZT_DEFAULTS.updateAuthorities.find(signedBy); + if (updateAuthority == ZT_DEFAULTS.updateAuthorities.end()) + return false; + return updateAuthority->second.verify(data,len,signature.data(),(unsigned int)signature.length()); +} + +void SoftwareUpdater::_cbHandleGetLatestVersionInfo(void *arg,int code,const std::string &url,const std::string &body) +{ + SoftwareUpdater *upd = (SoftwareUpdater *)arg; + const RuntimeEnvironment *RR = (const RuntimeEnvironment *)upd->RR; + Mutex::Lock _l(upd->_lock); + + if ((upd->_die)||(upd->_status != UPDATE_STATUS_GETTING_NFO)) { + upd->_status = UPDATE_STATUS_IDLE; + return; + } + + if (code != 200) { + LOG("software update check failed: server responded with code %d",code); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + + try { + unsigned int vMajor = 0,vMinor = 0,vRevision = 0; + Address signedBy; + std::string signature,url; + + const char *err = parseNfo(body.c_str(),vMajor,vMinor,vRevision,signedBy,signature,url); + + if (err) { + LOG("software update check aborted: .nfo file parse error: %s",err); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + + if (!ZT_DEFAULTS.updateAuthorities.count(signedBy)) { + LOG("software update check aborted: .nfo file specifies unknown signing authority"); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + +#ifndef ZT_ALWAYS_UPDATE /* for testing */ + if (packVersion(vMajor,vMinor,vRevision) <= upd->_myVersion) { + TRACE("software update check complete: version on update site is not newer than my version, no update necessary"); + upd->_status = UPDATE_STATUS_IDLE; + return; + } +#endif + + upd->_status = UPDATE_STATUS_GETTING_FILE; + upd->_signedBy = signedBy; + upd->_signature = signature; + + RR->http->GET(url,HttpClient::NO_HEADERS,ZT_UPDATE_HTTP_TIMEOUT,&_cbHandleGetLatestVersionBinary,arg); + } catch ( ... ) { + LOG("software update check failed: .nfo file invalid or missing field(s)"); + upd->_status = UPDATE_STATUS_IDLE; + } +} + +void SoftwareUpdater::_cbHandleGetLatestVersionBinary(void *arg,int code,const std::string &url,const std::string &body) +{ + SoftwareUpdater *upd = (SoftwareUpdater *)arg; + const RuntimeEnvironment *RR = (const RuntimeEnvironment *)upd->RR; + Mutex::Lock _l(upd->_lock); + + if (!validateUpdate(body.data(),(unsigned int)body.length(),upd->_signedBy,upd->_signature)) { + LOG("software update failed: update fetched from '%s' failed signature check (image size: %u)",url.c_str(),(unsigned int)body.length()); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + + size_t lastSlash = url.rfind('/'); + if (lastSlash == std::string::npos) { // sanity check, shouldn't happen + LOG("software update failed: invalid URL"); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + std::string updatesDir(RR->homePath + ZT_PATH_SEPARATOR_S + "updates.d"); + std::string updateFilename(url.substr(lastSlash + 1)); + if ((updateFilename.length() < 3)||(updateFilename.find("..") != std::string::npos)) { + LOG("software update failed: invalid URL: filename contains invalid characters"); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + for(std::string::iterator c(updateFilename.begin());c!=updateFilename.end();++c) { + // Only allow a list of whitelisted characters to make up the filename to prevent any + // path shenanigans, esp on Windows where / is not the path separator. + if (!strchr("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_.0123456789",*c)) { + LOG("software update failed: invalid URL: filename contains invalid characters"); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + } + std::string updatePath(updatesDir + ZT_PATH_SEPARATOR_S + updateFilename); +#ifdef __WINDOWS__ + CreateDirectoryA(updatesDir.c_str(),NULL); +#else + mkdir(updatesDir.c_str(),0755); +#endif + + FILE *upf = fopen(updatePath.c_str(),"wb"); + if (!upf) { + LOG("software update failed: unable to open %s for writing",updatePath.c_str()); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + if (fwrite(body.data(),body.length(),1,upf) != 1) { + LOG("software update failed: unable to write to %s",updatePath.c_str()); + upd->_status = UPDATE_STATUS_IDLE; + fclose(upf); + Utils::rm(updatePath); + return; + } + fclose(upf); + +#ifdef __UNIX_LIKE__ + ::chmod(updatePath.c_str(),0755); +#endif + + // We exit with this reason code and the path as the text. It is the + // caller's responsibility (main.c) to pick this up and do the right + // thing. + upd->_status = UPDATE_STATUS_IDLE; + RR->node->terminate(Node::NODE_RESTART_FOR_UPGRADE,updatePath.c_str()); +} + +} // namespace ZeroTier diff --git a/osdep/SoftwareUpdater.hpp b/osdep/SoftwareUpdater.hpp new file mode 100644 index 00000000..6c1e573a --- /dev/null +++ b/osdep/SoftwareUpdater.hpp @@ -0,0 +1,166 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef ZT_SOFTWAREUPDATER_HPP +#define ZT_SOFTWAREUPDATER_HPP + +#include <stdint.h> + +#include <string> + +#include "Constants.hpp" +#include "Mutex.hpp" +#include "Utils.hpp" +#include "Defaults.hpp" +#include "Address.hpp" + +namespace ZeroTier { + +class RuntimeEnvironment; + +/** + * Software updater + */ +class SoftwareUpdater +{ +public: + SoftwareUpdater(const RuntimeEnvironment *renv); + ~SoftwareUpdater(); + + /** + * Remove old updates in updates.d + */ + void cleanOldUpdates(); + + /** + * Called on each version message from a peer + * + * If a peer has a newer version, that causes an update to be started. + * + * @param vmaj Peer's major version + * @param vmin Peer's minor version + * @param rev Peer's revision + */ + void sawRemoteVersion(unsigned int vmaj,unsigned int vmin,unsigned int rev); + + /** + * Check for updates now regardless of last check time or version + * + * This only starts a check if one is not in progress. Otherwise it does + * nothing. + */ + void checkNow(); + + /** + * Check for updates now if it's been longer than ZT_UPDATE_MAX_INTERVAL + * + * This is called periodically from the main loop. + */ + inline void checkIfMaxIntervalExceeded(uint64_t now) + { + if ((now - _lastUpdateAttempt) >= ZT_UPDATE_MAX_INTERVAL) + checkNow(); + } + + /** + * Pack three-component version into a 64-bit integer + * + * @param vmaj Major version (0..65535) + * @param vmin Minor version (0..65535) + * @param rev Revision (0..65535) + * @return Version packed into an easily comparable 64-bit integer + */ + static inline uint64_t packVersion(unsigned int vmaj,unsigned int vmin,unsigned int rev) + throw() + { + return ( ((uint64_t)(vmaj & 0xffff) << 32) | ((uint64_t)(vmin & 0xffff) << 16) | (uint64_t)(rev & 0xffff) ); + } + + /** + * Parse NFO data from .nfo file on software update site + * + * The first argument is the NFO data, and all the remaining arguments are + * result parameters to be filled with results. If an error is returned the + * results in the parameters should be considered undefined. + * + * @param nfo NFO data + * @param vMajor Result: major version + * @param vMinor Result: minor version + * @param vRevision Result: revision number + * @param signedBy Result: signing identity + * @param signature Result: Ed25519 signature data + * @param url Result: URL of update binary + * @return NULL on success or error message on failure + */ + static const char *parseNfo( + const char *nfoText, + unsigned int &vMajor, + unsigned int &vMinor, + unsigned int &vRevision, + Address &signedBy, + std::string &signature, + std::string &url); + + /** + * Validate an update once downloaded + * + * This obtains the identity corresponding to the address from the compiled-in + * list of valid signing identities. + * + * @param data Update data + * @param len Length of update data + * @param signedBy Signing authority address + * @param signature Signing authority signature + * @return True on validation success, false if rejected + */ + static bool validateUpdate( + const void *data, + unsigned int len, + const Address &signedBy, + const std::string &signature); + +private: + static void _cbHandleGetLatestVersionInfo(void *arg,int code,const std::string &url,const std::string &body); + static void _cbHandleGetLatestVersionBinary(void *arg,int code,const std::string &url,const std::string &body); + + const RuntimeEnvironment *RR; + const uint64_t _myVersion; + volatile uint64_t _lastUpdateAttempt; + volatile enum { + UPDATE_STATUS_IDLE, + UPDATE_STATUS_GETTING_NFO, + UPDATE_STATUS_GETTING_FILE + } _status; + volatile bool _die; + Address _signedBy; + std::string _signature; + Mutex _lock; +}; + +} // namespace ZeroTier + +#endif diff --git a/osdep/Thread.hpp b/osdep/Thread.hpp new file mode 100644 index 00000000..c8c99f29 --- /dev/null +++ b/osdep/Thread.hpp @@ -0,0 +1,203 @@ +/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2015 ZeroTier, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * --
+ *
+ * ZeroTier may be used and distributed under the terms of the GPLv3, which
+ * are available at: http://www.gnu.org/licenses/gpl-3.0.html
+ *
+ * If you would like to embed ZeroTier into a commercial application or
+ * redistribute it in a modified binary form, please contact ZeroTier Networks
+ * LLC. Start here: http://www.zerotier.com/
+ */
+
+#ifndef ZT_THREAD_HPP
+#define ZT_THREAD_HPP
+
+#include <stdexcept>
+
+#include "Constants.hpp"
+
+#ifdef __WINDOWS__
+
+#include <WinSock2.h>
+#include <Windows.h>
+#include <string.h>
+#include "Mutex.hpp"
+
+namespace ZeroTier {
+
+template<typename C>
+static DWORD WINAPI ___zt_threadMain(LPVOID lpParam)
+{
+ try {
+ ((C *)lpParam)->threadMain();
+ } catch ( ... ) {}
+ return 0;
+}
+
+class Thread
+{
+public:
+ Thread()
+ throw()
+ {
+ _th = NULL;
+ _tid = 0;
+ }
+
+ template<typename C>
+ static inline Thread start(C *instance)
+ throw(std::runtime_error)
+ {
+ Thread t;
+ t._th = CreateThread(NULL,0,&___zt_threadMain<C>,(LPVOID)instance,0,&t._tid);
+ if (t._th == NULL)
+ throw std::runtime_error("CreateThread() failed");
+ return t;
+ }
+
+ static inline void join(const Thread &t)
+ {
+ if (t._th != NULL) {
+ for(;;) {
+ DWORD ec = STILL_ACTIVE;
+ GetExitCodeThread(t._th,&ec);
+ if (ec == STILL_ACTIVE)
+ WaitForSingleObject(t._th,1000);
+ else break;
+ }
+ }
+ }
+
+ static inline void sleep(unsigned long ms)
+ {
+ Sleep((DWORD)ms);
+ }
+
+ // Not available on *nix platforms
+ static inline void cancelIO(const Thread &t)
+ {
+ if (t._th != NULL)
+ CancelSynchronousIo(t._th);
+ }
+
+ inline operator bool() const throw() { return (_th != NULL); }
+
+private:
+ HANDLE _th;
+ DWORD _tid;
+};
+
+} // namespace ZeroTier
+
+#else
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+#include <unistd.h>
+
+namespace ZeroTier {
+
+template<typename C>
+static void *___zt_threadMain(void *instance)
+{
+ try {
+ ((C *)instance)->threadMain();
+ } catch ( ... ) {}
+ return (void *)0;
+}
+
+/**
+ * A thread identifier, and static methods to start and join threads
+ */
+class Thread
+{
+public:
+ Thread()
+ throw()
+ {
+ memset(&_tid,0,sizeof(_tid));
+ _started = false;
+ }
+
+ Thread(const Thread &t)
+ throw()
+ {
+ memcpy(&_tid,&(t._tid),sizeof(_tid));
+ _started = t._started;
+ }
+
+ inline Thread &operator=(const Thread &t)
+ throw()
+ {
+ memcpy(&_tid,&(t._tid),sizeof(_tid));
+ _started = t._started;
+ return *this;
+ }
+
+ /**
+ * Start a new thread
+ *
+ * @param instance Instance whose threadMain() method gets called by new thread
+ * @return Thread identifier
+ * @throws std::runtime_error Unable to create thread
+ * @tparam C Class containing threadMain()
+ */
+ template<typename C>
+ static inline Thread start(C *instance)
+ throw(std::runtime_error)
+ {
+ Thread t;
+ t._started = true;
+ if (pthread_create(&t._tid,(const pthread_attr_t *)0,&___zt_threadMain<C>,instance))
+ throw std::runtime_error("pthread_create() failed, unable to create thread");
+ return t;
+ }
+
+ /**
+ * Join to a thread, waiting for it to terminate (does nothing on null Thread values)
+ *
+ * @param t Thread to join
+ */
+ static inline void join(const Thread &t)
+ {
+ if (t._started)
+ pthread_join(t._tid,(void **)0);
+ }
+
+ /**
+ * Sleep the current thread
+ *
+ * @param ms Number of milliseconds to sleep
+ */
+ static inline void sleep(unsigned long ms) { usleep(ms * 1000); }
+
+ inline operator bool() const throw() { return (_started); }
+
+private:
+ pthread_t _tid;
+ volatile bool _started;
+};
+
+} // namespace ZeroTier
+
+#endif // __WINDOWS__ / !__WINDOWS__
+
+#endif
|