globoff.d \
happy-eyeballs-timeout-ms.d \
haproxy-protocol.d \
+ haproxy-clientip.d \
head.d \
header.d \
help.d \
--- /dev/null
+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.
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
--- /dev/null
+.\" **************************************************************************
+.\" * _ _ ____ _
+.\" * 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), "
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 \
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
--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
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 */
/* 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;
(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 || \
struct cf_haproxy_ctx *ctx = cf->ctx;
CURLcode result;
const char *tcp_version;
+ const char *client_ip;
DEBUGASSERT(ctx);
DEBUGASSERT(ctx->state == HAPROXY_INIT);
#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);
{"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},
*/
int Curl_easyopts_check(void)
{
- return ((CURLOPT_LASTENTRY%10000) != (322 + 1));
+ return ((CURLOPT_LASTENTRY%10000) != (323 + 1));
}
#endif
*/
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:
/*
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 -- */
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);
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 {
{"*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},
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;
{" --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},
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);
test3024 test3025 test3026 test3027 test3028 test3029 test3030 \
\
test3100 test3101 \
-test3200
+test3200 \
+test3201 test3202
--- /dev/null
+<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\r
+GET /%TESTNUMBER HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+User-Agent: curl/%VERSION\r
+Accept: */*\r
+Testno: %TESTNUMBER\r
+\r
+</protocol>
+</verify>
+</testcase>
--- /dev/null
+<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\r
+GET /%TESTNUMBER HTTP/1.1\r
+Host: %HOST6IP:%HTTP6PORT\r
+User-Agent: curl/%VERSION\r
+Accept: */*\r
+\r
+</protocol>
+</verify>
+</testcase>