You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
459 lines
12 KiB
459 lines
12 KiB
/*- |
|
* Copyright (c) 2017-2021 Carsten Sonne Larsen <cs@innolan.net> |
|
* 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 <stddef.h> |
|
#include <stdbool.h> |
|
|
|
#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"; |
|
}
|
|
|