2023-04-06 15:54:57 +08:00
|
|
|
/***************************************************************************
|
|
|
|
* _ _ ____ _
|
|
|
|
* Project ___| | | | _ \| |
|
|
|
|
* / __| | | | |_) | |
|
|
|
|
* | (__| |_| | _ <| |___
|
|
|
|
* \___|\___/|_| \_\_____|
|
|
|
|
*
|
|
|
|
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
|
|
|
|
*
|
|
|
|
* This software is licensed as described in the file COPYING, which
|
|
|
|
* you should have received as part of this distribution. The terms
|
|
|
|
* are also available at https://curl.se/docs/copyright.html.
|
|
|
|
*
|
|
|
|
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
|
|
|
* copies of the Software, and permit persons to whom the Software is
|
|
|
|
* furnished to do so, under the terms of the COPYING file.
|
|
|
|
*
|
|
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
|
|
* KIND, either express or implied.
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: curl
|
|
|
|
*
|
|
|
|
***************************************************************************/
|
|
|
|
|
|
|
|
#include "curl_setup.h"
|
|
|
|
|
|
|
|
#if !defined(CURL_DISABLE_PROXY)
|
|
|
|
|
|
|
|
#include <curl/curl.h>
|
|
|
|
#include "urldata.h"
|
|
|
|
#include "cfilters.h"
|
|
|
|
#include "cf-haproxy.h"
|
2023-08-03 23:32:25 +08:00
|
|
|
#include "curl_trc.h"
|
2023-04-06 15:54:57 +08:00
|
|
|
#include "multiif.h"
|
|
|
|
|
|
|
|
/* The last 3 #include files should be in this order */
|
|
|
|
#include "curl_printf.h"
|
|
|
|
#include "curl_memory.h"
|
|
|
|
#include "memdebug.h"
|
|
|
|
|
|
|
|
|
|
|
|
typedef enum {
|
|
|
|
HAPROXY_INIT, /* init/default/no tunnel state */
|
|
|
|
HAPROXY_SEND, /* data_out being sent */
|
|
|
|
HAPROXY_DONE /* all work done */
|
|
|
|
} haproxy_state;
|
|
|
|
|
|
|
|
struct cf_haproxy_ctx {
|
|
|
|
int state;
|
|
|
|
struct dynbuf data_out;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void cf_haproxy_ctx_reset(struct cf_haproxy_ctx *ctx)
|
|
|
|
{
|
|
|
|
DEBUGASSERT(ctx);
|
|
|
|
ctx->state = HAPROXY_INIT;
|
|
|
|
Curl_dyn_reset(&ctx->data_out);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void cf_haproxy_ctx_free(struct cf_haproxy_ctx *ctx)
|
|
|
|
{
|
|
|
|
if(ctx) {
|
|
|
|
Curl_dyn_free(&ctx->data_out);
|
|
|
|
free(ctx);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static CURLcode cf_haproxy_date_out_set(struct Curl_cfilter*cf,
|
|
|
|
struct Curl_easy *data)
|
|
|
|
{
|
|
|
|
struct cf_haproxy_ctx *ctx = cf->ctx;
|
|
|
|
CURLcode result;
|
|
|
|
const char *tcp_version;
|
2023-03-16 21:20:11 +08:00
|
|
|
const char *client_ip;
|
2023-04-06 15:54:57 +08:00
|
|
|
|
|
|
|
DEBUGASSERT(ctx);
|
|
|
|
DEBUGASSERT(ctx->state == HAPROXY_INIT);
|
|
|
|
#ifdef USE_UNIX_SOCKETS
|
|
|
|
if(cf->conn->unix_domain_socket)
|
|
|
|
/* the buffer is large enough to hold this! */
|
|
|
|
result = Curl_dyn_addn(&ctx->data_out, STRCONST("PROXY UNKNOWN\r\n"));
|
|
|
|
else {
|
|
|
|
#endif /* USE_UNIX_SOCKETS */
|
|
|
|
/* Emit the correct prefix for IPv6 */
|
|
|
|
tcp_version = cf->conn->bits.ipv6 ? "TCP6" : "TCP4";
|
2023-03-16 21:20:11 +08:00
|
|
|
if(data->set.str[STRING_HAPROXY_CLIENT_IP])
|
|
|
|
client_ip = data->set.str[STRING_HAPROXY_CLIENT_IP];
|
|
|
|
else
|
2023-08-08 19:49:21 +08:00
|
|
|
client_ip = data->info.conn_local_ip;
|
2023-04-06 15:54:57 +08:00
|
|
|
|
|
|
|
result = Curl_dyn_addf(&ctx->data_out, "PROXY %s %s %s %i %i\r\n",
|
|
|
|
tcp_version,
|
2023-03-16 21:20:11 +08:00
|
|
|
client_ip,
|
2023-08-08 19:49:21 +08:00
|
|
|
data->info.conn_primary_ip,
|
2023-04-06 15:54:57 +08:00
|
|
|
data->info.conn_local_port,
|
|
|
|
data->info.conn_primary_port);
|
|
|
|
|
|
|
|
#ifdef USE_UNIX_SOCKETS
|
|
|
|
}
|
|
|
|
#endif /* USE_UNIX_SOCKETS */
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static CURLcode cf_haproxy_connect(struct Curl_cfilter *cf,
|
|
|
|
struct Curl_easy *data,
|
|
|
|
bool blocking, bool *done)
|
|
|
|
{
|
|
|
|
struct cf_haproxy_ctx *ctx = cf->ctx;
|
|
|
|
CURLcode result;
|
|
|
|
size_t len;
|
|
|
|
|
|
|
|
DEBUGASSERT(ctx);
|
|
|
|
if(cf->connected) {
|
|
|
|
*done = TRUE;
|
|
|
|
return CURLE_OK;
|
|
|
|
}
|
|
|
|
|
2023-07-21 01:07:49 +08:00
|
|
|
result = cf->next->cft->do_connect(cf->next, data, blocking, done);
|
2023-04-06 15:54:57 +08:00
|
|
|
if(result || !*done)
|
|
|
|
return result;
|
|
|
|
|
|
|
|
switch(ctx->state) {
|
|
|
|
case HAPROXY_INIT:
|
|
|
|
result = cf_haproxy_date_out_set(cf, data);
|
|
|
|
if(result)
|
|
|
|
goto out;
|
|
|
|
ctx->state = HAPROXY_SEND;
|
2023-12-08 21:05:09 +08:00
|
|
|
FALLTHROUGH();
|
2023-04-06 15:54:57 +08:00
|
|
|
case HAPROXY_SEND:
|
|
|
|
len = Curl_dyn_len(&ctx->data_out);
|
|
|
|
if(len > 0) {
|
|
|
|
ssize_t written = Curl_conn_send(data, cf->sockindex,
|
|
|
|
Curl_dyn_ptr(&ctx->data_out),
|
|
|
|
len, &result);
|
|
|
|
if(written < 0)
|
|
|
|
goto out;
|
|
|
|
Curl_dyn_tail(&ctx->data_out, len - (size_t)written);
|
|
|
|
if(Curl_dyn_len(&ctx->data_out) > 0) {
|
|
|
|
result = CURLE_OK;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ctx->state = HAPROXY_DONE;
|
2023-12-08 21:05:09 +08:00
|
|
|
FALLTHROUGH();
|
2023-04-06 15:54:57 +08:00
|
|
|
default:
|
|
|
|
Curl_dyn_free(&ctx->data_out);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
|
|
|
*done = (!result) && (ctx->state == HAPROXY_DONE);
|
|
|
|
cf->connected = *done;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void cf_haproxy_destroy(struct Curl_cfilter *cf,
|
|
|
|
struct Curl_easy *data)
|
|
|
|
{
|
|
|
|
(void)data;
|
2023-08-03 23:32:25 +08:00
|
|
|
CURL_TRC_CF(data, cf, "destroy");
|
2023-04-06 15:54:57 +08:00
|
|
|
cf_haproxy_ctx_free(cf->ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void cf_haproxy_close(struct Curl_cfilter *cf,
|
|
|
|
struct Curl_easy *data)
|
|
|
|
{
|
2023-08-03 23:32:25 +08:00
|
|
|
CURL_TRC_CF(data, cf, "close");
|
2023-04-06 15:54:57 +08:00
|
|
|
cf->connected = FALSE;
|
|
|
|
cf_haproxy_ctx_reset(cf->ctx);
|
|
|
|
if(cf->next)
|
2023-07-21 01:07:49 +08:00
|
|
|
cf->next->cft->do_close(cf->next, data);
|
2023-04-06 15:54:57 +08:00
|
|
|
}
|
|
|
|
|
lib: introduce struct easy_poll_set for poll information
Connection filter had a `get_select_socks()` method, inspired by the
various `getsocks` functions involved during the lifetime of a
transfer. These, depending on transfer state (CONNECT/DO/DONE/ etc.),
return sockets to monitor and flag if this shall be done for POLLIN
and/or POLLOUT.
Due to this design, sockets and flags could only be added, not
removed. This led to problems in filters like HTTP/2 where flow control
prohibits the sending of data until the peer increases the flow
window. The general transfer loop wants to write, adds POLLOUT, the
socket is writeable but no data can be written.
This leads to cpu busy loops. To prevent that, HTTP/2 did set the
`SEND_HOLD` flag of such a blocked transfer, so the transfer loop cedes
further attempts. This works if only one such filter is involved. If a
HTTP/2 transfer goes through a HTTP/2 proxy, two filters are
setting/clearing this flag and may step on each other's toes.
Connection filters `get_select_socks()` is replaced by
`adjust_pollset()`. They get passed a `struct easy_pollset` that keeps
up to `MAX_SOCKSPEREASYHANDLE` sockets and their `POLLIN|POLLOUT`
flags. This struct is initialized in `multi_getsock()` by calling the
various `getsocks()` implementations based on transfer state, as before.
After protocol handlers/transfer loop have set the sockets and flags
they want, the `easy_pollset` is *always* passed to the filters. Filters
"higher" in the chain are called first, starting at the first
not-yet-connection one. Each filter may add sockets and/or change
flags. When all flags are removed, the socket itself is removed from the
pollset.
Example:
* transfer wants to send, adds POLLOUT
* http/2 filter has a flow control block, removes POLLOUT and adds
POLLIN (it is waiting on a WINDOW_UPDATE from the server)
* TLS filter is connected and changes nothing
* h2-proxy filter also has a flow control block on its tunnel stream,
removes POLLOUT and adds POLLIN also.
* socket filter is connected and changes nothing
* The resulting pollset is then mixed together with all other transfers
and their pollsets, just as before.
Use of `SEND_HOLD` is no longer necessary in the filters.
All filters are adapted for the changed method. The handling in
`multi.c` has been adjusted, but its state handling the the protocol
handlers' `getsocks` method are untouched.
The most affected filters are http/2, ngtcp2, quiche and h2-proxy. TLS
filters needed to be adjusted for the connecting handshake read/write
handling.
No noticeable difference in performance was detected in local scorecard
runs.
Closes #11833
2023-09-04 18:06:07 +08:00
|
|
|
static void cf_haproxy_adjust_pollset(struct Curl_cfilter *cf,
|
|
|
|
struct Curl_easy *data,
|
|
|
|
struct easy_pollset *ps)
|
2023-04-06 15:54:57 +08:00
|
|
|
{
|
lib: introduce struct easy_poll_set for poll information
Connection filter had a `get_select_socks()` method, inspired by the
various `getsocks` functions involved during the lifetime of a
transfer. These, depending on transfer state (CONNECT/DO/DONE/ etc.),
return sockets to monitor and flag if this shall be done for POLLIN
and/or POLLOUT.
Due to this design, sockets and flags could only be added, not
removed. This led to problems in filters like HTTP/2 where flow control
prohibits the sending of data until the peer increases the flow
window. The general transfer loop wants to write, adds POLLOUT, the
socket is writeable but no data can be written.
This leads to cpu busy loops. To prevent that, HTTP/2 did set the
`SEND_HOLD` flag of such a blocked transfer, so the transfer loop cedes
further attempts. This works if only one such filter is involved. If a
HTTP/2 transfer goes through a HTTP/2 proxy, two filters are
setting/clearing this flag and may step on each other's toes.
Connection filters `get_select_socks()` is replaced by
`adjust_pollset()`. They get passed a `struct easy_pollset` that keeps
up to `MAX_SOCKSPEREASYHANDLE` sockets and their `POLLIN|POLLOUT`
flags. This struct is initialized in `multi_getsock()` by calling the
various `getsocks()` implementations based on transfer state, as before.
After protocol handlers/transfer loop have set the sockets and flags
they want, the `easy_pollset` is *always* passed to the filters. Filters
"higher" in the chain are called first, starting at the first
not-yet-connection one. Each filter may add sockets and/or change
flags. When all flags are removed, the socket itself is removed from the
pollset.
Example:
* transfer wants to send, adds POLLOUT
* http/2 filter has a flow control block, removes POLLOUT and adds
POLLIN (it is waiting on a WINDOW_UPDATE from the server)
* TLS filter is connected and changes nothing
* h2-proxy filter also has a flow control block on its tunnel stream,
removes POLLOUT and adds POLLIN also.
* socket filter is connected and changes nothing
* The resulting pollset is then mixed together with all other transfers
and their pollsets, just as before.
Use of `SEND_HOLD` is no longer necessary in the filters.
All filters are adapted for the changed method. The handling in
`multi.c` has been adjusted, but its state handling the the protocol
handlers' `getsocks` method are untouched.
The most affected filters are http/2, ngtcp2, quiche and h2-proxy. TLS
filters needed to be adjusted for the connecting handshake read/write
handling.
No noticeable difference in performance was detected in local scorecard
runs.
Closes #11833
2023-09-04 18:06:07 +08:00
|
|
|
if(cf->next->connected && !cf->connected) {
|
2023-04-06 15:54:57 +08:00
|
|
|
/* If we are not connected, but the filter "below" is
|
|
|
|
* and not waiting on something, we are sending. */
|
lib: introduce struct easy_poll_set for poll information
Connection filter had a `get_select_socks()` method, inspired by the
various `getsocks` functions involved during the lifetime of a
transfer. These, depending on transfer state (CONNECT/DO/DONE/ etc.),
return sockets to monitor and flag if this shall be done for POLLIN
and/or POLLOUT.
Due to this design, sockets and flags could only be added, not
removed. This led to problems in filters like HTTP/2 where flow control
prohibits the sending of data until the peer increases the flow
window. The general transfer loop wants to write, adds POLLOUT, the
socket is writeable but no data can be written.
This leads to cpu busy loops. To prevent that, HTTP/2 did set the
`SEND_HOLD` flag of such a blocked transfer, so the transfer loop cedes
further attempts. This works if only one such filter is involved. If a
HTTP/2 transfer goes through a HTTP/2 proxy, two filters are
setting/clearing this flag and may step on each other's toes.
Connection filters `get_select_socks()` is replaced by
`adjust_pollset()`. They get passed a `struct easy_pollset` that keeps
up to `MAX_SOCKSPEREASYHANDLE` sockets and their `POLLIN|POLLOUT`
flags. This struct is initialized in `multi_getsock()` by calling the
various `getsocks()` implementations based on transfer state, as before.
After protocol handlers/transfer loop have set the sockets and flags
they want, the `easy_pollset` is *always* passed to the filters. Filters
"higher" in the chain are called first, starting at the first
not-yet-connection one. Each filter may add sockets and/or change
flags. When all flags are removed, the socket itself is removed from the
pollset.
Example:
* transfer wants to send, adds POLLOUT
* http/2 filter has a flow control block, removes POLLOUT and adds
POLLIN (it is waiting on a WINDOW_UPDATE from the server)
* TLS filter is connected and changes nothing
* h2-proxy filter also has a flow control block on its tunnel stream,
removes POLLOUT and adds POLLIN also.
* socket filter is connected and changes nothing
* The resulting pollset is then mixed together with all other transfers
and their pollsets, just as before.
Use of `SEND_HOLD` is no longer necessary in the filters.
All filters are adapted for the changed method. The handling in
`multi.c` has been adjusted, but its state handling the the protocol
handlers' `getsocks` method are untouched.
The most affected filters are http/2, ngtcp2, quiche and h2-proxy. TLS
filters needed to be adjusted for the connecting handshake read/write
handling.
No noticeable difference in performance was detected in local scorecard
runs.
Closes #11833
2023-09-04 18:06:07 +08:00
|
|
|
Curl_pollset_set_out_only(data, ps, Curl_conn_cf_get_socket(cf, data));
|
2023-04-06 15:54:57 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct Curl_cftype Curl_cft_haproxy = {
|
|
|
|
"HAPROXY",
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
cf_haproxy_destroy,
|
|
|
|
cf_haproxy_connect,
|
|
|
|
cf_haproxy_close,
|
|
|
|
Curl_cf_def_get_host,
|
lib: introduce struct easy_poll_set for poll information
Connection filter had a `get_select_socks()` method, inspired by the
various `getsocks` functions involved during the lifetime of a
transfer. These, depending on transfer state (CONNECT/DO/DONE/ etc.),
return sockets to monitor and flag if this shall be done for POLLIN
and/or POLLOUT.
Due to this design, sockets and flags could only be added, not
removed. This led to problems in filters like HTTP/2 where flow control
prohibits the sending of data until the peer increases the flow
window. The general transfer loop wants to write, adds POLLOUT, the
socket is writeable but no data can be written.
This leads to cpu busy loops. To prevent that, HTTP/2 did set the
`SEND_HOLD` flag of such a blocked transfer, so the transfer loop cedes
further attempts. This works if only one such filter is involved. If a
HTTP/2 transfer goes through a HTTP/2 proxy, two filters are
setting/clearing this flag and may step on each other's toes.
Connection filters `get_select_socks()` is replaced by
`adjust_pollset()`. They get passed a `struct easy_pollset` that keeps
up to `MAX_SOCKSPEREASYHANDLE` sockets and their `POLLIN|POLLOUT`
flags. This struct is initialized in `multi_getsock()` by calling the
various `getsocks()` implementations based on transfer state, as before.
After protocol handlers/transfer loop have set the sockets and flags
they want, the `easy_pollset` is *always* passed to the filters. Filters
"higher" in the chain are called first, starting at the first
not-yet-connection one. Each filter may add sockets and/or change
flags. When all flags are removed, the socket itself is removed from the
pollset.
Example:
* transfer wants to send, adds POLLOUT
* http/2 filter has a flow control block, removes POLLOUT and adds
POLLIN (it is waiting on a WINDOW_UPDATE from the server)
* TLS filter is connected and changes nothing
* h2-proxy filter also has a flow control block on its tunnel stream,
removes POLLOUT and adds POLLIN also.
* socket filter is connected and changes nothing
* The resulting pollset is then mixed together with all other transfers
and their pollsets, just as before.
Use of `SEND_HOLD` is no longer necessary in the filters.
All filters are adapted for the changed method. The handling in
`multi.c` has been adjusted, but its state handling the the protocol
handlers' `getsocks` method are untouched.
The most affected filters are http/2, ngtcp2, quiche and h2-proxy. TLS
filters needed to be adjusted for the connecting handshake read/write
handling.
No noticeable difference in performance was detected in local scorecard
runs.
Closes #11833
2023-09-04 18:06:07 +08:00
|
|
|
cf_haproxy_adjust_pollset,
|
2023-04-06 15:54:57 +08:00
|
|
|
Curl_cf_def_data_pending,
|
|
|
|
Curl_cf_def_send,
|
|
|
|
Curl_cf_def_recv,
|
|
|
|
Curl_cf_def_cntrl,
|
|
|
|
Curl_cf_def_conn_is_alive,
|
|
|
|
Curl_cf_def_conn_keep_alive,
|
|
|
|
Curl_cf_def_query,
|
|
|
|
};
|
|
|
|
|
|
|
|
static CURLcode cf_haproxy_create(struct Curl_cfilter **pcf,
|
|
|
|
struct Curl_easy *data)
|
|
|
|
{
|
|
|
|
struct Curl_cfilter *cf = NULL;
|
|
|
|
struct cf_haproxy_ctx *ctx;
|
|
|
|
CURLcode result;
|
|
|
|
|
|
|
|
(void)data;
|
2023-11-08 07:22:58 +08:00
|
|
|
ctx = calloc(1, sizeof(*ctx));
|
2023-04-06 15:54:57 +08:00
|
|
|
if(!ctx) {
|
|
|
|
result = CURLE_OUT_OF_MEMORY;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
ctx->state = HAPROXY_INIT;
|
|
|
|
Curl_dyn_init(&ctx->data_out, DYN_HAXPROXY);
|
|
|
|
|
|
|
|
result = Curl_cf_create(&cf, &Curl_cft_haproxy, ctx);
|
|
|
|
if(result)
|
|
|
|
goto out;
|
|
|
|
ctx = NULL;
|
|
|
|
|
|
|
|
out:
|
|
|
|
cf_haproxy_ctx_free(ctx);
|
|
|
|
*pcf = result? NULL : cf;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
CURLcode Curl_cf_haproxy_insert_after(struct Curl_cfilter *cf_at,
|
|
|
|
struct Curl_easy *data)
|
|
|
|
{
|
|
|
|
struct Curl_cfilter *cf;
|
|
|
|
CURLcode result;
|
|
|
|
|
|
|
|
result = cf_haproxy_create(&cf, data);
|
|
|
|
if(result)
|
|
|
|
goto out;
|
|
|
|
Curl_conn_cf_insert_after(cf_at, cf);
|
|
|
|
|
|
|
|
out:
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* !CURL_DISABLE_PROXY */
|