Split session functionality of SocketClient out into SocketSession.

This abstraction allows the session code to be shared between the socket client and (upcoming) server code. There should no difference in how the code works -- only the organization has changed. Note that no changes to the tests were required.

This same abstraction will be required for TlsClient but that will be done in a separate commit because it requires test changes.
This commit is contained in:
David Steele
2020-04-13 16:59:02 -04:00
parent b5347070af
commit b7d8d61526
8 changed files with 243 additions and 124 deletions
+8
View File
@@ -33,6 +33,14 @@
<p>Simplify storage driver info and list functions.</p>
</release-item>
<release-item>
<release-item-contributor-list>
<release-item-reviewer id="cynthia.shang"/>
</release-item-contributor-list>
<p>Split session functionality of <code>SocketClient</code> out into <code>SocketSession</code>.</p>
</release-item>
</release-development-list>
</release-core-list>
</release>
+1
View File
@@ -76,6 +76,7 @@ SRCS = \
common/io/read.c \
common/io/socket/client.c \
common/io/socket/common.c \
common/io/socket/session.c \
common/io/tls/client.c \
common/io/write.c \
common/ini.c \
+24 -93
View File
@@ -13,6 +13,7 @@ Socket Client
#include "common/log.h"
#include "common/io/socket/client.h"
#include "common/io/socket/common.h"
#include "common/io/socket/session.h"
#include "common/memContext.h"
#include "common/type/object.h"
#include "common/wait.h"
@@ -31,25 +32,10 @@ struct SocketClient
String *host; // Hostname or IP address
unsigned int port; // Port to connect to host on
TimeMSec timeout; // Timeout for any i/o operation (connect, read, etc.)
int fd; // File descriptor
};
OBJECT_DEFINE_GET(Fd, , SOCKET_CLIENT, int, fd);
OBJECT_DEFINE_GET(Host, const, SOCKET_CLIENT, const String *, host);
OBJECT_DEFINE_GET(Port, const, SOCKET_CLIENT, unsigned int, port);
OBJECT_DEFINE_MOVE(SOCKET_CLIENT);
/***********************************************************************************************************************************
Free connection
***********************************************************************************************************************************/
OBJECT_DEFINE_FREE_RESOURCE_BEGIN(SOCKET_CLIENT, LOG, logLevelTrace)
{
close(this->fd);
}
OBJECT_DEFINE_FREE_RESOURCE_END(LOG);
/**********************************************************************************************************************************/
SocketClient *
sckClientNew(const String *host, unsigned int port, TimeMSec timeout)
@@ -74,9 +60,6 @@ sckClientNew(const String *host, unsigned int port, TimeMSec timeout)
.host = strDup(host),
.port = port,
.timeout = timeout,
// Initialize file descriptor to -1 so we know when the socket is disconnected
.fd = -1,
};
sckClientStatLocal.object++;
@@ -87,7 +70,7 @@ sckClientNew(const String *host, unsigned int port, TimeMSec timeout)
}
/**********************************************************************************************************************************/
void
SocketSession *
sckClientOpen(SocketClient *this)
{
FUNCTION_LOG_BEGIN(logLevelTrace)
@@ -95,7 +78,8 @@ sckClientOpen(SocketClient *this)
FUNCTION_LOG_END();
ASSERT(this != NULL);
CHECK(this->fd == -1);
SocketSession *result = NULL;
MEM_CONTEXT_TEMP_BEGIN()
{
@@ -107,6 +91,7 @@ sckClientOpen(SocketClient *this)
{
// Assume there will be no retry
retry = false;
int fd = -1;
TRY_BEGIN()
{
@@ -124,26 +109,24 @@ sckClientOpen(SocketClient *this)
// Get an address for the host. We are only going to try the first address returned.
struct addrinfo *hostAddress;
int result;
int resultAddr;
if ((result = getaddrinfo(strPtr(this->host), port, &hints, &hostAddress)) != 0)
if ((resultAddr = getaddrinfo(strPtr(this->host), port, &hints, &hostAddress)) != 0)
{
THROW_FMT(
HostConnectError, "unable to get address for '%s': [%d] %s", strPtr(this->host), result,
gai_strerror(result));
HostConnectError, "unable to get address for '%s': [%d] %s", strPtr(this->host), resultAddr,
gai_strerror(resultAddr));
}
// Connect to the host
TRY_BEGIN()
{
this->fd = socket(hostAddress->ai_family, hostAddress->ai_socktype, hostAddress->ai_protocol);
THROW_ON_SYS_ERROR(this->fd == -1, HostConnectError, "unable to create socket");
fd = socket(hostAddress->ai_family, hostAddress->ai_socktype, hostAddress->ai_protocol);
THROW_ON_SYS_ERROR(fd == -1, HostConnectError, "unable to create socket");
memContextCallbackSet(this->memContext, sckClientFreeResource, this);
sckOptionSet(fd);
sckOptionSet(this->fd);
if (connect(this->fd, hostAddress->ai_addr, hostAddress->ai_addrlen) == -1)
if (connect(fd, hostAddress->ai_addr, hostAddress->ai_addrlen) == -1)
THROW_SYS_ERROR_FMT(HostConnectError, "unable to connect to '%s:%u'", strPtr(this->host), this->port);
}
FINALLY()
@@ -152,11 +135,21 @@ sckClientOpen(SocketClient *this)
}
TRY_END();
// Create the session
MEM_CONTEXT_PRIOR_BEGIN()
{
result = sckSessionNew(fd, this->host, this->port, this->timeout);
}
MEM_CONTEXT_PRIOR_END();
// Connection was successful
connected = true;
}
CATCH_ANY()
{
if (fd != -1)
close(fd);
// Retry if wait time has not expired
if (waitMore(wait))
{
@@ -165,8 +158,6 @@ sckClientOpen(SocketClient *this)
sckClientStatLocal.retry++;
}
sckClientClose(this);
}
TRY_END();
}
@@ -179,69 +170,9 @@ sckClientOpen(SocketClient *this)
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN_VOID();
FUNCTION_LOG_RETURN(SOCKET_SESSION, result);
}
/**********************************************************************************************************************************/
void
sckClientReadWait(SocketClient *this)
{
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(SOCKET_CLIENT, this);
FUNCTION_LOG_END();
ASSERT(this != NULL);
ASSERT(this->fd != -1);
// Initialize the file descriptor set used for select
fd_set selectSet;
FD_ZERO(&selectSet);
// We know the socket is not negative because it passed error handling, so it is safe to cast to unsigned
FD_SET((unsigned int)this->fd, &selectSet);
// Initialize timeout struct used for select. Recreate this structure each time since Linux (at least) will modify it.
struct timeval timeoutSelect;
timeoutSelect.tv_sec = (time_t)(this->timeout / MSEC_PER_SEC);
timeoutSelect.tv_usec = (time_t)(this->timeout % MSEC_PER_SEC * 1000);
// Determine if there is data to be read
int result = select(this->fd + 1, &selectSet, NULL, NULL, &timeoutSelect);
THROW_ON_SYS_ERROR_FMT(result == -1, AssertError, "unable to select from '%s:%u'", strPtr(this->host), this->port);
// If no data available after time allotted then error
if (!result)
{
THROW_FMT(
FileReadError, "timeout after %" PRIu64 "ms waiting for read from '%s:%u'", this->timeout, strPtr(this->host),
this->port);
}
FUNCTION_LOG_RETURN_VOID();
}
/**********************************************************************************************************************************/
void
sckClientClose(SocketClient *this)
{
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(SOCKET_CLIENT, this);
FUNCTION_LOG_END();
ASSERT(this != NULL);
// Close the socket
if (this->fd != -1)
{
memContextCallbackClear(this->memContext);
sckClientFreeResource(this);
this->fd = -1;
}
FUNCTION_LOG_RETURN_VOID();
}
/**********************************************************************************************************************************/
String *
sckClientStatStr(void)
+3 -20
View File
@@ -17,6 +17,7 @@ Object type
typedef struct SocketClient SocketClient;
#include "common/io/read.h"
#include "common/io/socket/session.h"
#include "common/io/write.h"
#include "common/time.h"
#include "common/type/string.h"
@@ -40,29 +41,11 @@ SocketClient *sckClientNew(const String *host, unsigned int port, TimeMSec timeo
Functions
***********************************************************************************************************************************/
// Open the connection
void sckClientOpen(SocketClient *this);
SocketSession *sckClientOpen(SocketClient *this);
// Wait for the socket to be readable
void sckClientReadWait(SocketClient *this);
// Close the connection
void sckClientClose(SocketClient *this);
// Move the socket to a new parent mem context
// Move to a new parent mem context
SocketClient *sckClientMove(SocketClient *this, MemContext *parentNew);
/***********************************************************************************************************************************
Getters/Setters
***********************************************************************************************************************************/
// Socket file descriptor
int sckClientFd(SocketClient *this);
// Socket host
const String *sckClientHost(const SocketClient *this);
// Socket port
unsigned int sckClientPort(const SocketClient *this);
// Statistics as a formatted string
String *sckClientStatStr(void);
+126
View File
@@ -0,0 +1,126 @@
/***********************************************************************************************************************************
Socket Session
***********************************************************************************************************************************/
#include "build.auto.h"
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <unistd.h>
#include "common/debug.h"
#include "common/log.h"
#include "common/io/socket/client.h"
#include "common/io/socket/common.h"
#include "common/memContext.h"
#include "common/type/object.h"
#include "common/wait.h"
/***********************************************************************************************************************************
Object type
***********************************************************************************************************************************/
struct SocketSession
{
MemContext *memContext; // Mem context
int fd; // File descriptor
String *host; // Hostname or IP address
unsigned int port; // Port to connect to host on
TimeMSec timeout; // Timeout for any i/o operation (connect, read, etc.)
};
OBJECT_DEFINE_GET(Fd, , SOCKET_SESSION, int, fd);
OBJECT_DEFINE_GET(Host, const, SOCKET_SESSION, const String *, host);
OBJECT_DEFINE_GET(Port, const, SOCKET_SESSION, unsigned int, port);
OBJECT_DEFINE_FREE(SOCKET_SESSION);
/***********************************************************************************************************************************
Free connection
***********************************************************************************************************************************/
OBJECT_DEFINE_FREE_RESOURCE_BEGIN(SOCKET_SESSION, LOG, logLevelTrace)
{
close(this->fd);
}
OBJECT_DEFINE_FREE_RESOURCE_END(LOG);
/**********************************************************************************************************************************/
SocketSession *
sckSessionNew(int fd, const String *host, unsigned int port, TimeMSec timeout)
{
FUNCTION_LOG_BEGIN(logLevelDebug)
FUNCTION_LOG_PARAM(INT, fd);
FUNCTION_LOG_PARAM(STRING, host);
FUNCTION_LOG_PARAM(UINT, port);
FUNCTION_LOG_PARAM(TIME_MSEC, timeout);
FUNCTION_LOG_END();
ASSERT(fd != -1);
ASSERT(host != NULL);
SocketSession *this = NULL;
MEM_CONTEXT_NEW_BEGIN("SocketSession")
{
this = memNew(sizeof(SocketSession));
*this = (SocketSession)
{
.memContext = MEM_CONTEXT_NEW(),
.fd = fd,
.host = strDup(host),
.port = port,
.timeout = timeout,
};
memContextCallbackSet(this->memContext, sckSessionFreeResource, this);
}
MEM_CONTEXT_NEW_END();
FUNCTION_LOG_RETURN(SOCKET_SESSION, this);
}
/**********************************************************************************************************************************/
void
sckSessionReadWait(SocketSession *this)
{
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(SOCKET_SESSION, this);
FUNCTION_LOG_END();
ASSERT(this != NULL);
ASSERT(this->fd != -1);
// Initialize the file descriptor set used for select
fd_set selectSet;
FD_ZERO(&selectSet);
// We know the socket is not negative because it passed error handling, so it is safe to cast to unsigned
FD_SET((unsigned int)this->fd, &selectSet);
// Initialize timeout struct used for select. Recreate this structure each time since Linux (at least) will modify it.
struct timeval timeoutSelect;
timeoutSelect.tv_sec = (time_t)(this->timeout / MSEC_PER_SEC);
timeoutSelect.tv_usec = (time_t)(this->timeout % MSEC_PER_SEC * 1000);
// Determine if there is data to be read
int result = select(this->fd + 1, &selectSet, NULL, NULL, &timeoutSelect);
THROW_ON_SYS_ERROR_FMT(result == -1, AssertError, "unable to select from '%s:%u'", strPtr(this->host), this->port);
// If no data available after time allotted then error
if (!result)
{
THROW_FMT(
FileReadError, "timeout after %" PRIu64 "ms waiting for read from '%s:%u'", this->timeout, strPtr(this->host),
this->port);
}
FUNCTION_LOG_RETURN_VOID();
}
/**********************************************************************************************************************************/
String *
sckSessionToLog(const SocketSession *this)
{
return strNewFmt("{fd: %d, host: %s, port: %u, timeout: %" PRIu64 "}", this->fd, strPtr(this->host), this->port, this->timeout);
}
+60
View File
@@ -0,0 +1,60 @@
/***********************************************************************************************************************************
Socket Session
A simple socket session intended to allow access to services that are exposed via a socket.
Currently this is not a full-featured session and is only intended to isolate socket functionality from the tls code.
***********************************************************************************************************************************/
#ifndef COMMON_IO_SOCKET_SESSION_H
#define COMMON_IO_SOCKET_SESSION_H
/***********************************************************************************************************************************
Object type
***********************************************************************************************************************************/
#define SOCKET_SESSION_TYPE SocketSession
#define SOCKET_SESSION_PREFIX sckSession
typedef struct SocketSession SocketSession;
#include "common/time.h"
#include "common/type/string.h"
/***********************************************************************************************************************************
Constructors
***********************************************************************************************************************************/
SocketSession *sckSessionNew(int fd, const String *host, unsigned int port, TimeMSec timeout);
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/
// Wait for the socket to be readable
void sckSessionReadWait(SocketSession *this);
/***********************************************************************************************************************************
Getters/Setters
***********************************************************************************************************************************/
// Socket file descriptor
int sckSessionFd(SocketSession *this);
// Socket host
const String *sckSessionHost(const SocketSession *this);
// Socket port
unsigned int sckSessionPort(const SocketSession *this);
/***********************************************************************************************************************************
Destructor
***********************************************************************************************************************************/
void sckSessionFree(SocketSession *this);
/***********************************************************************************************************************************
Macros for function logging
***********************************************************************************************************************************/
String *sckSessionToLog(const SocketSession *this);
#define FUNCTION_LOG_SOCKET_SESSION_TYPE \
SocketSession *
#define FUNCTION_LOG_SOCKET_SESSION_FORMAT(value, buffer, bufferSize) \
FUNCTION_LOG_STRING_OBJECT_FORMAT(value, sckSessionToLog, buffer, bufferSize)
#endif
+20 -11
View File
@@ -34,8 +34,9 @@ struct TlsClient
MemContext *memContext; // Mem context
TimeMSec timeout; // Timeout for any i/o operation (connect, read, etc.)
bool verifyPeer; // Should the peer (server) certificate be verified?
SocketClient *socket; // Client socket
SocketClient *socketClient; // Socket client
SocketSession *socketSession; // Socket session
SSL_CTX *context; // TLS context
SSL *session; // TLS session on the socket
@@ -132,7 +133,7 @@ tlsClientNew(SocketClient *socket, TimeMSec timeout, bool verifyPeer, const Stri
*this = (TlsClient)
{
.memContext = MEM_CONTEXT_NEW(),
.socket = sckClientMove(socket, MEM_CONTEXT_NEW()),
.socketClient = sckClientMove(socket, MEM_CONTEXT_NEW()),
.timeout = timeout,
.verifyPeer = verifyPeer,
};
@@ -342,7 +343,7 @@ tlsClientRead(THIS_VOID, Buffer *buffer, bool block)
{
// If no tls data pending then check the socket
if (!SSL_pending(this->session))
sckClientReadWait(this->socket);
sckSessionReadWait(this->socketSession);
// Read and handle errors
result = SSL_read(this->session, bufRemainsPtr(buffer), (int)bufRemains(buffer));
@@ -391,7 +392,7 @@ tlsWriteContinue(TlsClient *this, int writeResult, int writeError, size_t writeS
THROW_FMT(FileWriteError, "unable to write to tls [%d]", writeError);
// Wait for the socket to be readable for tls renegotiation
sckClientReadWait(this->socket);
sckSessionReadWait(this->socketSession);
}
}
else
@@ -447,7 +448,11 @@ tlsClientClose(TlsClient *this)
ASSERT(this != NULL);
// Close the socket
sckClientClose(this->socket);
if (this->socketSession != NULL)
{
sckSessionFree(this->socketSession);
this->socketSession = NULL;
}
// Free the TLS session
if (this->session != NULL)
@@ -512,16 +517,20 @@ tlsClientOpen(TlsClient *this)
TRY_BEGIN()
{
// Open the socket
sckClientOpen(this->socket);
MEM_CONTEXT_BEGIN(this->memContext)
{
this->socketSession = sckClientOpen(this->socketClient);
}
MEM_CONTEXT_END();
// Negotiate TLS
cryptoError((this->session = SSL_new(this->context)) == NULL, "unable to create TLS context");
cryptoError(
SSL_set_tlsext_host_name(this->session, strPtr(sckClientHost(this->socket))) != 1,
SSL_set_tlsext_host_name(this->session, strPtr(sckSessionHost(this->socketSession))) != 1,
"unable to set TLS host name");
cryptoError(
SSL_set_fd(this->session, sckClientFd(this->socket)) != 1, "unable to add socket to TLS context");
SSL_set_fd(this->session, sckSessionFd(this->socketSession)) != 1, "unable to add socket to TLS context");
cryptoError(SSL_connect(this->session) != 1, "unable to negotiate TLS connection");
// Connection was successful
@@ -559,13 +568,13 @@ tlsClientOpen(TlsClient *this)
{
THROW_FMT(
CryptoError, "unable to verify certificate presented by '%s:%u': [%ld] %s",
strPtr(sckClientHost(this->socket)), sckClientPort(this->socket), verifyResult,
strPtr(sckSessionHost(this->socketSession)), sckSessionPort(this->socketSession), verifyResult,
X509_verify_cert_error_string(verifyResult));
}
// Verify that the hostname appears in the certificate
X509 *certificate = SSL_get_peer_certificate(this->session);
bool nameResult = tlsClientHostVerify(sckClientHost(this->socket), certificate);
bool nameResult = tlsClientHostVerify(sckSessionHost(this->socketSession), certificate);
X509_free(certificate);
if (!nameResult)
@@ -573,7 +582,7 @@ tlsClientOpen(TlsClient *this)
THROW_FMT(
CryptoError,
"unable to find hostname '%s' in certificate common name or subject alternative names",
strPtr(sckClientHost(this->socket)));
strPtr(sckSessionHost(this->socketSession)));
}
}
+1
View File
@@ -246,6 +246,7 @@ unit:
common/io/tls/client: full
common/io/socket/client: full
common/io/socket/common: full
common/io/socket/session: full
# ----------------------------------------------------------------------------------------------------------------------------
- name: io-http