Skip to content

Commit f692ec9

Browse files
committed
[connection] add PROXY parser (haproxy protocol)
https://github.com/haproxy/haproxy/blob/master/doc/proxy-protocol.txt Change-Id: I85b28705a74686d9beee04751e8831119be2a228
1 parent 4874337 commit f692ec9

File tree

18 files changed

+740
-4
lines changed

18 files changed

+740
-4
lines changed

doc/plugin_core.xml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,15 @@
105105
Some clients don't send Content-Length for POST requests with empty body; they should send `Content-Length: 0`. When this check is enabled they'll get a `411 Length required` error.
106106
</markdown></description>
107107
</option>
108+
<option name="proxy_protocol.tlv_max_length">
109+
<short>maximum length of TLV parameters in PROXY v2 headers</short>
110+
<default><value>-1</value></default>
111+
<description><markdown><![CDATA[
112+
Maximum length of TLV section (after address data) in PROXY v2 headers. `-1` means no limit but also doesn't store it.
113+
114+
Also see <https://github.com/haproxy/haproxy/blob/master/doc/proxy-protocol.txt>.
115+
]]></markdown></description>
116+
</option>
108117

109118
<option name="static.exclude_extensions">
110119
<short>don't deliver static files with one of the listed extensions</short>
@@ -628,6 +637,25 @@
628637
</action>
629638
</section>
630639

640+
<action name="proxy_protocol.trust">
641+
<short>Trust PROXY header</short>
642+
643+
<description><markdown>
644+
When a [PROXY header](https://github.com/haproxy/haproxy/blob/master/doc/proxy-protocol.txt) was present and contained addresses, replace the `request.local*` and `request.remote*` variables with those from the PROXY header.
645+
646+
This only triggers once per connection, and should be done early in the config (so it happens for the first request); the new addresses are used for the following requests too (they are stored per connection).
647+
648+
You only should trust the PROXY header on connections from trusted IPs.
649+
</markdown></description>
650+
<example>
651+
<config>
652+
if request.remoteip == "198.51.100.1" or request.remoteip == "127.0.0.1" {
653+
proxy_protocol.trust;
654+
}
655+
</config>
656+
</example>
657+
</action>
658+
631659
<action name="io.buffer_out">
632660
<short>set memory limit for outgoing chunkqueues (default is 256KiB)</short>
633661
<parameter name="limit">

include/lighttpd/base.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <lighttpd/stream.h>
2222
#include <lighttpd/filter.h>
2323
#include <lighttpd/filter_chunked.h>
24+
#include <lighttpd/proxy_protocol.h>
2425
#include <lighttpd/radix.h>
2526
#include <lighttpd/fetch.h>
2627

include/lighttpd/connection.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ struct liConnection {
6464

6565
liStream in, out;
6666
liFilterChunkedDecodeState in_chunked_decode_state;
67+
liConnectionProxyProtocolFilter proxy_protocol_filter;
6768

6869
liVRequest *mainvr;
6970
liHttpRequestCtx req_parser_ctx;

include/lighttpd/plugin_core.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ enum liCoreOptions {
2020
LI_CORE_OPTION_BUFFER_ON_DISK_REQUEST_BODY,
2121

2222
LI_CORE_OPTION_STRICT_POST_CONTENT_LENGTH,
23+
24+
LI_CORE_OPTION_PROXY_PROTOCOL_TLV_MAX_LENGTH,
2325
};
2426

2527
enum liCoreOptionPtrs {

include/lighttpd/proxy_protocol.h

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/* proxy protocol filter:
2+
* https://github.com/haproxy/haproxy/blob/master/doc/proxy-protocol.txt
3+
*/
4+
5+
#ifndef _LIGHTTPD_PROXY_PROTOCOL_H_
6+
#define _LIGHTTPD_PROXY_PROTOCOL_H_
7+
8+
#ifndef _LIGHTTPD_BASE_H_
9+
#error Please include <lighttpd/base.h> instead of this file
10+
#endif
11+
12+
typedef enum {
13+
LI_PROXY_PROT_TRANSPORT_UNSPEC = 0x00,
14+
LI_PROXY_PROT_TRANSPORT_STREAM = 0x01,
15+
LI_PROXY_PROT_TRANSPORT_DGRAM = 0x02,
16+
} liProxyProtTransport;
17+
#define LI_PROXY_PROT_TRANSPORT_MAX ((unsigned char)LI_PROXY_PROT_TRANSPORT_DGRAM)
18+
19+
typedef struct liProxyProtocolData liProxyProtocolData;
20+
struct liProxyProtocolData {
21+
guint version;
22+
liProxyProtTransport transport;
23+
liSocketAddress remote;
24+
liSocketAddress local;
25+
26+
guint skip_bytes;
27+
guint remaining_tlv_bytes;
28+
29+
GByteArray *tlvs;
30+
};
31+
32+
LI_API void li_proxy_protocol_data_init(liProxyProtocolData *data);
33+
LI_API void li_proxy_protocol_data_clear(liProxyProtocolData *data);
34+
35+
typedef enum {
36+
LI_PROXY_PROTOCOL_PARSE_NEED_MORE_DATA = -2,
37+
LI_PROXY_PROTOCOL_PARSE_ERROR = -1,
38+
LI_PROXY_PROTOCOL_PARSE_DONE = 0,
39+
} liProxyProtocolParseResult;
40+
41+
LI_API liProxyProtocolParseResult li_proxy_protocol_parse(
42+
liVRequest *vr, /* probably con->mainvr, needed for logging and options */
43+
liProxyProtocolData *data,
44+
unsigned char *header,
45+
gssize header_len
46+
);
47+
48+
49+
/* liConnection integration */
50+
typedef struct liConnectionProxyProtocolFilter liConnectionProxyProtocolFilter;
51+
struct liConnectionProxyProtocolFilter {
52+
liStream stream;
53+
gboolean done;
54+
};
55+
56+
LI_API void li_connection_proxy_protocol_init(liConnection *con);
57+
58+
#endif

include/lighttpd/virtualrequest.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ struct liConInfo {
5454
gboolean keep_alive;
5555
gboolean aborted; /* network aborted connection before response was sent completely */
5656

57+
gboolean proxy_prot_used;
58+
liProxyProtocolData proxy_prot_data;
59+
5760
liStream *req;
5861
liStream *resp;
5962

src/main/connection.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ static void li_connection_reset2(liConnection *con); /* reset when dead and stre
3333
static void connection_check_reset(liJob *job) {
3434
liConnection *con = LI_CONTAINER_OF(job, liConnection, job_reset);
3535

36-
if (LI_CON_STATE_DEAD == con->state && (0 == con->in.refcount) && (0 == con->out.refcount)) {
36+
if (LI_CON_STATE_DEAD == con->state
37+
&& (0 == con->in.refcount) && (0 == con->out.refcount)
38+
&& (0 == con->proxy_protocol_filter.stream.refcount)
39+
) {
3740
li_connection_reset2(con);
3841
li_worker_con_put(con);
3942
}
@@ -371,6 +374,7 @@ void li_connection_start(liConnection *con, liSocketAddress remote_addr, int s,
371374

372375
li_stream_init(&con->in, &con->wrk->loop, _connection_http_in_cb);
373376
li_stream_init(&con->out, &con->wrk->loop, _connection_http_out_cb);
377+
li_connection_proxy_protocol_init(con);
374378

375379
con->info.req = &con->in;
376380
con->info.resp = &con->out;
@@ -570,13 +574,15 @@ void li_connection_reset(liConnection *con) {
570574
con_iostream_close(con, TRUE);
571575
li_stream_reset(&con->in);
572576
li_stream_reset(&con->out);
577+
li_stream_reset(&con->proxy_protocol_filter.stream);
573578

574579
/* keep details of last request around until final cleanup in li_connection_reset2;
575580
* don't actually do any "keepalive" here */
576581
li_vrequest_reset(con->mainvr, TRUE);
577582

578583
li_stream_release(&con->in);
579584
li_stream_release(&con->out);
585+
li_stream_release(&con->proxy_protocol_filter.stream);
580586

581587
con->info.keep_alive = TRUE;
582588
if (con->keep_alive_data.link) {
@@ -609,6 +615,9 @@ static void li_connection_reset2(liConnection *con) {
609615
li_stream_reset(&con->in);
610616
li_stream_reset(&con->out);
611617

618+
con->info.proxy_prot_used = FALSE;
619+
li_proxy_protocol_data_clear(&con->info.proxy_prot_data);
620+
612621
li_vrequest_reset(con->mainvr, FALSE);
613622

614623
li_http_request_parser_reset(&con->req_parser_ctx);

src/main/connection_http.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ gboolean li_connection_http_new(liConnection *con, int fd) {
7676
con->con_sock.data = data;
7777
con->con_sock.callbacks = &simple_tcp_cbs;
7878
con->con_sock.raw_out = &data->sock_stream->stream_out;
79-
con->con_sock.raw_in = &data->sock_stream->stream_in;
79+
li_stream_connect(&data->sock_stream->stream_in, &con->proxy_protocol_filter.stream);
80+
con->con_sock.raw_in = &con->proxy_protocol_filter.stream;
8081
li_stream_acquire(con->con_sock.raw_out);
8182
li_stream_acquire(con->con_sock.raw_in);
8283

src/main/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ src_shared = [
2727
'options.c',
2828
'pattern.c',
2929
'plugin.c',
30+
'proxy_protocol.c',
3031
'request.c',
3132
'response.c',
3233
'server.c',

src/main/plugin_core.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2030,6 +2030,52 @@ static liAction* core_map_cidr(liServer *srv, liWorker *wrk, liPlugin* p, liValu
20302030
return li_action_new_function(core_handle_map_cidr, NULL, core_map_cidr_free, md);
20312031
}
20322032

2033+
static liHandlerResult core_handle_proxy_prot_trust(liVRequest *vr, gpointer param, gpointer *context) {
2034+
liConInfo *coninfo = vr->coninfo;
2035+
UNUSED(param);
2036+
UNUSED(context);
2037+
2038+
if (coninfo->proxy_prot_used) return LI_HANDLER_GO_ON;
2039+
2040+
coninfo->proxy_prot_used = TRUE; /* don't try again */
2041+
2042+
if (0 == coninfo->proxy_prot_data.version) {
2043+
if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
2044+
VR_DEBUG(vr, "%s", "No PROXY header present");
2045+
}
2046+
return LI_HANDLER_GO_ON;
2047+
}
2048+
2049+
if (0 == coninfo->proxy_prot_data.remote.len || 0 == coninfo->proxy_prot_data.local.len) {
2050+
if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
2051+
VR_DEBUG(vr, "%s", "No addresses in PROXY header");
2052+
}
2053+
return LI_HANDLER_GO_ON;
2054+
}
2055+
2056+
li_sockaddr_clear(&coninfo->remote_addr);
2057+
coninfo->remote_addr = li_sockaddr_dup(coninfo->proxy_prot_data.remote);
2058+
li_sockaddr_to_string(coninfo->remote_addr, coninfo->remote_addr_str, FALSE);
2059+
2060+
li_sockaddr_clear(&coninfo->local_addr);
2061+
coninfo->local_addr = li_sockaddr_dup(coninfo->proxy_prot_data.local);
2062+
li_sockaddr_to_string(coninfo->local_addr, coninfo->local_addr_str, FALSE);
2063+
2064+
return LI_HANDLER_GO_ON;
2065+
}
2066+
2067+
static liAction* core_proxy_prot_trust(liServer *srv, liWorker *wrk, liPlugin* p, liValue *val, gpointer userdata) {
2068+
UNUSED(wrk); UNUSED(p); UNUSED(userdata);
2069+
2070+
val = li_value_get_single_argument(val);
2071+
if (NULL != val) {
2072+
ERROR(srv, "%s", "'proxy_protocol.trust' action doesn't take any parameters");
2073+
return NULL;
2074+
}
2075+
2076+
return li_action_new_function(core_handle_proxy_prot_trust, NULL, NULL, NULL);
2077+
}
2078+
20332079
static void fetch_files_static_lookup(liFetchDatabase* db, gpointer data, liFetchEntry *entry) {
20342080
GHashTable *stringdb = (GHashTable*) data;
20352081
UNUSED(db);
@@ -2217,6 +2263,8 @@ static const liPluginOption options[] = {
22172263

22182264
{ "strict.post_content_length", LI_VALUE_BOOLEAN, TRUE, NULL },
22192265

2266+
{ "proxy_protocol.tlv_max_length", LI_VALUE_NUMBER, -1, NULL },
2267+
22202268
{ NULL, 0, 0, NULL }
22212269
};
22222270

@@ -2270,6 +2318,8 @@ static const liPluginAction actions[] = {
22702318
{ "map", core_map, NULL },
22712319
{ "map_cidr", core_map_cidr, NULL },
22722320

2321+
{ "proxy_protocol.trust", core_proxy_prot_trust, NULL },
2322+
22732323
{ NULL, NULL, NULL }
22742324
};
22752325

0 commit comments

Comments
 (0)