diff --git a/newcoind.cfg b/newcoind.cfg index 01b72581eb..179a14802a 100644 --- a/newcoind.cfg +++ b/newcoind.cfg @@ -85,6 +85,9 @@ # 192.168.0.1 3939 # 2001:0db8:0100:f101:0210:a4ff:fee3:9566 # +# [sntp_servers] +# IP address or domain of servers to use for time synchronization. +# # [peer_ip]: # IP address or domain to bind to allow external connections from peers. # Defaults to not allow external connections from peers. @@ -140,6 +143,11 @@ [debug_logfile] debug.log +[sntp_servers] +time.windows.com +us.pool.ntp.org +time.apple.com + [validation_seed] shh1D4oj5czH3PUEjYES8c7Bay3tE diff --git a/src/Application.cpp b/src/Application.cpp index dd4fd40a26..0ceef99b75 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -38,7 +38,8 @@ DatabaseCon::~DatabaseCon() Application::Application() : mUNL(mIOService), mNetOps(mIOService, &mMasterLedger), mTempNodeCache(16384, 90), mHashedObjectStore(16384, 300), - mRpcDB(NULL), mTxnDB(NULL), mLedgerDB(NULL), mWalletDB(NULL), mHashNodeDB(NULL), mNetNodeDB(NULL), + mSNTPClient(mIOService), mRpcDB(NULL), mTxnDB(NULL), mLedgerDB(NULL), mWalletDB(NULL), + mHashNodeDB(NULL), mNetNodeDB(NULL), mConnectionPool(mIOService), mPeerDoor(NULL), mRPCDoor(NULL) { RAND_bytes(mNonce256.begin(), mNonce256.size()); @@ -68,6 +69,8 @@ void Application::run() if (!theConfig.DEBUG_LOGFILE.empty()) Log::setLogFile(theConfig.DEBUG_LOGFILE); + mSNTPClient.init(theConfig.SNTP_SERVERS); + // // Construct databases. // diff --git a/src/Application.h b/src/Application.h index 14281abbff..3ba29dee22 100644 --- a/src/Application.h +++ b/src/Application.h @@ -16,6 +16,7 @@ #include "TaggedCache.h" #include "ValidationCollection.h" #include "Suppression.h" +#include "SNTPClient.h" #include "../database/database.h" @@ -50,6 +51,7 @@ class Application ValidationCollection mValidations; SuppressionTable mSuppressions; HashedObjectStore mHashedObjectStore; + SNTPClient mSNTPClient; DatabaseCon *mRpcDB, *mTxnDB, *mLedgerDB, *mWalletDB, *mHashNodeDB, *mNetNodeDB; diff --git a/src/Config.cpp b/src/Config.cpp index e8e81747d3..cf9c135bfe 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -23,6 +23,7 @@ #define SECTION_RPC_ALLOW_REMOTE "rpc_allow_remote" #define SECTION_RPC_IP "rpc_ip" #define SECTION_RPC_PORT "rpc_port" +#define SECTION_SNTP "sntp_servers" #define SECTION_UNL_DEFAULT "unl_default" #define SECTION_VALIDATION_QUORUM "validation_quorum" #define SECTION_VALIDATION_SEED "validation_seed" @@ -193,6 +194,12 @@ void Config::load() // sectionEntriesPrint(&IPS, SECTION_IPS); } + smtTmp = sectionEntries(secConfig, SECTION_SNTP); + if (smtTmp) + { + SNTP_SERVERS = *smtTmp; + } + (void) sectionSingleB(secConfig, SECTION_VALIDATORS_SITE, VALIDATORS_SITE); (void) sectionSingleB(secConfig, SECTION_PEER_IP, PEER_IP); diff --git a/src/Config.h b/src/Config.h index 85de649e2d..585980b92b 100644 --- a/src/Config.h +++ b/src/Config.h @@ -54,6 +54,7 @@ public: std::string VALIDATORS_SITE; // Where to find validators.txt on the Internet. std::vector VALIDATORS; // Validators from newcoind.cfg. std::vector IPS; // Peer IPs from newcoind.cfg. + std::vector SNTP_SERVERS; // SNTP servers from newcoind.cfg. // Network parameters int NETWORK_START_TIME; // The Unix time we start ledger 0. diff --git a/src/SNTPClient.cpp b/src/SNTPClient.cpp new file mode 100644 index 0000000000..75226b0750 --- /dev/null +++ b/src/SNTPClient.cpp @@ -0,0 +1,180 @@ +#include "SNTPClient.h" + +#include +#include + +#include "utils.h" +#include "Log.h" + +static uint8_t SNTPQueryData[48] = { + 0x1B,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +}; + +SNTPClient::SNTPClient(boost::asio::io_service& service) : + mIOService(service), mSocket(service), mTimer(service), mResolver(service), + mOffset(0), mLastOffsetUpdate((time_t) -1), mReceiveBuffer(256) +{ + mSocket.open(boost::asio::ip::udp::v4()); + mSocket.async_receive_from(boost::asio::buffer(mReceiveBuffer, 256), mReceiveEndpoint, + boost::bind(&SNTPClient::receivePacket, this, boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred)); + + mTimer.expires_from_now(boost::posix_time::seconds(1)); + mTimer.async_wait(boost::bind(&SNTPClient::timerEntry, this, boost::asio::placeholders::error)); +} + +void SNTPClient::resolveComplete(const boost::system::error_code& error, boost::asio::ip::udp::resolver::iterator it) +{ + if (!error) + { + boost::asio::ip::udp::resolver::iterator sel = it; + int i = 1; + while (++it != boost::asio::ip::udp::resolver::iterator()) + if ((rand() % ++i) == 0) + sel = it; + if (sel != boost::asio::ip::udp::resolver::iterator()) + { + boost::mutex::scoped_lock sl(mLock); + SNTPQuery& query = mQueries[*sel]; + time_t now = time(NULL); + if ((query.mLocalTimeSent == now) || ((query.mLocalTimeSent + 1) == now)) + { + Log(lsINFO) << "SNTP: Redundant query suppressed"; + return; + } + query.mReceivedReply = false; + query.mLocalTimeSent = now; + mSocket.async_send_to(boost::asio::buffer(SNTPQueryData, 256), *sel, + boost::bind(&SNTPClient::sendComplete, this, + boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); + } + } +} + +void SNTPClient::receivePacket(const boost::system::error_code& error, std::size_t bytes_xferd) +{ + if (!error) + { + boost::mutex::scoped_lock sl(mLock); + Log(lsINFO) << "SNTP: Packet from " << mReceiveEndpoint; + std::map::iterator query = mQueries.find(mReceiveEndpoint); + if (query == mQueries.end()) + Log(lsINFO) << "SNTP: Reply found without matching query"; + else if (query->second.mReceivedReply) + Log(lsINFO) << "SNTP: Duplicate response to query"; + else + { + query->second.mReceivedReply = true; + if (time(NULL) > (query->second.mLocalTimeSent + 1)) + Log(lsINFO) << "SNTP: Late response"; + else + if (bytes_xferd < 48) + Log(lsINFO) << "SNTP: Short reply (" << bytes_xferd << ") " << mReceiveBuffer.size(); + else + processReply(); + } + } + + mSocket.async_receive_from(boost::asio::buffer(mReceiveBuffer, 256), mReceiveEndpoint, + boost::bind(&SNTPClient::receivePacket, this, boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred)); +} + +void SNTPClient::sendComplete(const boost::system::error_code& error, std::size_t) +{ + if (error) + Log(lsINFO) << "SNTP: Send error"; +} + +void SNTPClient::processReply() +{ + assert(mReceiveBuffer.size() >= 48); + uint32 *recvBuffer = reinterpret_cast(&mReceiveBuffer.front()); + + unsigned info = ntohl(recvBuffer[0]); + int64_t timev = ntohl(recvBuffer[8]); + unsigned stratum = (info >> 16) & 0xff; + + if ((info >> 30) == 3) + { + Log(lsINFO) << "SNTP: Alarm condition"; + return; + } + if ((stratum == 0) || (stratum > 14)) + { + Log(lsINFO) << "SNTP: Unreasonable stratum"; + return; + } + + timev -= time(NULL); + timev -= 0x83AA7E80ULL; + Log(lsINFO) << "SNTP: Offset is " << timev; + + // WRITEME +} + +void SNTPClient::timerEntry(const boost::system::error_code& error) +{ + if (!error) + { + doQuery(); + mTimer.expires_from_now(boost::posix_time::seconds(10)); + mTimer.async_wait(boost::bind(&SNTPClient::timerEntry, this, boost::asio::placeholders::error)); + } +} + +void SNTPClient::addServer(const std::string& server) +{ + boost::mutex::scoped_lock sl(mLock); + mServers.push_back(std::make_pair(server, (time_t) -1)); +} + +void SNTPClient::init(const std::vector& servers) +{ + std::vector::const_iterator it = servers.begin(); + if (it == servers.end()) + { + Log(lsINFO) << "SNTP: no server specified"; + return; + } + do + addServer(*it++); + while (it != servers.end()); + queryAll(); +} + +void SNTPClient::queryAll() +{ + while(doQuery()) + nothing(); +} + +bool SNTPClient::doQuery() +{ + boost::mutex::scoped_lock sl(mLock); + std::vector< std::pair >::iterator best = mServers.end(); + for (std::vector< std::pair >::iterator it = mServers.begin(), end = best; + it != end; ++it) + if ((best == end) || (it->second == (time_t) -1) || (it->second < best->second)) + best = it; + if (best == mServers.end()) + { + Log(lsINFO) << "SNTP: No server to query"; + return false; + } + time_t now = time(NULL); + if ((best->second == now) || (best->second == (now - 1))) + { + Log(lsINFO) << "SNTP: All servers recently queried"; + return false; + } + best->second = now; + + boost::asio::ip::udp::resolver::query query(boost::asio::ip::udp::v4(), best->first, "ntp"); + mResolver.async_resolve(query, + boost::bind(&SNTPClient::resolveComplete, this, + boost::asio::placeholders::error, boost::asio::placeholders::iterator)); + Log(lsINFO) << "SNTP: Resolve pending for " << best->first; + return true; +} diff --git a/src/SNTPClient.h b/src/SNTPClient.h new file mode 100644 index 0000000000..8e6489a0a9 --- /dev/null +++ b/src/SNTPClient.h @@ -0,0 +1,58 @@ +#ifndef __SNTPCLIENT__ +#define __SNTPCLIENT__ + +#include +#include +#include + +#include +#include + +class SNTPQuery +{ +public: + bool mReceivedReply; + time_t mLocalTimeSent; + + SNTPQuery(time_t j = (time_t) -1) : mReceivedReply(false), mLocalTimeSent(j) { ; } +}; + +class SNTPClient +{ +public: + typedef boost::shared_ptr pointer; + +protected: + std::map mQueries; + boost::mutex mLock; + + boost::asio::io_service& mIOService; + boost::asio::ip::udp::socket mSocket; + boost::asio::deadline_timer mTimer; + boost::asio::ip::udp::resolver mResolver; + + std::vector< std::pair > mServers; + int mOffset; + time_t mLastOffsetUpdate; + + std::vector mReceiveBuffer; + boost::asio::ip::udp::endpoint mReceiveEndpoint; + + void receivePacket(const boost::system::error_code& error, std::size_t bytes); + void resolveComplete(const boost::system::error_code& error, boost::asio::ip::udp::resolver::iterator iterator); + void sentPacket(boost::shared_ptr, const boost::system::error_code&, std::size_t); + void timerEntry(const boost::system::error_code&); + void sendComplete(const boost::system::error_code& error, std::size_t bytesTransferred); + void processReply(); + +public: + SNTPClient(boost::asio::io_service& service); + void init(const std::vector& servers); + void addServer(const std::string& mServer); + + void queryAll(); + bool doQuery(); + bool getOffset(int& offset); +}; + +#endif