#include "curl_setup.h"
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_SYS_UN_H
#include <sys/un.h>
#endif
#ifdef HAVE_LINUX_TCP_H
#include <linux/tcp.h>
#elif defined(HAVE_NETINET_TCP_H)
#include <netinet/tcp.h>
#endif
#ifdef HAVE_NETINET_UDP_H
#include <netinet/udp.h>
#endif
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#ifdef __VMS
#include <in.h>
#include <inet.h>
#endif
#ifdef __DragonFly__
#include <sys/param.h>
#endif
#include "urldata.h"
#include "bufq.h"
#include "sendf.h"
#include "if2ip.h"
#include "strerror.h"
#include "cfilters.h"
#include "cf-socket.h"
#include "connect.h"
#include "select.h"
#include "url.h"
#include "multiif.h"
#include "sockaddr.h"
#include "inet_ntop.h"
#include "curlx/inet_pton.h"
#include "progress.h"
#include "curlx/warnless.h"
#include "conncache.h"
#include "multihandle.h"
#include "rand.h"
#include "share.h"
#include "strdup.h"
#include "system_win32.h"
#include "curlx/version_win32.h"
#include "curlx/strparse.h"
#include "curl_printf.h"
#include "curl_memory.h"
#include "memdebug.h"
#if defined(USE_IPV6) && defined(IPV6_V6ONLY) && defined(_WIN32)
static void set_ipv6_v6only(curl_socket_t sockfd, int on)
{
(void)setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&on, sizeof(on));
}
#else
#define set_ipv6_v6only(x,y)
#endif
static void tcpnodelay(struct Curl_easy *data, curl_socket_t sockfd)
{
#if defined(TCP_NODELAY)
curl_socklen_t onoff = (curl_socklen_t) 1;
int level = IPPROTO_TCP;
char buffer[STRERROR_LEN];
if(setsockopt(sockfd, level, TCP_NODELAY,
(void *)&onoff, sizeof(onoff)) < 0)
infof(data, "Could not set TCP_NODELAY: %s",
Curl_strerror(SOCKERRNO, buffer, sizeof(buffer)));
#else
(void)data;
(void)sockfd;
#endif
}
#ifdef SO_NOSIGPIPE
static void nosigpipe(struct Curl_easy *data,
curl_socket_t sockfd)
{
int onoff = 1;
(void)data;
if(setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE,
(void *)&onoff, sizeof(onoff)) < 0) {
#if !defined(CURL_DISABLE_VERBOSE_STRINGS)
char buffer[STRERROR_LEN];
infof(data, "Could not set SO_NOSIGPIPE: %s",
Curl_strerror(SOCKERRNO, buffer, sizeof(buffer)));
#endif
}
}
#else
#define nosigpipe(x,y) Curl_nop_stmt
#endif
#if defined(USE_WINSOCK) && \
defined(TCP_KEEPIDLE) && defined(TCP_KEEPINTVL) && defined(TCP_KEEPCNT)
#define CURL_WINSOCK_KEEP_SSO
#define KEEPALIVE_FACTOR(x)
#elif defined(USE_WINSOCK) || \
(defined(__sun) && !defined(TCP_KEEPIDLE)) || \
(defined(__DragonFly__) && __DragonFly_version < 500702) || \
(defined(_WIN32) && !defined(TCP_KEEPIDLE))
#define KEEPALIVE_FACTOR(x) (x *= 1000)
#else
#define KEEPALIVE_FACTOR(x)
#endif
#if defined(USE_WINSOCK) && !defined(SIO_KEEPALIVE_VALS)
#define SIO_KEEPALIVE_VALS _WSAIOW(IOC_VENDOR,4)
struct tcp_keepalive {
u_long onoff;
u_long keepalivetime;
u_long keepaliveinterval;
};
#endif
static void
tcpkeepalive(struct Curl_easy *data,
curl_socket_t sockfd)
{
int optval = data->set.tcp_keepalive ? 1 : 0;
if(setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE,
(void *)&optval, sizeof(optval)) < 0) {
infof(data, "Failed to set SO_KEEPALIVE on fd "
"%" FMT_SOCKET_T ": errno %d",
sockfd, SOCKERRNO);
}
else {
#if defined(SIO_KEEPALIVE_VALS)
#if defined(CURL_WINSOCK_KEEP_SSO)
optval = curlx_sltosi(data->set.tcp_keepidle);
KEEPALIVE_FACTOR(optval);
if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE,
(const char *)&optval, sizeof(optval)) < 0) {
infof(data, "Failed to set TCP_KEEPIDLE on fd "
"%" FMT_SOCKET_T ": errno %d",
sockfd, SOCKERRNO);
}
optval = curlx_sltosi(data->set.tcp_keepintvl);
KEEPALIVE_FACTOR(optval);
if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL,
(const char *)&optval, sizeof(optval)) < 0) {
infof(data, "Failed to set TCP_KEEPINTVL on fd "
"%" FMT_SOCKET_T ": errno %d",
sockfd, SOCKERRNO);
}
optval = curlx_sltosi(data->set.tcp_keepcnt);
if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPCNT,
(const char *)&optval, sizeof(optval)) < 0) {
infof(data, "Failed to set TCP_KEEPCNT on fd "
"%" FMT_SOCKET_T ": errno %d",
sockfd, SOCKERRNO);
}
#else
struct tcp_keepalive vals;
DWORD dummy;
vals.onoff = 1;
optval = curlx_sltosi(data->set.tcp_keepidle);
KEEPALIVE_FACTOR(optval);
vals.keepalivetime = (u_long)optval;
optval = curlx_sltosi(data->set.tcp_keepintvl);
KEEPALIVE_FACTOR(optval);
vals.keepaliveinterval = (u_long)optval;
if(WSAIoctl(sockfd, SIO_KEEPALIVE_VALS, (LPVOID) &vals, sizeof(vals),
NULL, 0, &dummy, NULL, NULL) != 0) {
infof(data, "Failed to set SIO_KEEPALIVE_VALS on fd "
"%" FMT_SOCKET_T ": errno %d", sockfd, SOCKERRNO);
}
#endif
#else
#ifdef TCP_KEEPIDLE
optval = curlx_sltosi(data->set.tcp_keepidle);
KEEPALIVE_FACTOR(optval);
if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE,
(void *)&optval, sizeof(optval)) < 0) {
infof(data, "Failed to set TCP_KEEPIDLE on fd "
"%" FMT_SOCKET_T ": errno %d",
sockfd, SOCKERRNO);
}
#elif defined(TCP_KEEPALIVE)
optval = curlx_sltosi(data->set.tcp_keepidle);
KEEPALIVE_FACTOR(optval);
if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE,
(void *)&optval, sizeof(optval)) < 0) {
infof(data, "Failed to set TCP_KEEPALIVE on fd "
"%" FMT_SOCKET_T ": errno %d",
sockfd, SOCKERRNO);
}
#elif defined(TCP_KEEPALIVE_THRESHOLD)
optval = curlx_sltosi(data->set.tcp_keepidle);
KEEPALIVE_FACTOR(optval);
if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE_THRESHOLD,
(void *)&optval, sizeof(optval)) < 0) {
infof(data, "Failed to set TCP_KEEPALIVE_THRESHOLD on fd "
"%" FMT_SOCKET_T ": errno %d",
sockfd, SOCKERRNO);
}
#endif
#ifdef TCP_KEEPINTVL
optval = curlx_sltosi(data->set.tcp_keepintvl);
KEEPALIVE_FACTOR(optval);
if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL,
(void *)&optval, sizeof(optval)) < 0) {
infof(data, "Failed to set TCP_KEEPINTVL on fd "
"%" FMT_SOCKET_T ": errno %d",
sockfd, SOCKERRNO);
}
#elif defined(TCP_KEEPALIVE_ABORT_THRESHOLD)
optval = curlx_sltosi(data->set.tcp_keepcnt) *
curlx_sltosi(data->set.tcp_keepintvl);
KEEPALIVE_FACTOR(optval);
if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE_ABORT_THRESHOLD,
(void *)&optval, sizeof(optval)) < 0) {
infof(data, "Failed to set TCP_KEEPALIVE_ABORT_THRESHOLD on fd "
"%" FMT_SOCKET_T ": errno %d", sockfd, SOCKERRNO);
}
#endif
#ifdef TCP_KEEPCNT
optval = curlx_sltosi(data->set.tcp_keepcnt);
if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPCNT,
(void *)&optval, sizeof(optval)) < 0) {
infof(data, "Failed to set TCP_KEEPCNT on fd "
"%" FMT_SOCKET_T ": errno %d", sockfd, SOCKERRNO);
}
#endif
#endif
}
}
CURLcode Curl_sock_assign_addr(struct Curl_sockaddr_ex *dest,
const struct Curl_addrinfo *ai,
int transport)
{
dest->family = ai->ai_family;
switch(transport) {
case TRNSPRT_TCP:
dest->socktype = SOCK_STREAM;
dest->protocol = IPPROTO_TCP;
break;
case TRNSPRT_UNIX:
dest->socktype = SOCK_STREAM;
dest->protocol = IPPROTO_IP;
break;
default:
dest->socktype = SOCK_DGRAM;
dest->protocol = IPPROTO_UDP;
break;
}
dest->addrlen = (unsigned int)ai->ai_addrlen;
if(dest->addrlen > sizeof(struct Curl_sockaddr_storage)) {
DEBUGASSERT(0);
return CURLE_TOO_LARGE;
}
memcpy(&dest->curl_sa_addr, ai->ai_addr, dest->addrlen);
return CURLE_OK;
}
static CURLcode socket_open(struct Curl_easy *data,
struct Curl_sockaddr_ex *addr,
curl_socket_t *sockfd)
{
DEBUGASSERT(data);
DEBUGASSERT(data->conn);
if(data->set.fopensocket) {
Curl_set_in_callback(data, TRUE);
*sockfd = data->set.fopensocket(data->set.opensocket_client,
CURLSOCKTYPE_IPCXN,
(struct curl_sockaddr *)addr);
Curl_set_in_callback(data, FALSE);
}
else {
*sockfd = socket(addr->family, addr->socktype, addr->protocol);
}
if(*sockfd == CURL_SOCKET_BAD)
return CURLE_COULDNT_CONNECT;
#if defined(USE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID)
if(data->conn->scope_id && (addr->family == AF_INET6)) {
struct sockaddr_in6 * const sa6 = (void *)&addr->curl_sa_addr;
sa6->sin6_scope_id = data->conn->scope_id;
}
#endif
return CURLE_OK;
}
CURLcode Curl_socket_open(struct Curl_easy *data,
const struct Curl_addrinfo *ai,
struct Curl_sockaddr_ex *addr,
int transport,
curl_socket_t *sockfd)
{
struct Curl_sockaddr_ex dummy;
CURLcode result;
if(!addr)
addr = &dummy;
result = Curl_sock_assign_addr(addr, ai, transport);
if(result)
return result;
return socket_open(data, addr, sockfd);
}
static int socket_close(struct Curl_easy *data, struct connectdata *conn,
int use_callback, curl_socket_t sock)
{
if(CURL_SOCKET_BAD == sock)
return 0;
if(use_callback && conn && conn->fclosesocket) {
int rc;
Curl_multi_will_close(data, sock);
Curl_set_in_callback(data, TRUE);
rc = conn->fclosesocket(conn->closesocket_client, sock);
Curl_set_in_callback(data, FALSE);
return rc;
}
if(conn)
Curl_multi_will_close(data, sock);
sclose(sock);
return 0;
}
int Curl_socket_close(struct Curl_easy *data, struct connectdata *conn,
curl_socket_t sock)
{
return socket_close(data, conn, FALSE, sock);
}
#ifdef USE_WINSOCK
void Curl_sndbuf_init(curl_socket_t sockfd)
{
int val = CURL_MAX_WRITE_SIZE + 32;
int curval = 0;
int curlen = sizeof(curval);
if(Curl_isVistaOrGreater)
return;
if(getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (char *)&curval, &curlen) == 0)
if(curval > val)
return;
setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (const char *)&val, sizeof(val));
}
#endif
CURLcode Curl_parse_interface(const char *input,
char **dev, char **iface, char **host)
{
static const char if_prefix[] = "if!";
static const char host_prefix[] = "host!";
static const char if_host_prefix[] = "ifhost!";
size_t len;
DEBUGASSERT(dev);
DEBUGASSERT(iface);
DEBUGASSERT(host);
len = strlen(input);
if(len > 512)
return CURLE_BAD_FUNCTION_ARGUMENT;
if(!strncmp(if_prefix, input, strlen(if_prefix))) {
input += strlen(if_prefix);
if(!*input)
return CURLE_BAD_FUNCTION_ARGUMENT;
*iface = Curl_memdup0(input, len - strlen(if_prefix));
return *iface ? CURLE_OK : CURLE_OUT_OF_MEMORY;
}
else if(!strncmp(host_prefix, input, strlen(host_prefix))) {
input += strlen(host_prefix);
if(!*input)
return CURLE_BAD_FUNCTION_ARGUMENT;
*host = Curl_memdup0(input, len - strlen(host_prefix));
return *host ? CURLE_OK : CURLE_OUT_OF_MEMORY;
}
else if(!strncmp(if_host_prefix, input, strlen(if_host_prefix))) {
const char *host_part;
input += strlen(if_host_prefix);
len -= strlen(if_host_prefix);
host_part = memchr(input, '!', len);
if(!host_part || !*(host_part + 1))
return CURLE_BAD_FUNCTION_ARGUMENT;
*iface = Curl_memdup0(input, host_part - input);
if(!*iface)
return CURLE_OUT_OF_MEMORY;
++host_part;
*host = Curl_memdup0(host_part, len - (host_part - input));
if(!*host) {
free(*iface);
*iface = NULL;
return CURLE_OUT_OF_MEMORY;
}
return CURLE_OK;
}
if(!*input)
return CURLE_BAD_FUNCTION_ARGUMENT;
*dev = Curl_memdup0(input, len);
return *dev ? CURLE_OK : CURLE_OUT_OF_MEMORY;
}
#ifndef CURL_DISABLE_BINDLOCAL
static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
curl_socket_t sockfd, int af, unsigned int scope)
{
struct Curl_sockaddr_storage sa;
struct sockaddr *sock = (struct sockaddr *)&sa;
curl_socklen_t sizeof_sa = 0;
struct sockaddr_in *si4 = (struct sockaddr_in *)&sa;
#ifdef USE_IPV6
struct sockaddr_in6 *si6 = (struct sockaddr_in6 *)&sa;
#endif
struct Curl_dns_entry *h = NULL;
unsigned short port = data->set.localport;
int portnum = data->set.localportrange;
const char *dev = data->set.str[STRING_DEVICE];
const char *iface_input = data->set.str[STRING_INTERFACE];
const char *host_input = data->set.str[STRING_BINDHOST];
const char *iface = iface_input ? iface_input : dev;
const char *host = host_input ? host_input : dev;
int error;
#ifdef IP_BIND_ADDRESS_NO_PORT
int on = 1;
#endif
#ifndef USE_IPV6
(void)scope;
#endif
if(!iface && !host && !port)
return CURLE_OK;
else if(iface && (strlen(iface) >= 255) )
return CURLE_BAD_FUNCTION_ARGUMENT;
memset(&sa, 0, sizeof(struct Curl_sockaddr_storage));
if(iface || host) {
char myhost[256] = "";
int done = 0;
if2ip_result_t if2ip_result = IF2IP_NOT_FOUND;
#ifdef SO_BINDTODEVICE
if(iface) {
if(setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE,
iface, (curl_socklen_t)strlen(iface) + 1) == 0) {
if(!host_input) {
infof(data, "socket successfully bound to interface '%s'", iface);
return CURLE_OK;
}
}
}
#endif
if(!host_input) {
if2ip_result = Curl_if2ip(af,
#ifdef USE_IPV6
scope, conn->scope_id,
#endif
iface, myhost, sizeof(myhost));
}
switch(if2ip_result) {
case IF2IP_NOT_FOUND:
if(iface_input && !host_input) {
char buffer[STRERROR_LEN];
data->state.os_errno = error = SOCKERRNO;
failf(data, "Couldn't bind to interface '%s' with errno %d: %s",
iface, error, Curl_strerror(error, buffer, sizeof(buffer)));
return CURLE_INTERFACE_FAILED;
}
break;
case IF2IP_AF_NOT_SUPPORTED:
return CURLE_UNSUPPORTED_PROTOCOL;
case IF2IP_FOUND:
host = myhost;
infof(data, "Local Interface %s is ip %s using address family %i",
iface, host, af);
done = 1;
break;
}
if(!iface_input || host_input) {
int ip_version = (af == AF_INET) ?
CURL_IPRESOLVE_V4 : CURL_IPRESOLVE_WHATEVER;
#ifdef USE_IPV6
if(af == AF_INET6)
ip_version = CURL_IPRESOLVE_V6;
#endif
(void)Curl_resolv_blocking(data, host, 80, ip_version, &h);
if(h) {
int h_af = h->addr->ai_family;
Curl_printable_address(h->addr, myhost, sizeof(myhost));
infof(data, "Name '%s' family %i resolved to '%s' family %i",
host, af, myhost, h_af);
Curl_resolv_unlink(data, &h);
if(af != h_af) {
return CURLE_UNSUPPORTED_PROTOCOL;
}
done = 1;
}
else {
done = -1;
}
}
if(done > 0) {
#ifdef USE_IPV6
if(af == AF_INET6) {
#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID
char *scope_ptr = strchr(myhost, '%');
if(scope_ptr)
*(scope_ptr++) = '\0';
#endif
if(curlx_inet_pton(AF_INET6, myhost, &si6->sin6_addr) > 0) {
si6->sin6_family = AF_INET6;
si6->sin6_port = htons(port);
#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID
if(scope_ptr) {
curl_off_t scope_id;
if(curlx_str_number((const char **)CURL_UNCONST(&scope_ptr),
&scope_id, UINT_MAX))
return CURLE_UNSUPPORTED_PROTOCOL;
si6->sin6_scope_id = (unsigned int)scope_id;
}
#endif
}
sizeof_sa = sizeof(struct sockaddr_in6);
}
else
#endif
if((af == AF_INET) &&
(curlx_inet_pton(AF_INET, myhost, &si4->sin_addr) > 0)) {
si4->sin_family = AF_INET;
si4->sin_port = htons(port);
sizeof_sa = sizeof(struct sockaddr_in);
}
}
if(done < 1) {
char buffer[STRERROR_LEN];
data->state.errorbuf = FALSE;
data->state.os_errno = error = SOCKERRNO;
failf(data, "Couldn't bind to '%s' with errno %d: %s",
host, error, Curl_strerror(error, buffer, sizeof(buffer)));
return CURLE_INTERFACE_FAILED;
}
}
else {
#ifdef USE_IPV6
if(af == AF_INET6) {
si6->sin6_family = AF_INET6;
si6->sin6_port = htons(port);
sizeof_sa = sizeof(struct sockaddr_in6);
}
else
#endif
if(af == AF_INET) {
si4->sin_family = AF_INET;
si4->sin_port = htons(port);
sizeof_sa = sizeof(struct sockaddr_in);
}
}
#ifdef IP_BIND_ADDRESS_NO_PORT
(void)setsockopt(sockfd, SOL_IP, IP_BIND_ADDRESS_NO_PORT, &on, sizeof(on));
#endif
for(;;) {
if(bind(sockfd, sock, sizeof_sa) >= 0) {
infof(data, "Local port: %hu", port);
conn->bits.bound = TRUE;
return CURLE_OK;
}
if(--portnum > 0) {
port++;
if(port == 0)
break;
infof(data, "Bind to local port %d failed, trying next", port - 1);
if(sock->sa_family == AF_INET)
si4->sin_port = ntohs(port);
#ifdef USE_IPV6
else
si6->sin6_port = ntohs(port);
#endif
}
else
break;
}
{
char buffer[STRERROR_LEN];
data->state.os_errno = error = SOCKERRNO;
failf(data, "bind failed with errno %d: %s",
error, Curl_strerror(error, buffer, sizeof(buffer)));
}
return CURLE_INTERFACE_FAILED;
}
#endif
static bool verifyconnect(curl_socket_t sockfd, int *error)
{
bool rc = TRUE;
#ifdef SO_ERROR
int err = 0;
curl_socklen_t errSize = sizeof(err);
#ifdef _WIN32
#ifdef UNDER_CE
Sleep(0);
#else
SleepEx(0, FALSE);
#endif
#endif
if(0 != getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *)&err, &errSize))
err = SOCKERRNO;
#ifdef UNDER_CE
if(WSAENOPROTOOPT == err) {
SET_SOCKERRNO(0);
err = 0;
}
#endif
#if defined(EBADIOCTL) && defined(__minix)
if(EBADIOCTL == err) {
SET_SOCKERRNO(0);
err = 0;
}
#endif
if((0 == err) || (SOCKEISCONN == err))
rc = TRUE;
else
rc = FALSE;
if(error)
*error = err;
#else
(void)sockfd;
if(error)
*error = SOCKERRNO;
#endif
return rc;
}
static CURLcode socket_connect_result(struct Curl_easy *data,
const char *ipaddress, int error)
{
switch(error) {
case SOCKEINPROGRESS:
case SOCKEWOULDBLOCK:
#if defined(EAGAIN)
#if (EAGAIN) != (SOCKEWOULDBLOCK)
case EAGAIN:
#endif
#endif
return CURLE_OK;
default:
#ifdef CURL_DISABLE_VERBOSE_STRINGS
(void)ipaddress;
#else
{
char buffer[STRERROR_LEN];
infof(data, "Immediate connect fail for %s: %s",
ipaddress, Curl_strerror(error, buffer, sizeof(buffer)));
}
#endif
data->state.os_errno = error;
return CURLE_COULDNT_CONNECT;
}
}
struct cf_socket_ctx {
int transport;
struct Curl_sockaddr_ex addr;
curl_socket_t sock;
struct ip_quadruple ip;
struct curltime started_at;
struct curltime connected_at;
struct curltime first_byte_at;
#ifdef USE_WINSOCK
struct curltime last_sndbuf_query_at;
ULONG sndbuf_size;
#endif
int error;
#ifdef DEBUGBUILD
int wblock_percent;
int wpartial_percent;
int rblock_percent;
size_t recv_max;
#endif
BIT(got_first_byte);
BIT(listening);
BIT(accepted);
BIT(sock_connected);
BIT(active);
};
static CURLcode cf_socket_ctx_init(struct cf_socket_ctx *ctx,
const struct Curl_addrinfo *ai,
int transport)
{
CURLcode result;
memset(ctx, 0, sizeof(*ctx));
ctx->sock = CURL_SOCKET_BAD;
ctx->transport = transport;
result = Curl_sock_assign_addr(&ctx->addr, ai, transport);
if(result)
return result;
#ifdef DEBUGBUILD
{
const char *p = getenv("CURL_DBG_SOCK_WBLOCK");
if(p) {
curl_off_t l;
if(!curlx_str_number(&p, &l, 100))
ctx->wblock_percent = (int)l;
}
p = getenv("CURL_DBG_SOCK_WPARTIAL");
if(p) {
curl_off_t l;
if(!curlx_str_number(&p, &l, 100))
ctx->wpartial_percent = (int)l;
}
p = getenv("CURL_DBG_SOCK_RBLOCK");
if(p) {
curl_off_t l;
if(!curlx_str_number(&p, &l, 100))
ctx->rblock_percent = (int)l;
}
p = getenv("CURL_DBG_SOCK_RMAX");
if(p) {
curl_off_t l;
if(!curlx_str_number(&p, &l, CURL_OFF_T_MAX))
ctx->recv_max = (size_t)l;
}
}
#endif
return result;
}
static void cf_socket_close(struct Curl_cfilter *cf, struct Curl_easy *data)
{
struct cf_socket_ctx *ctx = cf->ctx;
if(ctx && CURL_SOCKET_BAD != ctx->sock) {
CURL_TRC_CF(data, cf, "cf_socket_close, fd=%" FMT_SOCKET_T, ctx->sock);
if(ctx->sock == cf->conn->sock[cf->sockindex])
cf->conn->sock[cf->sockindex] = CURL_SOCKET_BAD;
socket_close(data, cf->conn, !ctx->accepted, ctx->sock);
ctx->sock = CURL_SOCKET_BAD;
if(ctx->active && cf->sockindex == FIRSTSOCKET)
cf->conn->remote_addr = NULL;
ctx->active = FALSE;
memset(&ctx->started_at, 0, sizeof(ctx->started_at));
memset(&ctx->connected_at, 0, sizeof(ctx->connected_at));
}
cf->connected = FALSE;
}
static CURLcode cf_socket_shutdown(struct Curl_cfilter *cf,
struct Curl_easy *data,
bool *done)
{
if(cf->connected) {
struct cf_socket_ctx *ctx = cf->ctx;
CURL_TRC_CF(data, cf, "cf_socket_shutdown, fd=%" FMT_SOCKET_T, ctx->sock);
if(ctx->sock != CURL_SOCKET_BAD &&
ctx->transport == TRNSPRT_TCP &&
(curlx_nonblock(ctx->sock, TRUE) >= 0)) {
unsigned char buf[1024];
(void)sread(ctx->sock, buf, sizeof(buf));
}
}
*done = TRUE;
return CURLE_OK;
}
static void cf_socket_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
{
struct cf_socket_ctx *ctx = cf->ctx;
cf_socket_close(cf, data);
CURL_TRC_CF(data, cf, "destroy");
free(ctx);
cf->ctx = NULL;
}
static CURLcode set_local_ip(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct cf_socket_ctx *ctx = cf->ctx;
#ifdef HAVE_GETSOCKNAME
if((ctx->sock != CURL_SOCKET_BAD) &&
!(data->conn->handler->protocol & CURLPROTO_TFTP)) {
char buffer[STRERROR_LEN];
struct Curl_sockaddr_storage ssloc;
curl_socklen_t slen = sizeof(struct Curl_sockaddr_storage);
memset(&ssloc, 0, sizeof(ssloc));
if(getsockname(ctx->sock, (struct sockaddr*) &ssloc, &slen)) {
int error = SOCKERRNO;
failf(data, "getsockname() failed with errno %d: %s",
error, Curl_strerror(error, buffer, sizeof(buffer)));
return CURLE_FAILED_INIT;
}
if(!Curl_addr2string((struct sockaddr*)&ssloc, slen,
ctx->ip.local_ip, &ctx->ip.local_port)) {
failf(data, "ssloc inet_ntop() failed with errno %d: %s",
errno, Curl_strerror(errno, buffer, sizeof(buffer)));
return CURLE_FAILED_INIT;
}
}
#else
(void)data;
ctx->ip.local_ip[0] = 0;
ctx->ip.local_port = -1;
#endif
return CURLE_OK;
}
static CURLcode set_remote_ip(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct cf_socket_ctx *ctx = cf->ctx;
if(!Curl_addr2string(&ctx->addr.curl_sa_addr,
(curl_socklen_t)ctx->addr.addrlen,
ctx->ip.remote_ip, &ctx->ip.remote_port)) {
char buffer[STRERROR_LEN];
ctx->error = errno;
failf(data, "curl_sa_addr inet_ntop() failed with errno %d: %s",
errno, Curl_strerror(errno, buffer, sizeof(buffer)));
return CURLE_FAILED_INIT;
}
return CURLE_OK;
}
static CURLcode cf_socket_open(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct cf_socket_ctx *ctx = cf->ctx;
int error = 0;
bool isconnected = FALSE;
CURLcode result = CURLE_COULDNT_CONNECT;
bool is_tcp;
(void)data;
DEBUGASSERT(ctx->sock == CURL_SOCKET_BAD);
ctx->started_at = curlx_now();
#ifdef SOCK_NONBLOCK
if(!data->set.fopensocket)
ctx->addr.socktype |= SOCK_NONBLOCK;
#endif
result = socket_open(data, &ctx->addr, &ctx->sock);
#ifdef SOCK_NONBLOCK
if(!data->set.fopensocket)
ctx->addr.socktype &= ~SOCK_NONBLOCK;
#endif
if(result)
goto out;
result = set_remote_ip(cf, data);
if(result)
goto out;
#ifdef USE_IPV6
if(ctx->addr.family == AF_INET6) {
set_ipv6_v6only(ctx->sock, 0);
infof(data, " Trying [%s]:%d...", ctx->ip.remote_ip, ctx->ip.remote_port);
}
else
#endif
infof(data, " Trying %s:%d...", ctx->ip.remote_ip, ctx->ip.remote_port);
#ifdef USE_IPV6
is_tcp = (ctx->addr.family == AF_INET
|| ctx->addr.family == AF_INET6) &&
ctx->addr.socktype == SOCK_STREAM;
#else
is_tcp = (ctx->addr.family == AF_INET) &&
ctx->addr.socktype == SOCK_STREAM;
#endif
if(is_tcp && data->set.tcp_nodelay)
tcpnodelay(data, ctx->sock);
nosigpipe(data, ctx->sock);
Curl_sndbuf_init(ctx->sock);
if(is_tcp && data->set.tcp_keepalive)
tcpkeepalive(data, ctx->sock);
if(data->set.fsockopt) {
Curl_set_in_callback(data, TRUE);
error = data->set.fsockopt(data->set.sockopt_client,
ctx->sock,
CURLSOCKTYPE_IPCXN);
Curl_set_in_callback(data, FALSE);
if(error == CURL_SOCKOPT_ALREADY_CONNECTED)
isconnected = TRUE;
else if(error) {
result = CURLE_ABORTED_BY_CALLBACK;
goto out;
}
}
#ifndef CURL_DISABLE_BINDLOCAL
if(ctx->addr.family == AF_INET
#ifdef USE_IPV6
|| ctx->addr.family == AF_INET6
#endif
) {
result = bindlocal(data, cf->conn, ctx->sock, ctx->addr.family,
Curl_ipv6_scope(&ctx->addr.curl_sa_addr));
if(result) {
if(result == CURLE_UNSUPPORTED_PROTOCOL) {
result = CURLE_COULDNT_CONNECT;
}
goto out;
}
}
#endif
#ifndef SOCK_NONBLOCK
error = curlx_nonblock(ctx->sock, TRUE);
if(error < 0) {
result = CURLE_UNSUPPORTED_PROTOCOL;
ctx->error = SOCKERRNO;
goto out;
}
#else
if(data->set.fopensocket) {
error = curlx_nonblock(ctx->sock, TRUE);
if(error < 0) {
result = CURLE_UNSUPPORTED_PROTOCOL;
ctx->error = SOCKERRNO;
goto out;
}
}
#endif
ctx->sock_connected = (ctx->addr.socktype != SOCK_DGRAM);
out:
if(result) {
if(ctx->sock != CURL_SOCKET_BAD) {
socket_close(data, cf->conn, TRUE, ctx->sock);
ctx->sock = CURL_SOCKET_BAD;
}
}
else if(isconnected) {
set_local_ip(cf, data);
ctx->connected_at = curlx_now();
cf->connected = TRUE;
}
CURL_TRC_CF(data, cf, "cf_socket_open() -> %d, fd=%" FMT_SOCKET_T,
result, ctx->sock);
return result;
}
static int do_connect(struct Curl_cfilter *cf, struct Curl_easy *data,
bool is_tcp_fastopen)
{
struct cf_socket_ctx *ctx = cf->ctx;
#ifdef TCP_FASTOPEN_CONNECT
int optval = 1;
#endif
int rc = -1;
(void)data;
if(is_tcp_fastopen) {
#if defined(CONNECT_DATA_IDEMPOTENT)
# if defined(HAVE_BUILTIN_AVAILABLE)
if(__builtin_available(macOS 10.11, iOS 9.0, tvOS 9.0, watchOS 2.0, *)) {
sa_endpoints_t endpoints;
endpoints.sae_srcif = 0;
endpoints.sae_srcaddr = NULL;
endpoints.sae_srcaddrlen = 0;
endpoints.sae_dstaddr = &ctx->addr.curl_sa_addr;
endpoints.sae_dstaddrlen = ctx->addr.addrlen;
rc = connectx(ctx->sock, &endpoints, SAE_ASSOCID_ANY,
CONNECT_RESUME_ON_READ_WRITE | CONNECT_DATA_IDEMPOTENT,
NULL, 0, NULL, NULL);
}
else {
rc = connect(ctx->sock, &ctx->addr.curl_sa_addr, ctx->addr.addrlen);
}
# else
rc = connect(ctx->sock, &ctx->addr.curl_sa_addr, ctx->addr.addrlen);
# endif
#elif defined(TCP_FASTOPEN_CONNECT)
if(setsockopt(ctx->sock, IPPROTO_TCP, TCP_FASTOPEN_CONNECT,
(void *)&optval, sizeof(optval)) < 0)
infof(data, "Failed to enable TCP Fast Open on fd %" FMT_SOCKET_T,
ctx->sock);
rc = connect(ctx->sock, &ctx->addr.curl_sa_addr, ctx->addr.addrlen);
#elif defined(MSG_FASTOPEN)
if(Curl_conn_is_ssl(cf->conn, cf->sockindex))
rc = connect(ctx->sock, &ctx->addr.curl_sa_addr, ctx->addr.addrlen);
else
rc = 0;
#endif
}
else {
rc = connect(ctx->sock, &ctx->addr.curl_sa_addr,
(curl_socklen_t)ctx->addr.addrlen);
}
return rc;
}
static CURLcode cf_tcp_connect(struct Curl_cfilter *cf,
struct Curl_easy *data,
bool *done)
{
struct cf_socket_ctx *ctx = cf->ctx;
CURLcode result = CURLE_COULDNT_CONNECT;
int rc = 0;
(void)data;
if(cf->connected) {
*done = TRUE;
return CURLE_OK;
}
*done = FALSE;
if(ctx->sock == CURL_SOCKET_BAD) {
int error;
result = cf_socket_open(cf, data);
if(result)
goto out;
if(cf->connected) {
*done = TRUE;
return CURLE_OK;
}
rc = do_connect(cf, data, cf->conn->bits.tcp_fastopen);
error = SOCKERRNO;
set_local_ip(cf, data);
CURL_TRC_CF(data, cf, "local address %s port %d...",
ctx->ip.local_ip, ctx->ip.local_port);
if(-1 == rc) {
result = socket_connect_result(data, ctx->ip.remote_ip, error);
goto out;
}
}
#ifdef mpeix
(void)verifyconnect(ctx->sock, NULL);
#endif
rc = SOCKET_WRITABLE(ctx->sock, 0);
if(rc == 0) {
CURL_TRC_CF(data, cf, "not connected yet");
return CURLE_OK;
}
else if(rc == CURL_CSELECT_OUT || cf->conn->bits.tcp_fastopen) {
if(verifyconnect(ctx->sock, &ctx->error)) {
ctx->connected_at = curlx_now();
set_local_ip(cf, data);
*done = TRUE;
cf->connected = TRUE;
CURL_TRC_CF(data, cf, "connected");
return CURLE_OK;
}
}
else if(rc & CURL_CSELECT_ERR) {
(void)verifyconnect(ctx->sock, &ctx->error);
result = CURLE_COULDNT_CONNECT;
}
out:
if(result) {
if(ctx->error) {
set_local_ip(cf, data);
data->state.os_errno = ctx->error;
SET_SOCKERRNO(ctx->error);
#ifndef CURL_DISABLE_VERBOSE_STRINGS
{
char buffer[STRERROR_LEN];
infof(data, "connect to %s port %u from %s port %d failed: %s",
ctx->ip.remote_ip, ctx->ip.remote_port,
ctx->ip.local_ip, ctx->ip.local_port,
Curl_strerror(ctx->error, buffer, sizeof(buffer)));
}
#endif
}
if(ctx->sock != CURL_SOCKET_BAD) {
socket_close(data, cf->conn, TRUE, ctx->sock);
ctx->sock = CURL_SOCKET_BAD;
}
*done = FALSE;
}
return result;
}
static void cf_socket_get_host(struct Curl_cfilter *cf,
struct Curl_easy *data,
const char **phost,
const char **pdisplay_host,
int *pport)
{
struct cf_socket_ctx *ctx = cf->ctx;
(void)data;
*phost = cf->conn->host.name;
*pdisplay_host = cf->conn->host.dispname;
*pport = ctx->ip.remote_port;
}
static void cf_socket_adjust_pollset(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct easy_pollset *ps)
{
struct cf_socket_ctx *ctx = cf->ctx;
if(ctx->sock != CURL_SOCKET_BAD) {
if(ctx->listening) {
Curl_pollset_set_in_only(data, ps, ctx->sock);
CURL_TRC_CF(data, cf, "adjust_pollset, listening, POLLIN fd=%"
FMT_SOCKET_T, ctx->sock);
}
else if(!cf->connected) {
Curl_pollset_set_out_only(data, ps, ctx->sock);
CURL_TRC_CF(data, cf, "adjust_pollset, !connected, POLLOUT fd=%"
FMT_SOCKET_T, ctx->sock);
}
else if(!ctx->active) {
Curl_pollset_add_in(data, ps, ctx->sock);
CURL_TRC_CF(data, cf, "adjust_pollset, !active, POLLIN fd=%"
FMT_SOCKET_T, ctx->sock);
}
}
}
static bool cf_socket_data_pending(struct Curl_cfilter *cf,
const struct Curl_easy *data)
{
struct cf_socket_ctx *ctx = cf->ctx;
int readable;
(void)data;
readable = SOCKET_READABLE(ctx->sock, 0);
return readable > 0 && (readable & CURL_CSELECT_IN);
}
#ifdef USE_WINSOCK
#ifndef SIO_IDEAL_SEND_BACKLOG_QUERY
#define SIO_IDEAL_SEND_BACKLOG_QUERY 0x4004747B
#endif
static void win_update_sndbuf_size(struct cf_socket_ctx *ctx)
{
ULONG ideal;
DWORD ideallen;
struct curltime n = curlx_now();
if(curlx_timediff(n, ctx->last_sndbuf_query_at) > 1000) {
if(!WSAIoctl(ctx->sock, SIO_IDEAL_SEND_BACKLOG_QUERY, 0, 0,
&ideal, sizeof(ideal), &ideallen, 0, 0) &&
ideal != ctx->sndbuf_size &&
!setsockopt(ctx->sock, SOL_SOCKET, SO_SNDBUF,
(const char *)&ideal, sizeof(ideal))) {
ctx->sndbuf_size = ideal;
}
ctx->last_sndbuf_query_at = n;
}
}
#endif
static ssize_t cf_socket_send(struct Curl_cfilter *cf, struct Curl_easy *data,
const void *buf, size_t len, bool eos,
CURLcode *err)
{
struct cf_socket_ctx *ctx = cf->ctx;
curl_socket_t fdsave;
ssize_t nwritten;
size_t orig_len = len;
(void)eos;
*err = CURLE_OK;
fdsave = cf->conn->sock[cf->sockindex];
cf->conn->sock[cf->sockindex] = ctx->sock;
#ifdef DEBUGBUILD
if(ctx->wblock_percent > 0) {
unsigned char c = 0;
Curl_rand_bytes(data, FALSE, &c, 1);
if(c >= ((100-ctx->wblock_percent)*256/100)) {
CURL_TRC_CF(data, cf, "send(len=%zu) SIMULATE EWOULDBLOCK", orig_len);
*err = CURLE_AGAIN;
nwritten = -1;
cf->conn->sock[cf->sockindex] = fdsave;
return nwritten;
}
}
if(cf->cft != &Curl_cft_udp && ctx->wpartial_percent > 0 && len > 8) {
len = len * ctx->wpartial_percent / 100;
if(!len)
len = 1;
CURL_TRC_CF(data, cf, "send(len=%zu) SIMULATE partial write of %zu bytes",
orig_len, len);
}
#endif
#if defined(MSG_FASTOPEN) && !defined(TCP_FASTOPEN_CONNECT)
if(cf->conn->bits.tcp_fastopen) {
nwritten = sendto(ctx->sock, buf, len, MSG_FASTOPEN,
&cf->conn->remote_addr->curl_sa_addr,
cf->conn->remote_addr->addrlen);
cf->conn->bits.tcp_fastopen = FALSE;
}
else
#endif
nwritten = swrite(ctx->sock, buf, len);
if(-1 == nwritten) {
int sockerr = SOCKERRNO;
if(
#ifdef USE_WINSOCK
(SOCKEWOULDBLOCK == sockerr)
#else
(SOCKEWOULDBLOCK == sockerr) ||
(EAGAIN == sockerr) || (SOCKEINTR == sockerr) ||
(SOCKEINPROGRESS == sockerr)
#endif
) {
*err = CURLE_AGAIN;
}
else {
char buffer[STRERROR_LEN];
failf(data, "Send failure: %s",
Curl_strerror(sockerr, buffer, sizeof(buffer)));
data->state.os_errno = sockerr;
*err = CURLE_SEND_ERROR;
}
}
#if defined(USE_WINSOCK)
if(!*err)
win_update_sndbuf_size(ctx);
#endif
CURL_TRC_CF(data, cf, "send(len=%zu) -> %d, err=%d",
orig_len, (int)nwritten, *err);
cf->conn->sock[cf->sockindex] = fdsave;
return nwritten;
}
static ssize_t cf_socket_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
char *buf, size_t len, CURLcode *err)
{
struct cf_socket_ctx *ctx = cf->ctx;
ssize_t nread;
*err = CURLE_OK;
#ifdef DEBUGBUILD
if(cf->cft != &Curl_cft_udp && ctx->rblock_percent > 0) {
unsigned char c = 0;
Curl_rand(data, &c, 1);
if(c >= ((100-ctx->rblock_percent)*256/100)) {
CURL_TRC_CF(data, cf, "recv(len=%zu) SIMULATE EWOULDBLOCK", len);
*err = CURLE_AGAIN;
return -1;
}
}
if(cf->cft != &Curl_cft_udp && ctx->recv_max && ctx->recv_max < len) {
size_t orig_len = len;
len = ctx->recv_max;
CURL_TRC_CF(data, cf, "recv(len=%zu) SIMULATE max read of %zu bytes",
orig_len, len);
}
#endif
*err = CURLE_OK;
nread = sread(ctx->sock, buf, len);
if(-1 == nread) {
int sockerr = SOCKERRNO;
if(
#ifdef USE_WINSOCK
(SOCKEWOULDBLOCK == sockerr)
#else
(SOCKEWOULDBLOCK == sockerr) ||
(EAGAIN == sockerr) || (SOCKEINTR == sockerr)
#endif
) {
*err = CURLE_AGAIN;
}
else {
char buffer[STRERROR_LEN];
failf(data, "Recv failure: %s",
Curl_strerror(sockerr, buffer, sizeof(buffer)));
data->state.os_errno = sockerr;
*err = CURLE_RECV_ERROR;
}
}
CURL_TRC_CF(data, cf, "recv(len=%zu) -> %d, err=%d", len, (int)nread,
*err);
if(nread > 0 && !ctx->got_first_byte) {
ctx->first_byte_at = curlx_now();
ctx->got_first_byte = TRUE;
}
return nread;
}
static void cf_socket_update_data(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
if(cf->connected && (cf->sockindex == FIRSTSOCKET)) {
struct cf_socket_ctx *ctx = cf->ctx;
data->info.primary = ctx->ip;
data->info.conn_remote_port = cf->conn->remote_port;
}
}
static void cf_socket_active(struct Curl_cfilter *cf, struct Curl_easy *data)
{
struct cf_socket_ctx *ctx = cf->ctx;
cf->conn->sock[cf->sockindex] = ctx->sock;
set_local_ip(cf, data);
if(cf->sockindex == FIRSTSOCKET) {
cf->conn->primary = ctx->ip;
cf->conn->remote_addr = &ctx->addr;
#ifdef USE_IPV6
cf->conn->bits.ipv6 = (ctx->addr.family == AF_INET6);
#endif
}
else {
cf->conn->secondary = ctx->ip;
}
ctx->active = TRUE;
}
static CURLcode cf_socket_cntrl(struct Curl_cfilter *cf,
struct Curl_easy *data,
int event, int arg1, void *arg2)
{
struct cf_socket_ctx *ctx = cf->ctx;
(void)arg1;
(void)arg2;
switch(event) {
case CF_CTRL_CONN_INFO_UPDATE:
cf_socket_active(cf, data);
cf_socket_update_data(cf, data);
break;
case CF_CTRL_DATA_SETUP:
cf_socket_update_data(cf, data);
break;
case CF_CTRL_FORGET_SOCKET:
ctx->sock = CURL_SOCKET_BAD;
break;
}
return CURLE_OK;
}
static bool cf_socket_conn_is_alive(struct Curl_cfilter *cf,
struct Curl_easy *data,
bool *input_pending)
{
struct cf_socket_ctx *ctx = cf->ctx;
struct pollfd pfd[1];
int r;
*input_pending = FALSE;
(void)data;
if(!ctx || ctx->sock == CURL_SOCKET_BAD)
return FALSE;
pfd[0].fd = ctx->sock;
pfd[0].events = POLLRDNORM|POLLIN|POLLRDBAND|POLLPRI;
pfd[0].revents = 0;
r = Curl_poll(pfd, 1, 0);
if(r < 0) {
CURL_TRC_CF(data, cf, "is_alive: poll error, assume dead");
return FALSE;
}
else if(r == 0) {
CURL_TRC_CF(data, cf, "is_alive: poll timeout, assume alive");
return TRUE;
}
else if(pfd[0].revents & (POLLERR|POLLHUP|POLLPRI|POLLNVAL)) {
CURL_TRC_CF(data, cf, "is_alive: err/hup/etc events, assume dead");
return FALSE;
}
CURL_TRC_CF(data, cf, "is_alive: valid events, looks alive");
*input_pending = TRUE;
return TRUE;
}
static CURLcode cf_socket_query(struct Curl_cfilter *cf,
struct Curl_easy *data,
int query, int *pres1, void *pres2)
{
struct cf_socket_ctx *ctx = cf->ctx;
switch(query) {
case CF_QUERY_SOCKET:
DEBUGASSERT(pres2);
*((curl_socket_t *)pres2) = ctx->sock;
return CURLE_OK;
case CF_QUERY_CONNECT_REPLY_MS:
if(ctx->got_first_byte) {
timediff_t ms = curlx_timediff(ctx->first_byte_at, ctx->started_at);
*pres1 = (ms < INT_MAX) ? (int)ms : INT_MAX;
}
else
*pres1 = -1;
return CURLE_OK;
case CF_QUERY_TIMER_CONNECT: {
struct curltime *when = pres2;
switch(ctx->transport) {
case TRNSPRT_UDP:
case TRNSPRT_QUIC:
if(ctx->got_first_byte) {
*when = ctx->first_byte_at;
break;
}
FALLTHROUGH();
default:
*when = ctx->connected_at;
break;
}
return CURLE_OK;
}
case CF_QUERY_IP_INFO:
#ifdef USE_IPV6
*pres1 = (ctx->addr.family == AF_INET6);
#else
*pres1 = FALSE;
#endif
*(struct ip_quadruple *)pres2 = ctx->ip;
return CURLE_OK;
default:
break;
}
return cf->next ?
cf->next->cft->query(cf->next, data, query, pres1, pres2) :
CURLE_UNKNOWN_OPTION;
}
struct Curl_cftype Curl_cft_tcp = {
"TCP",
CF_TYPE_IP_CONNECT,
CURL_LOG_LVL_NONE,
cf_socket_destroy,
cf_tcp_connect,
cf_socket_close,
cf_socket_shutdown,
cf_socket_get_host,
cf_socket_adjust_pollset,
cf_socket_data_pending,
cf_socket_send,
cf_socket_recv,
cf_socket_cntrl,
cf_socket_conn_is_alive,
Curl_cf_def_conn_keep_alive,
cf_socket_query,
};
CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf,
struct Curl_easy *data,
struct connectdata *conn,
const struct Curl_addrinfo *ai,
int transport)
{
struct cf_socket_ctx *ctx = NULL;
struct Curl_cfilter *cf = NULL;
CURLcode result;
(void)data;
(void)conn;
DEBUGASSERT(transport == TRNSPRT_TCP);
ctx = calloc(1, sizeof(*ctx));
if(!ctx) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
result = cf_socket_ctx_init(ctx, ai, transport);
if(result)
goto out;
result = Curl_cf_create(&cf, &Curl_cft_tcp, ctx);
out:
*pcf = (!result) ? cf : NULL;
if(result) {
Curl_safefree(cf);
Curl_safefree(ctx);
}
return result;
}
static CURLcode cf_udp_setup_quic(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct cf_socket_ctx *ctx = cf->ctx;
int rc;
int one = 1;
(void)one;
DEBUGASSERT(ctx->sock != CURL_SOCKET_BAD);
rc = connect(ctx->sock, &ctx->addr.curl_sa_addr,
(curl_socklen_t)ctx->addr.addrlen);
if(-1 == rc) {
return socket_connect_result(data, ctx->ip.remote_ip, SOCKERRNO);
}
ctx->sock_connected = TRUE;
set_local_ip(cf, data);
CURL_TRC_CF(data, cf, "%s socket %" FMT_SOCKET_T
" connected: [%s:%d] -> [%s:%d]",
(ctx->transport == TRNSPRT_QUIC) ? "QUIC" : "UDP",
ctx->sock, ctx->ip.local_ip, ctx->ip.local_port,
ctx->ip.remote_ip, ctx->ip.remote_port);
#ifdef __linux__
switch(ctx->addr.family) {
#ifdef IP_MTU_DISCOVER
case AF_INET: {
int val = IP_PMTUDISC_DO;
(void)setsockopt(ctx->sock, IPPROTO_IP, IP_MTU_DISCOVER, &val,
sizeof(val));
break;
}
#endif
#ifdef IPV6_MTU_DISCOVER
case AF_INET6: {
int val = IPV6_PMTUDISC_DO;
(void)setsockopt(ctx->sock, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &val,
sizeof(val));
break;
}
#endif
}
#if defined(UDP_GRO) && \
(defined(HAVE_SENDMMSG) || defined(HAVE_SENDMSG)) && \
((defined(USE_NGTCP2) && defined(USE_NGHTTP3)) || defined(USE_QUICHE))
(void)setsockopt(ctx->sock, IPPROTO_UDP, UDP_GRO, &one,
(socklen_t)sizeof(one));
#endif
#endif
return CURLE_OK;
}
static CURLcode cf_udp_connect(struct Curl_cfilter *cf,
struct Curl_easy *data,
bool *done)
{
struct cf_socket_ctx *ctx = cf->ctx;
CURLcode result = CURLE_COULDNT_CONNECT;
if(cf->connected) {
*done = TRUE;
return CURLE_OK;
}
*done = FALSE;
if(ctx->sock == CURL_SOCKET_BAD) {
result = cf_socket_open(cf, data);
if(result) {
CURL_TRC_CF(data, cf, "cf_udp_connect(), open failed -> %d", result);
goto out;
}
if(ctx->transport == TRNSPRT_QUIC) {
result = cf_udp_setup_quic(cf, data);
if(result)
goto out;
CURL_TRC_CF(data, cf, "cf_udp_connect(), opened socket=%"
FMT_SOCKET_T " (%s:%d)",
ctx->sock, ctx->ip.local_ip, ctx->ip.local_port);
}
else {
CURL_TRC_CF(data, cf, "cf_udp_connect(), opened socket=%"
FMT_SOCKET_T " (unconnected)", ctx->sock);
}
*done = TRUE;
cf->connected = TRUE;
}
out:
return result;
}
struct Curl_cftype Curl_cft_udp = {
"UDP",
CF_TYPE_IP_CONNECT,
CURL_LOG_LVL_NONE,
cf_socket_destroy,
cf_udp_connect,
cf_socket_close,
cf_socket_shutdown,
cf_socket_get_host,
cf_socket_adjust_pollset,
cf_socket_data_pending,
cf_socket_send,
cf_socket_recv,
cf_socket_cntrl,
cf_socket_conn_is_alive,
Curl_cf_def_conn_keep_alive,
cf_socket_query,
};
CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf,
struct Curl_easy *data,
struct connectdata *conn,
const struct Curl_addrinfo *ai,
int transport)
{
struct cf_socket_ctx *ctx = NULL;
struct Curl_cfilter *cf = NULL;
CURLcode result;
(void)data;
(void)conn;
DEBUGASSERT(transport == TRNSPRT_UDP || transport == TRNSPRT_QUIC);
ctx = calloc(1, sizeof(*ctx));
if(!ctx) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
result = cf_socket_ctx_init(ctx, ai, transport);
if(result)
goto out;
result = Curl_cf_create(&cf, &Curl_cft_udp, ctx);
out:
*pcf = (!result) ? cf : NULL;
if(result) {
Curl_safefree(cf);
Curl_safefree(ctx);
}
return result;
}
struct Curl_cftype Curl_cft_unix = {
"UNIX",
CF_TYPE_IP_CONNECT,
CURL_LOG_LVL_NONE,
cf_socket_destroy,
cf_tcp_connect,
cf_socket_close,
cf_socket_shutdown,
cf_socket_get_host,
cf_socket_adjust_pollset,
cf_socket_data_pending,
cf_socket_send,
cf_socket_recv,
cf_socket_cntrl,
cf_socket_conn_is_alive,
Curl_cf_def_conn_keep_alive,
cf_socket_query,
};
CURLcode Curl_cf_unix_create(struct Curl_cfilter **pcf,
struct Curl_easy *data,
struct connectdata *conn,
const struct Curl_addrinfo *ai,
int transport)
{
struct cf_socket_ctx *ctx = NULL;
struct Curl_cfilter *cf = NULL;
CURLcode result;
(void)data;
(void)conn;
DEBUGASSERT(transport == TRNSPRT_UNIX);
ctx = calloc(1, sizeof(*ctx));
if(!ctx) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
result = cf_socket_ctx_init(ctx, ai, transport);
if(result)
goto out;
result = Curl_cf_create(&cf, &Curl_cft_unix, ctx);
out:
*pcf = (!result) ? cf : NULL;
if(result) {
Curl_safefree(cf);
Curl_safefree(ctx);
}
return result;
}
static timediff_t cf_tcp_accept_timeleft(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct cf_socket_ctx *ctx = cf->ctx;
timediff_t timeout_ms = DEFAULT_ACCEPT_TIMEOUT;
timediff_t other;
struct curltime now;
#ifndef CURL_DISABLE_FTP
if(data->set.accepttimeout > 0)
timeout_ms = data->set.accepttimeout;
#endif
now = curlx_now();
other = Curl_timeleft(data, &now, FALSE);
if(other && (other < timeout_ms))
timeout_ms = other;
else {
timeout_ms -= curlx_timediff(now, ctx->started_at);
if(!timeout_ms)
timeout_ms = -1;
}
return timeout_ms;
}
static void cf_tcp_set_accepted_remote_ip(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct cf_socket_ctx *ctx = cf->ctx;
#ifdef HAVE_GETPEERNAME
char buffer[STRERROR_LEN];
struct Curl_sockaddr_storage ssrem;
curl_socklen_t plen;
ctx->ip.remote_ip[0] = 0;
ctx->ip.remote_port = 0;
plen = sizeof(ssrem);
memset(&ssrem, 0, plen);
if(getpeername(ctx->sock, (struct sockaddr*) &ssrem, &plen)) {
int error = SOCKERRNO;
failf(data, "getpeername() failed with errno %d: %s",
error, Curl_strerror(error, buffer, sizeof(buffer)));
return;
}
if(!Curl_addr2string((struct sockaddr*)&ssrem, plen,
ctx->ip.remote_ip, &ctx->ip.remote_port)) {
failf(data, "ssrem inet_ntop() failed with errno %d: %s",
errno, Curl_strerror(errno, buffer, sizeof(buffer)));
return;
}
#else
ctx->ip.remote_ip[0] = 0;
ctx->ip.remote_port = 0;
(void)data;
#endif
}
static CURLcode cf_tcp_accept_connect(struct Curl_cfilter *cf,
struct Curl_easy *data,
bool *done)
{
struct cf_socket_ctx *ctx = cf->ctx;
#ifdef USE_IPV6
struct Curl_sockaddr_storage add;
#else
struct sockaddr_in add;
#endif
curl_socklen_t size = (curl_socklen_t) sizeof(add);
curl_socket_t s_accepted = CURL_SOCKET_BAD;
timediff_t timeout_ms;
int socketstate = 0;
bool incoming = FALSE;
(void)data;
if(cf->connected) {
*done = TRUE;
return CURLE_OK;
}
timeout_ms = cf_tcp_accept_timeleft(cf, data);
if(timeout_ms < 0) {
failf(data, "Accept timeout occurred while waiting server connect");
return CURLE_FTP_ACCEPT_TIMEOUT;
}
CURL_TRC_CF(data, cf, "Checking for incoming on fd=%" FMT_SOCKET_T
" ip=%s:%d", ctx->sock, ctx->ip.local_ip, ctx->ip.local_port);
socketstate = Curl_socket_check(ctx->sock, CURL_SOCKET_BAD,
CURL_SOCKET_BAD, 0);
CURL_TRC_CF(data, cf, "socket_check -> %x", socketstate);
switch(socketstate) {
case -1:
failf(data, "Error while waiting for server connect");
return CURLE_FTP_ACCEPT_FAILED;
default:
if(socketstate & CURL_CSELECT_IN) {
infof(data, "Ready to accept data connection from server");
incoming = TRUE;
}
break;
}
if(!incoming) {
CURL_TRC_CF(data, cf, "nothing heard from the server yet");
*done = FALSE;
return CURLE_OK;
}
if(0 == getsockname(ctx->sock, (struct sockaddr *) &add, &size)) {
size = sizeof(add);
#ifdef HAVE_ACCEPT4
s_accepted = accept4(ctx->sock, (struct sockaddr *) &add, &size,
SOCK_NONBLOCK | SOCK_CLOEXEC);
#else
s_accepted = accept(ctx->sock, (struct sockaddr *) &add, &size);
#endif
}
if(CURL_SOCKET_BAD == s_accepted) {
failf(data, "Error accept()ing server connect");
return CURLE_FTP_PORT_FAILED;
}
infof(data, "Connection accepted from server");
#ifndef HAVE_ACCEPT4
(void)curlx_nonblock(s_accepted, TRUE);
#endif
ctx->listening = FALSE;
ctx->accepted = TRUE;
socket_close(data, cf->conn, TRUE, ctx->sock);
ctx->sock = s_accepted;
cf->conn->sock[cf->sockindex] = ctx->sock;
cf_tcp_set_accepted_remote_ip(cf, data);
set_local_ip(cf, data);
ctx->active = TRUE;
ctx->connected_at = curlx_now();
cf->connected = TRUE;
CURL_TRC_CF(data, cf, "accepted_set(sock=%" FMT_SOCKET_T
", remote=%s port=%d)",
ctx->sock, ctx->ip.remote_ip, ctx->ip.remote_port);
if(data->set.fsockopt) {
int error = 0;
Curl_set_in_callback(data, true);
error = data->set.fsockopt(data->set.sockopt_client,
ctx->sock, CURLSOCKTYPE_ACCEPT);
Curl_set_in_callback(data, false);
if(error)
return CURLE_ABORTED_BY_CALLBACK;
}
*done = TRUE;
return CURLE_OK;
}
struct Curl_cftype Curl_cft_tcp_accept = {
"TCP-ACCEPT",
CF_TYPE_IP_CONNECT,
CURL_LOG_LVL_NONE,
cf_socket_destroy,
cf_tcp_accept_connect,
cf_socket_close,
cf_socket_shutdown,
cf_socket_get_host,
cf_socket_adjust_pollset,
cf_socket_data_pending,
cf_socket_send,
cf_socket_recv,
cf_socket_cntrl,
cf_socket_conn_is_alive,
Curl_cf_def_conn_keep_alive,
cf_socket_query,
};
CURLcode Curl_conn_tcp_listen_set(struct Curl_easy *data,
struct connectdata *conn,
int sockindex, curl_socket_t *s)
{
CURLcode result;
struct Curl_cfilter *cf = NULL;
struct cf_socket_ctx *ctx = NULL;
Curl_conn_cf_discard_all(data, conn, sockindex);
DEBUGASSERT(conn->sock[sockindex] == CURL_SOCKET_BAD);
ctx = calloc(1, sizeof(*ctx));
if(!ctx) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
ctx->transport = conn->transport;
ctx->sock = *s;
ctx->listening = TRUE;
ctx->accepted = FALSE;
result = Curl_cf_create(&cf, &Curl_cft_tcp_accept, ctx);
if(result)
goto out;
Curl_conn_cf_add(data, conn, sockindex, cf);
ctx->started_at = curlx_now();
conn->sock[sockindex] = ctx->sock;
set_local_ip(cf, data);
CURL_TRC_CF(data, cf, "set filter for listen socket fd=%" FMT_SOCKET_T
" ip=%s:%d", ctx->sock,
ctx->ip.local_ip, ctx->ip.local_port);
out:
if(result) {
Curl_safefree(cf);
Curl_safefree(ctx);
}
return result;
}
bool Curl_conn_is_tcp_listen(struct Curl_easy *data,
int sockindex)
{
struct Curl_cfilter *cf = data->conn->cfilter[sockindex];
while(cf) {
if(cf->cft == &Curl_cft_tcp_accept)
return TRUE;
cf = cf->next;
}
return FALSE;
}
static bool cf_is_socket(struct Curl_cfilter *cf)
{
return cf && (cf->cft == &Curl_cft_tcp ||
cf->cft == &Curl_cft_udp ||
cf->cft == &Curl_cft_unix ||
cf->cft == &Curl_cft_tcp_accept);
}
CURLcode Curl_cf_socket_peek(struct Curl_cfilter *cf,
struct Curl_easy *data,
curl_socket_t *psock,
const struct Curl_sockaddr_ex **paddr,
struct ip_quadruple *pip)
{
(void)data;
if(cf_is_socket(cf) && cf->ctx) {
struct cf_socket_ctx *ctx = cf->ctx;
if(psock)
*psock = ctx->sock;
if(paddr)
*paddr = &ctx->addr;
if(pip)
*pip = ctx->ip;
return CURLE_OK;
}
return CURLE_FAILED_INIT;
}