nats.zig/deps/nats.c/src/comsock.c
torque 79a45fd2e3
git subrepo clone (merge) --branch=v3.6.1 https://github.com/nats-io/nats.c.git deps/nats.c
subrepo:
  subdir:   "deps/nats.c"
  merged:   "66cec7f"
upstream:
  origin:   "https://github.com/nats-io/nats.c.git"
  branch:   "v3.6.1"
  commit:   "66cec7f"
git-subrepo:
  version:  "0.4.6"
  commit:   "b8b46501e"
2023-08-15 00:21:33 -07:00

665 lines
19 KiB
C

// Copyright 2015-2021 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "natsp.h"
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <time.h>
#include "status.h"
#include "comsock.h"
#include "mem.h"
#include "util.h"
natsStatus
natsSock_Init(natsSockCtx *ctx)
{
memset(ctx, 0, sizeof(natsSockCtx));
ctx->fd = NATS_SOCK_INVALID;
return NATS_OK;
}
static void
_closeFd(natsSock fd)
{
if (fd != NATS_SOCK_INVALID)
NATS_SOCK_CLOSE(fd);
}
void
natsSock_Close(natsSock fd)
{
_closeFd(fd);
}
void
natsSock_Shutdown(natsSock fd)
{
if (fd != NATS_SOCK_INVALID)
NATS_SOCK_SHUTDOWN(fd);
}
natsStatus
natsSock_SetCommonTcpOptions(natsSock fd)
{
struct linger l;
int yes = 1;
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (const char*) &yes, sizeof(yes)) == -1)
return nats_setError(NATS_SYS_ERROR, "setsockopt TCP_NO_DELAY error: %d",
NATS_SOCK_GET_ERROR);
yes = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char*) &yes, sizeof(yes)) == -1)
return nats_setError(NATS_SYS_ERROR, "setsockopt SO_REUSEADDR error: %d",
NATS_SOCK_GET_ERROR);
l.l_onoff = 1;
l.l_linger = 0;
if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (void*)&l, sizeof(l)) == -1)
return nats_setError(NATS_SYS_ERROR, "setsockopt SO_LINGER error: %d",
NATS_SOCK_GET_ERROR);
return NATS_OK;
}
void
natsSock_ShuffleIPs(natsSockCtx *ctx, struct addrinfo **tmp, int tmpSize, struct addrinfo **ipListHead, int count)
{
struct addrinfo **ips = NULL;
struct addrinfo *p = NULL;
bool doFree = false;
int i, j;
if (ctx->noRandomize || (ipListHead == NULL) || (*ipListHead == NULL) || count <= 1)
return;
if (count > tmpSize)
{
ips = (struct addrinfo**) NATS_CALLOC(count, sizeof(struct addrinfo*));
// Let's not fail the caller, simply don't shuffle.
if (ips == NULL)
return;
doFree = true;
}
else
{
ips = tmp;
}
p = *ipListHead;
// Put them in a array
for (i=0; i<count; i++)
{
ips[i] = p;
p = p->ai_next;
}
// Shuffle the array
for (i=0; i<count; i++)
{
j = rand() % (i + 1);
p = ips[i];
ips[i] = ips[j];
ips[j] = p;
}
// Relink them
for (i=0; i<count; i++)
{
if (i < count-1)
ips[i]->ai_next = ips[i+1];
else
ips[i]->ai_next = NULL;
}
// Update the list head with the first in the array.
*ipListHead = ips[0];
// If we allocated the array, free it.
if (doFree)
NATS_FREE(ips);
}
#define MAX_HOST_NAME (256)
natsStatus
natsSock_ConnectTcp(natsSockCtx *ctx, const char *phost, int port)
{
natsStatus s = NATS_OK;
int res;
char sport[6];
int i;
int max = 1;
char hosta[MAX_HOST_NAME];
int hostLen;
char *host;
struct addrinfo *servInfos[2] = {NULL, NULL};
int numServInfo = 0;
int numIPs = 0;
int64_t start = 0;
int64_t totalTimeout = 0;
int64_t timeoutPerIP = 0;
struct addrinfo *tmpStorage[64];
if (phost == NULL)
return nats_setError(NATS_ADDRESS_MISSING, "%s", "No host specified");
hostLen = (int) strlen(phost);
if ((hostLen == 0) || ((hostLen == 1) && phost[0] == '['))
return nats_setError(NATS_INVALID_ARG, "Invalid host name: %s", phost);
if (phost[0] == '[')
{
snprintf(hosta, sizeof(hosta), "%.*s", hostLen - 2, phost + 1);
host = (char*) hosta;
}
else
host = (char*) phost;
snprintf(sport, sizeof(sport), "%d", port);
if ((ctx->orderIP == 46) || (ctx->orderIP == 64))
max = 2;
start = nats_Now();
for (i=0; i<max; i++)
{
struct addrinfo hints;
struct addrinfo *servinfo = NULL;
int count = 0;
struct addrinfo *p;
memset(&hints,0,sizeof(hints));
hints.ai_socktype = SOCK_STREAM;
switch (ctx->orderIP)
{
case 4: hints.ai_family = AF_INET; break;
case 6: hints.ai_family = AF_INET6; break;
case 46: hints.ai_family = (i == 0 ? AF_INET : AF_INET6); break;
case 64: hints.ai_family = (i == 0 ? AF_INET6 : AF_INET); break;
default: hints.ai_family = AF_UNSPEC;
}
if ((res = getaddrinfo(host, sport, &hints, &servinfo)) != 0)
{
s = nats_setError(NATS_SYS_ERROR, "getaddrinfo error: %s",
gai_strerror(res));
continue;
}
servInfos[numServInfo] = servinfo;
for (p = servinfo; (p != NULL); p = p->ai_next)
{
count++;
numIPs++;
}
natsSock_ShuffleIPs(ctx, tmpStorage, sizeof(tmpStorage), &(servInfos[numServInfo]), count);
numServInfo++;
}
// If we got a getaddrinfo() and there is no servInfos to try to connect to
// bail out now.
if ((s != NATS_OK) && (numServInfo == 0))
return NATS_UPDATE_ERR_STACK(s);
// I don't think it can be the case if s == OK and/or numServInfo >= 1...
if (numIPs == 0)
{
for (i=0; i<numServInfo; i++)
nats_FreeAddrInfo(servInfos[i]);
return NATS_UPDATE_ERR_STACK(NATS_NO_SERVER);
}
// Check if there has been a deadline set.
totalTimeout = natsDeadline_GetTimeout(&(ctx->writeDeadline));
if (totalTimeout > 0)
{
// If so, compute a timeout based on the number of IPs we are going
// to possibly try to connect to.
timeoutPerIP = totalTimeout / numIPs;
// If really small, give at least a 10ms timeout
if (timeoutPerIP < 10)
timeoutPerIP = 10;
}
for (i=0; i<numServInfo; i++)
{
struct addrinfo *p;
for (p = servInfos[i]; (p != NULL); p = p->ai_next)
{
ctx->fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
if (ctx->fd == NATS_SOCK_INVALID)
{
s = nats_setError(NATS_SYS_ERROR, "socket error: %d", NATS_SOCK_GET_ERROR);
continue;
}
// Reset 's' for this loop iteration.
s = NATS_OK;
#ifdef SO_NOSIGPIPE
int set = 1;
if (setsockopt(ctx->fd, SOL_SOCKET, SO_NOSIGPIPE, (void*)&set, sizeof(int)) == -1)
{
s = nats_setError(NATS_SYS_ERROR,
"setsockopt SO_NOSIGPIPE error: %d",
NATS_SOCK_GET_ERROR);
}
#endif
if (s == NATS_OK)
s = natsSock_SetBlocking(ctx->fd, false);
if (s == NATS_OK)
{
res = connect(ctx->fd, p->ai_addr, (natsSockLen) p->ai_addrlen);
if ((res == NATS_SOCK_ERROR)
&& (NATS_SOCK_GET_ERROR == NATS_SOCK_CONNECT_IN_PROGRESS))
{
if (timeoutPerIP > 0)
natsDeadline_Init(&(ctx->writeDeadline), timeoutPerIP);
s = natsSock_WaitReady(WAIT_FOR_CONNECT, ctx);
if ((s == NATS_OK) && !natsSock_IsConnected(ctx->fd))
s = NATS_TIMEOUT;
}
else if (res == NATS_SOCK_ERROR)
{
s = nats_setDefaultError(NATS_NO_SERVER);
}
}
if (s == NATS_OK)
{
s = natsSock_SetCommonTcpOptions(ctx->fd);
// We have connected OK and completed setting options, so we are done.
if (s == NATS_OK)
break;
}
_closeFd(ctx->fd);
ctx->fd = NATS_SOCK_INVALID;
}
if (s == NATS_OK)
{
// Clear the error stack in case we got errors in the loop until
// being able to successfully connect.
nats_clearLastError();
break;
}
}
for (i=0; i<numServInfo; i++)
nats_FreeAddrInfo(servInfos[i]);
// If there was a deadline, reset the deadline with whatever is left.
if (totalTimeout > 0)
{
int64_t used = nats_Now() - start;
int64_t left = totalTimeout - used;
natsDeadline_Init(&(ctx->writeDeadline), (left > 0 ? left : 0));
}
return NATS_UPDATE_ERR_STACK(s);
}
natsStatus
natsSock_ReadLine(natsSockCtx *ctx, char *buffer, size_t maxBufferSize)
{
natsStatus s = NATS_OK;
int readBytes = 0;
size_t totalBytes = 0;
char *p = buffer;
char *eol;
// By contract, the caller needs to set buffer[0] to '\0' before the first
// call.
if (*p != '\0')
{
// We assume that this is not the first call with the given buffer.
// Move possible data after the first line to the beginning of the
// buffer.
char *nextLine;
size_t nextStart;
size_t len = 0;
// The start of the next line will be the length of the line at the
// start of the buffer + 2, which is the number of characters
// representing CRLF.
nextStart = strlen(buffer) + 2;
nextLine = (char*) (buffer + nextStart);
// There is some data...
if (*nextLine != '\0')
{
// The next line (even if partial) is guaranteed to be NULL
// terminated.
len = strlen(nextLine);
// Move to the beginning of the buffer (and include the NULL char)
memmove(buffer, nextLine, len + 1);
// Now, if the string contains a CRLF, we don't even need to read
// from the socket. Update the buffer and return.
if ((eol = strstr(buffer, _CRLF_)) != NULL)
{
// Replace the '\r' with '\0' to NULL terminate the string.
*eol = '\0';
// We are done!
return NATS_OK;
}
// This is a partial, we need to read more data until we get to
// the end of the line (\r\n).
p = (char*) (p + len);
totalBytes += len;
}
else
{
*p = '\0';
}
}
while (1)
{
s = natsSock_Read(ctx, p, (maxBufferSize - totalBytes), &readBytes);
if (s != NATS_OK)
return NATS_UPDATE_ERR_STACK(s);
if (totalBytes + readBytes == maxBufferSize)
return nats_setDefaultError(NATS_LINE_TOO_LONG);
// We need to append a NULL character after what we have received.
*(p + readBytes) = '\0';
if ((eol = strstr(p, _CRLF_)) != NULL)
{
*eol = '\0';
return NATS_OK;
}
p += readBytes;
totalBytes += readBytes;
}
}
natsStatus
natsSock_Read(natsSockCtx *ctx, char *buffer, size_t maxBufferSize, int *n)
{
natsStatus s = NATS_OK;
int readBytes = 0;
bool needRead = true;
while (needRead)
{
#if defined(NATS_HAS_TLS)
if (ctx->ssl != NULL)
readBytes = SSL_read(ctx->ssl, buffer, (int) maxBufferSize);
else
#endif
readBytes = recv(ctx->fd, buffer, (natsRecvLen) maxBufferSize, 0);
if (readBytes == 0)
{
return nats_setDefaultError(NATS_CONNECTION_CLOSED);
}
else if (readBytes < 0)
{
#if defined(NATS_HAS_TLS)
if (ctx->ssl != NULL)
{
int sslErr = SSL_get_error(ctx->ssl, readBytes);
if (sslErr == SSL_ERROR_ZERO_RETURN)
return nats_setDefaultError(NATS_CONNECTION_CLOSED);
if ((sslErr == SSL_ERROR_WANT_READ) || (sslErr == SSL_ERROR_WANT_WRITE))
{
int waitMode = (sslErr == SSL_ERROR_WANT_READ ? WAIT_FOR_READ : WAIT_FOR_WRITE);
if ((s = natsSock_WaitReady(waitMode, ctx)) != NATS_OK)
return NATS_UPDATE_ERR_STACK(s);
// SSL requires that we go back with the same buffer
// and size. We can't return until SSL_read returns
// success (bytes read) or a different error.
continue;
}
}
#endif
if (NATS_SOCK_GET_ERROR != NATS_SOCK_WOULD_BLOCK)
{
#if defined(NATS_HAS_TLS)
if (ctx->ssl != NULL)
return nats_setError(NATS_IO_ERROR, "SSL_read error: %s",
NATS_SSL_ERR_REASON_STRING);
else
#endif
return nats_setError(NATS_IO_ERROR, "recv error: %d",
NATS_SOCK_GET_ERROR);
}
else if (ctx->useEventLoop)
{
// When using an external event loop, we are done. We will be
// called again...
if (n != NULL)
*n = 0;
return NATS_OK;
}
// For non-blocking sockets, if the read would block, we need to
// wait up to the deadline.
s = natsSock_WaitReady(WAIT_FOR_READ, ctx);
if (s != NATS_OK)
return NATS_UPDATE_ERR_STACK(s);
continue;
}
if (n != NULL)
*n = readBytes;
needRead = false;
}
return NATS_OK;
}
natsStatus
natsSock_Write(natsSockCtx *ctx, const char *data, int len, int *n)
{
natsStatus s = NATS_OK;
int bytes = 0;
bool needWrite = true;
while (needWrite)
{
#if defined(NATS_HAS_TLS)
if (ctx->ssl != NULL)
bytes = SSL_write(ctx->ssl, data, len);
else
#endif
#ifdef MSG_NOSIGNAL
bytes = send(ctx->fd, data, len, MSG_NOSIGNAL);
#else
bytes = send(ctx->fd, data, len, 0);
#endif
if (bytes == 0)
{
return nats_setDefaultError(NATS_CONNECTION_CLOSED);
}
else if (bytes < 0)
{
#if defined(NATS_HAS_TLS)
if (ctx->ssl != NULL)
{
int sslErr = SSL_get_error(ctx->ssl, bytes);
if (sslErr == SSL_ERROR_ZERO_RETURN)
return nats_setDefaultError(NATS_CONNECTION_CLOSED);
if ((sslErr == SSL_ERROR_WANT_READ) || (sslErr == SSL_ERROR_WANT_WRITE))
{
int waitMode = (sslErr == SSL_ERROR_WANT_READ ? WAIT_FOR_READ : WAIT_FOR_WRITE);
if ((s = natsSock_WaitReady(waitMode, ctx)) != NATS_OK)
return NATS_UPDATE_ERR_STACK(s);
// SSL requires that we go back with the same buffer
// and size. We can't return until SSL_write returns
// success (bytes written) or a different error.
continue;
}
}
#endif
if (NATS_SOCK_GET_ERROR != NATS_SOCK_WOULD_BLOCK)
{
#if defined(NATS_HAS_TLS)
if (ctx->ssl != NULL)
return nats_setError(NATS_IO_ERROR, "SSL_write error: %s",
NATS_SSL_ERR_REASON_STRING);
else
#endif
return nats_setError(NATS_IO_ERROR, "send error: %d",
NATS_SOCK_GET_ERROR);
}
else if (ctx->useEventLoop)
{
// With external event loop, we are done now, we will be
// called later for more.
if (n != NULL)
*n = 0;
return NATS_OK;
}
// For non-blocking sockets, if the write would block, we need to
// wait up to the deadline.
s = natsSock_WaitReady(WAIT_FOR_WRITE, ctx);
if (s != NATS_OK)
return NATS_UPDATE_ERR_STACK(s);
continue;
}
if (n != NULL)
*n = bytes;
needWrite = false;
}
return NATS_OK;
}
natsStatus
natsSock_WriteFully(natsSockCtx *ctx, const char *data, int len)
{
natsStatus s = NATS_OK;
int n = 0;
if (len == 0)
return NATS_OK;
do
{
s = natsSock_Write(ctx, data, len, &n);
if (s == NATS_OK)
{
data += n;
len -= n;
// We have sent the whole buffer. Return.
if (len == 0)
return NATS_OK;
}
}
while (s == NATS_OK);
// If we are using a write deadline, shutdown the socket to trigger a
// possible reconnect
if (s == NATS_TIMEOUT)
{
natsSock_Shutdown(ctx->fd);
ctx->fdActive = false;
}
return NATS_UPDATE_ERR_STACK(s);
}
void
natsSock_ClearDeadline(natsSockCtx *ctx)
{
natsDeadline_Clear(&ctx->readDeadline);
natsDeadline_Clear(&ctx->writeDeadline);
}
void
natsSock_InitDeadline(natsSockCtx *ctx, int64_t timeout)
{
natsDeadline_Init(&ctx->readDeadline, timeout);
natsDeadline_Init(&ctx->writeDeadline, timeout);
}
natsStatus
natsSock_GetLocalIPAndPort(natsSockCtx *ctx, char **ip, int *port)
{
struct sockaddr_storage addr;
natsSockLen addrLen = (natsSockLen) sizeof(addr);
char localIP[64];
void *laddr = NULL;
int fam;
*ip = NULL;
*port = 0;
if (getsockname(ctx->fd, (struct sockaddr*) &addr, &addrLen) != 0)
return nats_setError(NATS_SYS_ERROR, "getsockname error: %d", NATS_SOCK_GET_ERROR);
fam = ((struct sockaddr*) &addr)->sa_family;
if (fam == AF_INET)
{
struct sockaddr_in *addr4 = (struct sockaddr_in*) &addr;
laddr = &(addr4->sin_addr);
*port = (int) ntohs(addr4->sin_port);
}
else if (fam == AF_INET6)
{
struct sockaddr_in6 *addr6 = (struct sockaddr_in6*) &addr;
laddr = &(addr6->sin6_addr);
*port = (int) ntohs(addr6->sin6_port);
}
else
{
return nats_setError(NATS_SYS_ERROR, "unknown INET family: %d", fam);
}
if (inet_ntop(fam, laddr, localIP, sizeof(localIP)) == NULL)
return nats_setError(NATS_SYS_ERROR, "inet_ntop error: %d", NATS_SOCK_GET_ERROR);
if ((*ip = NATS_STRDUP(localIP)) == NULL)
return nats_setDefaultError(NATS_NO_MEMORY);
return NATS_OK;
}