1 /*
2 * Copyright (c) 2013-2021, 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 {
GetLocalMillisecondsSinceEpoch()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 
GetLocalSecondsSinceEpoch()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 
GetLocalMinutesSinceEpoch()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 
GetLocalHoursSinceEpoch()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 
SyncTimeWithNTP(const std::string & address)60 	static void SyncTimeWithNTP (const std::string& address)
61 	{
62 		LogPrint (eLogInfo, "Timestamp: NTP request to ", address);
63 		boost::asio::io_service service;
64 		boost::system::error_code ec;
65 		auto it = boost::asio::ip::udp::resolver (service).resolve (
66 			boost::asio::ip::udp::resolver::query (address, "ntp"), ec);
67 		if (!ec)
68 		{
69 			bool found = false;
70 			boost::asio::ip::udp::resolver::iterator end;
71 			boost::asio::ip::udp::endpoint ep;
72 			while (it != end)
73 			{
74 				ep = *it;
75 				if (!ep.address ().is_unspecified ())
76 				{
77 					if (ep.address ().is_v4 ())
78 					{
79 						if (i2p::context.SupportsV4 ()) found = true;
80 					}
81 					else if (ep.address ().is_v6 ())
82 					{
83 						if (i2p::util::net::IsYggdrasilAddress (ep.address ()))
84 						{
85 							if (i2p::context.SupportsMesh ()) found = true;
86 						}
87 						else if (i2p::context.SupportsV6 ()) found = true;
88 					}
89 				}
90 				if (found) break;
91 				it++;
92 			}
93 			if (!found)
94 			{
95 				LogPrint (eLogError, "Timestamp: can't find compatible address for ", address);
96 				return;
97 			}
98 
99 			boost::asio::ip::udp::socket socket (service);
100 			socket.open (ep.protocol (), ec);
101 			if (!ec)
102 			{
103 				uint8_t buf[48];// 48 bytes NTP request/response
104 				memset (buf, 0, 48);
105 				htobe32buf (buf, (3 << 27) | (3 << 24)); // RFC 4330
106 				size_t len = 0;
107 				try
108 				{
109 					socket.send_to (boost::asio::buffer (buf, 48), ep);
110 					int i = 0;
111 					while (!socket.available() && i < 10) // 10 seconds max
112 					{
113 						std::this_thread::sleep_for (std::chrono::seconds(1));
114 						i++;
115 					}
116 					if (socket.available ())
117 						len = socket.receive_from (boost::asio::buffer (buf, 48), ep);
118 				}
119 				catch (std::exception& e)
120 				{
121 					LogPrint (eLogError, "Timestamp: NTP error: ", e.what ());
122 				}
123 				if (len >= 8)
124 				{
125 					auto ourTs = GetLocalSecondsSinceEpoch ();
126 					uint32_t ts = bufbe32toh (buf + 32);
127 					if (ts > 2208988800U) ts -= 2208988800U; // 1/1/1970 from 1/1/1900
128 					g_TimeOffset = ts - ourTs;
129 					LogPrint (eLogInfo, "Timestamp: ", address, " time offset from system time is ", g_TimeOffset, " seconds");
130 				}
131 			}
132 			else
133 				LogPrint (eLogError, "Timestamp: Couldn't open UDP socket");
134 		}
135 		else
136 			LogPrint (eLogError, "Timestamp: Couldn't resolve address ", address);
137 	}
138 
NTPTimeSync()139 	NTPTimeSync::NTPTimeSync (): m_IsRunning (false), m_Timer (m_Service)
140 	{
141 		i2p::config::GetOption("nettime.ntpsyncinterval", m_SyncInterval);
142 		std::string ntpservers; i2p::config::GetOption("nettime.ntpservers", ntpservers);
143 		boost::split (m_NTPServersList, ntpservers, boost::is_any_of(","), boost::token_compress_on);
144 	}
145 
~NTPTimeSync()146 	NTPTimeSync::~NTPTimeSync ()
147 	{
148 		Stop ();
149 	}
150 
Start()151 	void NTPTimeSync::Start()
152 	{
153 		if (m_NTPServersList.size () > 0)
154 		{
155 			m_IsRunning = true;
156 			LogPrint(eLogInfo, "Timestamp: NTP time sync starting");
157 			m_Service.post (std::bind (&NTPTimeSync::Sync, this));
158 			m_Thread.reset (new std::thread (std::bind (&NTPTimeSync::Run, this)));
159 		}
160 		else
161 			LogPrint (eLogWarning, "Timestamp: No NTP server found");
162 	}
163 
Stop()164 	void NTPTimeSync::Stop ()
165 	{
166 		if (m_IsRunning)
167 		{
168 			LogPrint(eLogInfo, "Timestamp: NTP time sync stopping");
169 			m_IsRunning = false;
170 			m_Timer.cancel ();
171 			m_Service.stop ();
172 			if (m_Thread)
173 			{
174 				m_Thread->join ();
175 				m_Thread.reset (nullptr);
176 			}
177 		}
178 	}
179 
Run()180 	void NTPTimeSync::Run ()
181 	{
182 		i2p::util::SetThreadName("Timesync");
183 
184 		while (m_IsRunning)
185 		{
186 			try
187 			{
188 				m_Service.run ();
189 			}
190 			catch (std::exception& ex)
191 			{
192 				LogPrint (eLogError, "Timestamp: NTP time sync exception: ", ex.what ());
193 			}
194 		}
195 	}
196 
Sync()197 	void NTPTimeSync::Sync ()
198 	{
199 		if (m_NTPServersList.size () > 0)
200 			SyncTimeWithNTP (m_NTPServersList[rand () % m_NTPServersList.size ()]);
201 		else
202 			m_IsRunning = false;
203 
204 		if (m_IsRunning)
205 		{
206 			m_Timer.expires_from_now (boost::posix_time::hours (m_SyncInterval));
207 			m_Timer.async_wait ([this](const boost::system::error_code& ecode)
208 			{
209 				if (ecode != boost::asio::error::operation_aborted)
210 					Sync ();
211 			});
212 		}
213 	}
214 
GetMillisecondsSinceEpoch()215 	uint64_t GetMillisecondsSinceEpoch ()
216 	{
217 		return GetLocalMillisecondsSinceEpoch () + g_TimeOffset*1000;
218 	}
219 
GetSecondsSinceEpoch()220 	uint64_t GetSecondsSinceEpoch ()
221 	{
222 		return GetLocalSecondsSinceEpoch () + g_TimeOffset;
223 	}
224 
GetMinutesSinceEpoch()225 	uint32_t GetMinutesSinceEpoch ()
226 	{
227 		return GetLocalMinutesSinceEpoch () + g_TimeOffset/60;
228 	}
229 
GetHoursSinceEpoch()230 	uint32_t GetHoursSinceEpoch ()
231 	{
232 		return GetLocalHoursSinceEpoch () + g_TimeOffset/3600;
233 	}
234 
GetCurrentDate(char * date)235 	void GetCurrentDate (char * date)
236 	{
237 		GetDateString (GetSecondsSinceEpoch (), date);
238 	}
239 
GetDateString(uint64_t timestamp,char * date)240 	void GetDateString (uint64_t timestamp, char * date)
241 	{
242 		using clock = std::chrono::system_clock;
243 		auto t = clock::to_time_t (clock::time_point (std::chrono::seconds(timestamp)));
244 		struct tm tm;
245 #ifdef _WIN32
246 		gmtime_s(&tm, &t);
247 		sprintf_s(date, 9, "%04i%02i%02i", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
248 #else
249 		gmtime_r(&t, &tm);
250 		sprintf(date, "%04i%02i%02i", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
251 #endif
252 	}
253 }
254 }
255