rippled
SNTPClock.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2012, 2013 Ripple Labs Inc.
5 
6  Permission to use, copy, modify, and/or distribute this software for any
7  purpose with or without fee is hereby granted, provided that the above
8  copyright notice and this permission notice appear in all copies.
9 
10  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 //==============================================================================
19 
20 #include <ripple/core/impl/SNTPClock.h>
21 #include <ripple/basics/Log.h>
22 #include <ripple/basics/random.h>
23 #include <ripple/beast/core/CurrentThreadName.h>
24 #include <boost/asio.hpp>
25 #include <boost/optional.hpp>
26 #include <cmath>
27 #include <deque>
28 #include <map>
29 #include <memory>
30 #include <mutex>
31 #include <thread>
32 
33 namespace ripple {
34 
35 static uint8_t SNTPQueryData[48] =
36 { 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 };
37 
38 using namespace std::chrono_literals;
39 // NTP query frequency - 4 minutes
40 auto constexpr NTP_QUERY_FREQUENCY = 4min;
41 
42 // NTP minimum interval to query same servers - 3 minutes
43 auto constexpr NTP_MIN_QUERY = 3min;
44 
45 // NTP sample window (should be odd)
46 #define NTP_SAMPLE_WINDOW 9
47 
48 // NTP timestamp constant
49 auto constexpr NTP_UNIX_OFFSET = 0x83AA7E80s;
50 
51 // NTP timestamp validity
53 
54 // SNTP packet offsets
55 #define NTP_OFF_INFO 0
56 #define NTP_OFF_ROOTDELAY 1
57 #define NTP_OFF_ROOTDISP 2
58 #define NTP_OFF_REFERENCEID 3
59 #define NTP_OFF_REFTS_INT 4
60 #define NTP_OFF_REFTS_FRAC 5
61 #define NTP_OFF_ORGTS_INT 6
62 #define NTP_OFF_ORGTS_FRAC 7
63 #define NTP_OFF_RECVTS_INT 8
64 #define NTP_OFF_RECVTS_FRAC 9
65 #define NTP_OFF_XMITTS_INT 10
66 #define NTP_OFF_XMITTS_FRAC 11
67 
69  : public SNTPClock
70 {
71 private:
72  template <class Duration>
74 
76 
77  struct Query
78  {
79  bool replied;
82 
83  explicit Query (sys_seconds j = sys_seconds::max())
84  : replied (false)
85  , sent (j)
86  {
87  }
88  };
89 
91  std::mutex mutable mutex_;
93  boost::asio::io_service io_service_;
94  boost::optional<
95  boost::asio::io_service::work> work_;
96 
98  boost::asio::ip::udp::socket socket_;
99  boost::asio::basic_waitable_timer<std::chrono::system_clock> timer_;
100  boost::asio::ip::udp::resolver resolver_;
106  boost::asio::ip::udp::endpoint ep_;
107 
108 public:
109  using error_code = boost::system::error_code;
110 
111  explicit
113  : j_ (j)
114  , work_(io_service_)
115  , socket_ (io_service_)
116  , timer_ (io_service_)
117  , resolver_ (io_service_)
118  , offset_ (0)
119  , lastUpdate_ (sys_seconds::max())
120  , buf_ (256)
121  {
122  }
123 
124  ~SNTPClientImp () override
125  {
126  if (thread_.joinable())
127  {
128  error_code ec;
129  timer_.cancel(ec);
130  socket_.cancel(ec);
131  work_ = boost::none;
132  thread_.join();
133  }
134  }
135 
136  //--------------------------------------------------------------------------
137 
138  void
139  run (const std::vector<std::string>& servers) override
140  {
142 
143  if (it == servers.end ())
144  {
145  JLOG(j_.info()) <<
146  "SNTP: no server specified";
147  return;
148  }
149 
150  {
151  std::lock_guard lock (mutex_);
152  for (auto const& item : servers)
153  servers_.emplace_back(
154  item, sys_seconds::max());
155  }
156  queryAll();
157 
158  using namespace boost::asio;
159  socket_.open (ip::udp::v4 ());
160  socket_.bind (ep_);
161  socket_.async_receive_from (buffer (buf_, 256),
162  ep_, std::bind(
163  &SNTPClientImp::onRead, this,
164  std::placeholders::_1,
165  std::placeholders::_2));
166  timer_.expires_from_now(NTP_QUERY_FREQUENCY);
167  timer_.async_wait(std::bind(
168  &SNTPClientImp::onTimer, this,
169  std::placeholders::_1));
170 
171  thread_ = std::thread(&SNTPClientImp::doRun, this);
172  }
173 
174  time_point
175  now() const override
176  {
177  std::lock_guard lock (mutex_);
178  using namespace std::chrono;
179  auto const when = time_point_cast<seconds>(clock_type::now());
180  if ((lastUpdate_ == sys_seconds::max()) ||
181  ((lastUpdate_ + NTP_TIMESTAMP_VALID) <
182  time_point_cast<seconds>(clock_type::now())))
183  return when;
184  return when + offset_;
185  }
186 
187  duration
188  offset() const override
189  {
190  std::lock_guard lock (mutex_);
191  return offset_;
192  }
193 
194  //--------------------------------------------------------------------------
195 
196  void doRun ()
197  {
198  beast::setCurrentThreadName("rippled: SNTPClock");
199  io_service_.run();
200  }
201 
202  void
203  onTimer (error_code const& ec)
204  {
205  using namespace boost::asio;
206  if (ec == error::operation_aborted)
207  return;
208  if (ec)
209  {
210  JLOG(j_.error()) <<
211  "SNTPClock::onTimer: " << ec.message();
212  return;
213  }
214 
215  doQuery ();
216  timer_.expires_from_now(NTP_QUERY_FREQUENCY);
217  timer_.async_wait(std::bind(
218  &SNTPClientImp::onTimer, this,
219  std::placeholders::_1));
220  }
221 
222  void
223  onRead (error_code const& ec, std::size_t bytes_xferd)
224  {
225  using namespace boost::asio;
226  using namespace std::chrono;
227  if (ec == error::operation_aborted)
228  return;
229 
230  // VFALCO Should we return on any error?
231  /*
232  if (ec)
233  return;
234  */
235 
236  if (! ec)
237  {
238  JLOG(j_.trace()) <<
239  "SNTP: Packet from " << ep_;
240  std::lock_guard lock (mutex_);
241  auto const query = queries_.find (ep_);
242  if (query == queries_.end ())
243  {
244  JLOG(j_.debug()) <<
245  "SNTP: Reply from " << ep_ << " found without matching query";
246  }
247  else if (query->second.replied)
248  {
249  JLOG(j_.debug()) <<
250  "SNTP: Duplicate response from " << ep_;
251  }
252  else
253  {
254  query->second.replied = true;
255 
256  if (time_point_cast<seconds>(clock_type::now()) > (query->second.sent + 1s))
257  {
258  JLOG(j_.warn()) <<
259  "SNTP: Late response from " << ep_;
260  }
261  else if (bytes_xferd < 48)
262  {
263  JLOG(j_.warn()) <<
264  "SNTP: Short reply from " << ep_ <<
265  " (" << bytes_xferd << ") " << buf_.size ();
266  }
267  else if (reinterpret_cast<std::uint32_t*>(
268  &buf_[0])[NTP_OFF_ORGTS_FRAC] !=
269  query->second.nonce)
270  {
271  JLOG(j_.warn()) <<
272  "SNTP: Reply from " << ep_ << "had wrong nonce";
273  }
274  else
275  {
276  processReply ();
277  }
278  }
279  }
280 
281  socket_.async_receive_from(buffer(buf_, 256),
282  ep_, std::bind(&SNTPClientImp::onRead, this,
283  std::placeholders::_1,
284  std::placeholders::_2));
285  }
286 
287  //--------------------------------------------------------------------------
288 
289  void addServer (std::string const& server)
290  {
291  std::lock_guard lock (mutex_);
292  servers_.push_back (std::make_pair (server, sys_seconds::max()));
293  }
294 
295  void queryAll ()
296  {
297  while (doQuery ())
298  {
299  }
300  }
301 
302  bool doQuery ()
303  {
304  std::lock_guard lock (mutex_);
305  auto best = servers_.end ();
306 
307  for (auto iter = servers_.begin (), end = best;
308  iter != end; ++iter)
309  if ((best == end) || (iter->second == sys_seconds::max()) ||
310  (iter->second < best->second))
311  best = iter;
312 
313  if (best == servers_.end ())
314  {
315  JLOG(j_.trace()) <<
316  "SNTP: No server to query";
317  return false;
318  }
319 
320  using namespace std::chrono;
321  auto now = time_point_cast<seconds>(clock_type::now());
322 
323  if ((best->second != sys_seconds::max()) && ((best->second + NTP_MIN_QUERY) >= now))
324  {
325  JLOG(j_.trace()) <<
326  "SNTP: All servers recently queried";
327  return false;
328  }
329 
330  best->second = now;
331 
332  boost::asio::ip::udp::resolver::query query(
333  boost::asio::ip::udp::v4 (), best->first, "ntp");
334  resolver_.async_resolve (query, std::bind (
336  std::placeholders::_1,
337  std::placeholders::_2));
338  JLOG(j_.trace()) <<
339  "SNTPClock: Resolve pending for " << best->first;
340  return true;
341  }
342 
343  void resolveComplete (error_code const& ec,
344  boost::asio::ip::udp::resolver::iterator it)
345  {
346  using namespace boost::asio;
347  if (ec == error::operation_aborted)
348  return;
349  if (ec)
350  {
351  JLOG(j_.trace()) <<
352  "SNTPClock::resolveComplete: " << ec.message();
353  return;
354  }
355 
356  assert (it != ip::udp::resolver::iterator());
357 
358  auto sel = it;
359  int i = 1;
360 
361  while (++it != ip::udp::resolver::iterator())
362  {
363  if (rand_int (i++) == 0)
364  sel = it;
365  }
366 
367  if (sel != ip::udp::resolver::iterator ())
368  {
369  std::lock_guard lock (mutex_);
370  Query& query = queries_[*sel];
371  using namespace std::chrono;
372  auto now = time_point_cast<seconds>(clock_type::now());
373 
374  if ((query.sent == now) || ((query.sent + 1s) == now))
375  {
376  // This can happen if the same IP address is reached through multiple names
377  JLOG(j_.trace()) <<
378  "SNTP: Redundant query suppressed";
379  return;
380  }
381 
382  query.replied = false;
383  query.sent = now;
384  query.nonce = rand_int<std::uint32_t>();
385  // The following line of code will overflow at 2036-02-07 06:28:16 UTC
386  // due to the 32 bit cast.
387  reinterpret_cast<std::uint32_t*> (SNTPQueryData)[NTP_OFF_XMITTS_INT] =
388  static_cast<std::uint32_t>((time_point_cast<seconds>(clock_type::now()) +
389  NTP_UNIX_OFFSET).time_since_epoch().count());
390  reinterpret_cast<std::uint32_t*> (SNTPQueryData)[NTP_OFF_XMITTS_FRAC] = query.nonce;
391  socket_.async_send_to(buffer(SNTPQueryData, 48),
392  *sel, std::bind (&SNTPClientImp::onSend, this,
393  std::placeholders::_1,
394  std::placeholders::_2));
395  }
396  }
397 
398  void onSend (error_code const& ec, std::size_t)
399  {
400  if (ec == boost::asio::error::operation_aborted)
401  return;
402 
403  if (ec)
404  {
405  JLOG(j_.warn()) <<
406  "SNTPClock::onSend: " << ec.message();
407  return;
408  }
409  }
410 
411  void processReply ()
412  {
413  using namespace std::chrono;
414  assert (buf_.size () >= 48);
415  std::uint32_t* recvBuffer = reinterpret_cast<std::uint32_t*> (&buf_.front ());
416 
417  unsigned info = ntohl (recvBuffer[NTP_OFF_INFO]);
418  auto timev = seconds{ntohl(recvBuffer[NTP_OFF_RECVTS_INT])};
419  unsigned stratum = (info >> 16) & 0xff;
420 
421  if ((info >> 30) == 3)
422  {
423  JLOG(j_.info()) <<
424  "SNTP: Alarm condition " << ep_;
425  return;
426  }
427 
428  if ((stratum == 0) || (stratum > 14))
429  {
430  JLOG(j_.info()) <<
431  "SNTP: Unreasonable stratum (" << stratum << ") from " << ep_;
432  return;
433  }
434 
435  using namespace std::chrono;
436  auto now = time_point_cast<seconds>(clock_type::now());
437  timev -= now.time_since_epoch();
438  timev -= NTP_UNIX_OFFSET;
439 
440  // add offset to list, replacing oldest one if appropriate
441  offsets_.push_back (timev);
442 
443  if (offsets_.size () >= NTP_SAMPLE_WINDOW)
444  offsets_.pop_front ();
445 
446  lastUpdate_ = now;
447 
448  // select median time
449  auto offsetList = offsets_;
450  std::sort(offsetList.begin(), offsetList.end());
451  auto j = offsetList.size ();
452  auto it = std::next(offsetList.begin (), j/2);
453  offset_ = *it;
454 
455  if ((j % 2) == 0)
456  offset_ = (offset_ + (*--it)) / 2;
457 
458  // debounce: small corrections likely
459  // do more harm than good
460  if ((offset_ == -1s) || (offset_ == 1s))
461  offset_ = 0s;
462 
463  if (timev != 0s || offset_ != 0s)
464  {
465  JLOG(j_.trace()) << "SNTP: Offset is " << timev.count() <<
466  ", new system offset is " << offset_.count();
467  }
468  }
469 };
470 
471 //------------------------------------------------------------------------------
472 
475 {
476  return std::make_unique<SNTPClientImp>(j);
477 }
478 
479 } // ripple
ripple::NTP_UNIX_OFFSET
constexpr auto NTP_UNIX_OFFSET
Definition: SNTPClock.cpp:49
ripple::SNTPClientImp::timer_
boost::asio::basic_waitable_timer< std::chrono::system_clock > timer_
Definition: SNTPClock.cpp:99
std::bind
T bind(T... args)
std::string
STL class.
ripple::SNTPClientImp::now
time_point now() const override
Definition: SNTPClock.cpp:175
ripple::SNTPClientImp::queries_
std::map< boost::asio::ip::udp::endpoint, Query > queries_
Definition: SNTPClock.cpp:97
ripple::SNTPClientImp::error_code
boost::system::error_code error_code
Definition: SNTPClock.cpp:109
beast::Journal::trace
Stream trace() const
Severity stream access functions.
Definition: Journal.h:287
ripple::SNTPClientImp::Query::sent
sys_seconds sent
Definition: SNTPClock.cpp:80
ripple::SNTPClientImp
Definition: SNTPClock.cpp:68
std::deque::pop_front
T pop_front(T... args)
std::vector
STL class.
std::map::find
T find(T... args)
std::vector::size
T size(T... args)
ripple::SNTPClientImp::Query
Definition: SNTPClock.cpp:77
std::chrono::seconds
ripple::rand_int
std::enable_if_t< std::is_integral< Integral >::value &&detail::is_engine< Engine >::value, Integral > rand_int(Engine &engine, Integral min, Integral max)
Return a uniformly distributed random integer.
Definition: ripple/basics/random.h:121
beast::Journal::warn
Stream warn() const
Definition: Journal.h:302
std::lock_guard
STL class.
ripple::SNTPClientImp::lastUpdate_
sys_seconds lastUpdate_
Definition: SNTPClock.cpp:103
ripple::NTP_QUERY_FREQUENCY
constexpr auto NTP_QUERY_FREQUENCY
Definition: SNTPClock.cpp:40
ripple::SNTPClientImp::onRead
void onRead(error_code const &ec, std::size_t bytes_xferd)
Definition: SNTPClock.cpp:223
ripple::SNTPClientImp::addServer
void addServer(std::string const &server)
Definition: SNTPClock.cpp:289
cmath
std::vector::front
T front(T... args)
std::sort
T sort(T... args)
ripple::SNTPClientImp::Query::replied
bool replied
Definition: SNTPClock.cpp:79
std::vector::push_back
T push_back(T... args)
ripple::SNTPClientImp::onSend
void onSend(error_code const &ec, std::size_t)
Definition: SNTPClock.cpp:398
ripple::SNTPClientImp::resolver_
boost::asio::ip::udp::resolver resolver_
Definition: SNTPClock.cpp:100
std::thread::joinable
T joinable(T... args)
ripple::SNTPClientImp::buf_
std::vector< uint8_t > buf_
Definition: SNTPClock.cpp:105
ripple::SNTPClientImp::socket_
boost::asio::ip::udp::socket socket_
Definition: SNTPClock.cpp:98
ripple::make_SNTPClock
std::unique_ptr< SNTPClock > make_SNTPClock(beast::Journal j)
Definition: SNTPClock.cpp:474
thread
ripple::SNTPClientImp::run
void run(const std::vector< std::string > &servers) override
Definition: SNTPClock.cpp:139
ripple::SNTPClientImp::j_
const beast::Journal j_
Definition: SNTPClock.cpp:90
ripple::NTP_MIN_QUERY
constexpr auto NTP_MIN_QUERY
Definition: SNTPClock.cpp:43
ripple::SNTPClientImp::processReply
void processReply()
Definition: SNTPClock.cpp:411
ripple::SNTPClientImp::servers_
std::vector< std::pair< std::string, sys_seconds > > servers_
Definition: SNTPClock.cpp:101
boost::asio
Definition: Overlay.h:42
ripple::SNTPClientImp::Query::Query
Query(sys_seconds j=sys_seconds::max())
Definition: SNTPClock.cpp:83
ripple::SNTPClientImp::ep_
boost::asio::ip::udp::endpoint ep_
Definition: SNTPClock.cpp:106
ripple::SNTPClientImp::offsets_
std::deque< std::chrono::seconds > offsets_
Definition: SNTPClock.cpp:104
beast::Journal::error
Stream error() const
Definition: Journal.h:307
beast::Journal::info
Stream info() const
Definition: Journal.h:297
std::chrono::time_point
deque
ripple::SNTPClientImp::Query::nonce
std::uint32_t nonce
Definition: SNTPClock.cpp:81
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:60
std::uint32_t
ripple::SNTPClientImp::doQuery
bool doQuery()
Definition: SNTPClock.cpp:302
map
memory
ripple::SNTPClientImp::io_service_
boost::asio::io_service io_service_
Definition: SNTPClock.cpp:93
ripple::SNTPClientImp::queryAll
void queryAll()
Definition: SNTPClock.cpp:295
ripple::SNTPClientImp::work_
boost::optional< boost::asio::io_service::work > work_
Definition: SNTPClock.cpp:95
beast::setCurrentThreadName
void setCurrentThreadName(std::string_view name)
Changes the name of the caller thread.
Definition: CurrentThreadName.cpp:113
std::vector::emplace_back
T emplace_back(T... args)
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::SNTPQueryData
static uint8_t SNTPQueryData[48]
Definition: SNTPClock.cpp:35
ripple::SNTPClientImp::offset
duration offset() const override
Definition: SNTPClock.cpp:188
std::vector::begin
T begin(T... args)
ripple::SNTPClientImp::mutex_
std::mutex mutex_
Definition: SNTPClock.cpp:91
ripple::SNTPClock
A clock based on system_clock and adjusted for SNTP.
Definition: SNTPClock.h:33
ripple::SNTPClientImp::onTimer
void onTimer(error_code const &ec)
Definition: SNTPClock.cpp:203
ripple::SNTPClientImp::doRun
void doRun()
Definition: SNTPClock.cpp:196
std::chrono::seconds::count
T count(T... args)
ripple::SNTPClientImp::thread_
std::thread thread_
Definition: SNTPClock.cpp:92
mutex
beast::Journal::debug
Stream debug() const
Definition: Journal.h:292
std::size_t
ripple::SNTPClientImp::SNTPClientImp
SNTPClientImp(beast::Journal j)
Definition: SNTPClock.cpp:112
std::make_pair
T make_pair(T... args)
std::vector::end
T end(T... args)
std::unique_ptr
STL class.
ripple::SNTPClientImp::resolveComplete
void resolveComplete(error_code const &ec, boost::asio::ip::udp::resolver::iterator it)
Definition: SNTPClock.cpp:343
ripple::SNTPClientImp::offset_
std::chrono::seconds offset_
Definition: SNTPClock.cpp:102
std::thread::join
T join(T... args)
ripple::SNTPClientImp::~SNTPClientImp
~SNTPClientImp() override
Definition: SNTPClock.cpp:124
ripple::NTP_TIMESTAMP_VALID
constexpr auto NTP_TIMESTAMP_VALID
Definition: SNTPClock.cpp:52
std::next
T next(T... args)
std::chrono