lib: connection filters (cfilter) addition to curl:

- general construct/destroy in connectdata
 - default implementations of callback functions
 - connect: cfilters for connect and accept
 - socks: cfilter for socks proxying
 - http_proxy: cfilter for http proxy tunneling
 - vtls: cfilters for primary and proxy ssl
 - change in general handling of data/conn
 - Curl_cfilter_setup() sets up filter chain based on data settings,
   if none are installed by the protocol handler setup
 - Curl_cfilter_connect() boot straps filters into `connected` status,
   used by handlers and multi to reach further stages
 - Curl_cfilter_is_connected() to check if a conn is connected,
   e.g. all filters have done their work
 - Curl_cfilter_get_select_socks() gets the sockets and READ/WRITE
   indicators for multi select to work
 - Curl_cfilter_data_pending() asks filters if the have incoming
   data pending for recv
 - Curl_cfilter_recv()/Curl_cfilter_send are the general callbacks
   installed in conn->recv/conn->send for io handling
 - Curl_cfilter_attach_data()/Curl_cfilter_detach_data() inform filters
   and addition/removal of a `data` from their connection
 - adding vtl functions to prevent use of Curl_ssl globals directly
   in other parts of the code.

Reviewed-by: Daniel Stenberg
Closes #9855
This commit is contained in:
Stefan Eissing 2022-11-11 11:45:34 +01:00 committed by Daniel Stenberg
parent 89ee5cfb38
commit dafdb20a26
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
49 changed files with 3325 additions and 1969 deletions

View file

@ -64,6 +64,7 @@
#include "sendf.h"
#include "if2ip.h"
#include "strerror.h"
#include "cfilters.h"
#include "connect.h"
#include "select.h"
#include "url.h" /* for Curl_safefree() */
@ -79,7 +80,6 @@
#include "share.h"
#include "version_win32.h"
#include "quic.h"
#include "socks.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
@ -237,10 +237,9 @@ timediff_t Curl_timeleft(struct Curl_easy *data,
return timeout_ms;
}
static CURLcode bindlocal(struct Curl_easy *data,
static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
curl_socket_t sockfd, int af, unsigned int scope)
{
struct connectdata *conn = data->conn;
struct Curl_sockaddr_storage sa;
struct sockaddr *sock = (struct sockaddr *)&sa; /* bind to this address */
curl_socklen_t sizeof_sa = 0; /* size of the data sock points to */
@ -776,95 +775,25 @@ void Curl_updateconninfo(struct Curl_easy *data, struct connectdata *conn,
Curl_persistconninfo(data, conn, local_ip, local_port);
}
/* After a TCP connection to the proxy has been verified, this function does
the next magic steps. If 'done' isn't set TRUE, it is not done yet and
must be called again.
Note: this function's sub-functions call failf()
*/
static CURLcode connect_SOCKS(struct Curl_easy *data, int sockindex,
bool *done)
{
CURLcode result = CURLE_OK;
#ifndef CURL_DISABLE_PROXY
CURLproxycode pxresult = CURLPX_OK;
struct connectdata *conn = data->conn;
if(conn->bits.socksproxy) {
/* for the secondary socket (FTP), use the "connect to host"
* but ignore the "connect to port" (use the secondary port)
*/
const char * const host =
conn->bits.httpproxy ?
conn->http_proxy.host.name :
conn->bits.conn_to_host ?
conn->conn_to_host.name :
sockindex == SECONDARYSOCKET ?
conn->secondaryhostname : conn->host.name;
const int port =
conn->bits.httpproxy ? (int)conn->http_proxy.port :
sockindex == SECONDARYSOCKET ? conn->secondary_port :
conn->bits.conn_to_port ? conn->conn_to_port :
conn->remote_port;
switch(conn->socks_proxy.proxytype) {
case CURLPROXY_SOCKS5:
case CURLPROXY_SOCKS5_HOSTNAME:
pxresult = Curl_SOCKS5(conn->socks_proxy.user, conn->socks_proxy.passwd,
host, port, sockindex, data, done);
break;
case CURLPROXY_SOCKS4:
case CURLPROXY_SOCKS4A:
pxresult = Curl_SOCKS4(conn->socks_proxy.user, host, port, sockindex,
data, done);
break;
default:
failf(data, "unknown proxytype option given");
result = CURLE_COULDNT_CONNECT;
} /* switch proxytype */
if(pxresult) {
result = CURLE_PROXY;
data->info.pxcode = pxresult;
}
}
else
#else
(void)data;
(void)sockindex;
#endif /* CURL_DISABLE_PROXY */
*done = TRUE; /* no SOCKS proxy, so consider us connected */
return result;
}
/*
* post_SOCKS() is called after a successful connect to the peer, which
* *could* be a SOCKS proxy
* post_connect() is called after a successful connect to the peer
*/
static void post_SOCKS(struct Curl_easy *data,
static void post_connect(struct Curl_easy *data,
struct connectdata *conn,
int sockindex,
bool *connected)
int sockindex)
{
conn->bits.tcpconnect[sockindex] = TRUE;
*connected = TRUE;
if(sockindex == FIRSTSOCKET)
Curl_pgrsTime(data, TIMER_CONNECT); /* connect done */
Curl_updateconninfo(data, conn, conn->sock[sockindex]);
Curl_verboseconnect(data, conn);
data->info.numconnects++; /* to track the number of connections made */
}
/*
* Curl_is_connected() checks if the socket has connected.
* is_connected() checks if the socket has connected.
*/
CURLcode Curl_is_connected(struct Curl_easy *data,
struct connectdata *conn,
int sockindex,
bool *connected)
static CURLcode is_connected(struct Curl_easy *data,
struct connectdata *conn,
int sockindex,
bool *connected)
{
CURLcode result = CURLE_OK;
timediff_t allow;
@ -877,22 +806,14 @@ CURLcode Curl_is_connected(struct Curl_easy *data,
*connected = FALSE; /* a very negative world view is best */
if(conn->bits.tcpconnect[sockindex]) {
/* we are connected already! */
*connected = TRUE;
return CURLE_OK;
}
now = Curl_now();
if(SOCKS_STATE(conn->cnnct.state)) {
/* still doing SOCKS */
result = connect_SOCKS(data, sockindex, connected);
if(!result && *connected)
post_SOCKS(data, conn, sockindex, connected);
return result;
}
/* Check if any of the conn->tempsock we use for establishing connections
* succeeded and, if so, close any ongoing other ones.
* Transfer the successful conn->tempsock to conn->sock[sockindex]
* and set conn->tempsock to CURL_SOCKET_BAD.
* If transport is QUIC, we need to shutdown the ongoing 'other'
* connect attempts in a QUIC appropriate way. */
for(i = 0; i<2; i++) {
const int other = i ^ 1;
if(conn->tempsock[i] == CURL_SOCKET_BAD)
@ -906,7 +827,7 @@ CURLcode Curl_is_connected(struct Curl_easy *data,
conn->sock[sockindex] = conn->tempsock[i];
conn->ip_addr = conn->tempaddr[i];
conn->tempsock[i] = CURL_SOCKET_BAD;
post_SOCKS(data, conn, sockindex, connected);
post_connect(data, conn, sockindex);
connkeep(conn, "HTTP/3 default");
if(conn->tempsock[other] != CURL_SOCKET_BAD)
Curl_quic_disconnect(data, conn, other);
@ -968,14 +889,7 @@ CURLcode Curl_is_connected(struct Curl_easy *data,
conn->tempsock[other] = CURL_SOCKET_BAD;
}
/* see if we need to kick off any SOCKS proxy magic once we
connected */
result = connect_SOCKS(data, sockindex, connected);
if(result || !*connected)
return result;
post_SOCKS(data, conn, sockindex, connected);
*connected = TRUE;
return CURLE_OK;
}
}
@ -1266,7 +1180,7 @@ static CURLcode singleipconnect(struct Curl_easy *data,
|| addr.family == AF_INET6
#endif
) {
result = bindlocal(data, sockfd, addr.family,
result = bindlocal(data, conn, sockfd, addr.family,
Curl_ipv6_scope(&addr.sa_addr));
if(result) {
Curl_closesocket(data, conn, sockfd); /* close socket and bail out */
@ -1529,7 +1443,7 @@ curl_socket_t Curl_getconnectinfo(struct Curl_easy *data,
bool Curl_connalive(struct connectdata *conn)
{
/* First determine if ssl */
if(conn->ssl[FIRSTSOCKET].use) {
if(Curl_ssl_use(conn, FIRSTSOCKET)) {
/* use the SSL context */
if(!Curl_ssl_check_cxn(conn))
return false; /* FIN received */
@ -1558,6 +1472,8 @@ bool Curl_connalive(struct connectdata *conn)
int Curl_closesocket(struct Curl_easy *data, struct connectdata *conn,
curl_socket_t sock)
{
DEBUGF(infof(data, "Curl_closesocket(conn #%ld, sock=%d), fclosesocket=%d",
conn->connection_id, (int)sock, conn->fclosesocket));
if(conn && conn->fclosesocket) {
if((sock == conn->sock[SECONDARYSOCKET]) && conn->bits.sock_accepted)
/* if this socket matches the second socket, and that was created with
@ -1720,15 +1636,313 @@ void Curl_conncontrol(struct connectdata *conn,
}
/* Data received can be cached at various levels, so check them all here. */
bool Curl_conn_data_pending(struct connectdata *conn, int sockindex)
bool Curl_conn_data_pending(struct Curl_easy *data, int sockindex)
{
return Curl_recv_has_postponed_data(data->conn, sockindex)
||Curl_cfilter_data_pending(data, data->conn, sockindex);
}
typedef enum {
SCFST_INIT,
SCFST_WAITING,
SCFST_DONE
} cf_connect_state;
struct socket_cf_ctx {
const struct Curl_dns_entry *remotehost;
cf_connect_state state;
};
static int socket_cf_get_select_socks(struct Curl_cfilter *cf,
struct Curl_easy *data,
curl_socket_t *socks)
{
struct connectdata *conn = cf->conn;
int i, s, rc = GETSOCK_BLANK;
(void)data;
if(cf->connected) {
return rc;
}
for(i = s = 0; i<2; i++) {
if(conn->tempsock[i] != CURL_SOCKET_BAD) {
socks[s] = conn->tempsock[i];
rc |= GETSOCK_WRITESOCK(s);
#ifdef ENABLE_QUIC
if(conn->transport == TRNSPRT_QUIC)
/* when connecting QUIC, we want to read the socket too */
rc |= GETSOCK_READSOCK(s);
#endif
s++;
}
}
return rc;
}
static CURLcode socket_cf_connect(struct Curl_cfilter *cf,
struct Curl_easy *data,
bool blocking, bool *done)
{
struct connectdata *conn = cf->conn;
int sockindex = cf->sockindex;
struct socket_cf_ctx *ctx = cf->ctx;
CURLcode result = CURLE_OK;
if(cf->connected) {
*done = TRUE;
return CURLE_OK;
}
(void)blocking;
DEBUGASSERT(ctx);
*done = FALSE;
switch(ctx->state) {
case SCFST_INIT:
DEBUGASSERT(CURL_SOCKET_BAD == conn->sock[sockindex]);
DEBUGASSERT(!cf->connected);
result = Curl_connecthost(data, conn, ctx->remotehost);
if(!result)
ctx->state = SCFST_WAITING;
DEBUGF(infof(data, "socket_cf_connect(handle=%p, index=%d) -> "
"connecthost() -> %d, done=%d",
data, sockindex, result, *done));
break;
case SCFST_WAITING:
result = is_connected(data, conn, sockindex, done);
if(!result && *done) {
Curl_pgrsTime(data, TIMER_CONNECT); /* we're connected already */
if(Curl_ssl_use(conn, FIRSTSOCKET) ||
(conn->handler->protocol & PROTO_FAMILY_SSH))
Curl_pgrsTime(data, TIMER_APPCONNECT); /* we're connected already */
post_connect(data, conn, sockindex);
ctx->state = SCFST_DONE;
cf->connected = TRUE;
}
DEBUGF(infof(data, "socket_cf_connect(handle=%p, index=%d) -> "
"is_connected() -> %d, done=%d",
data, sockindex, result, *done));
break;
case SCFST_DONE:
*done = TRUE;
DEBUGF(infof(data, "socket_cf_connect(handle=%p, index=%d) -> "
"already connected-> %d, done=%d",
data, sockindex, result, *done));
break;
}
return result;
}
static CURLcode socket_cf_setup(struct Curl_cfilter *cf,
struct Curl_easy *data,
const struct Curl_dns_entry *remotehost)
{
struct socket_cf_ctx *ctx = cf->ctx;
bool done;
DEBUGASSERT(ctx);
if(ctx->remotehost != remotehost) {
if(ctx->remotehost) {
/* switching dns entry? TODO: reset? */
}
ctx->remotehost = remotehost;
}
/* we start connecting right on setup */
DEBUGF(infof(data, "socket_cf_setup(handle=%p, remotehost=%s)",
data, cf->conn->hostname_resolve));
return socket_cf_connect(cf, data, FALSE, &done);
}
static void socket_cf_close(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
int sockindex = cf->sockindex;
struct socket_cf_ctx *ctx = cf->ctx;
DEBUGASSERT(ctx);
/* close possibly still open sockets */
if(CURL_SOCKET_BAD != cf->conn->sock[sockindex]) {
Curl_closesocket(data, cf->conn, cf->conn->sock[sockindex]);
cf->conn->sock[sockindex] = CURL_SOCKET_BAD;
}
if(CURL_SOCKET_BAD != cf->conn->tempsock[sockindex]) {
Curl_closesocket(data, cf->conn, cf->conn->tempsock[sockindex]);
cf->conn->tempsock[sockindex] = CURL_SOCKET_BAD;
}
cf->connected = FALSE;
ctx->state = SCFST_INIT;
}
static bool socket_cf_data_pending(struct Curl_cfilter *cf,
const struct Curl_easy *data)
{
int readable;
DEBUGASSERT(conn);
(void)data;
DEBUGASSERT(cf);
if(Curl_ssl_data_pending(conn, sockindex) ||
Curl_recv_has_postponed_data(conn, sockindex))
return true;
readable = SOCKET_READABLE(conn->sock[sockindex], 0);
readable = SOCKET_READABLE(cf->conn->sock[cf->sockindex], 0);
return (readable > 0 && (readable & CURL_CSELECT_IN));
}
static ssize_t socket_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data,
const void *buf, size_t len, CURLcode *err)
{
ssize_t nwritten;
nwritten = Curl_send_plain(data, cf->sockindex, buf, len, err);
/* DEBUGF(infof(data, "socket_cf_send(handle=%p, index=%d, len=%ld)"
"-> %ld, code=%d", data, cf->sockindex, len, nwritten, *err));*/
return nwritten;
}
static ssize_t socket_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
char *buf, size_t len, CURLcode *err)
{
ssize_t nread;
nread = Curl_recv_plain(data, cf->sockindex, buf, len, err);
/* DEBUGF(infof(data, "socket_cf_recv(handle=%p, index=%d) -> %ld",
data, cf->sockindex, nread));*/
return nread;
}
static void socket_cf_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
{
struct socket_cf_ctx *state = cf->ctx;
(void)data;
DEBUGF(infof(data, "socket_cf_destroy(conn #%ld, index=%d",
cf->conn->connection_id, cf->sockindex));
if(cf->connected) {
socket_cf_close(cf, data);
}
/* release any resources held in state */
Curl_safefree(state);
}
static const struct Curl_cftype cft_socket = {
"SOCKET",
socket_cf_destroy,
Curl_cf_def_attach_data,
Curl_cf_def_detach_data,
socket_cf_setup,
socket_cf_close,
socket_cf_connect,
socket_cf_get_select_socks,
socket_cf_data_pending,
socket_cf_send,
socket_cf_recv,
};
CURLcode Curl_cfilter_socket_set(struct Curl_easy *data,
struct connectdata *conn,
int sockindex)
{
CURLcode result;
struct Curl_cfilter *cf = NULL;
struct socket_cf_ctx *scf_ctx = NULL;
/* Need to be first */
DEBUGASSERT(!conn->cfilter[sockindex]);
scf_ctx = calloc(sizeof(*scf_ctx), 1);
if(!scf_ctx) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
result = Curl_cfilter_create(&cf, data, conn, sockindex,
&cft_socket, scf_ctx);
if(result)
goto out;
Curl_cfilter_add(data, conn, sockindex, cf);
out:
if(result) {
Curl_safefree(cf);
Curl_safefree(scf_ctx);
}
return result;
}
static CURLcode socket_accept_cf_connect(struct Curl_cfilter *cf,
struct Curl_easy *data,
bool blocking, bool *done)
{
/* we start accepted, if we ever close, we cannot go on */
(void)data;
(void)blocking;
if(cf->connected) {
*done = TRUE;
return CURLE_OK;
}
return CURLE_FAILED_INIT;
}
static CURLcode socket_accept_cf_setup(struct Curl_cfilter *cf,
struct Curl_easy *data,
const struct Curl_dns_entry *remotehost)
{
/* we start accepted, if we ever close, we cannot go on */
(void)data;
(void)remotehost;
if(cf->connected) {
return CURLE_OK;
}
return CURLE_FAILED_INIT;
}
static const struct Curl_cftype cft_socket_accept = {
"SOCKET-ACCEPT",
socket_cf_destroy,
Curl_cf_def_attach_data,
Curl_cf_def_detach_data,
socket_accept_cf_setup,
socket_cf_close,
socket_accept_cf_connect,
Curl_cf_def_get_select_socks,
socket_cf_data_pending,
socket_cf_send,
socket_cf_recv,
};
CURLcode Curl_cfilter_socket_accepted_set(struct Curl_easy *data,
struct connectdata *conn,
int sockindex, curl_socket_t *s)
{
CURLcode result;
struct Curl_cfilter *cf = NULL;
struct socket_cf_ctx *scf_ctx = NULL;
cf = conn->cfilter[sockindex];
if(cf && cf->cft == &cft_socket_accept) {
/* already an accept filter installed, just replace the socket */
scf_ctx = cf->ctx;
result = CURLE_OK;
}
else {
/* replace any existing */
Curl_cfilter_destroy(data, conn, sockindex);
scf_ctx = calloc(sizeof(*scf_ctx), 1);
if(!scf_ctx) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
result = Curl_cfilter_create(&cf, data, conn, sockindex,
&cft_socket_accept, scf_ctx);
if(result)
goto out;
Curl_cfilter_add(data, conn, sockindex, cf);
}
/* close any existing socket and replace */
Curl_closesocket(data, conn, conn->sock[sockindex]);
conn->sock[sockindex] = *s;
conn->bits.sock_accepted = TRUE;
cf->connected = TRUE;
scf_ctx->state = SCFST_DONE;
out:
if(result) {
Curl_safefree(cf);
Curl_safefree(scf_ctx);
}
return result;
}