mirror of
https://github.com/curl/curl.git
synced 2025-04-12 16:20:35 +08:00
haproxy: add --haproxy-clientip flag to spoof client IPs
CURLOPT_HAPROXY_CLIENT_IP in the library Closes #10779
This commit is contained in:
parent
9ad23c38e5
commit
0a75964d0d
@ -96,6 +96,7 @@ DPAGES = \
|
||||
globoff.d \
|
||||
happy-eyeballs-timeout-ms.d \
|
||||
haproxy-protocol.d \
|
||||
haproxy-clientip.d \
|
||||
head.d \
|
||||
header.d \
|
||||
help.d \
|
||||
|
29
docs/cmdline-opts/haproxy-clientip.d
Normal file
29
docs/cmdline-opts/haproxy-clientip.d
Normal file
@ -0,0 +1,29 @@
|
||||
c: Copyright (C) Daniel Stenberg, <daniel@haxx.se>, 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.
|
@ -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
|
||||
|
63
docs/libcurl/opts/CURLOPT_HAPROXY_CLIENT_IP.3
Normal file
63
docs/libcurl/opts/CURLOPT_HAPROXY_CLIENT_IP.3
Normal file
@ -0,0 +1,63 @@
|
||||
.\" **************************************************************************
|
||||
.\" * _ _ ____ _
|
||||
.\" * 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
|
||||
.\" *
|
||||
.\" **************************************************************************
|
||||
.\"
|
||||
.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 <curl/curl.h>
|
||||
|
||||
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), "
|
@ -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 \
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 || \
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
/*
|
||||
|
@ -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 -- */
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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},
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
63
tests/data/test3201
Normal file
63
tests/data/test3201
Normal file
@ -0,0 +1,63 @@
|
||||
<testcase>
|
||||
<info>
|
||||
<keywords>
|
||||
HTTP
|
||||
HTTP GET
|
||||
proxy
|
||||
haproxy
|
||||
</keywords>
|
||||
</info>
|
||||
|
||||
#
|
||||
# Server-side
|
||||
<reply name="%TESTNUMBER">
|
||||
<data nocheck="yes">
|
||||
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-
|
||||
</data>
|
||||
</reply>
|
||||
|
||||
#
|
||||
# Client-side
|
||||
<client>
|
||||
<server>
|
||||
http
|
||||
</server>
|
||||
<name>
|
||||
HTTP GET when PROXY Protocol enabled and spoofed client IP
|
||||
</name>
|
||||
<command>
|
||||
http://%HOSTIP:%HTTPPORT/%TESTNUMBER --haproxy-clientip "192.168.1.1" -H "Testno: %TESTNUMBER"
|
||||
</command>
|
||||
<features>
|
||||
proxy
|
||||
</features>
|
||||
</client>
|
||||
|
||||
#
|
||||
# Verify data after the test has been "shot"
|
||||
<verify>
|
||||
<strippart>
|
||||
s/^PROXY TCP4 %CLIENTIP 192.168.1.1 (\d*) %HTTPPORT/proxy-line/
|
||||
</strippart>
|
||||
<protocol>
|
||||
proxy-line
|
||||
GET /%TESTNUMBER HTTP/1.1
|
||||
Host: %HOSTIP:%HTTPPORT
|
||||
User-Agent: curl/%VERSION
|
||||
Accept: */*
|
||||
Testno: %TESTNUMBER
|
||||
|
||||
</protocol>
|
||||
</verify>
|
||||
</testcase>
|
67
tests/data/test3202
Normal file
67
tests/data/test3202
Normal file
@ -0,0 +1,67 @@
|
||||
<testcase>
|
||||
<info>
|
||||
<keywords>
|
||||
HTTP
|
||||
HTTP GET
|
||||
proxy
|
||||
haproxy
|
||||
IPv6
|
||||
</keywords>
|
||||
</info>
|
||||
#
|
||||
# Server-side
|
||||
<reply>
|
||||
<data nocheck="yes">
|
||||
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
|
||||
</data>
|
||||
</reply>
|
||||
|
||||
#
|
||||
# Client-side
|
||||
<client>
|
||||
<features>
|
||||
ipv6
|
||||
</features>
|
||||
<server>
|
||||
http-ipv6
|
||||
</server>
|
||||
<name>
|
||||
HTTP-IPv6 GET with PROXY protocol with spoofed client IP
|
||||
</name>
|
||||
<command>
|
||||
-g "http://%HOST6IP:%HTTP6PORT/%TESTNUMBER" --haproxy-clientip "2001:db8::"
|
||||
</command>
|
||||
<features>
|
||||
proxy
|
||||
</features>
|
||||
</client>
|
||||
|
||||
#
|
||||
# Verify data after the test has been "shot"
|
||||
<verify>
|
||||
# Strip off the (random) local port number. This test used to use a fixed
|
||||
# local port number that frequently causes the test to fail
|
||||
<strippart>
|
||||
s/PROXY TCP6 ::1 2001:db8:: (\d+) (\d+)/PROXY TCP6 ::1 2001:db8:: $2/
|
||||
</strippart>
|
||||
<protocol>
|
||||
PROXY TCP6 ::1 2001:db8:: %HTTP6PORT
|
||||
GET /%TESTNUMBER HTTP/1.1
|
||||
Host: %HOST6IP:%HTTP6PORT
|
||||
User-Agent: curl/%VERSION
|
||||
Accept: */*
|
||||
|
||||
</protocol>
|
||||
</verify>
|
||||
</testcase>
|
Loading…
x
Reference in New Issue
Block a user