Sleds/K2Daemon/main.cpp

916 lines
26 KiB
C++

/*
Copyright (c) 2013, 2014 IOnU Security Inc. All rights reserved. Copyright (c) 2015, 2016 Sequence Logic, Inc.
Created August 2013 by Kendrick Webster
K2Daemon/main.cpp - Main module for low-latency high-throughput UDP
push notification daemon
*/
#include <errno.h>
#include <string.h>
#include <strings.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <chrono>
#include "global.h"
#include "Version.h"
#include "ClientCache.h"
#include "MongoDB.h"
#include "MongoNotifyQueue.h"
#include "MongoDeviceStatus.h"
#include "../libeye/eyelog.h"
#define INVALID_SOCKET (-1)
#define IONU_HOME_ENV_KEY "SEQUENCELOGICHOME"
#define IONU_HOME_DEFAULT "/sequencelogic"
#define IONU_LOGS_SUBDIR "logs/"
#define IONU_LOGFILE_NAME "K2Daemon.log"
#define IONU_LOG_MAX_FILES 6
#define IONU_LOG_MAX_FILE_SIZE_KB (64 * 1024)
#define DEFAULT_LOG_LEVEL Log::informational
#define LOG_SHORT_IDENTIFIER "K2Daemon"
#define COMMMAND_LINE_HELP "\
Command-line options (case insensitive):\n\
?|help|-help|--help - Print this help text and exit\n\
\n\
--daemon - Run in daemon (background) mode\n\
\n\
--dump-clients [<n_seconds>] - Log connected clients periodically\n\
If <n_seconds> is not specified, it defaults to 60\n\
If this switch is not supplied, clients are not dumped to the log\n\
\n\
--log-echo-stdout - Echo log text to stdout\n\
--log-echo-syslog - Echo log text to syslog\n\
\n\
--log-echo-udp514 - Echo log text to UDP(localhost:514)\n\
\n\
--log-file|--logfile <filespec> - Write log output to specified file\n\
default = {$SEQUENCELOGICHOME}/logs/K2Daemon.log\n\
If SEQUENCELOGICHOME is not set in the environment, \"/sequencelogic\" is used.\n\
If <filespec> is relative, {$SEQUENCELOGICHOME}/logs/ is prepended.\n\
If \"syslog\" is specified as <filespec> then use Linux syslog facility.\n\
\n\
--log-level <n|LEV|level> - Set log filter level:\n\
0 EMG emergency\n\
1 ALR alert\n\
2 CRI critical\n\
3 ERR error\n\
4 WRN warning\n\
5 NTC notice\n\
6 INF informational\n\
7 DBG debug\n\
8 DB2 debug2\n\
9 DB3 debug3\n\
default = informational\n\
\n\
--log-max-file-size <n> - Maximum log file size in KiB\n\
default = 65536 (64MiB)\n\
\n\
--log-max-files <n> - Maximum log file count\n\
default = 6\n\
\n\
--mongo-host <host or replica set> - Use specified MongoDB server(s)\n\
default = 127.0.0.1:27017\n\
single host format: host:port\n\
replica set format: replica_set_name::host1:port1,host2:port2,...\n\
ports are optional, default is 27017\n\
\n\
--poll-interval <n> - Set database poll frequency\n\
<n> is in 100ms (tenths of seconds) units, default = 0 (no polling)\n\
Database reads are normally triggered via UDP messages pushed from\n\
CloudGuard. Polling can be used as a failsafe or for testing.\n\
\n\
--port <port number> - Listen on specified UDP port\n\
default = %d\n\
\n"
// externs (globals)
sc_hash_state_t rng_hash;
int sockfd = INVALID_SOCKET;
uint8_t tx_buf[K2IPC_MAX_PACKET_SIZE];
uint8_t rx_buf[K2IPC_MAX_PACKET_SIZE];
struct sockaddr_in recvaddr;
// module static (local) data
namespace
{
class MongoInitCleanup
{
public:
MongoInitCleanup() { printf("mongoc_init()\n");mongoc_init(); }
~MongoInitCleanup() { printf("mongoc_cleanup()\n");mongoc_cleanup(); }
};
Log::config_t logConfig;
unsigned int dump_clients_ticks;
unsigned int dump_clients_tick_counter;
unsigned int poll_database_ticks;
unsigned int poll_database_tick_counter;
bool poll_database;
bool daemonize;
uint16_t port = K2IPC_DEFAULT_UDP_PORT;
bool using_mongo_replica_set;
std::string mongo_replica_set_name;
std::string mongo_replica_set_host_list;
std::string mongo_host = MongoDB::DEFAULT_HOST;
uint16_t mongo_port = MongoDB::DEFAULT_PORT;
struct
{
bool is_open;
int fd;
} child_status_pipe;
}
static void finalizeLogging(void)
{
Log::Finalize();
}
static int errorExit(void)
{
logprintf(Log::error, "K2Daemon (pid = %d) exiting due to an error", getpid());
if (child_status_pipe.is_open)
{
char buf = 1;
if (1 != write(child_status_pipe.fd, &buf, 1))
{
logprintf(Log::error, "write(pipe): %s", strerror(errno));
}
if (0 != close(child_status_pipe.fd))
{
logprintf(Log::error, "close(pipe): %s", strerror(errno));
}
child_status_pipe.is_open = false;
}
// A divider line makes appended log files easier to read
logprintf(Log::informational, "################################################################################");
finalizeLogging();
return EXIT_FAILURE;
}
static void seedRNG(void)
{
using std::chrono::steady_clock;
steady_clock::time_point t = steady_clock::now();
sc_hash_vector(&rng_hash, (uint8_t*)&t, sizeof(t));
}
static success_t initSocket(void)
{
struct sockaddr_in servaddr;
struct timeval tv;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (INVALID_SOCKET == sockfd)
{
logprintf(Log::error, "socket: %s", strerror(errno));
return FAILURE;
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(port);
logprintf(Log::informational, "listening on UDP port %d", port);
if (0 != bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)))
{
logprintf(Log::error, "bind: %s", strerror(errno));
return FAILURE;
}
tv.tv_sec = 0;
tv.tv_usec = TIMER_MICROSECONDS / 2;
if (0 != setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)))
{
logprintf(Log::error, "setsockopt: %s", strerror(errno));
return FAILURE;
}
return SUCCESS;
}
#ifdef TEST_CHRONO
static void testChrono(void)
{
using std::chrono::steady_clock;
steady_clock::time_point t = steady_clock::now();
logprintf(Log::debug, "steady_clock::time_point, %ld bytes:", sizeof(t));
loghexdump(&t, sizeof(t));
}
#endif
#ifdef TEST_MONGODB
static void testMongoDB(void)
{
MongoDB::nqueue::doc_t *p, *q;
MongoDB::nqueue::AddTestData();
// TEB p = MongoDB::nqueue::GetK2();
bool tryAgain = false;
p = MongoDB::nqueue::GetByType(MongoDB::nqueue::TYPE_K2, tryAgain);
logprintf(Log::debug, "<<<<<------ MongoDB notification queue test dump -------------------------------------------------------------");
while (p)
{
logprintf(Log::debug, "id = \"%s\", device = \"%s\", info = \"%s\", type = \"%s\", expires = \"%s\", time = \"%s\"",
p->id.c_str(), p->device.c_str(), p->info.c_str(), p->type.c_str(), p->expires.c_str(), p->time.c_str());
q = p;
p = p->next;
delete q;
}
logprintf(Log::debug, ">>>>>------ END MongoDB notification queue test dump ---------------------------------------------------------");
}
#endif
// Early log initialization -- Sets logging to stdout (while parsing command-line)
static void initializeLoggingToStdout(void)
{
static Log::config_t c;
c.echoToStdout = true;
c.echoToSyslog = false;
c.echoToUDP514 = false;
c.logToFile = false;
c.filterLevel = Log::debug3;
Log::Initialize(&c);
}
// Normal log initialization, use defaults that can be overriden by command-line
static void setLogFile(const char* p)
{
if ('/' == p[0])
{
logConfig.file = p;
}
else
{
logConfig.file = IONU_HOME_DEFAULT;
char* e = getenv(IONU_HOME_ENV_KEY);
if (e)
{
logConfig.file = e;
}
if ('/' != logConfig.file[logConfig.file.length() - 1])
{
logConfig.file += '/';
}
logConfig.file += IONU_LOGS_SUBDIR;
logConfig.file += p;
}
logConfig.logToFile = true;
}
static void setLoggingDefaults(void)
{
logConfig.echoToStdout = false;
logConfig.echoToSyslog = false;
logConfig.echoToUDP514 = false;
logConfig.filterLevel = DEFAULT_LOG_LEVEL;
setLogFile(IONU_LOGFILE_NAME);
logConfig.nFilesMax = IONU_LOG_MAX_FILES;
logConfig.nBytesPerFileMax = IONU_LOG_MAX_FILE_SIZE_KB * 1024;
logConfig.shortIdentifier = LOG_SHORT_IDENTIFIER;
}
static void initializeLogging(void)
{
Log::Initialize(&logConfig);
}
static void sendNotifications(MongoDB::nqueue::doc_t* p)
{
MongoDB::nqueue::doc_t* q;
while (p)
{
dbg_mongo_nqueue(Log::debug3, "id = \"%s\", device = \"%s\", info = \"%s\", type = \"%s\", expires = \"%s\", time = \"%s\"",
p->id.c_str(), p->device.c_str(), p->info.c_str(), p->type.c_str(), p->expires.c_str(), p->time.c_str());
switch (ClientCache::SendMessage(p->id.c_str(), p->device.c_str(), p->info.c_str()))
{
case ClientCache::SENT_PENDING_ACK:
// to do: (to support multiple daemons) add daemon ID (host, port) ?
// ... or just use timestamps ? ...
// the deamon that enqueued a record could cleanup sooner since the timestamp
// would be usable as-is without any offset calculations
p->type = MongoDB::nqueue::TYPE_K2ACK;
dbg_mongo_nqueue(Log::debug3, "message sent, requeuing as %s", MongoDB::nqueue::TYPE_K2ACK);
break;
case ClientCache::CLIENT_OFFLINE:
// to do: ask other daemons, need another "pending" state for this ...
// for now, we assume that this is the one-and-only daemon
p->type = MongoDB::nqueue::TYPE_PUSH;
dbg_mongo_nqueue(Log::debug3, "client %s offline, requeuing as %s", p->device.c_str(), MongoDB::nqueue::TYPE_PUSH);
break;
case ClientCache::CLIENT_BUSY:
poll_database = true; // poll again on the next timer tick to retry K2DFR entries
p->type = MongoDB::nqueue::TYPE_K2DFR;
dbg_mongo_nqueue(Log::debug2, "client %s busy, requeuing as %s", p->device.c_str(), MongoDB::nqueue::TYPE_K2DFR);
break;
}
MongoDB::nqueue::Update(*p);
q = p;
p = p->next;
delete q;
}
}
static void pollMongoNotificationQueue(void)
{
bool try_again = false;
MongoDB::nqueue::doc_t* p;
/* first retry any K2DFR entries that may have been created on the last poll iteration */
p = MongoDB::nqueue::GetByType(MongoDB::nqueue::TYPE_K2DFR, try_again);
sendNotifications(p);
/* try new (from CloudGuard) entries, some may be requeued as K2DFR */
p = MongoDB::nqueue::GetByType(MongoDB::nqueue::TYPE_K2, try_again);
sendNotifications(p);
if (try_again)
{
poll_database = true;
}
}
// this is called every TIMER_MICROSECONDS (+/- some jitter)
static void onTimer(void)
{
seedRNG();
ClientCache::Timer();
if (poll_database)
{
poll_database = false;
poll_database_tick_counter = poll_database_ticks;
pollMongoNotificationQueue();
}
else if (0 != poll_database_ticks)
{
if (0 == --poll_database_tick_counter)
{
poll_database_tick_counter = poll_database_ticks;
pollMongoNotificationQueue();
}
}
if (0 != dump_clients_ticks)
{
if (0 == --dump_clients_tick_counter)
{
dump_clients_tick_counter = dump_clients_ticks;
ClientCache::DumpClients();
}
}
MongoDB::Timer();
MongoDB::devstatus::Timer();
MongoDB::nqueue::Timer();
LogMemstats::Timer();
if (child_status_pipe.is_open)
{
// Tell the parent process that the child started OK. This is here in onTimer() because the main
// loop would have aborted before calling onTimer() if anything was wrong with the socket.
logprintf(Log::informational, "Signalling parent process: child has started successfully");
char buf = 0;
if (1 != write(child_status_pipe.fd, &buf, 1))
{
logprintf(Log::error, "write(pipe): %s", strerror(errno));
}
if (0 != close(child_status_pipe.fd))
{
logprintf(Log::error, "close(pipe): %s", strerror(errno));
}
child_status_pipe.is_open = false;
}
#if 0 // test code -- verify timer rate
{
static int n;
if (++n >= 10)
{
logprintf(Log::debug, "tick");
n = 0;
}
}
#endif
}
static void onSignalExit(int s)
{
const char* t = "SIG<?>";
switch (s)
{
case SIGINT: t = "SIGINT"; break;
case SIGTERM: t = "SIGTERM"; break;
}
logprintf(Log::notice, "K2Daemon (pid = %d) caught %s, cleaning up and exiting", getpid(), t);
MongoDB::Disconnect();
// A divider line makes appended log files easier to read
logprintf(Log::informational, "################################################################################");
finalizeLogging();
exit(1);
}
static Log::severity_t parseLogLevel(const char* p)
{
if (isdigit(*p))
{
return static_cast<Log::severity_t>(atol(p));
}
#define X(level, abbrev) \
if (0 == strcasecmp(p, #abbrev)) {return Log::level;}
LOG_TRACE_SEVERITY_LEVELS
#undef X
#define X(level, abbrev) \
if (0 == strcasecmp(p, #level)) {return Log::level;}
LOG_TRACE_SEVERITY_LEVELS
#undef X
return Log::debug3;
}
// helper for parseMongoReplicaSetHost
static void splitMongoHostPort(const std::string& s, std::string& host, uint16_t& port)
{
const char* p = s.c_str();
const char* q = strchr(p, ':');
if (q)
{
host.assign(p, q - p);
port = static_cast<uint16_t>(atol(q + 1));
}
else
{
host = s;
port = MongoDB::DEFAULT_PORT;
}
}
// helper for parseMongoReplicaSetHostList
// parses one comma-delimited list item, returns true if an item was parsed
static bool parseMongoReplicaSetHost(const char*& p, std::string& host, uint16_t& port)
{
if (0 == *p)
{
return false;
}
std::string s;
const char* q = strchr(p, ',');
if (q)
{
s.assign(p, q - p);
p = q + 1;
}
else
{
s = p;
p += s.length();
}
splitMongoHostPort(s, host, port);
return true;
}
// mongo replica set host list format: host1:port1,host2:port2,..., ports are optional
static void parseMongoReplicaSetHostList(const char* p)
{
std::string host;
uint16_t port;
while (parseMongoReplicaSetHost(p, host, port))
{
std::stringstream tmpHost;
tmpHost << host << ":" << port << ",";
mongo_replica_set_host_list += tmpHost.str();
}
// Remove the last comma
mongo_replica_set_host_list.pop_back();
}
// --mongo-host command line parameter may be a replica set or a single host,
// replica sets are prefixed with "replica_set_name::"
static void parseMongoHost(const char* p)
{
using_mongo_replica_set = false;
const char* q = strstr(p, "::");
if (q)
{
using_mongo_replica_set = true;
parseMongoReplicaSetHostList(q + 2);
mongo_replica_set_name.assign(p, q - p);
}
else
{
q = strchr(p, ':');
if (q)
{
mongo_host.assign(p, q - p);
mongo_port = static_cast<uint16_t>(atol(q + 1));
}
else
{
mongo_host = p;
mongo_port = MongoDB::DEFAULT_PORT;
}
}
}
static void parseCommandLine(int argc, char *argv[])
{
long n;
enum
{
PCL_IDLE,
PCL_HELP,
PCL_DUMP_CLIENTS,
PCL_LOG_FILE,
PCL_LOG_LEVEL,
PCL_LOG_MAX_FILE_SIZE,
PCL_LOG_MAX_FILES,
PCL_MONGO_HOST,
PCL_POLL_INTERVAL,
PCL_PORT
}
state = PCL_IDLE;
++argv;
while (argc-- > 1)
{
const char *p = *(argv++);
if (('-' == p[0]) && ('-' == p[1]))
{
p += 2;
state = PCL_IDLE;
if (0 == strcasecmp(p, "help"))
{
state = PCL_HELP;
}
else if (0 == strcasecmp(p, "daemon"))
{
daemonize = true;
}
else if (0 == strcasecmp(p, "dump-clients"))
{
state = PCL_DUMP_CLIENTS;
}
else if (0 == strcasecmp(p, "log-echo-stdout"))
{
logConfig.echoToStdout = true;
}
else if (0 == strcasecmp(p, "log-echo-syslog"))
{
logConfig.echoToSyslog = true;
}
else if (0 == strcasecmp(p, "log-echo-UDP514"))
{
logConfig.echoToUDP514 = true;
}
else if (0 == strcasecmp(p, "log-file"))
{
state = PCL_LOG_FILE;
}
else if (0 == strcasecmp(p, "logfile"))
{
state = PCL_LOG_FILE;
}
else if (0 == strcasecmp(p, "log-level"))
{
state = PCL_LOG_LEVEL;
}
else if (0 == strcasecmp(p, "log-max-file-size"))
{
state = PCL_LOG_MAX_FILE_SIZE;
}
else if (0 == strcasecmp(p, "log-max-files"))
{
state = PCL_LOG_MAX_FILES;
}
else if (0 == strcasecmp(p, "mongo-host"))
{
state = PCL_MONGO_HOST;
}
else if (0 == strcasecmp(p, "poll-interval"))
{
state = PCL_POLL_INTERVAL;
}
else if (0 == strcasecmp(p, "port"))
{
state = PCL_PORT;
}
}
else if ((0 == strcasecmp(p, "?"))
|| (0 == strcasecmp(p, "help"))
|| (0 == strcasecmp(p, "-help")))
{
state = PCL_HELP;
}
else // parameter for previous flag
{
switch (state)
{
case PCL_DUMP_CLIENTS:
n = atol(p);
dump_clients_ticks = static_cast<unsigned int>(TIMER_TICKS_PER_SECOND * n);
dump_clients_tick_counter = dump_clients_ticks;
state = PCL_IDLE;
break;
case PCL_LOG_FILE:
setLogFile(p);
state = PCL_IDLE;
break;
case PCL_LOG_LEVEL:
logConfig.filterLevel = parseLogLevel(p);
state = PCL_IDLE;
break;
case PCL_LOG_MAX_FILE_SIZE:
n = atol(p);
logConfig.nBytesPerFileMax = static_cast<size_t>(n * 1024);
state = PCL_IDLE;
break;
case PCL_LOG_MAX_FILES:
n = atol(p);
logConfig.nFilesMax = static_cast<unsigned int>(n);
state = PCL_IDLE;
break;
case PCL_MONGO_HOST:
parseMongoHost(p);
state = PCL_IDLE;
break;
case PCL_POLL_INTERVAL:
n = atol(p);
poll_database_ticks = static_cast<unsigned int>(n);
poll_database_tick_counter = poll_database_ticks;
state = PCL_IDLE;
break;
case PCL_PORT:
n = atol(p);
port = static_cast<uint16_t>(n);
state = PCL_IDLE;
break;
default: // ignore
break;
}
}
// Handle parameter-less states and defaults for no parameters supplied
switch (state)
{
case PCL_HELP:
printf(COMMMAND_LINE_HELP, K2IPC_DEFAULT_UDP_PORT);
//printf("about to exit\n");
exit(EXIT_SUCCESS);
//printf("after exit\n");
break;
case PCL_DUMP_CLIENTS:
dump_clients_ticks = 60 * TIMER_TICKS_PER_SECOND;
dump_clients_tick_counter = dump_clients_ticks;
break;
default: // do nothing
break;
}
}
}
static void logLine(const std::string& line)
{
if (line.length())
{
logprintf(Log::informational, "%s", line.c_str());
}
}
static void logCommandLine(int argc, char *argv[])
{
std::string line;
logprintf(Log::informational, "------ Command Line ------");
for (int i = 1; i < argc; ++i)
{
if ('-' == argv[i][0])
{
logLine(line);
line = argv[i];
}
else
{
line += ' ';
line += argv[i];
}
}
logLine(line);
logprintf(Log::informational, "--------------------------");
}
static void logHello(int argc, char *argv[])
{
// A divider line makes appended log files easier to read
logprintf(Log::informational, "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
Version::Log();
char b[256];
ssize_t n = readlink("/proc/self/exe", b, sizeof(b) - 1);
if (0 > n)
{
logprintf(Log::error, "readlink(/proc/self/exe) : %s", strerror(errno));
}
else
{
b[n] = 0;
logprintf(Log::informational, "%s", b);
}
if (argc > 1)
{
logCommandLine(argc, argv);
}
else
{
logprintf(Log::informational, "no command-line args");
}
}
void Main::PollDatabase(void)
{
poll_database = true;
}
int main(int argc, char *argv[])
{
parseCommandLine(argc, argv); // will exit() before logging anything if only showing help
//printf("made it here\n");
seedRNG();
initializeLoggingToStdout();
logprintf(Log::debug, "K2 socket notifier daemon (pid %d, ppid %d) starting", getpid(), getppid());
setLoggingDefaults();
parseCommandLine(argc, argv);
if (daemonize)
{
int pipefd[2];
if (0 != pipe(pipefd))
{
logprintf(Log::error, "%s: %s", "pipe", strerror(errno));
return errorExit();
}
pid_t pid, sid;
pid = fork(); // returns 0 for child
if (pid < 0)
{
logprintf(Log::error, "%s: %s", "fork", strerror(errno));
return errorExit();
}
if (pid > 0)
{
// parent process ... wait to see if child started OK, then exit
char buf = 1;
close(pipefd[1]); // write end
if (-1 == read(pipefd[0], &buf, 1))
{
logprintf(Log::error, "%s: %s", "read(pipe)", strerror(errno));
return errorExit();
}
if (0 == buf)
{
logprintf(Log::debug, "parent process exiting with SUCCESS status");
return EXIT_SUCCESS;
}
else
{
logprintf(Log::error, "parent process exiting with FAILURE status, see log for details");
return EXIT_FAILURE;
}
}
// child process ...
close(pipefd[0]); // read end
child_status_pipe.is_open = true;
child_status_pipe.fd = pipefd[1];
umask(0);
initializeLogging();
sid = setsid();
if (sid < 0)
{
logprintf(Log::error, "%s: %s", "setsid", strerror(errno));
return errorExit();
}
if ((chdir("/")) < 0)
{
logprintf(Log::error, "%s: %s", "chdir(\"/\")", strerror(errno));
return errorExit();
}
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
logHello(argc, argv);
logprintf(Log::informational, "Running as daemon, sid %d, pid %d, ppid %d", sid, getpid(), getppid());
}
else
{
logprintf(Log::informational, "Initializing logging...");
initializeLogging();
logHello(argc, argv);
logprintf(Log::informational, "Running in console mode, pid %d, ppid %d", getpid(), getppid());
}
#ifdef TEST_CHRONO
testChrono();
testChrono();
testChrono();
#endif
if (FAILURE == initSocket())
{
return errorExit();
}
seedRNG();
{
struct sigaction sa;
sa.sa_handler = onSignalExit;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
}
MongoInitCleanup mongoInit;
if (using_mongo_replica_set)
{
if (SUCCESS != MongoDB::ConnectReplica(mongo_replica_set_name, mongo_replica_set_host_list))
{
logprintf(Log::error, "Unable to connect to MongoDB replica set");
return errorExit();
}
}
else
{
if (SUCCESS != MongoDB::Connect(mongo_host.c_str(), mongo_port))
{
logprintf(Log::error, "Unable to connect to MongoDB host");
return errorExit();
}
}
#ifdef TEST_MONGODB
testMongoDB();
#endif
logprintf(Log::debug, "Cleaning MongoDB device status collection");
MongoDB::devstatus::Clean();
logprintf(Log::debug, "Done cleaning MongoDB device status collection");
seedRNG();
CStopWatch stopwatch;
for (;;)
{
socklen_t recvaddr_len = sizeof(recvaddr);
int n = recvfrom(sockfd, rx_buf, sizeof(rx_buf), 0,
reinterpret_cast<struct sockaddr *>(&recvaddr), &recvaddr_len);
if (n > 0)
{
ClientCache::HandlePacket(static_cast<unsigned int>(n));
}
else if (EAGAIN != errno)
{
logprintf(Log::debug, "recvfrom() returned %d", n);
logprintf(Log::error, "%s: %s", "recvfrom", strerror(errno));
return errorExit();
}
if (TIMER_MICROSECONDS <= stopwatch.Microseconds())
{
stopwatch.DecreaseMicroseconds(TIMER_MICROSECONDS);
onTimer();
}
}
}