From: Raito Bezarius Date: Thu, 16 Mar 2023 13:20:11 +0000 (+0100) Subject: haproxy: add --haproxy-clientip flag to spoof client IPs X-Git-Tag: curl-8_2_0~154 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=0a75964d0d94a4;p=thirdparty%2Fcurl.git haproxy: add --haproxy-clientip flag to spoof client IPs CURLOPT_HAPROXY_CLIENT_IP in the library Closes #10779 --- diff --git a/docs/cmdline-opts/Makefile.inc b/docs/cmdline-opts/Makefile.inc index 149db5878a..10cc14b03a 100644 --- a/docs/cmdline-opts/Makefile.inc +++ b/docs/cmdline-opts/Makefile.inc @@ -96,6 +96,7 @@ DPAGES = \ globoff.d \ happy-eyeballs-timeout-ms.d \ haproxy-protocol.d \ + haproxy-clientip.d \ head.d \ header.d \ help.d \ diff --git a/docs/cmdline-opts/haproxy-clientip.d b/docs/cmdline-opts/haproxy-clientip.d new file mode 100644 index 0000000000..22eda0796d --- /dev/null +++ b/docs/cmdline-opts/haproxy-clientip.d @@ -0,0 +1,29 @@ +c: Copyright (C) Daniel Stenberg, , et al. +SPDX-License-Identifier: curl +Long: haproxy-clientip +Help: Sets client IP in HAProxy PROXY protocol v1 header +Protocols: HTTP +Added: 8.2.0 +Category: http proxy +Example: --haproxy-clientip $IP +See-also: proxy +Multi: single +--- +Sets a client IP in HAProxy PROXY protocol v1 header at the beginning of the +connection. + +For valid requests, IPv4 addresses must be indicated as a series of exactly +4 integers in the range [0..255] inclusive written in decimal representation +separated by exactly one dot between each other. Heading zeroes are not +permitted in front of numbers in order to avoid any possible confusion +with octal numbers. IPv6 addresses must be indicated as series of 4 hexadecimal +digits (upper or lower case) delimited by colons between each other, with the +acceptance of one double colon sequence to replace the largest acceptable range +of consecutive zeroes. The total number of decoded bits must exactly be 128. + +Otherwise, any string can be accepted for the client IP and will be sent. + +It replaces `--haproxy-protocol` if used, it is not necessary to specify both flags. + +This option is primarily useful when sending test requests to +verify a service is working as intended. diff --git a/docs/libcurl/curl_easy_setopt.3 b/docs/libcurl/curl_easy_setopt.3 index b7257c425c..784495d6ec 100644 --- a/docs/libcurl/curl_easy_setopt.3 +++ b/docs/libcurl/curl_easy_setopt.3 @@ -208,6 +208,8 @@ Socks5 GSSAPI NEC mode. See \fICURLOPT_SOCKS5_GSSAPI_NEC(3)\fP Proxy authentication service name. \fICURLOPT_PROXY_SERVICE_NAME(3)\fP .IP CURLOPT_HAPROXYPROTOCOL Send an HAProxy PROXY protocol v1 header. See \fICURLOPT_HAPROXYPROTOCOL(3)\fP +.IP CURLOPT_HAPROXY_CLIENT_IP +Spoof the client IP in an HAProxy PROXY protocol v1 header. See \fICURLOPT_HAPROXY_CLIENT_IP(3)\fP .IP CURLOPT_SERVICE_NAME Authentication service name. \fICURLOPT_SERVICE_NAME(3)\fP .IP CURLOPT_INTERFACE diff --git a/docs/libcurl/opts/CURLOPT_HAPROXY_CLIENT_IP.3 b/docs/libcurl/opts/CURLOPT_HAPROXY_CLIENT_IP.3 new file mode 100644 index 0000000000..6c54ee89bd --- /dev/null +++ b/docs/libcurl/opts/CURLOPT_HAPROXY_CLIENT_IP.3 @@ -0,0 +1,63 @@ +.\" ************************************************************************** +.\" * _ _ ____ _ +.\" * Project ___| | | | _ \| | +.\" * / __| | | | |_) | | +.\" * | (__| |_| | _ <| |___ +.\" * \___|\___/|_| \_\_____| +.\" * +.\" * Copyright (C) Daniel Stenberg, , 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 +.\" * +.\" ************************************************************************** +.\" +.TH CURLOPT_HAPROXY_CLIENT_IP 3 "8 May 2023" libcurl libcurl +.SH NAME +CURLOPT_HAPROXY_CLIENT_IP \- set HAProxy PROXY protocol client IP +.SH SYNOPSIS +.nf +#include + +CURLcode curl_easy_setopt(CURL *handle, CURLOPT_HAPROXY_CLIENT_IP, + char *client_ip); +.fi +.SH DESCRIPTION +When this parameter is set to a valid IPv4 or IPv6, the library will +not send this address in the HAProxy PROXY protocol +v1 header at beginning of the connection. + +This option is primarily useful when sending test requests to verify that +a service is working as intended. + +.SH DEFAULT +no HAProxy header will be sent +.SH PROTOCOLS +HTTP, HAProxy PROTOCOL +.SH EXAMPLE +.nf +CURL *curl = curl_easy_init(); +if(curl) { + CURLcode ret; + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/"); + curl_easy_setopt(curl, CURLOPT_HAPROXY_CLIENT_IP, "1.1.1.1"); + ret = curl_easy_perform(curl); +} +.fi +.SH AVAILABILITY +Along with HTTP. Added in 8.2.0. +.SH RETURN VALUE +Returns CURLE_OK if HTTP is enabled, and CURLE_UNKNOWN_OPTION if not. +.SH SEE ALSO +.BR CURLOPT_PROXY "(3), " +.BR CURLOPT_HAPROXYPROTOCOL "(3), " diff --git a/docs/libcurl/opts/Makefile.inc b/docs/libcurl/opts/Makefile.inc index d4da3167f9..8577b0317f 100644 --- a/docs/libcurl/opts/Makefile.inc +++ b/docs/libcurl/opts/Makefile.inc @@ -186,6 +186,7 @@ man_MANS = \ CURLOPT_GSSAPI_DELEGATION.3 \ CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS.3 \ CURLOPT_HAPROXYPROTOCOL.3 \ + CURLOPT_HAPROXY_CLIENT_IP.3 \ CURLOPT_HEADER.3 \ CURLOPT_HEADERDATA.3 \ CURLOPT_HEADERFUNCTION.3 \ diff --git a/docs/libcurl/symbols-in-versions b/docs/libcurl/symbols-in-versions index d82401f38b..7b08c62c0b 100644 --- a/docs/libcurl/symbols-in-versions +++ b/docs/libcurl/symbols-in-versions @@ -643,6 +643,7 @@ CURLOPT_FTPSSLAUTH 7.12.2 CURLOPT_GSSAPI_DELEGATION 7.22.0 CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS 7.59.0 CURLOPT_HAPROXYPROTOCOL 7.60.0 +CURLOPT_HAPROXY_CLIENT_IP 8.2.0 CURLOPT_HEADER 7.1 CURLOPT_HEADERDATA 7.10 CURLOPT_HEADERFUNCTION 7.7.2 diff --git a/docs/options-in-versions b/docs/options-in-versions index a4307b6abf..d11d139e04 100644 --- a/docs/options-in-versions +++ b/docs/options-in-versions @@ -82,6 +82,7 @@ --globoff (-g) 7.6 --happy-eyeballs-timeout-ms 7.59.0 --haproxy-protocol 7.60.0 +--haproxy-clientip 8.2.0 --head (-I) 4.0 --header (-H) 5.0 --help (-h) 4.0 diff --git a/include/curl/curl.h b/include/curl/curl.h index e033361a8d..38393b9684 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -781,7 +781,7 @@ typedef enum { CURLPROXY_HTTP_1_0 = 1, /* added in 7.19.4, force to use CONNECT HTTP/1.0 */ CURLPROXY_HTTPS = 2, /* HTTPS but stick to HTTP/1 added in 7.52.0 */ - CURLPROXY_HTTPS2 = 3, /* HTTPS and attempt HTTP/2 added in 8.1.0 */ + CURLPROXY_HTTPS2 = 3, /* HTTPS and attempt HTTP/2 added in 8.2.0 */ CURLPROXY_SOCKS4 = 4, /* support added in 7.15.2, enum existed already in 7.10 */ CURLPROXY_SOCKS5 = 5, /* added in 7.10 */ @@ -2207,6 +2207,9 @@ typedef enum { /* Can leak things, gonna exit() soon */ CURLOPT(CURLOPT_QUICK_EXIT, CURLOPTTYPE_LONG, 322), + /* set a specific client IP for HAProxy PROXY protocol header? */ + CURLOPT(CURLOPT_HAPROXY_CLIENT_IP, CURLOPTTYPE_STRINGPOINT, 323), + CURLOPT_LASTENTRY /* the last unused */ } CURLoption; diff --git a/include/curl/typecheck-gcc.h b/include/curl/typecheck-gcc.h index bc8d7a78ce..b880f3dc60 100644 --- a/include/curl/typecheck-gcc.h +++ b/include/curl/typecheck-gcc.h @@ -280,6 +280,7 @@ CURLWARNING(_curl_easy_getinfo_err_curl_off_t, (option) == CURLOPT_FTP_ALTERNATIVE_TO_USER || \ (option) == CURLOPT_FTPPORT || \ (option) == CURLOPT_HSTS || \ + (option) == CURLOPT_HAPROXY_CLIENT_IP || \ (option) == CURLOPT_INTERFACE || \ (option) == CURLOPT_ISSUERCERT || \ (option) == CURLOPT_KEYPASSWD || \ diff --git a/lib/cf-haproxy.c b/lib/cf-haproxy.c index 86d7fd1837..97bd5fa580 100644 --- a/lib/cf-haproxy.c +++ b/lib/cf-haproxy.c @@ -71,6 +71,7 @@ static CURLcode cf_haproxy_date_out_set(struct Curl_cfilter*cf, struct cf_haproxy_ctx *ctx = cf->ctx; CURLcode result; const char *tcp_version; + const char *client_ip; DEBUGASSERT(ctx); DEBUGASSERT(ctx->state == HAPROXY_INIT); @@ -82,11 +83,15 @@ static CURLcode cf_haproxy_date_out_set(struct Curl_cfilter*cf, #endif /* USE_UNIX_SOCKETS */ /* Emit the correct prefix for IPv6 */ tcp_version = cf->conn->bits.ipv6 ? "TCP6" : "TCP4"; + if(data->set.str[STRING_HAPROXY_CLIENT_IP]) + client_ip = data->set.str[STRING_HAPROXY_CLIENT_IP]; + else + client_ip = data->info.conn_primary_ip; result = Curl_dyn_addf(&ctx->data_out, "PROXY %s %s %s %i %i\r\n", tcp_version, data->info.conn_local_ip, - data->info.conn_primary_ip, + client_ip, data->info.conn_local_port, data->info.conn_primary_port); diff --git a/lib/easyoptions.c b/lib/easyoptions.c index 1e588c3766..e69c658b0c 100644 --- a/lib/easyoptions.c +++ b/lib/easyoptions.c @@ -120,6 +120,7 @@ struct curl_easyoption Curl_easyopts[] = { {"HAPPY_EYEBALLS_TIMEOUT_MS", CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS, CURLOT_LONG, 0}, {"HAPROXYPROTOCOL", CURLOPT_HAPROXYPROTOCOL, CURLOT_LONG, 0}, + {"HAPROXY_CLIENT_IP", CURLOPT_HAPROXY_CLIENT_IP, CURLOT_STRING, 0}, {"HEADER", CURLOPT_HEADER, CURLOT_LONG, 0}, {"HEADERDATA", CURLOPT_HEADERDATA, CURLOT_CBPTR, 0}, {"HEADERFUNCTION", CURLOPT_HEADERFUNCTION, CURLOT_FUNCTION, 0}, @@ -372,6 +373,6 @@ struct curl_easyoption Curl_easyopts[] = { */ int Curl_easyopts_check(void) { - return ((CURLOPT_LASTENTRY%10000) != (322 + 1)); + return ((CURLOPT_LASTENTRY%10000) != (323 + 1)); } #endif diff --git a/lib/setopt.c b/lib/setopt.c index afe7f7416a..b05162a556 100644 --- a/lib/setopt.c +++ b/lib/setopt.c @@ -1867,6 +1867,15 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) */ data->set.haproxyprotocol = (0 != va_arg(param, long)) ? TRUE : FALSE; break; + case CURLOPT_HAPROXY_CLIENT_IP: + /* + * Set the client IP to send through HAProxy PROXY protocol + */ + result = Curl_setstropt(&data->set.str[STRING_HAPROXY_CLIENT_IP], + va_arg(param, char *)); + /* We enable implicitly the HAProxy protocol if we use this flag. */ + data->set.haproxyprotocol = TRUE; + break; #endif case CURLOPT_INTERFACE: /* diff --git a/lib/urldata.h b/lib/urldata.h index f02e665414..ef81fc01b8 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -1563,6 +1563,7 @@ enum dupstring { STRING_DNS_LOCAL_IP6, STRING_SSL_EC_CURVES, STRING_AWS_SIGV4, /* Parameters for V4 signature */ + STRING_HAPROXY_CLIENT_IP, /* CURLOPT_HAPROXY_CLIENT_IP */ /* -- end of null-terminated strings -- */ diff --git a/src/tool_cfgable.c b/src/tool_cfgable.c index 2031bd9268..04f0b05f44 100644 --- a/src/tool_cfgable.c +++ b/src/tool_cfgable.c @@ -54,6 +54,7 @@ static void free_config_fields(struct OperationConfig *config) Curl_safefree(config->useragent); Curl_safefree(config->altsvc); Curl_safefree(config->hsts); + Curl_safefree(config->haproxy_clientip); curl_slist_free_all(config->cookies); Curl_safefree(config->cookiejar); curl_slist_free_all(config->cookiefiles); diff --git a/src/tool_cfgable.h b/src/tool_cfgable.h index a0442ff434..7cd6e12ecc 100644 --- a/src/tool_cfgable.h +++ b/src/tool_cfgable.h @@ -278,6 +278,7 @@ struct OperationConfig { long happy_eyeballs_timeout_ms; /* happy eyeballs timeout in milliseconds. 0 is valid. default: CURL_HET_DEFAULT. */ bool haproxy_protocol; /* whether to send HAProxy protocol v1 */ + char *haproxy_clientip; /* client IP for HAProxy protocol */ bool disallow_username_in_url; /* disallow usernames in URLs */ char *aws_sigv4; enum { diff --git a/src/tool_getparam.c b/src/tool_getparam.c index cee56d0baf..29ed6516b4 100644 --- a/src/tool_getparam.c +++ b/src/tool_getparam.c @@ -122,6 +122,7 @@ static const struct LongShort aliases[]= { {"*x", "krb4", ARG_STRING}, /* 'krb4' is the previous name */ {"*X", "haproxy-protocol", ARG_BOOL}, + {"*P", "haproxy-clientip", ARG_STRING}, {"*y", "max-filesize", ARG_STRING}, {"*z", "disable-eprt", ARG_BOOL}, {"*Z", "eprt", ARG_BOOL}, @@ -1055,6 +1056,9 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */ case 'X': /* --haproxy-protocol */ config->haproxy_protocol = toggle; break; + case 'P': /* --haproxy-clientip */ + GetStr(&config->haproxy_clientip, nextarg); + break; case 'y': /* --max-filesize */ { curl_off_t value; diff --git a/src/tool_listhelp.c b/src/tool_listhelp.c index b6f85fcb81..1e6bd72273 100644 --- a/src/tool_listhelp.c +++ b/src/tool_listhelp.c @@ -249,6 +249,9 @@ const struct helptxt helptext[] = { {" --haproxy-protocol", "Send HAProxy PROXY protocol v1 header", CURLHELP_HTTP | CURLHELP_PROXY}, + {" --haproxy-clientip", + "Sets the HAProxy PROXY protocol v1 client IP", + CURLHELP_HTTP | CURLHELP_PROXY}, {"-I, --head", "Show document info only", CURLHELP_HTTP | CURLHELP_FTP | CURLHELP_FILE}, diff --git a/src/tool_operate.c b/src/tool_operate.c index 9dea412b03..a72e8b6d27 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -2161,6 +2161,11 @@ static CURLcode single_transfer(struct GlobalConfig *global, if(config->haproxy_protocol) my_setopt(curl, CURLOPT_HAPROXYPROTOCOL, 1L); + /* new in 8.2.0 */ + if(config->haproxy_clientip) + my_setopt_str(curl, CURLOPT_HAPROXY_CLIENT_IP, + config->haproxy_clientip); + if(config->disallow_username_in_url) my_setopt(curl, CURLOPT_DISALLOW_USERNAME_IN_URL, 1L); diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc index f054175edb..91f2275a98 100644 --- a/tests/data/Makefile.inc +++ b/tests/data/Makefile.inc @@ -258,4 +258,5 @@ test3016 test3017 test3018 test3019 test3020 test3021 test3022 test3023 \ test3024 test3025 test3026 test3027 test3028 test3029 test3030 \ \ test3100 test3101 \ -test3200 +test3200 \ +test3201 test3202 diff --git a/tests/data/test3201 b/tests/data/test3201 new file mode 100644 index 0000000000..4fb0b8fc64 --- /dev/null +++ b/tests/data/test3201 @@ -0,0 +1,63 @@ + + + +HTTP +HTTP GET +proxy +haproxy + + + +# +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT +ETag: "21025-dc7-39462498" +Accept-Ranges: bytes +Content-Length: 6 +Connection: close +Content-Type: text/html +Funny-head: barkbark + +-foo- + + + +# +# Client-side + + +http + + +HTTP GET when PROXY Protocol enabled and spoofed client IP + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER --haproxy-clientip "192.168.1.1" -H "Testno: %TESTNUMBER" + + +proxy + + + +# +# Verify data after the test has been "shot" + + +s/^PROXY TCP4 %CLIENTIP 192.168.1.1 (\d*) %HTTPPORT/proxy-line/ + + +proxy-line +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* +Testno: %TESTNUMBER + + + + diff --git a/tests/data/test3202 b/tests/data/test3202 new file mode 100644 index 0000000000..8990abc83a --- /dev/null +++ b/tests/data/test3202 @@ -0,0 +1,67 @@ + + + +HTTP +HTTP GET +proxy +haproxy +IPv6 + + +# +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT +ETag: "21025-dc7-39462498" +Accept-Ranges: bytes +Content-Length: 6 +Connection: close +Content-Type: text/html +Funny-head: yesyes + +These data aren't actually sent to the client + + + +# +# Client-side + + +ipv6 + + +http-ipv6 + + +HTTP-IPv6 GET with PROXY protocol with spoofed client IP + + +-g "http://%HOST6IP:%HTTP6PORT/%TESTNUMBER" --haproxy-clientip "2001:db8::" + + +proxy + + + +# +# Verify data after the test has been "shot" + +# Strip off the (random) local port number. This test used to use a fixed +# local port number that frequently causes the test to fail + +s/PROXY TCP6 ::1 2001:db8:: (\d+) (\d+)/PROXY TCP6 ::1 2001:db8:: $2/ + + +PROXY TCP6 ::1 2001:db8:: %HTTP6PORT +GET /%TESTNUMBER HTTP/1.1 +Host: %HOST6IP:%HTTP6PORT +User-Agent: curl/%VERSION +Accept: */* + + + +