mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2024-12-12 10:04:14 +02:00
Improved support for dual stack connections.
Connections are established using the "happy eyeballs" approach from RFC 8305, i.e. new addresses (if available) are tried if the prior address has already had a reasonable time to connect. This prevents waiting too long on a failed connection but does not try all the addresses at once. Prior connections that are still waiting are rechecked periodically if no subsequent connection is successful.
This improves substantially on 39bb8a0
, which failed to take into account connection attempts that do not fail (but never connect) and use up all the available time.
This commit is contained in:
parent
f287178b70
commit
7448fde157
@ -34,6 +34,19 @@
|
|||||||
</release-bug-list>
|
</release-bug-list>
|
||||||
|
|
||||||
<release-improvement-list>
|
<release-improvement-list>
|
||||||
|
<release-item>
|
||||||
|
<github-issue id="2154"/>
|
||||||
|
<github-pull-request id="2235"/>
|
||||||
|
|
||||||
|
<release-item-contributor-list>
|
||||||
|
<release-item-ideator id="timothee.peignier"/>
|
||||||
|
<release-item-contributor id="david.steele"/>
|
||||||
|
<release-item-reviewer id="stephen.frost"/>
|
||||||
|
</release-item-contributor-list>
|
||||||
|
|
||||||
|
<p>Improved support for dual stack connections.</p>
|
||||||
|
</release-item>
|
||||||
|
|
||||||
<release-item>
|
<release-item>
|
||||||
<commit subject="Make meson the primary build system.">
|
<commit subject="Make meson the primary build system.">
|
||||||
<github-pull-request id="2264"/>
|
<github-pull-request id="2264"/>
|
||||||
|
@ -18,8 +18,25 @@ Object type
|
|||||||
struct AddressInfo
|
struct AddressInfo
|
||||||
{
|
{
|
||||||
AddressInfoPub pub; // Publicly accessible variables
|
AddressInfoPub pub; // Publicly accessible variables
|
||||||
|
struct addrinfo *info; // Linked list of addresses
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Local variables
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
// Store preferred addresses for hosts
|
||||||
|
typedef struct AddressInfoPreference
|
||||||
|
{
|
||||||
|
const String *const host; // Host
|
||||||
|
String *address; // Preferred address for host
|
||||||
|
} AddressInfoPreference;
|
||||||
|
|
||||||
|
static struct AddressInfoLocal
|
||||||
|
{
|
||||||
|
MemContext *memContext; // Mem context
|
||||||
|
List *prefList; // List of preferred addresses for hosts
|
||||||
|
} addressInfoLocal;
|
||||||
|
|
||||||
/***********************************************************************************************************************************
|
/***********************************************************************************************************************************
|
||||||
Free addrinfo linked list allocated by getaddrinfo()
|
Free addrinfo linked list allocated by getaddrinfo()
|
||||||
***********************************************************************************************************************************/
|
***********************************************************************************************************************************/
|
||||||
@ -34,11 +51,90 @@ addrInfoFreeResource(THIS_VOID)
|
|||||||
|
|
||||||
ASSERT(this != NULL);
|
ASSERT(this != NULL);
|
||||||
|
|
||||||
freeaddrinfo(*(struct addrinfo **)lstGet(this->pub.list, 0));
|
freeaddrinfo(this->info);
|
||||||
|
|
||||||
FUNCTION_LOG_RETURN_VOID();
|
FUNCTION_LOG_RETURN_VOID();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**********************************************************************************************************************************/
|
||||||
|
FN_EXTERN void
|
||||||
|
addrInfoSort(AddressInfo *const this)
|
||||||
|
{
|
||||||
|
FUNCTION_TEST_BEGIN();
|
||||||
|
FUNCTION_TEST_PARAM(ADDRESS_INFO, this);
|
||||||
|
FUNCTION_TEST_END();
|
||||||
|
|
||||||
|
// Only sort if there is more than one address
|
||||||
|
if (lstSize(this->pub.list) > 1)
|
||||||
|
{
|
||||||
|
// By default start with IPv6 and first address
|
||||||
|
int family = AF_INET6;
|
||||||
|
unsigned int addrIdx = 0;
|
||||||
|
|
||||||
|
// If a preferred address is in the list then move it to the first position and update family
|
||||||
|
const AddressInfoPreference *const addrPref = lstFind(addressInfoLocal.prefList, &this->pub.host);
|
||||||
|
|
||||||
|
if (addrPref != NULL)
|
||||||
|
{
|
||||||
|
AddressInfoItem *const addrFindItem = lstFind(this->pub.list, &addrPref->address);
|
||||||
|
|
||||||
|
if (addrFindItem != NULL)
|
||||||
|
{
|
||||||
|
// Swap with first address if the address is not already first
|
||||||
|
AddressInfoItem *const addrFirstItem = lstGet(this->pub.list, 0);
|
||||||
|
|
||||||
|
if (addrFirstItem != addrFindItem)
|
||||||
|
{
|
||||||
|
const AddressInfoItem addrCopyItem = *addrFirstItem;
|
||||||
|
*addrFirstItem = *addrFindItem;
|
||||||
|
*addrFindItem = addrCopyItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set family and skip first address
|
||||||
|
family = addrFirstItem->info->ai_family == AF_INET6 ? AF_INET : AF_INET6;
|
||||||
|
addrIdx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alternate IPv6 and IPv4 addresses
|
||||||
|
for (; addrIdx < lstSize(this->pub.list); addrIdx++)
|
||||||
|
{
|
||||||
|
AddressInfoItem *const addrItem = lstGet(this->pub.list, addrIdx);
|
||||||
|
|
||||||
|
// If not the family we expect, search for one
|
||||||
|
if (addrItem->info->ai_family != family)
|
||||||
|
{
|
||||||
|
unsigned int addrFindIdx = addrIdx + 1;
|
||||||
|
|
||||||
|
for (; addrFindIdx < lstSize(this->pub.list); addrFindIdx++)
|
||||||
|
{
|
||||||
|
AddressInfoItem *const addrFindItem = lstGet(this->pub.list, addrFindIdx);
|
||||||
|
|
||||||
|
if (addrFindItem->info->ai_family == family)
|
||||||
|
{
|
||||||
|
// Swap addresses
|
||||||
|
const AddressInfoItem addrCopyItem = *addrItem;
|
||||||
|
*addrItem = *addrFindItem;
|
||||||
|
*addrFindItem = addrCopyItem;
|
||||||
|
|
||||||
|
// Move on to next address
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address of the required family not found so sorting is done
|
||||||
|
if (addrFindIdx == lstSize(this->pub.list))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch family
|
||||||
|
family = family == AF_INET6 ? AF_INET : AF_INET6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FUNCTION_TEST_RETURN_VOID();
|
||||||
|
}
|
||||||
|
|
||||||
/**********************************************************************************************************************************/
|
/**********************************************************************************************************************************/
|
||||||
FN_EXTERN AddressInfo *
|
FN_EXTERN AddressInfo *
|
||||||
addrInfoNew(const String *const host, unsigned int port)
|
addrInfoNew(const String *const host, unsigned int port)
|
||||||
@ -51,6 +147,21 @@ addrInfoNew(const String *const host, unsigned int port)
|
|||||||
ASSERT(host != NULL);
|
ASSERT(host != NULL);
|
||||||
ASSERT(port != 0);
|
ASSERT(port != 0);
|
||||||
|
|
||||||
|
// Initialize mem context and preference list
|
||||||
|
if (addressInfoLocal.memContext == NULL)
|
||||||
|
{
|
||||||
|
MEM_CONTEXT_BEGIN(memContextTop())
|
||||||
|
{
|
||||||
|
MEM_CONTEXT_NEW_BEGIN(AddressInfo, .childQty = MEM_CONTEXT_QTY_MAX)
|
||||||
|
{
|
||||||
|
addressInfoLocal.memContext = MEM_CONTEXT_NEW();
|
||||||
|
addressInfoLocal.prefList = lstNewP(sizeof(AddressInfoPreference), .comparator = lstComparatorStr);
|
||||||
|
}
|
||||||
|
MEM_CONTEXT_NEW_END();
|
||||||
|
}
|
||||||
|
MEM_CONTEXT_END();
|
||||||
|
}
|
||||||
|
|
||||||
OBJ_NEW_BEGIN(AddressInfo, .childQty = MEM_CONTEXT_QTY_MAX, .callbackQty = 1)
|
OBJ_NEW_BEGIN(AddressInfo, .childQty = MEM_CONTEXT_QTY_MAX, .callbackQty = 1)
|
||||||
{
|
{
|
||||||
*this = (AddressInfo)
|
*this = (AddressInfo)
|
||||||
@ -59,7 +170,7 @@ addrInfoNew(const String *const host, unsigned int port)
|
|||||||
{
|
{
|
||||||
.host = strDup(host),
|
.host = strDup(host),
|
||||||
.port = port,
|
.port = port,
|
||||||
.list = lstNewP(sizeof(struct addrinfo *)),
|
.list = lstNewP(sizeof(AddressInfoItem), .comparator = lstComparatorStr),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -79,23 +190,28 @@ addrInfoNew(const String *const host, unsigned int port)
|
|||||||
cvtUIntToZ(port, portZ, sizeof(portZ));
|
cvtUIntToZ(port, portZ, sizeof(portZ));
|
||||||
|
|
||||||
// Do the lookup
|
// Do the lookup
|
||||||
struct addrinfo *result;
|
|
||||||
int error;
|
int error;
|
||||||
|
|
||||||
if ((error = getaddrinfo(strZ(host), portZ, &hints, &result)) != 0)
|
if ((error = getaddrinfo(strZ(host), portZ, &hints, &this->info)) != 0)
|
||||||
THROW_FMT(HostConnectError, "unable to get address for '%s': [%d] %s", strZ(host), error, gai_strerror(error));
|
THROW_FMT(HostConnectError, "unable to get address for '%s': [%d] %s", strZ(host), error, gai_strerror(error));
|
||||||
|
|
||||||
// Set free callback to ensure address info is freed
|
|
||||||
memContextCallbackSet(objMemContext(this), addrInfoFreeResource, this);
|
|
||||||
|
|
||||||
// Convert address linked list to list
|
// Convert address linked list to list
|
||||||
lstAdd(this->pub.list, &result);
|
MEM_CONTEXT_OBJ_BEGIN(this->pub.list)
|
||||||
|
|
||||||
while (result->ai_next != NULL)
|
|
||||||
{
|
{
|
||||||
lstAdd(this->pub.list, &result->ai_next);
|
struct addrinfo *info = this->info;
|
||||||
result = result->ai_next;
|
|
||||||
|
lstAdd(this->pub.list, &(AddressInfoItem){.name = addrInfoToStr(info), .info = info});
|
||||||
|
|
||||||
|
// Set free callback to ensure address info is freed
|
||||||
|
memContextCallbackSet(objMemContext(this), addrInfoFreeResource, this);
|
||||||
|
|
||||||
|
while (info->ai_next != NULL)
|
||||||
|
{
|
||||||
|
info = info->ai_next;
|
||||||
|
lstAdd(this->pub.list, &(AddressInfoItem){.name = addrInfoToStr(info), .info = info});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
MEM_CONTEXT_OBJ_END();
|
||||||
}
|
}
|
||||||
MEM_CONTEXT_TEMP_END();
|
MEM_CONTEXT_TEMP_END();
|
||||||
}
|
}
|
||||||
@ -104,6 +220,41 @@ addrInfoNew(const String *const host, unsigned int port)
|
|||||||
FUNCTION_LOG_RETURN(ADDRESS_INFO, this);
|
FUNCTION_LOG_RETURN(ADDRESS_INFO, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**********************************************************************************************************************************/
|
||||||
|
FN_EXTERN void
|
||||||
|
addrInfoPrefer(AddressInfo *this, unsigned int index)
|
||||||
|
{
|
||||||
|
FUNCTION_LOG_BEGIN(logLevelDebug);
|
||||||
|
FUNCTION_LOG_PARAM(ADDRESS_INFO, this);
|
||||||
|
FUNCTION_LOG_PARAM(UINT, index);
|
||||||
|
FUNCTION_LOG_END();
|
||||||
|
|
||||||
|
AddressInfoPreference *const addrPref = lstFind(addressInfoLocal.prefList, &this->pub.host);
|
||||||
|
const String *const address = addrInfoGet(this, index)->name;
|
||||||
|
|
||||||
|
MEM_CONTEXT_OBJ_BEGIN(addressInfoLocal.prefList)
|
||||||
|
{
|
||||||
|
// If no preference set yet
|
||||||
|
if (addrPref == NULL)
|
||||||
|
{
|
||||||
|
lstAdd(addressInfoLocal.prefList, &(AddressInfoPreference){.host = strDup(this->pub.host), .address = strDup(address)});
|
||||||
|
lstSort(addressInfoLocal.prefList, sortOrderAsc);
|
||||||
|
}
|
||||||
|
// Else update address
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Free the old address
|
||||||
|
strFree(addrPref->address);
|
||||||
|
|
||||||
|
// Assign new address
|
||||||
|
addrPref->address = strDup(address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MEM_CONTEXT_OBJ_END();
|
||||||
|
|
||||||
|
FUNCTION_LOG_RETURN_VOID();
|
||||||
|
}
|
||||||
|
|
||||||
/***********************************************************************************************************************************
|
/***********************************************************************************************************************************
|
||||||
Convert address to a zero-terminated string
|
Convert address to a zero-terminated string
|
||||||
***********************************************************************************************************************************/
|
***********************************************************************************************************************************/
|
||||||
@ -166,21 +317,18 @@ addrInfoToStr(const struct addrinfo *const addrInfo)
|
|||||||
FN_EXTERN void
|
FN_EXTERN void
|
||||||
addrInfoToLog(const AddressInfo *const this, StringStatic *const debugLog)
|
addrInfoToLog(const AddressInfo *const this, StringStatic *const debugLog)
|
||||||
{
|
{
|
||||||
char address[48];
|
|
||||||
|
|
||||||
strStcFmt(debugLog, "{host: ");
|
strStcFmt(debugLog, "{host: ");
|
||||||
strToLog(addrInfoHost(this), debugLog);
|
strToLog(addrInfoHost(this), debugLog);
|
||||||
strStcFmt(debugLog, ", port: %u, list: [", addrInfoPort(this));
|
strStcFmt(debugLog, ", port: %u, list: [", addrInfoPort(this));
|
||||||
|
|
||||||
for (unsigned int listIdx = 0; listIdx < addrInfoSize(this); listIdx++)
|
for (unsigned int listIdx = 0; listIdx < addrInfoSize(this); listIdx++)
|
||||||
{
|
{
|
||||||
const struct addrinfo *const addrInfo = addrInfoGet(this, listIdx);
|
const AddressInfoItem *const addrItem = addrInfoGet(this, listIdx);
|
||||||
|
|
||||||
if (listIdx != 0)
|
if (listIdx != 0)
|
||||||
strStcCat(debugLog, ", ");
|
strStcCat(debugLog, ", ");
|
||||||
|
|
||||||
addrInfoToZ(addrInfo, address, sizeof(address));
|
strStcCat(debugLog, strZ(addrItem->name));
|
||||||
strStcCat(debugLog, address);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
strStcCat(debugLog, "]}");
|
strStcCat(debugLog, "]}");
|
||||||
|
@ -25,6 +25,12 @@ FN_EXTERN AddressInfo *addrInfoNew(const String *const host, unsigned int port);
|
|||||||
/***********************************************************************************************************************************
|
/***********************************************************************************************************************************
|
||||||
Getters/Setters
|
Getters/Setters
|
||||||
***********************************************************************************************************************************/
|
***********************************************************************************************************************************/
|
||||||
|
typedef struct AddressInfoItem
|
||||||
|
{
|
||||||
|
String *name; // Address as a string
|
||||||
|
struct addrinfo *info; // Full address info
|
||||||
|
} AddressInfoItem;
|
||||||
|
|
||||||
typedef struct AddressInfoPub
|
typedef struct AddressInfoPub
|
||||||
{
|
{
|
||||||
const String *host; // Host for address info lookup
|
const String *host; // Host for address info lookup
|
||||||
@ -33,10 +39,10 @@ typedef struct AddressInfoPub
|
|||||||
} AddressInfoPub;
|
} AddressInfoPub;
|
||||||
|
|
||||||
// Get address
|
// Get address
|
||||||
FN_INLINE_ALWAYS const struct addrinfo *
|
FN_INLINE_ALWAYS const AddressInfoItem *
|
||||||
addrInfoGet(const AddressInfo *const this, const unsigned int index)
|
addrInfoGet(const AddressInfo *const this, const unsigned int index)
|
||||||
{
|
{
|
||||||
return *(const struct addrinfo **)lstGet(THIS_PUB(AddressInfo)->list, index);
|
return (const AddressInfoItem *)lstGet(THIS_PUB(AddressInfo)->list, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get lookup host
|
// Get lookup host
|
||||||
@ -60,6 +66,15 @@ addrInfoSize(const AddressInfo *const this)
|
|||||||
return lstSize(THIS_PUB(AddressInfo)->list);
|
return lstSize(THIS_PUB(AddressInfo)->list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Functions
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
// Sort addresses alternating between IPv6 and IPv4
|
||||||
|
FN_EXTERN void addrInfoSort(AddressInfo *this);
|
||||||
|
|
||||||
|
// Set preferred address for host, which will be preferred in future lookups
|
||||||
|
FN_EXTERN void addrInfoPrefer(AddressInfo *this, unsigned int index);
|
||||||
|
|
||||||
/***********************************************************************************************************************************
|
/***********************************************************************************************************************************
|
||||||
Destructor
|
Destructor
|
||||||
***********************************************************************************************************************************/
|
***********************************************************************************************************************************/
|
||||||
|
@ -11,6 +11,7 @@ Socket Client
|
|||||||
#include "common/debug.h"
|
#include "common/debug.h"
|
||||||
#include "common/error/retry.h"
|
#include "common/error/retry.h"
|
||||||
#include "common/io/client.h"
|
#include "common/io/client.h"
|
||||||
|
#include "common/io/fd.h"
|
||||||
#include "common/io/socket/address.h"
|
#include "common/io/socket/address.h"
|
||||||
#include "common/io/socket/client.h"
|
#include "common/io/socket/client.h"
|
||||||
#include "common/io/socket/common.h"
|
#include "common/io/socket/common.h"
|
||||||
@ -57,7 +58,64 @@ sckClientToLog(const THIS_VOID, StringStatic *const debugLog)
|
|||||||
#define FUNCTION_LOG_SOCKET_CLIENT_FORMAT(value, buffer, bufferSize) \
|
#define FUNCTION_LOG_SOCKET_CLIENT_FORMAT(value, buffer, bufferSize) \
|
||||||
FUNCTION_LOG_OBJECT_FORMAT(value, sckClientToLog, buffer, bufferSize)
|
FUNCTION_LOG_OBJECT_FORMAT(value, sckClientToLog, buffer, bufferSize)
|
||||||
|
|
||||||
/**********************************************************************************************************************************/
|
/***********************************************************************************************************************************
|
||||||
|
Connections are established using the "happy eyeballs" approach from RFC 8305, i.e. new addresses (if available) are tried if the
|
||||||
|
prior address has already had a reasonable time to connect. This prevents waiting too long on a failed connection but does not try
|
||||||
|
all the addresses at once. Prior connections that are still waiting are rechecked periodically if no subsequent connection is
|
||||||
|
successful.
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
typedef struct SckClientOpenData
|
||||||
|
{
|
||||||
|
const struct addrinfo *const address; // IP address
|
||||||
|
const char *name; // Combination of host, address, port
|
||||||
|
int fd; // File descriptor for socket
|
||||||
|
int errNo; // Current error (may just indicate to keep waiting)
|
||||||
|
} SckClientOpenData;
|
||||||
|
|
||||||
|
// Helper to determine if non-blocking connection has been established
|
||||||
|
static bool
|
||||||
|
sckClientOpenWait(SckClientOpenData *const openData, const TimeMSec timeout)
|
||||||
|
{
|
||||||
|
ASSERT(openData != NULL);
|
||||||
|
|
||||||
|
FUNCTION_LOG_BEGIN(logLevelTrace);
|
||||||
|
FUNCTION_LOG_PARAM(STRINGZ, openData->name);
|
||||||
|
FUNCTION_LOG_PARAM(INT, openData->fd);
|
||||||
|
FUNCTION_LOG_PARAM(INT, openData->errNo);
|
||||||
|
FUNCTION_LOG_PARAM(TIME_MSEC, timeout);
|
||||||
|
FUNCTION_LOG_END();
|
||||||
|
|
||||||
|
ASSERT(openData->name != NULL);
|
||||||
|
ASSERT(openData->fd > 0);
|
||||||
|
ASSERT(openData->errNo != 0);
|
||||||
|
|
||||||
|
bool result = true;
|
||||||
|
|
||||||
|
// The connection has started but since we are in non-blocking mode it has not completed yet
|
||||||
|
if (openData->errNo == EINPROGRESS || openData->errNo == EINTR)
|
||||||
|
{
|
||||||
|
// Wait for write-ready
|
||||||
|
if (fdReadyWrite(openData->fd, timeout))
|
||||||
|
{
|
||||||
|
// Check for success or error. If the connection was successful this will set errNo to 0.
|
||||||
|
socklen_t errNoLen = sizeof(openData->errNo);
|
||||||
|
|
||||||
|
THROW_ON_SYS_ERROR_FMT(
|
||||||
|
getsockopt(openData->fd, SOL_SOCKET, SO_ERROR, &openData->errNo, &errNoLen) == -1, HostConnectError,
|
||||||
|
"unable to get socket error for '%s'", openData->name);
|
||||||
|
}
|
||||||
|
// Else still waiting on connection
|
||||||
|
else
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Throw error if it is still set
|
||||||
|
if (result && openData->errNo != 0)
|
||||||
|
THROW_SYS_ERROR_CODE_FMT(openData->errNo, HostConnectError, "unable to connect to '%s'", openData->name);
|
||||||
|
|
||||||
|
FUNCTION_LOG_RETURN(BOOL, result);
|
||||||
|
}
|
||||||
|
|
||||||
static IoSession *
|
static IoSession *
|
||||||
sckClientOpen(THIS_VOID)
|
sckClientOpen(THIS_VOID)
|
||||||
{
|
{
|
||||||
@ -77,47 +135,124 @@ sckClientOpen(THIS_VOID)
|
|||||||
ErrorRetry *const errRetry = errRetryNew();
|
ErrorRetry *const errRetry = errRetryNew();
|
||||||
bool retry;
|
bool retry;
|
||||||
|
|
||||||
// Get an address list for the host
|
// Get an address list for the host and sort it
|
||||||
const AddressInfo *const addrInfo = addrInfoNew(this->host, this->port);
|
AddressInfo *const addrInfo = addrInfoNew(this->host, this->port);
|
||||||
unsigned int addrInfoIdx = 0;
|
addrInfoSort(addrInfo);
|
||||||
|
|
||||||
|
// Try addresses until success or timeout
|
||||||
|
List *const openDataList = lstNewP(sizeof(SckClientOpenData));
|
||||||
|
volatile unsigned int addrInfoIdx = 0;
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
// Assume there will be no retry
|
// Assume there will be no retry
|
||||||
|
SckClientOpenData *volatile openData = NULL;
|
||||||
|
const ErrorType *errLastType = NULL;
|
||||||
|
String *errLastMessage = NULL;
|
||||||
retry = false;
|
retry = false;
|
||||||
volatile int fd = -1;
|
|
||||||
|
|
||||||
|
// Try connection
|
||||||
TRY_BEGIN()
|
TRY_BEGIN()
|
||||||
{
|
{
|
||||||
// Get next address for the host
|
// Iterate waiting connections and see if any of them have completed
|
||||||
const struct addrinfo *const addressFound = addrInfoGet(addrInfo, addrInfoIdx);
|
for (unsigned int openDataIdx = 0; openDataIdx < lstSize(openDataList); openDataIdx++)
|
||||||
|
|
||||||
// Connect to the host
|
|
||||||
fd = socket(addressFound->ai_family, addressFound->ai_socktype, addressFound->ai_protocol);
|
|
||||||
THROW_ON_SYS_ERROR(fd == -1, HostConnectError, "unable to create socket");
|
|
||||||
|
|
||||||
sckOptionSet(fd);
|
|
||||||
sckConnect(fd, this->host, this->port, addressFound, this->timeoutConnect);
|
|
||||||
|
|
||||||
// Create the session
|
|
||||||
MEM_CONTEXT_PRIOR_BEGIN()
|
|
||||||
{
|
{
|
||||||
result = sckSessionNew(ioSessionRoleClient, fd, this->host, this->port, this->timeoutSession);
|
SckClientOpenData *const openDataWait = lstGet(openDataList, openDataIdx);
|
||||||
}
|
|
||||||
MEM_CONTEXT_PRIOR_END();
|
|
||||||
|
|
||||||
// Update client name to include address
|
if (openDataWait->fd != 0 && sckClientOpenWait(openDataWait, 0))
|
||||||
strTrunc(this->name);
|
{
|
||||||
strCat(this->name, addrInfoToName(this->host, this->port, addressFound));
|
openData = openDataWait;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try or retry a connection since none of the waiting connections completed
|
||||||
|
if (openData == NULL)
|
||||||
|
{
|
||||||
|
// If connection does not exist yet then create it
|
||||||
|
if (addrInfoIdx == lstSize(openDataList))
|
||||||
|
{
|
||||||
|
openData = lstAdd(openDataList, &(SckClientOpenData){.address = addrInfoGet(addrInfo, addrInfoIdx)->info});
|
||||||
|
openData->name = strZ(addrInfoToName(this->host, this->port, openData->address));
|
||||||
|
}
|
||||||
|
// Else search for a connection that is not waiting
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (; addrInfoIdx < lstSize(openDataList); addrInfoIdx++)
|
||||||
|
{
|
||||||
|
SckClientOpenData *const openDataNoWait = lstGet(openDataList, addrInfoIdx);
|
||||||
|
|
||||||
|
if (openDataNoWait->fd == 0)
|
||||||
|
{
|
||||||
|
openData = openDataNoWait;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt connection to host
|
||||||
|
if (openData != NULL)
|
||||||
|
{
|
||||||
|
openData->fd = socket(
|
||||||
|
openData->address->ai_family, openData->address->ai_socktype, openData->address->ai_protocol);
|
||||||
|
THROW_ON_SYS_ERROR(openData->fd == -1, HostConnectError, "unable to create socket");
|
||||||
|
|
||||||
|
sckOptionSet(openData->fd);
|
||||||
|
|
||||||
|
if (connect(openData->fd, openData->address->ai_addr, openData->address->ai_addrlen) == -1)
|
||||||
|
{
|
||||||
|
// Save the error and wait for connection. The initial wait time will be fixed at 250ms, the default
|
||||||
|
// value recommended by RFC 8305.
|
||||||
|
openData->errNo = errno;
|
||||||
|
|
||||||
|
sckClientOpenWait(openData, 250);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connection was successful so open session and set result
|
||||||
|
if (openData != NULL && openData->errNo == 0)
|
||||||
|
{
|
||||||
|
// Create the session
|
||||||
|
MEM_CONTEXT_PRIOR_BEGIN()
|
||||||
|
{
|
||||||
|
result = sckSessionNew(ioSessionRoleClient, openData->fd, this->host, this->port, this->timeoutSession);
|
||||||
|
}
|
||||||
|
MEM_CONTEXT_PRIOR_END();
|
||||||
|
|
||||||
|
// Update client name to include address
|
||||||
|
strTrunc(this->name);
|
||||||
|
strCatZ(this->name, openData->name);
|
||||||
|
|
||||||
|
// Set preferred address
|
||||||
|
addrInfoPrefer(addrInfo, addrInfoIdx);
|
||||||
|
|
||||||
|
// Clear socket so it will not be freed later
|
||||||
|
openData->fd = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
CATCH_ANY()
|
CATCH_ANY()
|
||||||
{
|
{
|
||||||
// Close socket
|
// Close socket
|
||||||
close(fd);
|
ASSERT(openData != NULL);
|
||||||
|
close(openData->fd);
|
||||||
|
|
||||||
|
// Clear socket so the connection can be retried
|
||||||
|
openData->fd = 0;
|
||||||
|
openData->errNo = 0;
|
||||||
|
|
||||||
// Add the error retry info
|
// Add the error retry info
|
||||||
errRetryAddP(errRetry);
|
errRetryAddP(errRetry);
|
||||||
|
|
||||||
|
// Store error info for later logging
|
||||||
|
errLastType = errorType();
|
||||||
|
errLastMessage = strNewZ(errorMessage());
|
||||||
|
}
|
||||||
|
TRY_END();
|
||||||
|
|
||||||
|
// If the connection was not completed then wait and/or retry
|
||||||
|
if (result == NULL)
|
||||||
|
{
|
||||||
// Increment address info index and reset if the end has been reached. When the end has been reached sleep for a bit
|
// Increment address info index and reset if the end has been reached. When the end has been reached sleep for a bit
|
||||||
// to hopefully have better chance at succeeding, otherwise continue right to the next address.
|
// to hopefully have better chance at succeeding, otherwise continue right to the next address.
|
||||||
addrInfoIdx++;
|
addrInfoIdx++;
|
||||||
@ -130,18 +265,47 @@ sckClientOpen(THIS_VOID)
|
|||||||
else
|
else
|
||||||
retry = true;
|
retry = true;
|
||||||
|
|
||||||
// Error when no retries remain
|
// General timeout errors when no retries remain
|
||||||
if (!retry)
|
if (!retry)
|
||||||
THROWP(errRetryType(errRetry), strZ(errRetryMessage(errRetry)));
|
{
|
||||||
|
for (unsigned int openDataIdx = 0; openDataIdx < lstSize(openDataList); openDataIdx++)
|
||||||
|
{
|
||||||
|
SckClientOpenData *const openDataTimeout = lstGet(openDataList, openDataIdx);
|
||||||
|
|
||||||
// Log retry
|
if (openDataTimeout->fd != 0)
|
||||||
LOG_DEBUG_FMT("retry %s: %s", errorTypeName(errorType()), errorMessage());
|
{
|
||||||
statInc(SOCKET_STAT_RETRY_STR);
|
errRetryAddP(
|
||||||
|
errRetry, .type = &HostConnectError,
|
||||||
|
.message = strNewFmt("timeout connecting to '%s'", openDataTimeout->name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log retry when there was an error
|
||||||
|
if (errLastType != NULL)
|
||||||
|
{
|
||||||
|
LOG_DEBUG_FMT("retry %s: %s", errorTypeName(errLastType), strZ(errLastMessage));
|
||||||
|
strFree(errLastMessage);
|
||||||
|
|
||||||
|
statInc(SOCKET_STAT_RETRY_STR);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
TRY_END();
|
|
||||||
}
|
}
|
||||||
while (retry);
|
while (retry);
|
||||||
|
|
||||||
|
// Free any connections that were in progress but never completed
|
||||||
|
for (unsigned int openDataIdx = 0; openDataIdx < lstSize(openDataList); openDataIdx++)
|
||||||
|
{
|
||||||
|
SckClientOpenData *const openDataFree = lstGet(openDataList, openDataIdx);
|
||||||
|
|
||||||
|
if (openDataFree->fd != 0)
|
||||||
|
close(openDataFree->fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error when no result
|
||||||
|
if (result == NULL)
|
||||||
|
THROWP(errRetryType(errRetry), strZ(errRetryMessage(errRetry)));
|
||||||
|
|
||||||
statInc(SOCKET_STAT_SESSION_STR);
|
statInc(SOCKET_STAT_SESSION_STR);
|
||||||
}
|
}
|
||||||
MEM_CONTEXT_TEMP_END();
|
MEM_CONTEXT_TEMP_END();
|
||||||
|
@ -9,7 +9,6 @@ Socket Common Functions
|
|||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
|
|
||||||
#include "common/debug.h"
|
#include "common/debug.h"
|
||||||
#include "common/io/fd.h"
|
|
||||||
#include "common/io/socket/address.h"
|
#include "common/io/socket/address.h"
|
||||||
#include "common/io/socket/common.h"
|
#include "common/io/socket/common.h"
|
||||||
#include "common/log.h"
|
#include "common/log.h"
|
||||||
@ -137,55 +136,3 @@ sckOptionSet(int fd)
|
|||||||
|
|
||||||
FUNCTION_TEST_RETURN_VOID();
|
FUNCTION_TEST_RETURN_VOID();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**********************************************************************************************************************************/
|
|
||||||
static bool
|
|
||||||
sckConnectInProgress(int errNo)
|
|
||||||
{
|
|
||||||
return errNo == EINPROGRESS || errNo == EINTR;
|
|
||||||
}
|
|
||||||
|
|
||||||
FN_EXTERN void
|
|
||||||
sckConnect(int fd, const String *host, unsigned int port, const struct addrinfo *hostAddress, TimeMSec timeout)
|
|
||||||
{
|
|
||||||
FUNCTION_LOG_BEGIN(logLevelTrace);
|
|
||||||
FUNCTION_LOG_PARAM(INT, fd);
|
|
||||||
FUNCTION_LOG_PARAM(STRING, host);
|
|
||||||
FUNCTION_LOG_PARAM(UINT, port);
|
|
||||||
FUNCTION_LOG_PARAM_P(VOID, hostAddress);
|
|
||||||
FUNCTION_LOG_PARAM(TIME_MSEC, timeout);
|
|
||||||
FUNCTION_LOG_END();
|
|
||||||
|
|
||||||
ASSERT(host != NULL);
|
|
||||||
ASSERT(hostAddress != NULL);
|
|
||||||
|
|
||||||
// Attempt connection
|
|
||||||
if (connect(fd, hostAddress->ai_addr, hostAddress->ai_addrlen) == -1)
|
|
||||||
{
|
|
||||||
// Save the error
|
|
||||||
int errNo = errno;
|
|
||||||
|
|
||||||
// The connection has started but since we are in non-blocking mode it has not completed yet
|
|
||||||
if (sckConnectInProgress(errNo))
|
|
||||||
{
|
|
||||||
// Wait for write-ready
|
|
||||||
if (!fdReadyWrite(fd, timeout))
|
|
||||||
THROW_FMT(HostConnectError, "timeout connecting to '%s'", strZ(addrInfoToName(host, port, hostAddress)));
|
|
||||||
|
|
||||||
// Check for success or error. If the connection was successful this will set errNo to 0.
|
|
||||||
socklen_t errNoLen = sizeof(errNo);
|
|
||||||
|
|
||||||
THROW_ON_SYS_ERROR(
|
|
||||||
getsockopt(fd, SOL_SOCKET, SO_ERROR, &errNo, &errNoLen) == -1, HostConnectError, "unable to get socket error");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Throw error if it is still set
|
|
||||||
if (errNo != 0)
|
|
||||||
{
|
|
||||||
THROW_SYS_ERROR_CODE_FMT(
|
|
||||||
errNo, HostConnectError, "unable to connect to '%s'", strZ(addrInfoToName(host, port, hostAddress)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FUNCTION_LOG_RETURN_VOID();
|
|
||||||
}
|
|
||||||
|
@ -18,7 +18,4 @@ FN_EXTERN void sckInit(bool block, bool keepAlive, int tcpKeepAliveCount, int tc
|
|||||||
// Set options on a socket
|
// Set options on a socket
|
||||||
FN_EXTERN void sckOptionSet(int fd);
|
FN_EXTERN void sckOptionSet(int fd);
|
||||||
|
|
||||||
// Connect socket to an IP address
|
|
||||||
FN_EXTERN void sckConnect(int fd, const String *host, unsigned int port, const struct addrinfo *hostAddress, TimeMSec timeout);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -165,7 +165,7 @@ sckServerNew(const String *const address, const unsigned int port, const TimeMSe
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Lookup address
|
// Lookup address
|
||||||
const struct addrinfo *const addressFound = addrInfoGet(addrInfoNew(this->address, this->port), 0);
|
const struct addrinfo *const addressFound = addrInfoGet(addrInfoNew(this->address, this->port), 0)->info;
|
||||||
|
|
||||||
// Create socket
|
// Create socket
|
||||||
THROW_ON_SYS_ERROR(
|
THROW_ON_SYS_ERROR(
|
||||||
|
@ -371,6 +371,7 @@ unit:
|
|||||||
common/io/socket/client:
|
common/io/socket/client:
|
||||||
function:
|
function:
|
||||||
- sckClientOpen
|
- sckClientOpen
|
||||||
|
- sckClientOpenWait
|
||||||
|
|
||||||
coverage:
|
coverage:
|
||||||
- common/io/client
|
- common/io/client
|
||||||
@ -404,6 +405,9 @@ unit:
|
|||||||
- common/io/http/session
|
- common/io/http/session
|
||||||
- common/io/http/url
|
- common/io/http/url
|
||||||
|
|
||||||
|
include:
|
||||||
|
- common/io/socket/address
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------------------------------------------------------
|
||||||
- name: exec
|
- name: exec
|
||||||
total: 2
|
total: 2
|
||||||
|
@ -3,7 +3,6 @@ Harness for Io Testing
|
|||||||
***********************************************************************************************************************************/
|
***********************************************************************************************************************************/
|
||||||
#include "build.auto.h"
|
#include "build.auto.h"
|
||||||
|
|
||||||
#include "common/harnessConfig.h"
|
|
||||||
#include "common/harnessDebug.h"
|
#include "common/harnessDebug.h"
|
||||||
#include "common/harnessSocket.h"
|
#include "common/harnessSocket.h"
|
||||||
|
|
||||||
@ -19,8 +18,61 @@ static struct
|
|||||||
{
|
{
|
||||||
// Shim clientOpen() to use a fake file descriptor
|
// Shim clientOpen() to use a fake file descriptor
|
||||||
bool localShimSckClientOpen;
|
bool localShimSckClientOpen;
|
||||||
|
|
||||||
|
// Shim sckClientOpenWait() to return false for an address one time
|
||||||
|
const char *clientOpenWaitAddress;
|
||||||
} hrnIoStatic;
|
} hrnIoStatic;
|
||||||
|
|
||||||
|
/**********************************************************************************************************************************/
|
||||||
|
void
|
||||||
|
hrnSckClientOpenWaitShimInstall(const char *const address)
|
||||||
|
{
|
||||||
|
FUNCTION_HARNESS_BEGIN();
|
||||||
|
FUNCTION_HARNESS_PARAM(STRINGZ, address);
|
||||||
|
FUNCTION_HARNESS_END();
|
||||||
|
|
||||||
|
hrnIoStatic.clientOpenWaitAddress = address;
|
||||||
|
|
||||||
|
FUNCTION_HARNESS_RETURN_VOID();
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Shim sckClientOpen()
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
static bool
|
||||||
|
sckClientOpenWait(SckClientOpenData *const openData, const TimeMSec timeout)
|
||||||
|
{
|
||||||
|
ASSERT(openData != NULL);
|
||||||
|
|
||||||
|
FUNCTION_HARNESS_BEGIN();
|
||||||
|
FUNCTION_HARNESS_PARAM(STRINGZ, openData->name);
|
||||||
|
FUNCTION_HARNESS_PARAM(INT, openData->fd);
|
||||||
|
FUNCTION_HARNESS_PARAM(INT, openData->errNo);
|
||||||
|
FUNCTION_HARNESS_PARAM(TIME_MSEC, timeout);
|
||||||
|
FUNCTION_HARNESS_END();
|
||||||
|
|
||||||
|
bool result = sckClientOpenWait_SHIMMED(openData, timeout);
|
||||||
|
|
||||||
|
// If this is the shimmed address then return false and update error code
|
||||||
|
if (hrnIoStatic.clientOpenWaitAddress != NULL)
|
||||||
|
{
|
||||||
|
String *const address = addrInfoToStr(openData->address);
|
||||||
|
|
||||||
|
if (strcmp(hrnIoStatic.clientOpenWaitAddress, strZ(address)) == 0)
|
||||||
|
{
|
||||||
|
result = false;
|
||||||
|
openData->errNo = EINPROGRESS;
|
||||||
|
|
||||||
|
// Reset the shim
|
||||||
|
hrnIoStatic.clientOpenWaitAddress = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
strFree(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
FUNCTION_HARNESS_RETURN(BOOL, result);
|
||||||
|
}
|
||||||
|
|
||||||
/***********************************************************************************************************************************
|
/***********************************************************************************************************************************
|
||||||
Shim sckClientOpen()
|
Shim sckClientOpen()
|
||||||
***********************************************************************************************************************************/
|
***********************************************************************************************************************************/
|
||||||
|
@ -11,6 +11,9 @@ Constants
|
|||||||
/***********************************************************************************************************************************
|
/***********************************************************************************************************************************
|
||||||
Functions
|
Functions
|
||||||
***********************************************************************************************************************************/
|
***********************************************************************************************************************************/
|
||||||
|
// Install shim for sckClientOpenWait() to return false for an address one time
|
||||||
|
void hrnSckClientOpenWaitShimInstall(const char *address);
|
||||||
|
|
||||||
// Install/uninstall shim for clientOpen() to use a fake file descriptor
|
// Install/uninstall shim for clientOpen() to use a fake file descriptor
|
||||||
void hrnSckClientOpenShimInstall(void);
|
void hrnSckClientOpenShimInstall(void);
|
||||||
void hrnSckClientOpenShimUninstall(void);
|
void hrnSckClientOpenShimUninstall(void);
|
||||||
|
@ -5,6 +5,7 @@ Test HTTP
|
|||||||
|
|
||||||
#include "common/io/fdRead.h"
|
#include "common/io/fdRead.h"
|
||||||
#include "common/io/fdWrite.h"
|
#include "common/io/fdWrite.h"
|
||||||
|
#include "common/io/socket/address.h"
|
||||||
#include "common/io/socket/client.h"
|
#include "common/io/socket/client.h"
|
||||||
#include "common/io/tls/client.h"
|
#include "common/io/tls/client.h"
|
||||||
|
|
||||||
@ -25,6 +26,13 @@ testRun(void)
|
|||||||
{
|
{
|
||||||
FUNCTION_HARNESS_VOID();
|
FUNCTION_HARNESS_VOID();
|
||||||
|
|
||||||
|
// Ensure that the ipv4 loopback address will be selected
|
||||||
|
const String *const ipLoop4 = STRDEF("127.0.0.1");
|
||||||
|
AddressInfo *addrInfo = NULL;
|
||||||
|
|
||||||
|
TEST_ASSIGN(addrInfo, addrInfoNew(STRDEF("localhost"), 443), "localhost addr list");
|
||||||
|
TEST_RESULT_VOID(addrInfoPrefer(addrInfo, lstFindIdx(addrInfo->pub.list, &ipLoop4)), "prefer 127.0.0.1");
|
||||||
|
|
||||||
// *****************************************************************************************************************************
|
// *****************************************************************************************************************************
|
||||||
if (testBegin("httpUriEncode() and httpUriDecode()"))
|
if (testBegin("httpUriEncode() and httpUriDecode()"))
|
||||||
{
|
{
|
||||||
|
@ -155,20 +155,22 @@ testRun(void)
|
|||||||
if (testBegin("AddressInfo"))
|
if (testBegin("AddressInfo"))
|
||||||
{
|
{
|
||||||
#ifdef TEST_CONTAINER_REQUIRED
|
#ifdef TEST_CONTAINER_REQUIRED
|
||||||
HRN_SYSTEM("echo \"127.0.0.1 test-addr-loop.pgbackrest.org\" | sudo tee -a /etc/hosts > /dev/null");
|
#define TEST_ADDR_LOOP_HOST "test-addr-loop.pgbackrest.org"
|
||||||
HRN_SYSTEM("echo \"::1 test-addr-loop.pgbackrest.org\" | sudo tee -a /etc/hosts > /dev/null");
|
|
||||||
|
HRN_SYSTEM("echo \"127.0.0.1 " TEST_ADDR_LOOP_HOST "\" | sudo tee -a /etc/hosts > /dev/null");
|
||||||
|
HRN_SYSTEM("echo \"::1 " TEST_ADDR_LOOP_HOST "\" | sudo tee -a /etc/hosts > /dev/null");
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
TEST_TITLE("lookup address info");
|
TEST_TITLE("lookup address info");
|
||||||
|
|
||||||
AddressInfo *addrInfo = NULL;
|
AddressInfo *addrInfo = NULL;
|
||||||
TEST_ASSIGN(addrInfo, addrInfoNew(STRDEF("test-addr-loop.pgbackrest.org"), 443), "addr list");
|
TEST_ASSIGN(addrInfo, addrInfoNew(STRDEF(TEST_ADDR_LOOP_HOST), 443), "addr list");
|
||||||
TEST_RESULT_STR_Z(addrInfoHost(addrInfo), "test-addr-loop.pgbackrest.org", "check host");
|
TEST_RESULT_STR_Z(addrInfoHost(addrInfo), TEST_ADDR_LOOP_HOST, "check host");
|
||||||
TEST_RESULT_UINT(addrInfoPort(addrInfo), 443, "check port");
|
TEST_RESULT_UINT(addrInfoPort(addrInfo), 443, "check port");
|
||||||
TEST_RESULT_UINT(addrInfoSize(addrInfo), 2, "check size");
|
TEST_RESULT_UINT(addrInfoSize(addrInfo), 2, "check size");
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
TEST_TITLE("addrInfoToLog");
|
TEST_TITLE("addrInfoToLog()");
|
||||||
|
|
||||||
char logBuf[STACK_TRACE_PARAM_MAX];
|
char logBuf[STACK_TRACE_PARAM_MAX];
|
||||||
|
|
||||||
@ -176,21 +178,94 @@ testRun(void)
|
|||||||
TEST_RESULT_Z(
|
TEST_RESULT_Z(
|
||||||
logBuf,
|
logBuf,
|
||||||
zNewFmt(
|
zNewFmt(
|
||||||
"{host: {\"test-addr-loop.pgbackrest.org\"}, port: 443, list: [%s, %s]}",
|
"{host: {\"" TEST_ADDR_LOOP_HOST "\"}, port: 443, list: [%s, %s]}",
|
||||||
strZ(addrInfoToStr(addrInfoGet(addrInfo, 0))), strZ(addrInfoToStr(addrInfoGet(addrInfo, 1)))),
|
strZ(addrInfoGet(addrInfo, 0)->name), strZ(addrInfoGet(addrInfo, 1)->name)),
|
||||||
"check log");
|
"check log");
|
||||||
|
|
||||||
|
addrInfoSort(addrInfo);
|
||||||
|
|
||||||
|
TEST_RESULT_VOID(FUNCTION_LOG_OBJECT_FORMAT(addrInfo, addrInfoToLog, logBuf, sizeof(logBuf)), "addrInfoToLog");
|
||||||
|
TEST_RESULT_Z(logBuf, "{host: {\"" TEST_ADDR_LOOP_HOST "\"}, port: 443, list: [::1, 127.0.0.1]}", "check log");
|
||||||
|
|
||||||
// Munge address so it is invalid
|
// Munge address so it is invalid
|
||||||
(*(struct addrinfo **)lstGet(addrInfo->pub.list, 0))->ai_addr = NULL;
|
((AddressInfoItem *)lstGet(addrInfo->pub.list, 0))->info->ai_addr = NULL;
|
||||||
|
TEST_RESULT_STR_Z(addrInfoToStr(addrInfoGet(addrInfo, 0)->info), "invalid", "check invalid");
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
|
TEST_TITLE("addrInfoSort() break early");
|
||||||
|
|
||||||
|
struct addrinfo addr4 = {.ai_family = AF_INET};
|
||||||
|
struct addrinfo addr6 = {.ai_family = AF_INET6};
|
||||||
|
|
||||||
|
MEM_CONTEXT_OBJ_BEGIN(addrInfo)
|
||||||
|
{
|
||||||
|
addrInfo->pub.host = strNewZ("test");
|
||||||
|
lstClear(addrInfo->pub.list);
|
||||||
|
|
||||||
|
lstAdd(addrInfo->pub.list, &(AddressInfoItem){.name = strNewZ("127.0.0.1"), .info = &addr4});
|
||||||
|
lstAdd(addrInfo->pub.list, &(AddressInfoItem){.name = strNewZ("::1"), .info = &addr6});
|
||||||
|
lstAdd(addrInfo->pub.list, &(AddressInfoItem){.name = strNewZ("127.0.0.2"), .info = &addr4});
|
||||||
|
lstAdd(addrInfo->pub.list, &(AddressInfoItem){.name = strNewZ("::2"), .info = &addr6});
|
||||||
|
lstAdd(addrInfo->pub.list, &(AddressInfoItem){.name = strNewZ("::3"), .info = &addr6});
|
||||||
|
lstAdd(addrInfo->pub.list, &(AddressInfoItem){.name = strNewZ("::4"), .info = &addr6});
|
||||||
|
addrInfoSort(addrInfo);
|
||||||
|
}
|
||||||
|
MEM_CONTEXT_OBJ_END();
|
||||||
|
|
||||||
TEST_RESULT_VOID(FUNCTION_LOG_OBJECT_FORMAT(addrInfo, addrInfoToLog, logBuf, sizeof(logBuf)), "addrInfoToLog");
|
TEST_RESULT_VOID(FUNCTION_LOG_OBJECT_FORMAT(addrInfo, addrInfoToLog, logBuf, sizeof(logBuf)), "addrInfoToLog");
|
||||||
TEST_RESULT_Z(
|
TEST_RESULT_Z(
|
||||||
logBuf,
|
logBuf, zNewFmt("{host: {\"test\"}, port: 443, list: [::1, 127.0.0.1, ::2, 127.0.0.2, ::3, ::4]}"), "check log");
|
||||||
zNewFmt(
|
|
||||||
"{host: {\"test-addr-loop.pgbackrest.org\"}, port: 443, list: [invalid, %s]}",
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
strZ(addrInfoToStr(addrInfoGet(addrInfo, 1)))),
|
TEST_TITLE("addrInfoSort()");
|
||||||
"check log");
|
|
||||||
TEST_RESULT_STR_Z(addrInfoToStr(addrInfoGet(addrInfo, 0)), "invalid", "check invalid");
|
// Set a preference that won't be found
|
||||||
|
addrInfoPrefer(addrInfo, 5);
|
||||||
|
|
||||||
|
MEM_CONTEXT_OBJ_BEGIN(addrInfo)
|
||||||
|
{
|
||||||
|
lstClear(addrInfo->pub.list);
|
||||||
|
|
||||||
|
lstAdd(addrInfo->pub.list, &(AddressInfoItem){.name = strNewZ("127.0.0.1"), .info = &addr4});
|
||||||
|
lstAdd(addrInfo->pub.list, &(AddressInfoItem){.name = strNewZ("127.0.0.2"), .info = &addr4});
|
||||||
|
lstAdd(addrInfo->pub.list, &(AddressInfoItem){.name = strNewZ("::1"), .info = &addr6});
|
||||||
|
lstAdd(addrInfo->pub.list, &(AddressInfoItem){.name = strNewZ("::2"), .info = &addr6});
|
||||||
|
addrInfoSort(addrInfo);
|
||||||
|
}
|
||||||
|
MEM_CONTEXT_OBJ_END();
|
||||||
|
|
||||||
|
TEST_RESULT_VOID(FUNCTION_LOG_OBJECT_FORMAT(addrInfo, addrInfoToLog, logBuf, sizeof(logBuf)), "addrInfoToLog");
|
||||||
|
TEST_RESULT_Z(
|
||||||
|
logBuf, zNewFmt("{host: {\"test\"}, port: 443, list: [::1, 127.0.0.2, ::2, 127.0.0.1]}"), "check log");
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
|
TEST_TITLE("addrInfoSort() prefer v4");
|
||||||
|
|
||||||
|
addrInfoPrefer(addrInfo, 1);
|
||||||
|
addrInfoSort(addrInfo);
|
||||||
|
|
||||||
|
TEST_RESULT_VOID(FUNCTION_LOG_OBJECT_FORMAT(addrInfo, addrInfoToLog, logBuf, sizeof(logBuf)), "addrInfoToLog");
|
||||||
|
TEST_RESULT_Z(
|
||||||
|
logBuf, zNewFmt("{host: {\"test\"}, port: 443, list: [127.0.0.2, ::1, 127.0.0.1, ::2]}"), "check log");
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
|
TEST_TITLE("addrInfoSort() prefer v4 (already first)");
|
||||||
|
|
||||||
|
addrInfoSort(addrInfo);
|
||||||
|
|
||||||
|
TEST_RESULT_VOID(FUNCTION_LOG_OBJECT_FORMAT(addrInfo, addrInfoToLog, logBuf, sizeof(logBuf)), "addrInfoToLog");
|
||||||
|
TEST_RESULT_Z(
|
||||||
|
logBuf, zNewFmt("{host: {\"test\"}, port: 443, list: [127.0.0.2, ::1, 127.0.0.1, ::2]}"), "check log");
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
|
TEST_TITLE("addrInfoSort() prefer v6");
|
||||||
|
|
||||||
|
addrInfoPrefer(addrInfo, 3);
|
||||||
|
addrInfoSort(addrInfo);
|
||||||
|
|
||||||
|
TEST_RESULT_VOID(FUNCTION_LOG_OBJECT_FORMAT(addrInfo, addrInfoToLog, logBuf, sizeof(logBuf)), "addrInfoToLog");
|
||||||
|
TEST_RESULT_Z(
|
||||||
|
logBuf, zNewFmt("{host: {\"test\"}, port: 443, list: [::2, 127.0.0.1, ::1, 127.0.0.2]}"), "check log");
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
TEST_TITLE("free");
|
TEST_TITLE("free");
|
||||||
@ -343,11 +418,6 @@ testRun(void)
|
|||||||
TEST_ERROR(
|
TEST_ERROR(
|
||||||
ioClientOpen(socketClient), HostConnectError, "unable to connect to '127.0.0.1:7777': [111] Connection refused");
|
ioClientOpen(socketClient), HostConnectError, "unable to connect to '127.0.0.1:7777': [111] Connection refused");
|
||||||
socketLocal.block = false;
|
socketLocal.block = false;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------------------------------------------
|
|
||||||
TEST_TITLE("uncovered conditions for sckConnect()");
|
|
||||||
|
|
||||||
TEST_RESULT_BOOL(sckConnectInProgress(EINTR), true, "connection in progress (EINTR)");
|
|
||||||
}
|
}
|
||||||
FINALLY()
|
FINALLY()
|
||||||
{
|
{
|
||||||
@ -365,6 +435,11 @@ testRun(void)
|
|||||||
if (testBegin("SocketClient/SocketServer"))
|
if (testBegin("SocketClient/SocketServer"))
|
||||||
{
|
{
|
||||||
IoClient *client = NULL;
|
IoClient *client = NULL;
|
||||||
|
AddressInfo *addrInfo = NULL;
|
||||||
|
const String *const ipLoop4 = STRDEF("127.0.0.1");
|
||||||
|
|
||||||
|
TEST_ASSIGN(addrInfo, addrInfoNew(STRDEF("localhost"), 443), "localhost addr list");
|
||||||
|
TEST_RESULT_VOID(addrInfoPrefer(addrInfo, lstFindIdx(addrInfo->pub.list, &ipLoop4)), "prefer 127.0.0.1");
|
||||||
|
|
||||||
TEST_ASSIGN(client, sckClientNew(STRDEF("localhost"), HRN_SERVER_PORT_BOGUS, 100, 100), "new client");
|
TEST_ASSIGN(client, sckClientNew(STRDEF("localhost"), HRN_SERVER_PORT_BOGUS, 100, 100), "new client");
|
||||||
TEST_ERROR(
|
TEST_ERROR(
|
||||||
@ -374,10 +449,71 @@ testRun(void)
|
|||||||
|
|
||||||
// This address should not be in use in a test environment -- if it is the test will fail
|
// This address should not be in use in a test environment -- if it is the test will fail
|
||||||
TEST_ASSIGN(client, sckClientNew(STRDEF("172.31.255.255"), HRN_SERVER_PORT_BOGUS, 100, 100), "new client");
|
TEST_ASSIGN(client, sckClientNew(STRDEF("172.31.255.255"), HRN_SERVER_PORT_BOGUS, 100, 100), "new client");
|
||||||
TEST_ERROR(
|
TEST_ERROR(ioClientOpen(client), HostConnectError, "timeout connecting to '172.31.255.255:34342'");
|
||||||
ioClientOpen(client), HostConnectError,
|
|
||||||
"timeout connecting to '172.31.255.255:34342'\n"
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
"[RETRY DETAIL OMITTED]");
|
#ifdef TEST_CONTAINER_REQUIRED
|
||||||
|
#define TEST_ADDR_CONN_HOST "test-addr-conn.pgbackrest.org"
|
||||||
|
|
||||||
|
HRN_SYSTEM("echo \"127.0.0.1 " TEST_ADDR_CONN_HOST "\" | sudo tee -a /etc/hosts > /dev/null");
|
||||||
|
HRN_SYSTEM("echo \"::1 " TEST_ADDR_CONN_HOST "\" | sudo tee -a /etc/hosts > /dev/null");
|
||||||
|
HRN_SYSTEM("echo \"172.31.255.255 " TEST_ADDR_CONN_HOST "\" | sudo tee -a /etc/hosts > /dev/null");
|
||||||
|
|
||||||
|
// Explicitly set the order of the address list for the test below. The first IP must not respond, the second must error,
|
||||||
|
// and only the last will succeed.
|
||||||
|
const String *ipPrefer = STRDEF("172.31.255.255");
|
||||||
|
TEST_ASSIGN(addrInfo, addrInfoNew(STRDEF(TEST_ADDR_CONN_HOST), 443), "localhost addr list");
|
||||||
|
TEST_RESULT_VOID(addrInfoPrefer(addrInfo, lstFindIdx(addrInfo->pub.list, &ipPrefer)), "prefer 172.31.255.255");
|
||||||
|
|
||||||
|
TEST_ASSIGN(addrInfo, addrInfoNew(STRDEF(TEST_ADDR_CONN_HOST), 443), "localhost addr list");
|
||||||
|
TEST_RESULT_VOID(addrInfoSort(addrInfo), "sort addresses");
|
||||||
|
TEST_RESULT_STR(addrInfoToStr(addrInfoGet(addrInfo, 0)->info), ipPrefer, "first 172.31.255.255");
|
||||||
|
TEST_RESULT_STR_Z(addrInfoToStr(addrInfoGet(addrInfo, 1)->info), "::1", "second ::1");
|
||||||
|
TEST_RESULT_STR_Z(addrInfoToStr(addrInfoGet(addrInfo, 2)->info), "127.0.0.1", "third 127.0.0.1");
|
||||||
|
|
||||||
|
HRN_FORK_BEGIN()
|
||||||
|
{
|
||||||
|
const unsigned int testPort = hrnServerPortNext();
|
||||||
|
|
||||||
|
HRN_FORK_CHILD_BEGIN(.prefix = "test server", .timeout = 5000)
|
||||||
|
{
|
||||||
|
TEST_RESULT_VOID(hrnServerRunP(HRN_FORK_CHILD_READ(), hrnServerProtocolSocket, testPort), "socket server");
|
||||||
|
}
|
||||||
|
HRN_FORK_CHILD_END();
|
||||||
|
|
||||||
|
HRN_FORK_PARENT_BEGIN()
|
||||||
|
{
|
||||||
|
IoWrite *server = hrnServerScriptBegin(HRN_FORK_PARENT_WRITE(0));
|
||||||
|
IoClient *client;
|
||||||
|
IoSession *session;
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------------------------------------------
|
||||||
|
TEST_TITLE("connect to 127.0.0.1 when ::1 errors and 172.31.255.255 never responds");
|
||||||
|
|
||||||
|
TEST_ASSIGN(client, sckClientNew(STRDEF(TEST_ADDR_CONN_HOST), testPort, 1000, 1000), "new client");
|
||||||
|
|
||||||
|
hrnServerScriptAccept(server);
|
||||||
|
hrnServerScriptClose(server);
|
||||||
|
|
||||||
|
// Shim the server address to return false one time for write ready. This tests connections that take longer.
|
||||||
|
hrnSckClientOpenWaitShimInstall("127.0.0.1");
|
||||||
|
|
||||||
|
TEST_ASSIGN(session, ioClientOpen(client), "connection established");
|
||||||
|
TEST_RESULT_VOID(
|
||||||
|
sckClientOpenWait(
|
||||||
|
&(SckClientOpenData){.name = "test", .fd = ((SocketSession *)session->pub.driver)->fd, .errNo = EINTR}, 99),
|
||||||
|
"check EINTR wait condition");
|
||||||
|
TEST_RESULT_VOID(ioSessionFree(session), "connection closed");
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------------------------------------------
|
||||||
|
TEST_TITLE("end server process");
|
||||||
|
|
||||||
|
hrnServerScriptEnd(server);
|
||||||
|
}
|
||||||
|
HRN_FORK_PARENT_END();
|
||||||
|
}
|
||||||
|
HRN_FORK_END();
|
||||||
|
#endif
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
TEST_TITLE("sckServerAccept() returns NULL on interrupt");
|
TEST_TITLE("sckServerAccept() returns NULL on interrupt");
|
||||||
|
Loading…
Reference in New Issue
Block a user