mirror of
https://github.com/curl/curl.git
synced 2026-04-18 07:51:41 +03:00
When a user supplies an IP address to use for the HAPROXY protocol, the IP version reported must be deduced from the address and has no relation to the IP version used for the upstream connection. Add test3220 to verify. Fixes #21340 Reported-by: Fiona Klute Closes #21341
258 lines
6.9 KiB
C
258 lines
6.9 KiB
C
/***************************************************************************
|
|
* _ _ ____ _
|
|
* 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"
|
|
|
|
#ifndef CURL_DISABLE_PROXY
|
|
|
|
#include "urldata.h"
|
|
#include "cfilters.h"
|
|
#include "cf-haproxy.h"
|
|
#include "curl_addrinfo.h"
|
|
#include "curl_trc.h"
|
|
#include "select.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;
|
|
curlx_dyn_reset(&ctx->data_out);
|
|
}
|
|
|
|
static void cf_haproxy_ctx_free(struct cf_haproxy_ctx *ctx)
|
|
{
|
|
if(ctx) {
|
|
curlx_dyn_free(&ctx->data_out);
|
|
curlx_free(ctx);
|
|
}
|
|
}
|
|
|
|
static CURLcode cf_haproxy_date_out_set(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data)
|
|
{
|
|
/* We fake a client connection report to the upstream server
|
|
* with the HAProxy protocol, reporting the client's source
|
|
* and destination IP addresses and ports.
|
|
* addresses: either the ones used to talk to the upstream
|
|
* OR the value supplied by the user
|
|
* ports: the ports used in the upstream connection */
|
|
const char *client_source_ip;
|
|
const char *client_dest_ip;
|
|
struct cf_haproxy_ctx *ctx = cf->ctx;
|
|
CURLcode result;
|
|
struct ip_quadruple ipquad;
|
|
bool is_ipv6;
|
|
|
|
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 = curlx_dyn_addn(&ctx->data_out, STRCONST("PROXY UNKNOWN\r\n"));
|
|
else {
|
|
#endif /* USE_UNIX_SOCKETS */
|
|
result = Curl_conn_cf_get_ip_info(cf->next, data, &is_ipv6, &ipquad);
|
|
if(result)
|
|
return result;
|
|
|
|
if(data->set.str[STRING_HAPROXY_CLIENT_IP]) {
|
|
client_source_ip = data->set.str[STRING_HAPROXY_CLIENT_IP];
|
|
client_dest_ip = client_source_ip;
|
|
is_ipv6 = !Curl_is_ipv4addr(client_source_ip);
|
|
}
|
|
else {
|
|
client_source_ip = ipquad.local_ip;
|
|
client_dest_ip = ipquad.remote_ip;
|
|
}
|
|
|
|
result = curlx_dyn_addf(&ctx->data_out, "PROXY %s %s %s %i %i\r\n",
|
|
is_ipv6 ? "TCP6" : "TCP4",
|
|
client_source_ip, client_dest_ip,
|
|
ipquad.local_port, ipquad.remote_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 *done)
|
|
{
|
|
struct cf_haproxy_ctx *ctx = cf->ctx;
|
|
CURLcode result;
|
|
size_t len;
|
|
|
|
DEBUGASSERT(ctx);
|
|
if(cf->connected) {
|
|
*done = TRUE;
|
|
return CURLE_OK;
|
|
}
|
|
|
|
result = cf->next->cft->do_connect(cf->next, data, done);
|
|
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;
|
|
FALLTHROUGH();
|
|
case HAPROXY_SEND:
|
|
len = curlx_dyn_len(&ctx->data_out);
|
|
if(len > 0) {
|
|
size_t nwritten;
|
|
result = Curl_conn_cf_send(cf->next, data,
|
|
curlx_dyn_uptr(&ctx->data_out), len, FALSE,
|
|
&nwritten);
|
|
if(result) {
|
|
if(result != CURLE_AGAIN)
|
|
goto out;
|
|
result = CURLE_OK;
|
|
nwritten = 0;
|
|
}
|
|
curlx_dyn_tail(&ctx->data_out, len - nwritten);
|
|
if(curlx_dyn_len(&ctx->data_out) > 0) {
|
|
result = CURLE_OK;
|
|
goto out;
|
|
}
|
|
}
|
|
ctx->state = HAPROXY_DONE;
|
|
FALLTHROUGH();
|
|
default:
|
|
curlx_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)
|
|
{
|
|
CURL_TRC_CF(data, cf, "destroy");
|
|
cf_haproxy_ctx_free(cf->ctx);
|
|
}
|
|
|
|
static void cf_haproxy_close(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data)
|
|
{
|
|
CURL_TRC_CF(data, cf, "close");
|
|
cf->connected = FALSE;
|
|
cf_haproxy_ctx_reset(cf->ctx);
|
|
if(cf->next)
|
|
cf->next->cft->do_close(cf->next, data);
|
|
}
|
|
|
|
static CURLcode cf_haproxy_adjust_pollset(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
struct easy_pollset *ps)
|
|
{
|
|
if(cf->next->connected && !cf->connected) {
|
|
/* If we are not connected, but the filter "below" is
|
|
* and not waiting on something, we are sending. */
|
|
return Curl_pollset_set_out_only(
|
|
data, ps, Curl_conn_cf_get_socket(cf, data));
|
|
}
|
|
return CURLE_OK;
|
|
}
|
|
|
|
struct Curl_cftype Curl_cft_haproxy = {
|
|
"HAPROXY",
|
|
CF_TYPE_PROXY | CF_TYPE_SETUP,
|
|
0,
|
|
cf_haproxy_destroy,
|
|
cf_haproxy_connect,
|
|
cf_haproxy_close,
|
|
Curl_cf_def_shutdown,
|
|
cf_haproxy_adjust_pollset,
|
|
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;
|
|
ctx = curlx_calloc(1, sizeof(*ctx));
|
|
if(!ctx) {
|
|
result = CURLE_OUT_OF_MEMORY;
|
|
goto out;
|
|
}
|
|
ctx->state = HAPROXY_INIT;
|
|
curlx_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 */
|