916 lines
26 KiB
C++
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();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|