From 6baeb6df35d24740c55239f24b5fc4ce86f375a5 Mon Sep 17 00:00:00 2001 From: Lawrence Matthews Date: Thu, 1 Dec 2016 04:05:04 -0800 Subject: [PATCH] CURLOPT_HAPROXYPROTOCOL: support the HAProxy PROXY protocol Add --haproxy-protocol for the command line tool Closes #2162 --- docs/cmdline-opts/haproxy-protocol.d | 11 ++++ docs/libcurl/curl_easy_setopt.3 | 2 + docs/libcurl/opts/CURLOPT_HAPROXYPROTOCOL.3 | 57 ++++++++++++++++++++ docs/libcurl/opts/Makefile.inc | 1 + docs/libcurl/symbols-in-versions | 1 + include/curl/curl.h | 3 ++ lib/http.c | 50 +++++++++++++++++ lib/setopt.c | 7 +++ lib/urldata.h | 2 + src/tool_cfgable.h | 1 + src/tool_getparam.c | 4 ++ src/tool_help.c | 2 + src/tool_operate.c | 4 ++ tests/data/Makefile.inc | 2 +- tests/data/test1455 | 56 +++++++++++++++++++ tests/data/test1456 | 59 +++++++++++++++++++++ 16 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 docs/cmdline-opts/haproxy-protocol.d create mode 100644 docs/libcurl/opts/CURLOPT_HAPROXYPROTOCOL.3 create mode 100644 tests/data/test1455 create mode 100644 tests/data/test1456 diff --git a/docs/cmdline-opts/haproxy-protocol.d b/docs/cmdline-opts/haproxy-protocol.d new file mode 100644 index 000000000..52e156058 --- /dev/null +++ b/docs/cmdline-opts/haproxy-protocol.d @@ -0,0 +1,11 @@ +Long: haproxy-protocol +Help: Send HAProxy PROXY protocol header +Protocols: HTTP +Added: 7.60.0 +--- +Send a HAProxy PROXY protocol header at the beginning of the connection. This +is used by some load balancers and reverse proxies to indicate the client's +true IP address and port. + +This option is primarily useful when sending test requests to a service that +expects this header. diff --git a/docs/libcurl/curl_easy_setopt.3 b/docs/libcurl/curl_easy_setopt.3 index 1efb467e6..b7d67f360 100644 --- a/docs/libcurl/curl_easy_setopt.3 +++ b/docs/libcurl/curl_easy_setopt.3 @@ -185,6 +185,8 @@ Socks5 GSSAPI service name. \fICURLOPT_SOCKS5_GSSAPI_SERVICE(3)\fP Socks5 GSSAPI NEC mode. See \fICURLOPT_SOCKS5_GSSAPI_NEC(3)\fP .IP CURLOPT_PROXY_SERVICE_NAME Proxy authentication service name. \fICURLOPT_PROXY_SERVICE_NAME(3)\fP +.IP CURLOPT_HAPROXYPROTOCOL +Send an HAProxy PROXY protocol header. See \fICURLOPT_HAPROXYPROTOCOL(3)\fP .IP CURLOPT_SERVICE_NAME Authentication service name. \fICURLOPT_SERVICE_NAME(3)\fP .IP CURLOPT_INTERFACE diff --git a/docs/libcurl/opts/CURLOPT_HAPROXYPROTOCOL.3 b/docs/libcurl/opts/CURLOPT_HAPROXYPROTOCOL.3 new file mode 100644 index 000000000..01e667d16 --- /dev/null +++ b/docs/libcurl/opts/CURLOPT_HAPROXYPROTOCOL.3 @@ -0,0 +1,57 @@ +.\" ************************************************************************** +.\" * _ _ ____ _ +.\" * Project ___| | | | _ \| | +.\" * / __| | | | |_) | | +.\" * | (__| |_| | _ <| |___ +.\" * \___|\___/|_| \_\_____| +.\" * +.\" * Copyright (C) 1998 - 2017, 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.haxx.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. +.\" * +.\" ************************************************************************** +.\" +.TH CURLOPT_HAPROXYPROTOCOL 3 "5 Feb 2018" "libcurl 7.60.0" "curl_easy_setopt options" +.SH NAME +CURLOPT_HAPROXYPROTOCOL \- send HAProxy PROXY protocol header +.SH SYNOPSIS +#include + +CURLcode curl_easy_setopt(CURL *handle, CURLOPT_HAPROXYPROTOCOL, + long haproxy_protocol); +.SH DESCRIPTION +A long parameter set to 1 tells the library to send an HAProxy PROXY +protocol header at beginning of the connection. The default action is not to +send this header. + +This option is primarily useful when sending test requests to a service that +expects this header. + +Most applications do not need this option. +.SH DEFAULT +0, do not send HAProxy PROXY protocol header +.SH PROTOCOLS +HTTP +.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_HAPROXYPROTOCOL, 1L); + ret = curl_easy_perform(curl); +} +.fi +.SH AVAILABILITY +Along with HTTP. Added in 7.60.0. +.SH RETURN VALUE +Returns CURLE_OK if HTTP is enabled, and CURLE_UNKNOWN_OPTION if not. diff --git a/docs/libcurl/opts/Makefile.inc b/docs/libcurl/opts/Makefile.inc index 2aa1acf33..b370082d6 100644 --- a/docs/libcurl/opts/Makefile.inc +++ b/docs/libcurl/opts/Makefile.inc @@ -137,6 +137,7 @@ man_MANS = \ CURLOPT_FTP_USE_PRET.3 \ CURLOPT_GSSAPI_DELEGATION.3 \ CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS.3 \ + CURLOPT_HAPROXYPROTOCOL.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 c58086fb7..2877de7f1 100644 --- a/docs/libcurl/symbols-in-versions +++ b/docs/libcurl/symbols-in-versions @@ -404,6 +404,7 @@ CURLOPT_FTP_USE_EPSV 7.9.2 CURLOPT_FTP_USE_PRET 7.20.0 CURLOPT_GSSAPI_DELEGATION 7.22.0 CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS 7.59.0 +CURLOPT_HAPROXYPROTOCOL 7.60.0 CURLOPT_HEADER 7.1 CURLOPT_HEADERDATA 7.10 CURLOPT_HEADERFUNCTION 7.7.2 diff --git a/include/curl/curl.h b/include/curl/curl.h index fa019eca9..43d5e031f 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -1841,6 +1841,9 @@ typedef enum { /* User data to pass to the resolver start callback. */ CINIT(RESOLVER_START_DATA, OBJECTPOINT, 273), + /* send HAProxy PROXY protocol header? */ + CINIT(HAPROXYPROTOCOL, LONG, 274), + CURLOPT_LASTENTRY /* the last unused */ } CURLoption; diff --git a/lib/http.c b/lib/http.c index 841f6cc0b..29dcf6562 100644 --- a/lib/http.c +++ b/lib/http.c @@ -92,6 +92,8 @@ static int http_getsock_do(struct connectdata *conn, int numsocks); static int http_should_fail(struct connectdata *conn); +static CURLcode add_haproxy_protocol_header(struct connectdata *conn); + #ifdef USE_SSL static CURLcode https_connecting(struct connectdata *conn, bool *done); static int https_getsock(struct connectdata *conn, @@ -1358,6 +1360,13 @@ CURLcode Curl_http_connect(struct connectdata *conn, bool *done) /* nothing else to do except wait right now - we're not done here. */ return CURLE_OK; + if(conn->data->set.haproxyprotocol) { + /* add HAProxy PROXY protocol header */ + result = add_haproxy_protocol_header(conn); + if(result) + return result; + } + if(conn->given->protocol & CURLPROTO_HTTPS) { /* perform SSL initialization */ result = https_connecting(conn, done); @@ -1383,6 +1392,47 @@ static int http_getsock_do(struct connectdata *conn, return GETSOCK_WRITESOCK(0); } +static CURLcode add_haproxy_protocol_header(struct connectdata *conn) +{ + char proxy_header[128]; + Curl_send_buffer *req_buffer; + CURLcode result; + char tcp_version[5]; + + /* Emit the correct prefix for IPv6 */ + if(conn->bits.ipv6) { + strcpy(tcp_version, "TCP6"); + } + else { + strcpy(tcp_version, "TCP4"); + } + + snprintf(proxy_header, + sizeof proxy_header, + "PROXY %s %s %s %i %i\r\n", + tcp_version, + conn->data->info.conn_local_ip, + conn->data->info.conn_primary_ip, + conn->data->info.conn_local_port, + conn->data->info.conn_primary_port); + + req_buffer = Curl_add_buffer_init(); + if(!req_buffer) + return CURLE_OUT_OF_MEMORY; + + result = Curl_add_bufferf(req_buffer, proxy_header); + if(result) + return result; + + result = Curl_add_buffer_send(req_buffer, + conn, + &conn->data->info.request_size, + 0, + FIRSTSOCKET); + + return result; +} + #ifdef USE_SSL static CURLcode https_connecting(struct connectdata *conn, bool *done) { diff --git a/lib/setopt.c b/lib/setopt.c index 9c96eb358..737a60f85 100644 --- a/lib/setopt.c +++ b/lib/setopt.c @@ -1603,6 +1603,13 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, data->set.crlf = (0 != va_arg(param, long)) ? TRUE : FALSE; break; + case CURLOPT_HAPROXYPROTOCOL: + /* + * Set to send the HAProxy Proxy Protocol header + */ + data->set.haproxyprotocol = (0 != va_arg(param, long)) ? TRUE : FALSE; + break; + case CURLOPT_INTERFACE: /* * Set what interface or address/hostname to bind the socket to when diff --git a/lib/urldata.h b/lib/urldata.h index 0da5fbce0..dad31cd4e 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -1678,6 +1678,8 @@ struct UserDefined { bool stream_depends_e; /* set or don't set the Exclusive bit */ int stream_weight; + bool haproxyprotocol; /* whether to send HAProxy PROXY protocol header */ + struct Curl_http2_dep *stream_dependents; bool abstract_unix_socket; diff --git a/src/tool_cfgable.h b/src/tool_cfgable.h index 743ce725d..9abaa9d39 100644 --- a/src/tool_cfgable.h +++ b/src/tool_cfgable.h @@ -252,6 +252,7 @@ struct OperationConfig { bool ssh_compression; /* enable/disable SSH compression */ long happy_eyeballs_timeout_ms; /* happy eyeballs timeout in milliseconds. 0 is valid. default: CURL_HET_DEFAULT. */ + bool haproxy_protocol; /* whether to send HAProxy PROXY protocol */ struct GlobalConfig *global; struct OperationConfig *prev; struct OperationConfig *next; /* Always last in the struct */ diff --git a/src/tool_getparam.c b/src/tool_getparam.c index 7ce9c28c7..19454c84a 100644 --- a/src/tool_getparam.c +++ b/src/tool_getparam.c @@ -112,6 +112,7 @@ static const struct LongShort aliases[]= { {"*x", "krb", ARG_STRING}, {"*x", "krb4", ARG_STRING}, /* 'krb4' is the previous name */ + {"*X", "haproxy-protocol", ARG_BOOL}, {"*y", "max-filesize", ARG_STRING}, {"*z", "disable-eprt", ARG_BOOL}, {"*Z", "eprt", ARG_BOOL}, @@ -779,6 +780,9 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */ else return PARAM_LIBCURL_DOESNT_SUPPORT; break; + case 'X': /* --haproxy-protocol */ + config->haproxy_protocol = toggle; + break; case 'y': /* --max-filesize */ { curl_off_t value; diff --git a/src/tool_help.c b/src/tool_help.c index 9796b7e87..4bd65269a 100644 --- a/src/tool_help.c +++ b/src/tool_help.c @@ -164,6 +164,8 @@ static const struct helptxt helptext[] = { "How long to wait in milliseconds for IPv6 before trying IPv4"}, {"-I, --head", "Show document info only"}, + {" --haproxy-protocol", + "Send HAProxy PROXY protocol header"}, {"-H, --header
", "Pass custom header(s) to server"}, {"-h, --help", diff --git a/src/tool_operate.c b/src/tool_operate.c index 15cdc13da..0aad54282 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -1445,6 +1445,10 @@ static CURLcode operate_do(struct GlobalConfig *global, my_setopt(curl, CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS, config->happy_eyeballs_timeout_ms); + /* new in 7.60.0 */ + if(config->haproxy_protocol) + my_setopt(curl, CURLOPT_HAPROXYPROTOCOL, 1L); + /* initialize retry vars for loop below */ retry_sleep_default = (config->retry_delay) ? config->retry_delay*1000L : RETRY_SLEEP_DEFAULT; /* ms */ diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc index 5fcffc6eb..ca0c7edd1 100644 --- a/tests/data/Makefile.inc +++ b/tests/data/Makefile.inc @@ -165,7 +165,7 @@ test1424 test1425 test1426 test1427 \ test1428 test1429 test1430 test1431 test1432 test1433 test1434 test1435 \ test1436 test1437 test1438 test1439 test1440 test1441 test1442 test1443 \ test1444 test1445 test1446 test1447 test1448 test1449 test1450 test1451 \ -test1452 test1453 test1454 \ +test1452 test1453 test1454 test1455 test1456 \ test1500 test1501 test1502 test1503 test1504 test1505 test1506 test1507 \ test1508 test1509 test1510 test1511 test1512 test1513 test1514 test1515 \ test1516 test1517 \ diff --git a/tests/data/test1455 b/tests/data/test1455 new file mode 100644 index 000000000..7768a1f89 --- /dev/null +++ b/tests/data/test1455 @@ -0,0 +1,56 @@ + + + +HTTP +HTTP GET + + + +# +# Server-side + + +HTTP/1.1 200 OK +Date: Thu, 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 + + +http://%HOSTIP:%HTTPPORT/1455 --haproxy-protocol --local-port 37756 + + + +# +# Verify data after the test has been "shot" + + +^User-Agent:.* + + +PROXY TCP4 %CLIENTIP %HOSTIP 37756 %HTTPPORT +GET /1455 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* + + + + diff --git a/tests/data/test1456 b/tests/data/test1456 new file mode 100644 index 000000000..07a6e7c03 --- /dev/null +++ b/tests/data/test1456 @@ -0,0 +1,59 @@ + + + +HTTP +HTTP GET +IPv6 + + +# +# Server-side + + +HTTP/1.1 200 OK +Date: Thu, 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 + +-foo- + + + +# +# Client-side + + +ipv6 + + +http-ipv6 + + +HTTP-IPv6 GET with PROXY protocol + + +-g "http://%HOST6IP:%HTTP6PORT/1456" --local-port 44444 --haproxy-protocol + + + +# +# Verify data after the test has been "shot" + + +^User-Agent: + + +PROXY TCP6 ::1 ::1 44444 %HTTP6PORT +GET /1456 HTTP/1.1 +Host: %HOST6IP:%HTTP6PORT +Accept: */* + + + + -- 2.40.0