/ libi2pd / Timestamp.cpp
Timestamp.cpp
  1  /*
  2  * Copyright (c) 2013-2024, The PurpleI2P Project
  3  *
  4  * This file is part of Purple i2pd project and licensed under BSD3
  5  *
  6  * See full license text in LICENSE file at top of project tree
  7  */
  8  
  9  #include <time.h>
 10  #include <stdio.h>
 11  #include <inttypes.h>
 12  #include <string.h>
 13  #include <chrono>
 14  #include <future>
 15  #include <boost/asio.hpp>
 16  #include <boost/algorithm/string.hpp>
 17  #include "Config.h"
 18  #include "Log.h"
 19  #include "RouterContext.h"
 20  #include "I2PEndian.h"
 21  #include "Timestamp.h"
 22  #include "util.h"
 23  
 24  #ifdef _WIN32
 25  	#ifndef _WIN64
 26  		#define _USE_32BIT_TIME_T
 27  	#endif
 28  #endif
 29  
 30  namespace i2p
 31  {
 32  namespace util
 33  {
 34  	static uint64_t GetLocalMillisecondsSinceEpoch ()
 35  	{
 36  		return std::chrono::duration_cast<std::chrono::milliseconds>(
 37  			std::chrono::system_clock::now().time_since_epoch()).count ();
 38  	}
 39  
 40  	static uint64_t GetLocalSecondsSinceEpoch ()
 41  	{
 42  		return std::chrono::duration_cast<std::chrono::seconds>(
 43  			std::chrono::system_clock::now().time_since_epoch()).count ();
 44  	}
 45  
 46  	static uint32_t GetLocalMinutesSinceEpoch ()
 47  	{
 48  		return std::chrono::duration_cast<std::chrono::minutes>(
 49  			std::chrono::system_clock::now().time_since_epoch()).count ();
 50  	}
 51  
 52  	static uint32_t GetLocalHoursSinceEpoch ()
 53  	{
 54  		return std::chrono::duration_cast<std::chrono::hours>(
 55  			std::chrono::system_clock::now().time_since_epoch()).count ();
 56  	}
 57  
 58  	static int64_t g_TimeOffset = 0; // in seconds
 59  
 60  	static void SyncTimeWithNTP (const std::string& address)
 61  	{
 62  		LogPrint (eLogInfo, "Timestamp: NTP request to ", address);
 63  		boost::asio::io_context service;
 64  		boost::system::error_code ec;
 65  		auto endpoints = boost::asio::ip::udp::resolver (service).resolve (address, "ntp", ec);
 66  		if (!ec)
 67  		{
 68  			bool found = false;
 69  			boost::asio::ip::udp::endpoint ep;
 70  			for (const auto& it: endpoints)
 71  			{
 72  				ep = it;
 73  				if (!ep.address ().is_unspecified ())
 74  				{
 75  					if (ep.address ().is_v4 ())
 76  					{
 77  						if (i2p::context.SupportsV4 ()) found = true;
 78  					}
 79  					else if (ep.address ().is_v6 ())
 80  					{
 81  						if (i2p::util::net::IsYggdrasilAddress (ep.address ()))
 82  						{
 83  							if (i2p::context.SupportsMesh ()) found = true;
 84  						}
 85  						else if (i2p::context.SupportsV6 ()) found = true;
 86  					}
 87  				}
 88  				if (found) break;
 89  			}
 90  			if (!found)
 91  			{
 92  				LogPrint (eLogError, "Timestamp: can't find compatible address for ", address);
 93  				return;
 94  			}
 95  
 96  			boost::asio::ip::udp::socket socket (service);
 97  			socket.open (ep.protocol (), ec);
 98  			if (!ec)
 99  			{
100  				uint8_t buf[48];// 48 bytes NTP request/response
101  				memset (buf, 0, 48);
102  				htobe32buf (buf, (3 << 27) | (3 << 24)); // RFC 4330
103  				size_t len = 0;
104  				try
105  				{
106  					socket.send_to (boost::asio::buffer (buf, 48), ep);
107  					int i = 0;
108  					while (!socket.available() && i < 10) // 10 seconds max
109  					{
110  						std::this_thread::sleep_for (std::chrono::seconds(1));
111  						i++;
112  					}
113  					if (socket.available ())
114  						len = socket.receive_from (boost::asio::buffer (buf, 48), ep);
115  				}
116  				catch (std::exception& e)
117  				{
118  					LogPrint (eLogError, "Timestamp: NTP error: ", e.what ());
119  				}
120  				if (len >= 8)
121  				{
122  					auto ourTs = GetLocalSecondsSinceEpoch ();
123  					uint32_t ts = bufbe32toh (buf + 32);
124  					if (ts > 2208988800U) ts -= 2208988800U; // 1/1/1970 from 1/1/1900
125  					g_TimeOffset = ts - ourTs;
126  					LogPrint (eLogInfo, "Timestamp: ", address, " time offset from system time is ", g_TimeOffset, " seconds");
127  				}
128  			}
129  			else
130  				LogPrint (eLogError, "Timestamp: Couldn't open UDP socket");
131  		}
132  		else
133  			LogPrint (eLogError, "Timestamp: Couldn't resolve address ", address);
134  	}
135  
136  	NTPTimeSync::NTPTimeSync (): m_IsRunning (false), m_Timer (m_Service)
137  	{
138  		i2p::config::GetOption("nettime.ntpsyncinterval", m_SyncInterval);
139  		std::string ntpservers; i2p::config::GetOption("nettime.ntpservers", ntpservers);
140  		boost::split (m_NTPServersList, ntpservers, boost::is_any_of(","), boost::token_compress_on);
141  	}
142  
143  	NTPTimeSync::~NTPTimeSync ()
144  	{
145  		Stop ();
146  	}
147  
148  	void NTPTimeSync::Start()
149  	{
150  		if (m_NTPServersList.size () > 0)
151  		{
152  			m_IsRunning = true;
153  			LogPrint(eLogInfo, "Timestamp: NTP time sync starting");
154  			boost::asio::post (m_Service, std::bind (&NTPTimeSync::Sync, this));
155  			m_Thread.reset (new std::thread (std::bind (&NTPTimeSync::Run, this)));
156  		}
157  		else
158  			LogPrint (eLogWarning, "Timestamp: No NTP server found");
159  	}
160  
161  	void NTPTimeSync::Stop ()
162  	{
163  		if (m_IsRunning)
164  		{
165  			LogPrint(eLogInfo, "Timestamp: NTP time sync stopping");
166  			m_IsRunning = false;
167  			m_Timer.cancel ();
168  			m_Service.stop ();
169  			if (m_Thread)
170  			{
171  				m_Thread->join ();
172  				m_Thread.reset (nullptr);
173  			}
174  		}
175  	}
176  
177  	void NTPTimeSync::Run ()
178  	{
179  		i2p::util::SetThreadName("Timesync");
180  
181  		while (m_IsRunning)
182  		{
183  			try
184  			{
185  				m_Service.run ();
186  			}
187  			catch (std::exception& ex)
188  			{
189  				LogPrint (eLogError, "Timestamp: NTP time sync exception: ", ex.what ());
190  			}
191  		}
192  	}
193  
194  	void NTPTimeSync::Sync ()
195  	{
196  		if (m_NTPServersList.size () > 0)
197  			SyncTimeWithNTP (m_NTPServersList[rand () % m_NTPServersList.size ()]);
198  		else
199  			m_IsRunning = false;
200  
201  		if (m_IsRunning)
202  		{
203  			m_Timer.expires_from_now (boost::posix_time::hours (m_SyncInterval));
204  			m_Timer.async_wait ([this](const boost::system::error_code& ecode)
205  			{
206  				if (ecode != boost::asio::error::operation_aborted)
207  					Sync ();
208  			});
209  		}
210  	}
211  
212  	uint64_t GetMillisecondsSinceEpoch ()
213  	{
214  		return GetLocalMillisecondsSinceEpoch () + g_TimeOffset*1000;
215  	}
216  
217  	uint64_t GetSecondsSinceEpoch ()
218  	{
219  		return GetLocalSecondsSinceEpoch () + g_TimeOffset;
220  	}
221  
222  	uint32_t GetMinutesSinceEpoch ()
223  	{
224  		return GetLocalMinutesSinceEpoch () + g_TimeOffset/60;
225  	}
226  
227  	uint32_t GetHoursSinceEpoch ()
228  	{
229  		return GetLocalHoursSinceEpoch () + g_TimeOffset/3600;
230  	}
231  
232  	uint64_t GetMonotonicMicroseconds()
233  	{
234  		return std::chrono::duration_cast<std::chrono::microseconds>(
235  			std::chrono::steady_clock::now().time_since_epoch()).count();
236  	}
237  
238  	uint64_t GetMonotonicMilliseconds()
239  	{
240  		return std::chrono::duration_cast<std::chrono::milliseconds>(
241  			std::chrono::steady_clock::now().time_since_epoch()).count();
242  	}
243  
244  	uint64_t GetMonotonicSeconds ()
245  	{
246  		return std::chrono::duration_cast<std::chrono::seconds>(
247  			std::chrono::steady_clock::now().time_since_epoch()).count();
248  	}	
249  	
250  	void GetCurrentDate (char * date)
251  	{
252  		GetDateString (GetSecondsSinceEpoch (), date);
253  	}
254  
255  	void GetNextDayDate (char * date)
256  	{
257  		GetDateString (GetSecondsSinceEpoch () + 24*60*60, date);
258  	}	
259  	
260  	void GetDateString (uint64_t timestamp, char * date)
261  	{
262  		using clock = std::chrono::system_clock;
263  		auto t = clock::to_time_t (clock::time_point (std::chrono::seconds(timestamp)));
264  		struct tm tm;
265  #ifdef _WIN32
266  		gmtime_s(&tm, &t);
267  		sprintf_s(date, 9, "%04i%02i%02i", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
268  #else
269  		gmtime_r(&t, &tm);
270  		sprintf(date, "%04i%02i%02i", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
271  #endif
272  	}
273  
274  	void AdjustTimeOffset (int64_t offset)
275  	{
276  		g_TimeOffset += offset;
277  	}
278  }
279  }