/* 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 #ifndef WIN32 #include #include #include #include #include #include #include #else #include #include #pragma comment (lib, "Ws2_32.lib") #define strdup _strdup #endif #include #include #include #include #include #include #ifdef __MACH__ #include #else #include #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 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 , 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 , 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 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 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 and */ 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; } }