/*- * Copyright (c) 2017-2021 Carsten Sonne Larsen * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include #include #include "sync.h" #include "config.h" #include "global.h" #include "setting.h" #include "message.h" #include "timer.h" #include "conv.h" #include "sntp.h" #include "text.h" #include "ptz.h" #include "mem.h" #include "net.h" #include "tz.h" #include "logmod.h" #define MODULENAME "Syncer" #define SYNCPROCNAME APP_SHORT_NAME " Synchronizer" #define POOLSUFFIX "pool.ntp.org" #define POOLPORT "123" struct AppCom { struct TimerInfo *TimerInfo; struct sntp *Client; int TimerSigBit; bool Restart; int PoolServerNumber; char *ServerName; char *ServerPort; }; static void SyncProc(void); static int SyncClock(void); static void ChooseServer(void); static const char *SntpErrorText(enum sntp_err error); static struct AppCom Synchronizer; volatile bool SynchronizerRunning = false; volatile struct timeval LastSync = {0, 0}; volatile struct timeval LastAdjust = {0, 0}; void StartSynchronizer(void) { bool running; struct Task *task; Forbid(); running = SynchronizerRunning; if (!running) { task = (struct Task *)CreateNewProcTags( NP_Entry, (IPTR)SyncProc, NP_Name, (IPTR)SYNCPROCNAME, NP_StackSize, 64 * 1024, NP_Input, 0, NP_Output, 0, NP_Error, 0, NP_CloseInput, FALSE, NP_CloseOutput, FALSE, NP_CloseError, FALSE, NP_WindowPtr, 0, NP_ConsoleTask, 0, NP_Cli, FALSE, TAG_DONE); SynchronizerRunning = (task != NULL); } Permit(); if (running) { LogDebug(Text2P, "Synchronizer", TextAlreadyRunning); } else if (task != NULL) { LogDebug("Created synchronizer process"); } else { LogError("Failed to create synchronizer process"); } } static int ComInit(void) { if (OpenSocketLibrary() != LIB_OK) { return COM_ERROR; } if (Synchronizer.Client != NULL) { sntp_destroy(Synchronizer.Client); Synchronizer.Client = NULL; } ChooseServer(); Synchronizer.Client = sntp_create( Synchronizer.ServerName, Synchronizer.ServerPort, NULL, NULL); if (Synchronizer.Client == NULL) { LogWarn("Could not create SNTP client"); return COM_ERROR; } return COM_OK; } static void ComDestroy(void) { if (Synchronizer.Client != NULL) { sntp_destroy(Synchronizer.Client); Synchronizer.Client = NULL; } CloseSocketLibrary(); } static void SendSync(void) { if (ComInit() == COM_OK) { if (SyncClock() == COM_OK) LogDebug("Sending next NTP request in %ld milliseconds", Settings->Interval); else LogDebug("Retry in %ld milliseconds", Settings->Interval); } ComDestroy(); } static void MsgLoop(void) { bool loop = true; bool restart = false; ULONG procPortSigMask = GetPortSignalMask(MSGPORT_SYNCER); ULONG timerSigMask = (1 << Synchronizer.TimerSigBit); ULONG sigMask = procPortSigMask | timerSigMask; SendSync(); do { ULONG sigrcvd = Wait(sigMask); if (sigrcvd & timerSigMask) { SendSync(); } if (sigrcvd & procPortSigMask) { struct ApplicationMesage *msg; while ((msg = (struct ApplicationMesage *)GetNewMessage(MSGPORT_SYNCER))) { switch (msg->MsgId) { case ATK_DISABLE: case ATK_SHUTDOWN: loop = false; break; case ATK_RESTART: restart = true; break; default: break; } ReplyMsg((struct Message *)msg); } } if (loop && restart) { struct timeval tv; tv.tv_secs = Settings->Interval / 1000; tv.tv_micro = Settings->Interval % 1000; SetInterruptTimerInterval(Synchronizer.TimerInfo, &tv); SendSync(); restart = false; } } while (loop); } static void SyncProc(void) { struct timeval tv; bool success; Synchronizer.TimerInfo = NULL; Synchronizer.Client = NULL; Synchronizer.TimerSigBit = -1; Synchronizer.Restart = false; Synchronizer.PoolServerNumber = -1; Synchronizer.ServerName = NULL; Synchronizer.ServerPort = NULL; Synchronizer.TimerSigBit = AllocSignal(-1); if (Synchronizer.TimerSigBit == -1) { LogError("Could not allocate signal for synchronizer"); return; } success = CreateMessagePort(MSGPORT_SYNCER); if (!success) { FreeSignal(Synchronizer.TimerSigBit); return; } Synchronizer.TimerInfo = CreateInterruptTimer( FindTask(NULL), Synchronizer.TimerSigBit); if (Synchronizer.TimerInfo == NULL) { LogError("Could not create timer for synchronizer"); FreeSignal(Synchronizer.TimerSigBit); DestroyMessagePort(MSGPORT_SYNCER); return; } tv.tv_secs = Settings->Interval / 1000; tv.tv_micro = Settings->Interval % 1000; SetInterruptTimerInterval(Synchronizer.TimerInfo, &tv); StartInterruptTimer(Synchronizer.TimerInfo); MsgLoop(); LogTrace("Loop exiting"); DeleteTimer(Synchronizer.TimerInfo); FreeSignal(Synchronizer.TimerSigBit); CloseSocketLibrary(); DestroyMessagePort(MSGPORT_SYNCER); if (Synchronizer.ServerName != NULL) { FreeMemSafe(Synchronizer.ServerName); Synchronizer.ServerName = NULL; LogTrace("ServerName cleared"); } if (Synchronizer.ServerPort != NULL) { FreeMemSafe(Synchronizer.ServerPort); Synchronizer.ServerPort = NULL; LogTrace("ServerPort cleared"); } Synchronizer.TimerInfo = NULL; LogTrace("Loop exited"); SynchronizerRunning = false; } static void ChooseServer(void) { int i; char *server = Settings->DestinationAddress; int len = StrLen(server); bool isPool = EndsWith(server, POOLSUFFIX) && !StartsWith(server, "0.") && !StartsWith(server, "1.") && !StartsWith(server, "2.") && !StartsWith(server, "3."); if (Synchronizer.ServerName != NULL) { FreeMemSafe(Synchronizer.ServerName); } if (Synchronizer.ServerPort != NULL) { FreeMemSafe(Synchronizer.ServerPort); } if (!isPool) { Synchronizer.ServerName = StrDupSafe(server); Synchronizer.ServerPort = StrDupSafe(Settings->DestinationPort); return; } Synchronizer.ServerName = AllocStringSafe(len + 3); Synchronizer.ServerPort = StrDupSafe(POOLPORT); do { i = RandomFast() % 4; } while (i < 0 || i == Synchronizer.PoolServerNumber); Synchronizer.PoolServerNumber = i; SNPrintf(Synchronizer.ServerName, len + 3, "%d.%s", i, server); LogNotice("Choosing pool server %s", Synchronizer.ServerName); } static int SyncClock(void) { struct timeval rtv, ltv, tv; long long lt, rt, dt, adt, th; struct ntptime nt; bool readOnly; int ret; int stratum; th = Settings->Threshold; readOnly = Settings->Readonly; LogInfo("Sending request to %s:%s", Synchronizer.ServerName, Synchronizer.ServerPort); ret = sntp_send(Synchronizer.Client); if (ret != SNTP_OK) { LogInfo("SNTP send failed: %s", SntpErrorText(ret)); if (ret == SNTP_DNSERR || ret == SNTP_SYSERR) { Synchronizer.Restart = true; } return COM_ERROR; } ret = sntp_poll(Synchronizer.Client, Settings->Timeout); if (ret != SNTP_OK) { LogWarn("SNTP poll failed: %s", SntpErrorText(ret)); return COM_ERROR; } ret = sntp_recv(Synchronizer.Client, &nt, &stratum); if (ret != SNTP_OK) { LogWarn("SNTP receive failed: %s", SntpErrorText(ret)); if (ret == SNTP_BACKOFF) { LogWarn("Increasing polling interval: %ld -> %ld", Settings->Interval, Settings->Interval * 2); Settings->Interval *= 2; } return COM_ERROR; } nt2tv(&nt, &rtv); GetTimeOfDay(<v); lt = 1000000LL * ltv.tv_secs + ltv.tv_micro; rt = 1000000LL * rtv.tv_secs + rtv.tv_micro; dt = rt - lt; adt = dt < 0 ? -dt : dt; if (!readOnly && adt < th) { static char out[MAXLONGLONGCHARSIZE]; static char out2[MAXLONGLONGCHARSIZE]; LogDebug("Stratum %ld server", (unsigned long)stratum); LongLongToStr(adt, out); LongLongToStr(th, out2); LogInfo("%s us < %s us, not setting clock", out, out2); } else if (!readOnly) { LogDebug("Stratum %ld server", (unsigned long)stratum); tv.tv_secs = rt / 1000000; tv.tv_micro = rt % 1000000; SetTimeOfDay(Synchronizer.TimerInfo, &tv); LastAdjust = rtv; { // Log after setting clock static char out[MAXLONGLONGCHARSIZE]; static char out2[MAXLONGLONGCHARSIZE]; LongLongToStr(adt, out); LongLongToStr(th, out2); LogNotice("%s us > %s us, setting software clock", out, out2); if (BattClockBase != NULL) { LogNotice("Setting hardware clock"); SaveTimeOfDay(&tv); } } } { static char out[MAXLONGLONGCHARSIZE]; LongLongToStr(rt, out); LogDebug("True time %s", out); LongLongToStr(lt, out); LogDebug("Kernel time %s", out); LongLongToStr(dt, out); LogDebug("Delta %s", out); } LastSync = rtv; SendWindowMessage(ATK_LASTSYNC); return COM_OK; } static const char *SntpErrorText(enum sntp_err error) { switch (error) { case SNTP_OK: return "OK"; case SNTP_SYSERR: return GetErrorText(); case SNTP_DNSERR: return GetHostErrorText(); case SNTP_NOREQ: return "No request sent"; case SNTP_NORESP: return "No response received"; case SNTP_BADRESP: return "Invalid response received"; case SNTP_LAME: return "Server is lame / unsynchronized"; case SNTP_BACKOFF: return "Polling too frequently"; default: return "Unknown error"; } return "Unknown error"; }