978 lines
25 KiB
C++
978 lines
25 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
|
|
|
|
K2Client/platform_binding.c - implementation of platform_binding.h
|
|
(see platform_binding.h for module description)
|
|
*/
|
|
#include <sys/types.h>
|
|
#ifndef WIN32
|
|
#include <netdb.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <strings.h>
|
|
#include <unistd.h>
|
|
#include <sys/time.h>
|
|
#else
|
|
#include <Winsock2.h>
|
|
#include <ws2tcpip.h>
|
|
#pragma comment (lib, "Ws2_32.lib")
|
|
#define strdup _strdup
|
|
#endif
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <signal.h>
|
|
#include <errno.h>
|
|
#include <pthread.h>
|
|
#ifdef __MACH__
|
|
#include <mach/mach_time.h>
|
|
#else
|
|
#include <time.h>
|
|
#endif
|
|
#include "constants.h"
|
|
#include "util.h"
|
|
#include "K2IPC.h"
|
|
#include "K2Client.h"
|
|
#include "proxy.h"
|
|
#include "servers.h"
|
|
#include "platform_binding.h"
|
|
|
|
/* internal plumbing constants and macros */
|
|
#define TIMER_MICROSECONDS (1000000 / K2CLI_TIMER_TICKS_PER_SECOND)
|
|
#define TIMER_MILLISECONDS (TIMER_MICROSECONDS / 1000)
|
|
#define TICK_MINIMUM_MS (TIMER_MILLISECONDS * 2 / 3)
|
|
#define LOCALHOST_ADDRESS "127.0.0.1"
|
|
|
|
/* data accessed only by this module's internal thread */
|
|
static SOCKET sockfd = INVALID_SOCKET; /* UDP socket file descriptor */
|
|
static struct sockaddr_in servaddr; /* address of K2Daemon server */
|
|
static struct sockaddr_in recvaddr; /* address of received packet */
|
|
static struct sockaddr_in selfaddr; /* address that <sockfd> is bound to */
|
|
static uint8_t buf[K2IPC_MAX_PACKET_SIZE]; /* UDP packet buffer for Rx and Tx */
|
|
static char* status_select_offices_copy; /* thread-safe copy of status_select_offices */
|
|
static int ticksSkipped; /* count of timer ticks skipped due to insufficient clock time elapsed (glitch detector) */
|
|
|
|
/* ID of this module's internal thread */
|
|
static pthread_t thread_id;
|
|
|
|
/* inter-thread flags */
|
|
static volatile int running; /* TRUE if internal thread was started and not yet stopped */
|
|
static volatile int end_thread; /* TRUE if internal thread should exit */
|
|
static volatile int login; /* TRUE if a K2Daemon login should be initiated */
|
|
static volatile int poll_db; /* TRUE if K2Daemon should be signalled to poll the database */
|
|
static volatile int get_stats; /* TRUE if K2Daemon should be signalled to return CPU and memory statistics */
|
|
static volatile int have_stats; /* TRUE if stats were received in response to an earlier get_stats */
|
|
static volatile int stats_pending; /* TRUE if waiting for stats from daemon */
|
|
static volatile int duplicate_urn; /* TRUE if URN is already in use by another client */
|
|
|
|
/* inter-thread flags for testing */
|
|
static volatile int newsock; /* triggers socket close/re-open */
|
|
static volatile int no_logout; /* stop internal thread without sending a logout packet */
|
|
|
|
/*
|
|
mutex for serializing access to the module interface functions so that they can
|
|
safely be called by multi-threaded library clients
|
|
|
|
this also ensures that this module's thread state and flags are consistent with
|
|
calls to K2_Start(...) et. al.
|
|
*/
|
|
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
#define LOCK_MUTEX pthread_mutex_lock(&mutex);
|
|
#define UNLOCK_MUTEX pthread_mutex_unlock(&mutex);
|
|
|
|
/*
|
|
params and callbacks from higher-layer code, registered via K2_Start(...),
|
|
modified only when thread is not running
|
|
*/
|
|
static char urn[128];
|
|
static char servers[896];
|
|
static k2_message_callback_t upcallMessage;
|
|
|
|
/*
|
|
data and signals from higher-layer code that may be modified while the
|
|
thread is running (mutex protected)
|
|
*/
|
|
static pthread_mutex_t signal_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
#define LOCK_SIGNAL_MUTEX pthread_mutex_lock(&signal_mutex);
|
|
#define UNLOCK_SIGNAL_MUTEX pthread_mutex_unlock(&signal_mutex);
|
|
static char* volatile status_select_offices; /* from K2_StatusSelect(offices) */
|
|
static volatile int status_select_offices_changed;
|
|
|
|
/*
|
|
data to higher-layer code
|
|
*/
|
|
static pthread_mutex_t stats_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
#define LOCK_STATS_MUTEX pthread_mutex_lock(&stats_mutex);
|
|
#define UNLOCK_STATS_MUTEX pthread_mutex_unlock(&stats_mutex);
|
|
static char* volatile daemon_stats;
|
|
|
|
/* this module's callbacks, registered with (called by) lower-layer code */
|
|
static void onMessage(const char* info);
|
|
static void onEvent(unsigned int event);
|
|
static void onDeviceStatus(const char* status);
|
|
static void onStats(const char* stats);
|
|
static void sendUDP(unsigned int len);
|
|
static int stopwatch(int reset);
|
|
|
|
|
|
|
|
|
|
/* ====================== Implementation functions ====================== */
|
|
/*
|
|
Functions are grouped into purpose-specific sections with comments
|
|
above each section.
|
|
|
|
Lower-level functions are generally towards the top.
|
|
*/
|
|
|
|
/* ------------------------------ Timing ----------------------------- */
|
|
#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
|
|
#define log_skipped_ticks(...)
|
|
#else
|
|
static void log_skipped_ticks(void)
|
|
{
|
|
if (ticksSkipped)
|
|
{
|
|
log_warn("skipped %d timer ticks", ticksSkipped);
|
|
ticksSkipped = 0;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static int get_uptime_ms()
|
|
{
|
|
#ifdef __MACH__
|
|
const int64_t k = 1000 * 1000;
|
|
static mach_timebase_info_data_t s;
|
|
if (0 == s.denom)
|
|
{
|
|
mach_timebase_info(&s);
|
|
}
|
|
return (int)((mach_absolute_time() * s.numer) / (k * s.denom));
|
|
#else
|
|
#ifdef WIN32
|
|
static double k;
|
|
static BOOL i = FALSE;
|
|
if (!i)
|
|
{
|
|
i = TRUE;
|
|
LARGE_INTEGER f;
|
|
QueryPerformanceFrequency(&f);
|
|
k = (double)f.QuadPart / 1000.0;
|
|
}
|
|
LARGE_INTEGER t;
|
|
QueryPerformanceCounter(&t);
|
|
return (int)((double)t.QuadPart / k);
|
|
#else
|
|
const int64_t k = 1000;
|
|
const int64_t M = k * k;
|
|
struct timespec t;
|
|
clock_gettime(CLOCK_MONOTONIC, &t);
|
|
return (int)((t.tv_sec * k) + (t.tv_nsec / M));
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
|
|
/* ----------------------- Message upcalls to lib client callback ----------------------- */
|
|
|
|
static void upcall_message_1(const char* message)
|
|
{
|
|
if (NULL != upcallMessage)
|
|
{
|
|
upcallMessage(message);
|
|
}
|
|
log_verbose("message: %s", message);
|
|
}
|
|
|
|
/* all message upcalls funnel through this function and its helper (above) */
|
|
static void upcall_message(const char* type, const char* info)
|
|
{
|
|
int n = (int)(strlen(type) + strlen(info) + 1);
|
|
char* buf = (char *)malloc(n);
|
|
snprintf(buf, n, "%s%s", type, info);
|
|
upcall_message_1(buf);
|
|
free(buf);
|
|
}
|
|
|
|
/* for generic K2Client messsages */
|
|
void upcall_k2cli_message(const char* message)
|
|
{
|
|
upcall_message(K2_MESSAGE_PREFIX_INTERNAL, message);
|
|
}
|
|
|
|
|
|
/* ----------------------- Error message upcalls ----------------------- */
|
|
|
|
void upcall_error_string(const char* where, const char* error_string)
|
|
{
|
|
int n = (int)(strlen(K2_MESSAGE_2ND_PREFIX_FOR_ERRORS) + strlen(error_string) + strlen(where) + 3);
|
|
char* buf = (char *)malloc(n);
|
|
snprintf(buf, n, "%s%s: %s", K2_MESSAGE_2ND_PREFIX_FOR_ERRORS, where, error_string);
|
|
upcall_k2cli_message(buf);
|
|
free(buf);
|
|
}
|
|
|
|
#ifdef WIN32
|
|
/**
|
|
* Format a Windows error number. Caller MUST call 'LocalFree' on the returned pointer.
|
|
*/
|
|
static LPTSTR formatWinError(DWORD nErr)
|
|
{
|
|
LPVOID lpMsgBuf;
|
|
|
|
FormatMessage(
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
FORMAT_MESSAGE_FROM_SYSTEM |
|
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
NULL,
|
|
nErr,
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
(LPTSTR)&lpMsgBuf,
|
|
0, NULL);
|
|
|
|
return (LPTSTR)lpMsgBuf;
|
|
}
|
|
#endif
|
|
|
|
#ifdef WIN32
|
|
void upcall_error(const char* where, int errnum)
|
|
{
|
|
LPTSTR pErrMsg = formatWinError(errnum);
|
|
const char ERR_MSG[] = "Socket error:\n";
|
|
size_t nMsgLen = strlen(ERR_MSG) + strlen(pErrMsg) + 1;
|
|
char* buf = (char *)malloc(nMsgLen);
|
|
snprintf(buf, nMsgLen, "%s%s", ERR_MSG, pErrMsg);
|
|
upcall_error_string(where, buf);
|
|
free(buf);
|
|
LocalFree((LPVOID)pErrMsg);
|
|
}
|
|
#else
|
|
void upcall_error(const char* where, int errnum)
|
|
{
|
|
upcall_error_string(where, strerror(errnum));
|
|
}
|
|
#endif
|
|
|
|
#ifdef WIN32
|
|
void upcall_errno(const char* where)
|
|
{
|
|
upcall_error(where, WSAGetLastError());
|
|
}
|
|
#else
|
|
void upcall_errno(const char* where)
|
|
{
|
|
upcall_error(where, errno);
|
|
}
|
|
#endif
|
|
|
|
|
|
/* ----------------------- Network, socket, address boilerplate ----------------------- */
|
|
|
|
static void init_server_address(void)
|
|
{
|
|
memset(&servaddr, 0, sizeof(servaddr));
|
|
servaddr.sin_family = AF_INET;
|
|
servaddr.sin_addr.s_addr = inet_addr(LOCALHOST_ADDRESS);
|
|
servaddr.sin_port = htons(K2IPC_DEFAULT_UDP_PORT);
|
|
}
|
|
|
|
static void address_to_string(const struct sockaddr_in* a, char* buf, unsigned int buflen)
|
|
{
|
|
if (htonl(INADDR_ANY) == a->sin_addr.s_addr)
|
|
{
|
|
snprintf(buf, buflen, "INADDR_ANY");
|
|
}
|
|
else
|
|
{
|
|
#if 0 /*
|
|
If or when we decide to support IPV6, we need to use inet_ntop(), however,
|
|
it is not availavble under Windows XP, so for now we use inet_ntoa().
|
|
|
|
The code in this #if 0 block works on all platforms except Windows XP.
|
|
*/
|
|
#ifdef WIN32
|
|
if (!inet_ntop(a->sin_family, (PVOID)&(a->sin_addr), buf, buflen))
|
|
#else
|
|
if (!inet_ntop(a->sin_family, (const void *)&(a->sin_addr), buf, buflen))
|
|
#endif
|
|
{
|
|
snprintf(buf, buflen, "inet_ntop ERROR");
|
|
}
|
|
#endif /* 0 */
|
|
|
|
snprintf(buf, buflen, "%s", inet_ntoa(a->sin_addr));
|
|
}
|
|
}
|
|
|
|
static void show_socket_address(void)
|
|
{
|
|
char a[ADDRESS_STRING_LENGTH], b[ADDRESS_STRING_LENGTH + 64];
|
|
address_to_string(&selfaddr, a, sizeof(a));
|
|
snprintf(b, sizeof(b), "socket %d bound to %s port %d", sockfd, a, ntohs(selfaddr.sin_port));
|
|
upcall_k2cli_message(b);
|
|
}
|
|
|
|
static int get_socket_address(void)
|
|
{
|
|
socklen_t addr_len = sizeof(selfaddr);
|
|
memset(&selfaddr, 0, sizeof(selfaddr));
|
|
selfaddr.sin_family = AF_INET;
|
|
selfaddr.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
selfaddr.sin_port = htons(0);
|
|
if (0 != getsockname(sockfd, (struct sockaddr *)&selfaddr, &addr_len))
|
|
{
|
|
upcall_errno("getsockname");
|
|
return 1;
|
|
}
|
|
show_socket_address();
|
|
return 0;
|
|
}
|
|
|
|
static int set_socket_timeout(void)
|
|
{
|
|
#ifdef WIN32
|
|
//DWORD tv = TIMER_MICROSECONDS / 1000; /* milliseconds */
|
|
// Windows, being lame again. It seems that you cannot set recieve/send timeouts to be
|
|
// less then 500ms. We want 100ms (or the like).
|
|
// Set the socket to non-blocking...
|
|
u_long mode = 1;
|
|
if (0 != ioctlsocket(sockfd, FIONBIO, &mode))
|
|
{
|
|
upcall_errno("ioctlsocket(FIONBIO)");
|
|
return 1;
|
|
}
|
|
#else
|
|
struct timeval tv;
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = TIMER_MICROSECONDS;
|
|
//#endif
|
|
|
|
if (0 != setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof(tv)))
|
|
{
|
|
upcall_errno("setsockopt(SO_RCVTIMEO)");
|
|
return 1;
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static int bind_socket(void)
|
|
{
|
|
memset(&selfaddr, 0, sizeof(selfaddr));
|
|
selfaddr.sin_family = AF_INET;
|
|
selfaddr.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
selfaddr.sin_port = htons(0);
|
|
if (0 != bind(sockfd, (struct sockaddr *)&selfaddr, sizeof(selfaddr)))
|
|
{
|
|
upcall_errno("bind");
|
|
return 1;
|
|
}
|
|
return get_socket_address();
|
|
}
|
|
|
|
static int init_socket(void)
|
|
{
|
|
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
|
|
if (INVALID_SOCKET == sockfd)
|
|
{
|
|
upcall_errno("socket");
|
|
return 1;
|
|
}
|
|
#ifdef __APPLE__
|
|
int set = 1;
|
|
// Prevent SIGPIPE if socket closed in K2Daemon (iOSX)
|
|
if (0 != setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int)))
|
|
{
|
|
upcall_errno("setsockopt(SO_NOSIGPIPE)");
|
|
return 1;
|
|
}
|
|
#endif
|
|
if (set_socket_timeout())
|
|
{
|
|
return 1;
|
|
}
|
|
if (bind_socket())
|
|
{
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int receive_packet(void)
|
|
{
|
|
socklen_t recvaddr_len;
|
|
recvaddr_len = sizeof(recvaddr);
|
|
int retVal = 0;
|
|
#ifdef WIN32
|
|
// Again, lame... See comments in 'set_socket_timeout'
|
|
WaitForSingleObject((HANDLE)sockfd, TIMER_MICROSECONDS / 1000);
|
|
retVal = (int)recvfrom(sockfd, (char *)buf, sizeof(buf), 0, (struct sockaddr *)&recvaddr, &recvaddr_len);
|
|
#else
|
|
retVal = (int)recvfrom(sockfd, (char *)buf, sizeof(buf), 0, (struct sockaddr *)&recvaddr, &recvaddr_len);
|
|
#endif
|
|
return retVal;
|
|
}
|
|
|
|
/* callback from <proxy_start>, sends UDP packet to self */
|
|
static void sendToSelf(uint8_t* buf, size_t len)
|
|
{
|
|
selfaddr.sin_addr.s_addr = inet_addr(LOCALHOST_ADDRESS);
|
|
sendto(
|
|
sockfd,
|
|
#ifdef WIN32
|
|
(const char *)buf, (int) len,
|
|
#else
|
|
buf, len,
|
|
#endif
|
|
0, (struct sockaddr *)&selfaddr, sizeof(selfaddr));
|
|
}
|
|
|
|
/* callback from <select_server>, called with address of the server that was selected */
|
|
static void onServerSelected(const void* address, const char* host)
|
|
{
|
|
const struct sockaddr_in* addr = (const struct sockaddr_in*)address;
|
|
servaddr.sin_addr.s_addr = addr->sin_addr.s_addr;
|
|
servaddr.sin_port = addr->sin_port;
|
|
if (is_using_proxy)
|
|
{
|
|
proxy_start(&servaddr, host, urn, sendToSelf);
|
|
}
|
|
else
|
|
{
|
|
proxy_stop();
|
|
}
|
|
}
|
|
|
|
/* sets <servaddr> to the address of the next server to try to use */
|
|
static void next_server_address(void)
|
|
{
|
|
/*
|
|
As a default (in case host lookup fails, etc.) set the server address to loopback.
|
|
This will cause this module to receive a K2CLI_ERROR_NOT_CONNECTED after a delay of
|
|
a few seconds. Receiving that event will cause this module to pick a new server.
|
|
|
|
The delay is good. It prevents us from hammering the message upcall with error
|
|
messages when the network is down or the server list is bad.
|
|
*/
|
|
init_server_address();
|
|
|
|
/* randomly selects a server, tries direct UDP before proxy */
|
|
select_server(onServerSelected);
|
|
}
|
|
|
|
|
|
/* ----------------------- Main thread procedure loop ----------------------- */
|
|
|
|
static void timer_tick(void)
|
|
{
|
|
#define FINISH_LOGIN_TIMEOUT_TICKS 50
|
|
static int finish_login = 0;
|
|
static int n = 0;
|
|
char* p;
|
|
seed_random();
|
|
if (newsock)
|
|
{
|
|
newsock = FALSE;
|
|
#ifdef WIN32
|
|
closesocket(sockfd);
|
|
#else
|
|
close(sockfd);
|
|
#endif
|
|
init_socket();
|
|
}
|
|
if (login)
|
|
{
|
|
login = FALSE;
|
|
next_server_address();
|
|
seed_random();
|
|
finish_login = FINISH_LOGIN_TIMEOUT_TICKS;
|
|
}
|
|
if (finish_login)
|
|
{
|
|
if (!is_using_proxy || is_proxy_session_active)
|
|
{
|
|
finish_login = 0;
|
|
K2Cli_Login();
|
|
if (status_select_offices_copy && (0 < strlen(status_select_offices_copy)))
|
|
{
|
|
K2Cli_StatusSelect(status_select_offices_copy);
|
|
}
|
|
}
|
|
else if (0 == --finish_login)
|
|
{
|
|
log_warn("timed out waiting for proxy connect or init");
|
|
K2Cli_Login(); // lets state machine continue, it will trigger a re-connect to a different server
|
|
}
|
|
}
|
|
if (poll_db)
|
|
{
|
|
poll_db = FALSE;
|
|
K2Cli_Poll_DB();
|
|
}
|
|
if (status_select_offices_changed)
|
|
{
|
|
LOCK_SIGNAL_MUTEX
|
|
{
|
|
p = status_select_offices;
|
|
status_select_offices = NULL;
|
|
status_select_offices_changed = FALSE;
|
|
}
|
|
UNLOCK_SIGNAL_MUTEX
|
|
free(status_select_offices_copy);
|
|
status_select_offices_copy = p;
|
|
K2Cli_StatusSelect(status_select_offices_copy);
|
|
}
|
|
if (get_stats)
|
|
{
|
|
get_stats = FALSE;
|
|
K2Cli_QueryStats();
|
|
}
|
|
if (++n >= K2CLI_TIMER_TICKS_PER_SECOND) // once per second
|
|
{
|
|
n = 0;
|
|
log_skipped_ticks();
|
|
}
|
|
K2Cli_Timer();
|
|
}
|
|
|
|
static void* threadproc(void* arg)
|
|
{
|
|
int n, t0 = 0, t, fatal_error = FALSE;
|
|
|
|
#ifdef WIN32
|
|
/* Initialize the socket library */
|
|
WSADATA wsaData;
|
|
int nRes = ::WSAStartup(MAKEWORD(2, 2), &wsaData);
|
|
if (nRes != 0)
|
|
{
|
|
upcall_error("WSAStartup(2.2)", nRes);
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
init_server_address();
|
|
if (0 != init_socket())
|
|
{
|
|
#ifdef WIN32
|
|
/* Clean up the socket library */
|
|
::WSACleanup();
|
|
#endif
|
|
return NULL;
|
|
}
|
|
seed_random_with_bytes(urn, strlen(urn));
|
|
seed_random();
|
|
set_servers(servers);
|
|
|
|
/* Start client IPC loop */
|
|
K2Cli_Initialize(buf, urn, onMessage, onEvent, onDeviceStatus, onStats, sendUDP, stopwatch);
|
|
login = TRUE;
|
|
timer_tick();
|
|
while (!(end_thread || fatal_error))
|
|
{
|
|
n = receive_packet();
|
|
if (n <= 0)
|
|
{
|
|
#ifdef WIN32
|
|
int nError = WSAGetLastError();
|
|
if (WSAEWOULDBLOCK == nError)
|
|
#else
|
|
if (EAGAIN == errno)
|
|
#endif
|
|
{
|
|
t = get_uptime_ms();
|
|
if ((t < t0) || (t >= (t0 + TICK_MINIMUM_MS)))
|
|
{
|
|
t0 = t;
|
|
timer_tick();
|
|
}
|
|
else
|
|
{
|
|
++ticksSkipped;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#ifdef WIN32
|
|
if (WSAECONNRESET == nError) {
|
|
closesocket(sockfd);
|
|
#else
|
|
if (ECONNRESET == errno) {
|
|
close(sockfd);
|
|
#endif
|
|
upcall_k2cli_message("connection reset");
|
|
init_socket();
|
|
} else {
|
|
#ifdef WIN32
|
|
upcall_error("recvfrom", nError);
|
|
#else
|
|
upcall_errno("recvfrom");
|
|
#endif
|
|
login = TRUE; // lets state machine continue, it will trigger a re-connect to a different server
|
|
//fatal_error = TRUE;
|
|
}
|
|
}
|
|
}
|
|
else if (!end_thread)
|
|
{
|
|
K2Cli_HandlePacket(n);
|
|
}
|
|
}
|
|
|
|
if (!no_logout)
|
|
{
|
|
K2Cli_Logout();
|
|
K2Cli_Timer();
|
|
}
|
|
|
|
proxy_stop();
|
|
clear_servers();
|
|
|
|
#ifdef WIN32
|
|
if (0 != closesocket(sockfd))
|
|
#else
|
|
if (0 != close(sockfd))
|
|
#endif
|
|
{
|
|
upcall_errno("close(sockfd)");
|
|
}
|
|
sockfd = INVALID_SOCKET;
|
|
|
|
#ifdef WIN32
|
|
/* Clean up the socket library */
|
|
::WSACleanup();
|
|
#endif
|
|
|
|
/*
|
|
Normally the <running> flag is updated by the module interface functions that
|
|
start or stop the thread. Fatal errors can also cause the thread to stop.
|
|
*/
|
|
if (fatal_error && !end_thread)
|
|
{
|
|
running = FALSE;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/* ------------------ Module interface function helpers ------------------ */
|
|
/* These functions run in the library client's thread with this module's mutex locked */
|
|
|
|
static void start_thread(void)
|
|
{
|
|
int error = pthread_create(&thread_id, NULL, threadproc, NULL);
|
|
if (error)
|
|
{
|
|
upcall_error("pthread_create", error);
|
|
}
|
|
else
|
|
{
|
|
running = TRUE;
|
|
}
|
|
}
|
|
|
|
static void stop_thread(void)
|
|
{
|
|
int error;
|
|
char buf[1] = {0};
|
|
|
|
end_thread = TRUE;
|
|
|
|
/* send UDP packet to self to awaken thread sooner than timer expiry */
|
|
selfaddr.sin_addr.s_addr = inet_addr(LOCALHOST_ADDRESS);
|
|
sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&selfaddr, sizeof(selfaddr));
|
|
|
|
error = pthread_join(thread_id, NULL);
|
|
|
|
if (error)
|
|
{
|
|
upcall_error("pthread_join", error);
|
|
}
|
|
running = end_thread = FALSE;
|
|
}
|
|
|
|
/* runs only when this module's thread is not running, modifies <urn> and <servers> */
|
|
static void start(const char* urn_, const char* servers_, k2_message_callback_t onMessage)
|
|
{
|
|
duplicate_urn = FALSE;
|
|
upcallMessage = onMessage;
|
|
if (safe_strcpy(urn, sizeof(urn), urn_))
|
|
{
|
|
upcall_error_string("K2_Start", "URN too long");
|
|
}
|
|
if (safe_strcpy(servers, sizeof(servers), servers_))
|
|
{
|
|
upcall_error_string("K2_Start", "Servers string too long");
|
|
}
|
|
start_thread();
|
|
}
|
|
|
|
|
|
/* ----------------------- Callbacks from K2Client.c ----------------------- */
|
|
|
|
static void onMessage(const char* info)
|
|
{
|
|
upcall_message(K2_MESSAGE_PREFIX_FROM_CLOUD, info);
|
|
}
|
|
|
|
static void onEvent(unsigned int event)
|
|
{
|
|
char a[ADDRESS_STRING_LENGTH];
|
|
char b[ADDRESS_STRING_LENGTH + 128];
|
|
const char* event_text = "(unknown event)";
|
|
|
|
switch (event)
|
|
{
|
|
#define X(name, text) case name: event_text = text; break;
|
|
K2CLI_EVENTS
|
|
#undef X
|
|
}
|
|
|
|
/* translate event to text for message upcall */
|
|
switch (event)
|
|
{
|
|
case K2CLI_ERROR_BAD_OPCODE:
|
|
case K2CLI_ERROR_BAD_MSG_SEQUENCE:
|
|
case K2CLI_ERROR_BAD_ACK_SEQUENCE:
|
|
case K2CLI_ERROR_WRONG_URN:
|
|
case K2CLI_ERROR_MISSING_NUL:
|
|
case K2CLI_INFO_SPURIOUS_PACKET:
|
|
address_to_string(&recvaddr, a, sizeof(a));
|
|
snprintf(b, sizeof(b), "%s (from %s port %d)", event_text, a, ntohs(recvaddr.sin_port));
|
|
upcall_k2cli_message(b);
|
|
break;
|
|
|
|
default:
|
|
upcall_k2cli_message(event_text);
|
|
break;
|
|
}
|
|
|
|
/* take special actions based on certain events */
|
|
switch (event)
|
|
{
|
|
case K2CLI_ERROR_NOT_CONNECTED:
|
|
if (!duplicate_urn)
|
|
{
|
|
login = TRUE; /* causes server to be randomly selected from list of servers */
|
|
}
|
|
break;
|
|
|
|
case K2CLI_ERROR_DUPLICATE_URN:
|
|
duplicate_urn = TRUE; /* avoids endless loop attempting server connect */
|
|
break;
|
|
}
|
|
|
|
if (stats_pending && (K2CLI_ERROR_NOT_CONNECTED <= event))
|
|
{
|
|
onStats(NULL);
|
|
}
|
|
}
|
|
|
|
static void onDeviceStatus(const char* status)
|
|
{
|
|
upcall_message(K2_MESSAGE_PREFIX_DEVICE_STATUS, status);
|
|
}
|
|
|
|
static void onStats(const char* stats)
|
|
{
|
|
char *p, *q;
|
|
|
|
if (stats)
|
|
{
|
|
q = strdup(stats);
|
|
}
|
|
else
|
|
{
|
|
q = NULL;
|
|
}
|
|
|
|
LOCK_STATS_MUTEX
|
|
{
|
|
p = daemon_stats;
|
|
daemon_stats = q;
|
|
}
|
|
UNLOCK_STATS_MUTEX
|
|
|
|
stats_pending = FALSE;
|
|
have_stats = TRUE;
|
|
free(p);
|
|
}
|
|
|
|
static void sendUDP(unsigned int len)
|
|
{
|
|
if (is_using_proxy)
|
|
{
|
|
proxy_send(buf, len);
|
|
}
|
|
else
|
|
{
|
|
sendto(
|
|
sockfd,
|
|
// Prevent SIGPIPE if socket closed in K2Daemon (Android and Linux)
|
|
#ifdef WIN32
|
|
(const char *)buf, len, 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
|
|
#elif defined (__APPLE__)
|
|
buf, len, 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
|
|
#else
|
|
buf, len, MSG_NOSIGNAL, (struct sockaddr *)&servaddr, sizeof(servaddr));
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static int stopwatch(int reset)
|
|
{
|
|
static time_t t0;
|
|
time_t t = time(NULL);
|
|
if (reset || (0 == t0))
|
|
{
|
|
t0 = t;
|
|
}
|
|
return (int)(t - t0);
|
|
}
|
|
|
|
|
|
/* ----------------------- Module interface functions for testing (unpublicized) ----------------------- */
|
|
|
|
/* close old socket and open a new one (to get a new dynamic local port number) */
|
|
void K2_Newsock(void)
|
|
{
|
|
newsock = TRUE;
|
|
}
|
|
|
|
/* stop the client thread without loggging out (produces a heartbeat timeout) */
|
|
void K2_Stop_NoLogout(void)
|
|
{
|
|
no_logout = TRUE;
|
|
K2_Stop();
|
|
}
|
|
|
|
/* ----------------------- Module interface functions ----------------------- */
|
|
|
|
void K2_Start(const char* urn_, const char* servers_, k2_message_callback_t onMessage)
|
|
{
|
|
initialize_logging();
|
|
log_info("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
|
|
log_info("K2_Start");
|
|
LOCK_MUTEX
|
|
{
|
|
if (running)
|
|
{
|
|
stop_thread();
|
|
}
|
|
start(urn_, servers_, onMessage);
|
|
}
|
|
UNLOCK_MUTEX
|
|
}
|
|
|
|
void K2_Stop(void)
|
|
{
|
|
log_info("K2_Stop");
|
|
LOCK_MUTEX
|
|
{
|
|
if (running)
|
|
{
|
|
stop_thread();
|
|
}
|
|
}
|
|
UNLOCK_MUTEX
|
|
log_info("################################################################################");
|
|
finalize_logging();
|
|
}
|
|
|
|
void K2_Restart(void)
|
|
{
|
|
initialize_logging();
|
|
log_info("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
|
|
log_info("K2_Restart");
|
|
LOCK_MUTEX
|
|
{
|
|
if (!running)
|
|
{
|
|
start_thread();
|
|
}
|
|
}
|
|
UNLOCK_MUTEX
|
|
}
|
|
|
|
void K2_StatusSelect(const char* offices)
|
|
{
|
|
char *p, *q;
|
|
|
|
if (offices && (0 < strlen(offices)))
|
|
{
|
|
q = strdup(offices);
|
|
}
|
|
else
|
|
{
|
|
q = NULL;
|
|
}
|
|
|
|
LOCK_MUTEX
|
|
{
|
|
LOCK_SIGNAL_MUTEX
|
|
{
|
|
p = status_select_offices;
|
|
status_select_offices = q;
|
|
status_select_offices_changed = TRUE;
|
|
}
|
|
UNLOCK_SIGNAL_MUTEX
|
|
}
|
|
UNLOCK_MUTEX
|
|
|
|
free(p);
|
|
}
|
|
|
|
void K2_Poll_DB(void)
|
|
{
|
|
poll_db = TRUE;
|
|
}
|
|
|
|
int K2_GetStats(char* buf, unsigned int bufsize)
|
|
{
|
|
int timeout = 700; /* message retry timeout is 6.3 seconds, if > 7 seconds, something is really wrong */
|
|
char* stats = NULL;
|
|
LOCK_MUTEX
|
|
{
|
|
have_stats = FALSE;
|
|
stats_pending = TRUE;
|
|
get_stats = TRUE;
|
|
while (running && !have_stats && timeout--)
|
|
{
|
|
#ifdef WIN32
|
|
Sleep(10);
|
|
#else
|
|
usleep(10000);
|
|
#endif
|
|
}
|
|
LOCK_STATS_MUTEX
|
|
{
|
|
stats = daemon_stats;
|
|
daemon_stats = NULL;
|
|
}
|
|
UNLOCK_STATS_MUTEX
|
|
}
|
|
UNLOCK_MUTEX
|
|
|
|
snprintf(buf, bufsize, "%s", stats ? stats : "");
|
|
free(stats);
|
|
return stats ? TRUE : FALSE;
|
|
}
|
|
|
|
void K2_SetLogVerbosityLevel(log_verbosity_level_t level)
|
|
{
|
|
switch (level)
|
|
{
|
|
case LOG_LEVEL_FATAL: set_log_level(LOG_FATAL); break;
|
|
case LOG_LEVEL_ERROR: set_log_level(LOG_ERROR); break;
|
|
case LOG_LEVEL_WARNING: set_log_level(LOG_WARNING); break;
|
|
case LOG_LEVEL_INFORMATIONAL: set_log_level(LOG_INFORMATIONAL); break;
|
|
case LOG_LEVEL_DEBUG: set_log_level(LOG_DEBUG); break;
|
|
case LOG_LEVEL_VERBOSE: set_log_level(LOG_VERBOSE); break;
|
|
default:
|
|
log_error("Log verbosity level %d is unknown, ignoring call to K2_SetLogVerbosityLevel", level);
|
|
break;
|
|
}
|
|
}
|