]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
CURLOPT_HAPROXYPROTOCOL: support the HAProxy PROXY protocol
authorLawrence Matthews <lmatthew@yelp.com>
Thu, 1 Dec 2016 12:05:04 +0000 (04:05 -0800)
committerDaniel Stenberg <daniel@haxx.se>
Sat, 17 Mar 2018 10:50:06 +0000 (11:50 +0100)
Add --haproxy-protocol for the command line tool

Closes #2162

16 files changed:
docs/cmdline-opts/haproxy-protocol.d [new file with mode: 0644]
docs/libcurl/curl_easy_setopt.3
docs/libcurl/opts/CURLOPT_HAPROXYPROTOCOL.3 [new file with mode: 0644]
docs/libcurl/opts/Makefile.inc
docs/libcurl/symbols-in-versions
include/curl/curl.h
lib/http.c
lib/setopt.c
lib/urldata.h
src/tool_cfgable.h
src/tool_getparam.c
src/tool_help.c
src/tool_operate.c
tests/data/Makefile.inc
tests/data/test1455 [new file with mode: 0644]
tests/data/test1456 [new file with mode: 0644]

diff --git a/docs/cmdline-opts/haproxy-protocol.d b/docs/cmdline-opts/haproxy-protocol.d
new file mode 100644 (file)
index 0000000..52e1560
--- /dev/null
@@ -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.
index 1efb467e6424ce82fcdc4d157bd71545d13a3224..b7d67f360196d52440271e776de8ec4421ffffef 100644 (file)
@@ -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 (file)
index 0000000..01e667d
--- /dev/null
@@ -0,0 +1,57 @@
+.\" **************************************************************************
+.\" *                                  _   _ ____  _
+.\" *  Project                     ___| | | |  _ \| |
+.\" *                             / __| | | | |_) | |
+.\" *                            | (__| |_| |  _ <| |___
+.\" *                             \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2017, 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.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 <curl/curl.h>
+
+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.
index 2aa1acf33f77b731349f933abfb8f8a4b551dc26..b370082d6095633d9f28dd655b79aa4315607e09 100644 (file)
@@ -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                      \
index c58086fb7ad0a0f6204317b865843b1a1e0a2bff..2877de7f1d5ab8ffacd3e95c26f2ee803855d0df 100644 (file)
@@ -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
index fa019eca9dc8871e9a5def757f34b743f985010a..43d5e031f8f2d7f4ca86f8c1e21ea6f7d2bda5ec 100644 (file)
@@ -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;
 
index 841f6cc0b41616db37fb399e54450d3318af894b..29dcf656204e5f973b201df185d47710b1bd9c00 100644 (file)
@@ -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)
 {
index 9c96eb3586b872757c8b1ff22507f4b0fb992e77..737a60f8569351b1ce09b9883d1a1ae840e4e7a4 100644 (file)
@@ -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
index 0da5fbce068d79850da862f5c19d63a82d916f05..dad31cd4e597ab7cf0d93bb9f8b83b8f35769a0c 100644 (file)
@@ -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;
index 743ce725d29c690de4efd3268f1426cb88440afd..9abaa9d394fedaa233d564735fa55e3ef8f5fb22 100644 (file)
@@ -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 */
index 7ce9c28c767d1302d3a14ea03d29d53b709bfe1b..19454c84a8b98145f39c1d26c78b22625d9572f6 100644 (file)
@@ -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;
index 9796b7e87356b52e059a1ffa1d7c90d23f337b16..4bd65269a975b01ed28c659994e66cdd050137f7 100644 (file)
@@ -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 <header/@file>",
    "Pass custom header(s) to server"},
   {"-h, --help",
index 15cdc13da2931dc59f7060e432a499453760d0a0..0aad54282658ef8680c6917ce9b7be2f588c9836 100644 (file)
@@ -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 */
index 5fcffc6ebc24bd4074c1fb4216dc24ff4545c412..ca0c7edd14f83065f46901fbdd000d96dca2f50e 100644 (file)
@@ -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 (file)
index 0000000..7768a1f
--- /dev/null
@@ -0,0 +1,56 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP GET
+</keywords>
+</info>
+
+#
+# Server-side
+<reply name="1455">
+<data nocheck=yes>
+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-
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<server>
+http
+</server>
+<name>
+HTTP GET when PROXY Protocol enabled
+</name>
+<command>
+http://%HOSTIP:%HTTPPORT/1455 --haproxy-protocol --local-port 37756
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<strip>
+^User-Agent:.*
+</strip>
+<protocol>
+PROXY TCP4 %CLIENTIP %HOSTIP 37756 %HTTPPORT\r
+GET /1455 HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+Accept: */*\r
+\r
+</protocol>
+</verify>
+</testcase>
diff --git a/tests/data/test1456 b/tests/data/test1456
new file mode 100644 (file)
index 0000000..07a6e7c
--- /dev/null
@@ -0,0 +1,59 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP GET
+IPv6
+</keywords>
+</info>
+#
+# Server-side
+<reply>
+<data nocheck=yes>
+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-
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<features>
+ipv6
+</features>
+<server>
+http-ipv6
+</server>
+ <name>
+HTTP-IPv6 GET with PROXY protocol
+ </name>
+ <command>
+-g "http://%HOST6IP:%HTTP6PORT/1456" --local-port 44444 --haproxy-protocol
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<strip>
+^User-Agent:
+</strip>
+<protocol>
+PROXY TCP6 ::1 ::1 44444 %HTTP6PORT\r
+GET /1456 HTTP/1.1\r
+Host: %HOST6IP:%HTTP6PORT\r
+Accept: */*\r
+\r
+</protocol>
+</verify>
+</testcase>