int KineticSocket_Connect(const char* host, int port) { char port_str[32]; struct addrinfo hints; struct addrinfo* ai_result = NULL; struct addrinfo* ai = NULL; socket99_result result; // Setup server address info socket99_config cfg = { .host = (char*)host, .port = port, .nonblocking = true, }; sprintf(port_str, "%d", port); // Open socket LOGF1("Connecting to %s:%d", host, port); if (!socket99_open(&cfg, &result)) { char err_buf[256]; socket99_snprintf(err_buf, 256, &result); LOGF0("Failed to open socket connection with host: %s", err_buf); return KINETIC_SOCKET_DESCRIPTOR_INVALID; } // Configure the socket socket99_set_hints(&cfg, &hints); if (getaddrinfo(cfg.host, port_str, &hints, &ai_result) != 0) { LOGF0("Failed to get socket address info: errno %d", errno); close(result.fd); return KINETIC_SOCKET_DESCRIPTOR_INVALID; } KineticSocket_EnableTCPNoDelay(result.fd); for (ai = ai_result; ai != NULL; ai = ai->ai_next) { int setsockopt_result; int buffer_size = KINETIC_OBJ_SIZE; #if defined(SO_NOSIGPIPE) && !defined(__APPLE__) // On BSD-like systems we can set SO_NOSIGPIPE on the socket to // prevent it from sending a PIPE signal and bringing down the whole // application if the server closes the socket forcibly int enable = 1; setsockopt_result = setsockopt(result.fd, SOL_SOCKET, SO_NOSIGPIPE, &enable, sizeof(enable)); // Allow ENOTSOCK because it allows tests to use pipes instead of // real sockets if (setsockopt_result != 0 && setsockopt_result != ENOTSOCK) { LOG0("Failed to set SO_NOSIGPIPE on socket"); continue; } #endif // Increase send buffer to KINETIC_OBJ_SIZE // Note: OS allocates 2x this value for its overhead setsockopt_result = setsockopt(result.fd, SOL_SOCKET, SO_SNDBUF, &buffer_size, sizeof(buffer_size)); if (setsockopt_result == -1) { LOG0("Error setting socket send buffer size"); continue; } // Increase receive buffer to KINETIC_OBJ_SIZE // Note: OS allocates 2x this value for its overheadbuffer_size setsockopt_result = setsockopt(result.fd, SOL_SOCKET, SO_RCVBUF, &buffer_size, sizeof(buffer_size)); if (setsockopt_result == -1) { LOG0("Error setting socket receive buffer size"); continue; } break; } freeaddrinfo(ai_result); if (ai == NULL || result.fd == KINETIC_SOCKET_DESCRIPTOR_INVALID) { // we went through all addresses without finding one we could bind to LOGF0("Could not connect to %s:%d", host, port); return KINETIC_SOCKET_DESCRIPTOR_INVALID; } else { LOGF1("Successfully connected to %s:%d (fd=%d)", host, port, result.fd); return result.fd; } } void KineticSocket_Close(int fd) { if (fd == -1) { LOG1("Not connected so no cleanup needed"); } else { LOGF0("Closing socket with fd=%d", fd); if (close(fd) == 0) { LOG2("Socket closed successfully"); } else { LOGF0("Error closing socket file descriptor!" " (fd=%d, errno=%d, desc='%s')", fd, errno, strerror(errno)); } } } void KineticSocket_EnableTCPNoDelay(int fd) { int on = 1; setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)); }
int KineticSocket_Connect(const char* host, int port, bool nonBlocking) { char port_str[32]; struct addrinfo hints; struct addrinfo* ai_result = NULL; struct addrinfo* ai = NULL; socket99_result result; // Setup server address info socket99_config cfg = { .host = (char*)host, .port = port, .nonblocking = nonBlocking, }; sprintf(port_str, "%d", port); // Open socket LOGF("Connecting to %s:%d", host, port); if (!socket99_open(&cfg, &result)) { LOGF("Failed to open socket connection" "with host: status %d, errno %d", result.status, result.saved_errno); return KINETIC_SOCKET_DESCRIPTOR_INVALID; } // Configure the socket socket99_set_hints(&cfg, &hints); if (getaddrinfo(cfg.host, port_str, &hints, &ai_result) != 0) { LOGF("Failed to get socket address info: errno %d", errno); close(result.fd); return KINETIC_SOCKET_DESCRIPTOR_INVALID; } for (ai = ai_result; ai != NULL; ai = ai->ai_next) { int setsockopt_result; int buffer_size = PDU_VALUE_MAX_LEN; #if defined(SO_NOSIGPIPE) && !defined(__APPLE__) // On BSD-like systems we can set SO_NOSIGPIPE on the socket to // prevent it from sending a PIPE signal and bringing down the whole // application if the server closes the socket forcibly int enable = 1; setsockopt_result = setsockopt(result.fd, SOL_SOCKET, SO_NOSIGPIPE, &enable, sizeof(enable)); // Allow ENOTSOCK because it allows tests to use pipes instead of // real sockets if (setsockopt_result != 0 && setsockopt_result != ENOTSOCK) { LOG("Failed to set SO_NOSIGPIPE on socket"); continue; } #endif // Increase send buffer to PDU_VALUE_MAX_LEN // Note: OS allocates 2x this value for its overhead setsockopt_result = setsockopt(result.fd, SOL_SOCKET, SO_SNDBUF, &buffer_size, sizeof(buffer_size)); if (setsockopt_result == -1) { LOG("Error setting socket send buffer size"); continue; } // Increase receive buffer to PDU_VALUE_MAX_LEN // Note: OS allocates 2x this value for its overheadbuffer_size setsockopt_result = setsockopt(result.fd, SOL_SOCKET, SO_RCVBUF, &buffer_size, sizeof(buffer_size)); if (setsockopt_result == -1) { LOG("Error setting socket receive buffer size"); continue; } break; } freeaddrinfo(ai_result); if (ai == NULL || result.fd == KINETIC_SOCKET_DESCRIPTOR_INVALID) { // we went through all addresses without finding one we could bind to LOGF("Could not connect to %s:%d", host, port); return KINETIC_SOCKET_DESCRIPTOR_INVALID; } else { LOGF("Successfully connected to %s:%d (fd=%d)", host, port, result.fd); return result.fd; } } void KineticSocket_Close(int socket) { if (socket == -1) { LOG("Not connected so no cleanup needed"); } else { LOGF("Closing socket with fd=%d", socket); if (close(socket) == 0) { LOG("Socket closed successfully"); } else { LOGF("Error closing socket file descriptor!" " (fd=%d, errno=%d, desc='%s')", socket, errno, strerror(errno)); } } } KineticStatus KineticSocket_Read(int socket, ByteBuffer* dest, size_t len) { #ifdef KINETIC_LOG_SOCKET_OPERATIONS LOGF("Reading %zd bytes into buffer @ 0x%zX from fd=%d", len, (size_t)dest->array.data, socket); #endif KineticStatus status = KINETIC_STATUS_INVALID; // Read "up to" the allocated number of bytes into dest buffer size_t bytesToReadIntoBuffer = len; if (dest->array.len < len) { bytesToReadIntoBuffer = dest->array.len; } while (dest->bytesUsed < bytesToReadIntoBuffer) { int opStatus; fd_set readSet; struct timeval timeout; // Time out after 5 seconds timeout.tv_sec = 5; timeout.tv_usec = 0; FD_ZERO(&readSet); FD_SET(socket, &readSet); opStatus = select(socket + 1, &readSet, NULL, NULL, &timeout); if (opStatus < 0) { // Error occurred LOGF("Failed waiting to read from socket!" " status=%d, errno=%d, desc='%s'", opStatus, errno, strerror(errno)); return KINETIC_STATUS_SOCKET_ERROR; } else if (opStatus == 0) { // Timeout occurred LOG("Timed out waiting for socket data to arrive!"); return KINETIC_STATUS_SOCKET_TIMEOUT; } else if (opStatus > 0) { // Data available to read // The socket is ready for reading opStatus = read(socket, &dest->array.data[dest->bytesUsed], dest->array.len - dest->bytesUsed); // Retry if no data yet... if (opStatus == -1 && ((errno == EINTR) || (errno == EAGAIN) || (errno == EWOULDBLOCK) )) { continue; } else if (opStatus <= 0) { LOGF("Failed to read from socket!" " status=%d, errno=%d, desc='%s'", opStatus, errno, strerror(errno)); return KINETIC_STATUS_SOCKET_ERROR; } else { dest->bytesUsed += opStatus; #ifdef KINETIC_LOG_SOCKET_OPERATIONS LOGF("Received %d bytes (%zd of %zd)", opStatus, dest->bytesUsed, len); #endif } } } // Flush any remaining data, in case of a truncated read w/short dest buffer if (dest->bytesUsed < len) { bool abortFlush = false; uint8_t* discardedBytes = malloc(len - dest->bytesUsed); if (discardedBytes == NULL) { LOG("Failed allocating a socket read discard buffer!"); abortFlush = true; status = KINETIC_STATUS_MEMORY_ERROR; } while (!abortFlush && dest->bytesUsed < len) { int opStatus; fd_set readSet; struct timeval timeout; size_t remainingLen = len - dest->bytesUsed; // Time out after 5 seconds timeout.tv_sec = 5; timeout.tv_usec = 0; FD_ZERO(&readSet); FD_SET(socket, &readSet); opStatus = select(socket + 1, &readSet, NULL, NULL, &timeout); if (opStatus < 0) { // Error occurred LOGF("Failure trying to flush read socket data!" " status=%d, errno=%d, desc='%s'", status, errno, strerror(errno)); abortFlush = true; status = KINETIC_STATUS_SOCKET_ERROR; continue; } else if (opStatus == 0) { // Timeout occurred LOG("Timed out waiting to flush socket data!"); abortFlush = true; status = KINETIC_STATUS_SOCKET_TIMEOUT; continue; } else if (opStatus > 0) { // Data available to read // The socket is ready for reading opStatus = read(socket, discardedBytes, remainingLen); // Retry if no data yet... if (opStatus == -1 && ((errno == EINTR) || (errno == EAGAIN) || (errno == EWOULDBLOCK) )) { continue; } else if (opStatus <= 0) { LOGF("Failed to read from socket while flushing!" " status=%d, errno=%d, desc='%s'", opStatus, errno, strerror(errno)); abortFlush = true; status = KINETIC_STATUS_SOCKET_ERROR; } else { dest->bytesUsed += opStatus; LOGF("Flushed %d bytes from socket read pipe (%zd of %zd)", opStatus, dest->bytesUsed, len); } } } // Free up dynamically allocated memory before returning if (discardedBytes != NULL) { free(discardedBytes); } // Report any error that occurred during socket flush if (abortFlush) { LOG("Socket read pipe flush aborted!"); assert(status == KINETIC_STATUS_SUCCESS); return status; } // Report truncation of data for any variable length byte arrays LOGF("Socket read buffer was truncated due to buffer overrun!" " received=%zu, copied=%zu", len, dest->array.len); return KINETIC_STATUS_BUFFER_OVERRUN; } #ifdef KINETIC_LOG_SOCKET_OPERATIONS LOGF("Received %zd of %zd bytes requested", dest->bytesUsed, len); #endif return KINETIC_STATUS_SUCCESS; }
int KineticSocket_Connect(const char* host, int port, bool nonBlocking) { char port_str[32]; struct addrinfo hints; struct addrinfo* ai_result = NULL; struct addrinfo* ai = NULL; socket99_result result; // Setup server address info socket99_config cfg = { .host = (char*)host, .port = port, .nonblocking = nonBlocking, }; sprintf(port_str, "%d", port); // Open socket LOGF0("Connecting to %s:%d", host, port); if (!socket99_open(&cfg, &result)) { LOGF0("Failed to open socket connection with host: status %d, errno %d", result.status, result.saved_errno); return KINETIC_SOCKET_DESCRIPTOR_INVALID; } // Configure the socket socket99_set_hints(&cfg, &hints); if (getaddrinfo(cfg.host, port_str, &hints, &ai_result) != 0) { LOGF0("Failed to get socket address info: errno %d", errno); close(result.fd); return KINETIC_SOCKET_DESCRIPTOR_INVALID; } for (ai = ai_result; ai != NULL; ai = ai->ai_next) { int setsockopt_result; int buffer_size = KINETIC_OBJ_SIZE; #if defined(SO_NOSIGPIPE) && !defined(__APPLE__) // On BSD-like systems we can set SO_NOSIGPIPE on the socket to // prevent it from sending a PIPE signal and bringing down the whole // application if the server closes the socket forcibly int enable = 1; setsockopt_result = setsockopt(result.fd, SOL_SOCKET, SO_NOSIGPIPE, &enable, sizeof(enable)); // Allow ENOTSOCK because it allows tests to use pipes instead of // real sockets if (setsockopt_result != 0 && setsockopt_result != ENOTSOCK) { LOG0("Failed to set SO_NOSIGPIPE on socket"); continue; } #endif // Increase send buffer to KINETIC_OBJ_SIZE // Note: OS allocates 2x this value for its overhead setsockopt_result = setsockopt(result.fd, SOL_SOCKET, SO_SNDBUF, &buffer_size, sizeof(buffer_size)); if (setsockopt_result == -1) { LOG0("Error setting socket send buffer size"); continue; } // Increase receive buffer to KINETIC_OBJ_SIZE // Note: OS allocates 2x this value for its overheadbuffer_size setsockopt_result = setsockopt(result.fd, SOL_SOCKET, SO_RCVBUF, &buffer_size, sizeof(buffer_size)); if (setsockopt_result == -1) { LOG0("Error setting socket receive buffer size"); continue; } break; } freeaddrinfo(ai_result); if (ai == NULL || result.fd == KINETIC_SOCKET_DESCRIPTOR_INVALID) { // we went through all addresses without finding one we could bind to LOGF0("Could not connect to %s:%d", host, port); return KINETIC_SOCKET_DESCRIPTOR_INVALID; } else { LOGF1("Successfully connected to %s:%d (fd=%d)", host, port, result.fd); return result.fd; } } void KineticSocket_Close(int socket) { if (socket == -1) { LOG1("Not connected so no cleanup needed"); } else { LOGF0("Closing socket with fd=%d", socket); if (close(socket) == 0) { LOG2("Socket closed successfully"); } else { LOGF0("Error closing socket file descriptor!" " (fd=%d, errno=%d, desc='%s')", socket, errno, strerror(errno)); } } } KineticWaitStatus KineticSocket_WaitUntilDataAvailable(int socket, int timeout) { if (socket < 0) {return -1;} struct pollfd fd = { .fd = socket, .events = POLLIN, .revents = 0, }; int res = poll(&fd, 1, timeout); if (res > 0) { //if (fd.revents & POLLHUP) // hung up if (fd.revents & POLLIN) { return KINETIC_WAIT_STATUS_DATA_AVAILABLE; } else { return KINETIC_WAIT_STATUS_FATAL_ERROR; } } else if (res == 0) { return KINETIC_WAIT_STATUS_TIMED_OUT; } else if ((errno & (EAGAIN | EINTR)) != 0) { return KINETIC_WAIT_STATUS_RETRYABLE_ERROR; } else { return KINETIC_WAIT_STATUS_FATAL_ERROR; } } int KineticSocket_DataBytesAvailable(int socket) { if (socket < 0) {return -1;} int count = -1; ioctl(socket, FIONREAD, &count); return count; } KineticStatus KineticSocket_Read(int socket, ByteBuffer* dest, size_t len) { LOGF2("Reading %zd bytes into buffer @ 0x%zX from fd=%d", len, (size_t)dest->array.data, socket); KineticStatus status = KINETIC_STATUS_INVALID; // Read "up to" the allocated number of bytes into dest buffer size_t bytesToReadIntoBuffer = len; if (dest->array.len < len) { bytesToReadIntoBuffer = dest->array.len; } while (dest->bytesUsed < bytesToReadIntoBuffer) { int opStatus; fd_set readSet; struct timeval timeout; // Time out after 5 seconds timeout.tv_sec = 5; timeout.tv_usec = 0; FD_ZERO(&readSet); FD_SET(socket, &readSet); opStatus = select(socket + 1, &readSet, NULL, NULL, &timeout); if (opStatus < 0) { // Error occurred LOGF0("Failed waiting to read from socket!" " status=%d, errno=%d, desc='%s'", opStatus, errno, strerror(errno)); return KINETIC_STATUS_SOCKET_ERROR; } else if (opStatus == 0) { // Timeout occurred LOG0("Timed out waiting for socket data to arrive!"); return KINETIC_STATUS_SOCKET_TIMEOUT; } else if (opStatus > 0) { // Data available to read // The socket is ready for reading opStatus = read(socket, &dest->array.data[dest->bytesUsed], dest->array.len - dest->bytesUsed); // Retry if no data yet... if (opStatus == -1 && ((errno == EINTR) || (errno == EAGAIN) || (errno == EWOULDBLOCK) )) { continue; } else if (opStatus <= 0) { LOGF0("Failed to read from socket!" " status=%d, errno=%d, desc='%s'", opStatus, errno, strerror(errno)); return KINETIC_STATUS_SOCKET_ERROR; } else { dest->bytesUsed += opStatus; LOGF3("Received %d bytes (%zd of %zd)", opStatus, dest->bytesUsed, len); } } } // Flush any remaining data, in case of a truncated read w/short dest buffer if (dest->bytesUsed < len) { bool abortFlush = false; uint8_t* discardedBytes = malloc(len - dest->bytesUsed); if (discardedBytes == NULL) { LOG0("Failed allocating a socket read discard buffer!"); abortFlush = true; status = KINETIC_STATUS_MEMORY_ERROR; } while (!abortFlush && dest->bytesUsed < len) { int opStatus; fd_set readSet; struct timeval timeout; size_t remainingLen = len - dest->bytesUsed; // Time out after 5 seconds timeout.tv_sec = 5; timeout.tv_usec = 0; FD_ZERO(&readSet); FD_SET(socket, &readSet); opStatus = select(socket + 1, &readSet, NULL, NULL, &timeout); if (opStatus < 0) { // Error occurred LOGF0("Failure trying to flush read socket data!" " status=%d, errno=%d, desc='%s'", status, errno, strerror(errno)); abortFlush = true; status = KINETIC_STATUS_SOCKET_ERROR; continue; } else if (opStatus == 0) { // Timeout occurred LOG0("Timed out waiting to flush socket data!"); abortFlush = true; status = KINETIC_STATUS_SOCKET_TIMEOUT; continue; } else if (opStatus > 0) { // Data available to read // The socket is ready for reading opStatus = read(socket, discardedBytes, remainingLen); // Retry if no data yet... if (opStatus == -1 && ((errno == EINTR) || (errno == EAGAIN) || (errno == EWOULDBLOCK) )) { continue; } else if (opStatus <= 0) { LOGF0("Failed to read from socket while flushing!" " status=%d, errno=%d, desc='%s'", opStatus, errno, strerror(errno)); abortFlush = true; status = KINETIC_STATUS_SOCKET_ERROR; } else { dest->bytesUsed += opStatus; LOGF3("Flushed %d bytes from socket read pipe (%zd of %zd)", opStatus, dest->bytesUsed, len); } } } // Free up dynamically allocated memory before returning if (discardedBytes != NULL) { free(discardedBytes); } // Report any error that occurred during socket flush if (abortFlush) { LOG0("Socket read pipe flush aborted!"); assert(status == KINETIC_STATUS_SUCCESS); return status; } // Report truncation of data for any variable length byte arrays LOGF1("Socket read buffer was truncated due to buffer overrun!" " received=%zu, copied=%zu", len, dest->array.len); return KINETIC_STATUS_BUFFER_OVERRUN; } LOGF3("Received %zd of %zd bytes requested", dest->bytesUsed, len); return KINETIC_STATUS_SUCCESS; } KineticStatus KineticSocket_ReadProtobuf(int socket, KineticPDU* pdu) { size_t bytesToRead = pdu->header.protobufLength; LOGF2("Reading %zd bytes of protobuf", bytesToRead); uint8_t* packed = (uint8_t*)malloc(bytesToRead); if (packed == NULL) { LOG0("Failed allocating memory for protocol buffer"); return KINETIC_STATUS_MEMORY_ERROR; } ByteBuffer recvBuffer = ByteBuffer_Create(packed, bytesToRead, 0); KineticStatus status = KineticSocket_Read(socket, &recvBuffer, bytesToRead); if (status != KINETIC_STATUS_SUCCESS) { LOG0("Protobuf read failed!"); free(packed); return status; } else { pdu->proto = KineticProto_Message__unpack( NULL, recvBuffer.bytesUsed, recvBuffer.array.data); } free(packed); if (pdu->proto == NULL) { pdu->protobufDynamicallyExtracted = false; LOG0("Error unpacking incoming Kinetic protobuf message!"); return KINETIC_STATUS_DATA_ERROR; } else { pdu->protobufDynamicallyExtracted = true; LOG3("Protobuf unpacked successfully!"); return KINETIC_STATUS_SUCCESS; } }
static bool make_tcp_udp(socket99_config *cfg, socket99_result *out) { struct addrinfo hints; struct addrinfo *res = NULL; int fd = -1; char port_str[PORT_STR_BUFSZ]; memset(port_str, 0, PORT_STR_BUFSZ); socket99_set_hints(cfg, &hints); if (PORT_STR_BUFSZ < snprintf(port_str, PORT_STR_BUFSZ, "%u", cfg->port)) { return fail_with_errno(out, SOCKET99_ERROR_SNPRINTF); } struct addrinfo *ai = NULL; int addr_res = getaddrinfo(cfg->host, port_str, &hints, &res); if (addr_res != 0) { out->getaddrinfo_error = addr_res; return fail_with_errno(out, SOCKET99_ERROR_GETADDRINFO); } for (ai = res; ai != NULL; ai=ai->ai_next) { fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (fd == -1) { /* Save errno, but will be clobbered if others succeed. */ out->status = SOCKET99_ERROR_SOCKET; out->saved_errno = errno; errno = 0; continue; } if (!set_socket_options(cfg, out, fd)) { return false; } if (cfg->server) { int bind_res = bind(fd, res->ai_addr, res->ai_addrlen); if (bind_res == -1) { return fail_with_errno(out, SOCKET99_ERROR_BIND); } if (!cfg->datagram) { int listen_res = listen(fd, cfg->backlog_size); if (listen_res == -1) { return fail_with_errno(out, SOCKET99_ERROR_LISTEN); } } break; } else /* client */ { if (cfg->datagram) { break; } int res = connect(fd, ai->ai_addr, ai->ai_addrlen); if (res == 0) { break; } else { close(fd); fd = -1; out->status = SOCKET99_ERROR_CONNECT; continue; } } } if (fd == -1) { if (out->status == SOCKET99_OK) { return fail_with_errno(out, SOCKET99_ERROR_UNKNOWN); } else { out->saved_errno = errno; errno = 0; return false; } } out->status = SOCKET99_OK; freeaddrinfo(res); out->saved_errno = 0; out->fd = fd; return true; }