460 lines
12 KiB
C
460 lines
12 KiB
C
/*-
|
|
* 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";
|
|
}
|