summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorGraham Leggett <minfrin@apache.org>2018-02-14 10:55:44 +0000
committerGraham Leggett <minfrin@apache.org>2018-02-14 10:55:44 +0000
commit397103c9e0944124798587ffb08eca7354742df8 (patch)
tree8bf13374383d3faac5039208aad9edb6b1cc915f /modules
parent602b4c56188d431cc55127bc8bc8f2ecb57d691a (diff)
downloadhttpd-397103c9e0944124798587ffb08eca7354742df8.tar.gz
mod_remoteip: Add PROXY protocol support
trunk patch: http://svn.apache.org/r1776575 http://svn.apache.org/r1776578 (doc fix) http://svn.apache.org/r1776624 http://svn.apache.org/r1776627 (shortened name + doc fix) http://svn.apache.org/r1776674 (attribution moved to CHANGES) http://svn.apache.org/r1776734 http://svn.apache.org/r1776740 (attribution updated in mod_remotip.c) http://svn.apache.org/r1778268 (fix compiler warning) http://svn.apache.org/r1780725 (set buckets aside) http://svn.apache.org/r1781030 (fix strict GCC warning) http://svn.apache.org/r1781031 (reference the filter by handle) http://svn.apache.org/r1781701 (rework optional processing case) http://svn.apache.org/r1788674 (final edge cases/ignore slave conns) http://svn.apache.org/r1789800 (remove optional processing) http://svn.apache.org/r1790169 (rename "exception" directive) http://svn.apache.org/r1790457 (Update directive name in err message) http://svn.apache.org/r1790691 http://svn.apache.org/r1806985 http://svn.apache.org/r1818279 2.4 convenience patch (includes CHANGES): http://home.apache.org/~ylavic/patches/RemoteIPProxyProtocol.2.4-v3.patch +1: druggeri, jim, minfrin [Reverted by r1824246] git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1824221 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'modules')
-rw-r--r--modules/metadata/mod_remoteip.c772
1 files changed, 770 insertions, 2 deletions
diff --git a/modules/metadata/mod_remoteip.c b/modules/metadata/mod_remoteip.c
index 28e01df297..8604d6b95e 100644
--- a/modules/metadata/mod_remoteip.c
+++ b/modules/metadata/mod_remoteip.c
@@ -16,11 +16,13 @@
#include "ap_config.h"
#include "ap_mmn.h"
+#include "ap_listen.h"
#include "httpd.h"
#include "http_config.h"
#include "http_connection.h"
#include "http_protocol.h"
#include "http_log.h"
+#include "http_main.h"
#include "apr_strings.h"
#include "apr_lib.h"
#define APR_WANT_BYTEFUNC
@@ -36,6 +38,12 @@ typedef struct {
void *internal;
} remoteip_proxymatch_t;
+typedef struct remoteip_addr_info {
+ struct remoteip_addr_info *next;
+ apr_sockaddr_t *addr;
+ server_rec *source;
+} remoteip_addr_info;
+
typedef struct {
/** The header to retrieve a proxy-via IP list */
const char *header_name;
@@ -48,6 +56,17 @@ typedef struct {
* with the most commonly encountered listed first
*/
apr_array_header_t *proxymatch_ip;
+
+ remoteip_addr_info *proxy_protocol_enabled;
+ remoteip_addr_info *proxy_protocol_optional;
+ remoteip_addr_info *proxy_protocol_disabled;
+
+ /** A flag indicating whether or not proxyprotocol
+ * is optoinal for this specific server
+ */
+ int pp_optional;
+
+ apr_pool_t *pool;
} remoteip_config_t;
typedef struct {
@@ -59,12 +78,92 @@ typedef struct {
const char *proxied_remote;
} remoteip_req_t;
+/* For PROXY protocol processing */
+static const char *remoteip_filter_name = "REMOTEIP_INPUT";
+
+typedef struct {
+ char line[108];
+} proxy_v1;
+
+typedef union {
+ struct { /* for TCP/UDP over IPv4, len = 12 */
+ apr_uint32_t src_addr;
+ apr_uint32_t dst_addr;
+ apr_uint16_t src_port;
+ apr_uint16_t dst_port;
+ } ip4;
+ struct { /* for TCP/UDP over IPv6, len = 36 */
+ apr_byte_t src_addr[16];
+ apr_byte_t dst_addr[16];
+ apr_uint16_t src_port;
+ apr_uint16_t dst_port;
+ } ip6;
+ struct { /* for AF_UNIX sockets, len = 216 */
+ apr_byte_t src_addr[108];
+ apr_byte_t dst_addr[108];
+ } unx;
+} proxy_v2_addr;
+
+typedef struct {
+ apr_byte_t sig[12]; /* hex 0D 0A 0D 0A 00 0D 0A 51 55 49 54 0A */
+ apr_byte_t ver_cmd; /* protocol version and command */
+ apr_byte_t fam; /* protocol family and address */
+ apr_uint16_t len; /* number of following bytes part of the header */
+ proxy_v2_addr addr;
+} proxy_v2;
+
+typedef union {
+ proxy_v1 v1;
+ proxy_v2 v2;
+} proxy_header;
+
+static const char v2sig[12] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";
+#define MIN_V1_HDR_LEN 15
+#define MIN_V2_HDR_LEN 16
+#define MIN_HDR_LEN MIN_V1_HDR_LEN
+
+/* XXX: Unsure if this is needed if v6 support is not available on
+ this platform */
+#ifndef INET6_ADDRSTRLEN
+#define INET6_ADDRSTRLEN 46
+#endif
+
+typedef struct {
+ char header[sizeof(proxy_header)];
+ apr_size_t rcvd;
+ apr_size_t need;
+ int version;
+ ap_input_mode_t mode;
+ apr_bucket_brigade *bb;
+ int done;
+} remoteip_filter_context;
+
+/** Holds the resolved proxy info for this connection and any addition
+ configurable parameters
+*/
+typedef struct {
+ /** The parsed client address in native format */
+ apr_sockaddr_t *client_addr;
+ /** Character representation of the client */
+ char *client_ip;
+ /** Flag indicating that the PROXY header may be omitted on this
+ connection (do not abort if it is missing). */
+ int proxy_protocol_optional;
+} remoteip_conn_config_t;
+
+typedef enum { HDR_DONE, HDR_ERROR, HDR_MISSING, HDR_NEED_MORE } remoteip_parse_status_t;
+
static void *create_remoteip_server_config(apr_pool_t *p, server_rec *s)
{
remoteip_config_t *config = apr_pcalloc(p, sizeof *config);
/* config->header_name = NULL;
* config->proxies_header_name = NULL;
*/
+ config->proxy_protocol_enabled = NULL;
+ config->proxy_protocol_optional = NULL;
+ config->proxy_protocol_disabled = NULL;
+ config->pp_optional = 0;
+ config->pool = p;
return config;
}
@@ -85,6 +184,9 @@ static void *merge_remoteip_server_config(apr_pool_t *p, void *globalv,
config->proxymatch_ip = server->proxymatch_ip
? server->proxymatch_ip
: global->proxymatch_ip;
+ config->pp_optional = server->pp_optional
+ ? server->pp_optional
+ : global->pp_optional;
return config;
}
@@ -215,13 +317,184 @@ static const char *proxylist_read(cmd_parms *cmd, void *cfg,
return NULL;
}
+/** Similar apr_sockaddr_equal, except that it compares ports too. */
+static int remoteip_sockaddr_equal(apr_sockaddr_t *addr1, apr_sockaddr_t *addr2)
+{
+ return (addr1->port == addr2->port && apr_sockaddr_equal(addr1, addr2));
+}
+
+/** Similar remoteip_sockaddr_equal, except that it handles wildcard addresses
+ * and ports too.
+ */
+static int remoteip_sockaddr_compat(apr_sockaddr_t *addr1, apr_sockaddr_t *addr2)
+{
+ /* test exact address equality */
+ if (apr_sockaddr_equal(addr1, addr2) &&
+ (addr1->port == addr2->port || addr1->port == 0 || addr2->port == 0)) {
+ return 1;
+ }
+
+ /* test address wildcards */
+ if (apr_sockaddr_is_wildcard(addr1) &&
+ (addr1->port == 0 || addr1->port == addr2->port)) {
+ return 1;
+ }
+
+ if (apr_sockaddr_is_wildcard(addr2) &&
+ (addr2->port == 0 || addr2->port == addr1->port)) {
+ return 1;
+ }
+
+ return 0;
+}
+
+static int remoteip_addr_in_list(remoteip_addr_info *list, apr_sockaddr_t *addr)
+{
+ for (; list; list = list->next) {
+ if (remoteip_sockaddr_compat(list->addr, addr)) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static void remoteip_warn_enable_conflict(remoteip_addr_info *prev, server_rec *new, const char* arg)
+{
+ char buf[INET6_ADDRSTRLEN];
+
+ apr_sockaddr_ip_getbuf(buf, sizeof(buf), prev->addr);
+
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, new, APLOGNO(03491)
+ "RemoteIPProxyProtocolEnable: previous setting for %s:%hu from virtual "
+ "host {%s:%hu in %s} is being overriden by virtual host "
+ "{%s:%hu in %s}; new setting is '%s'",
+ buf, prev->addr->port, prev->source->server_hostname,
+ prev->source->addrs->host_port, prev->source->defn_name,
+ new->server_hostname, new->addrs->host_port, new->defn_name,
+ arg);
+}
+
+static const char *remoteip_enable_proxy_protocol(cmd_parms *cmd, void *config,
+ const char *arg)
+{
+ remoteip_config_t *global_conf;
+ remoteip_config_t *server_conf;
+ server_addr_rec *addr;
+ remoteip_addr_info **add;
+ int list_len = 2;
+ remoteip_addr_info **rem_list[list_len];
+ remoteip_addr_info *list;
+ int i;
+
+ global_conf = ap_get_module_config(ap_server_conf->module_config,
+ &remoteip_module);
+ server_conf = ap_get_module_config(cmd->server->module_config,
+ &remoteip_module);
+
+ if (strcasecmp(arg, "On") == 0) {
+ add = &global_conf->proxy_protocol_enabled;
+ rem_list[0] = &global_conf->proxy_protocol_optional;
+ rem_list[1] = &global_conf->proxy_protocol_disabled;
+ }
+ else if (strcasecmp(arg, "Optional") == 0) {
+ add = &global_conf->proxy_protocol_optional;
+ rem_list[0] = &global_conf->proxy_protocol_enabled;
+ rem_list[1] = &global_conf->proxy_protocol_disabled;
+ server_conf->pp_optional = 1;
+ }
+ else if (strcasecmp(arg, "Off") == 0 ) {
+ add = &global_conf->proxy_protocol_disabled;
+ rem_list[0] = &global_conf->proxy_protocol_enabled;
+ rem_list[1] = &global_conf->proxy_protocol_optional;
+ }
+ else {
+ return apr_pstrcat(cmd->pool, "Unrecognized option for ProxyProtocolEnable `%s'", arg, NULL);
+ }
+
+ for (addr = cmd->server->addrs; addr; addr = addr->next) {
+ /* remove address from other lists */
+ for (i = 0; i < list_len ; i++) {
+ remoteip_addr_info **rem = rem_list[i];
+ if (*rem) {
+ if (remoteip_sockaddr_equal((*rem)->addr, addr->host_addr)) {
+ remoteip_warn_enable_conflict(*rem, cmd->server, arg);
+ *rem = (*rem)->next;
+ }
+ else {
+ for (list = *rem; list->next; list = list->next) {
+ if (remoteip_sockaddr_equal(list->next->addr, addr->host_addr)) {
+ remoteip_warn_enable_conflict(list->next, cmd->server, arg);
+ list->next = list->next->next;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /* add address to desired list */
+ if (!remoteip_addr_in_list(*add, addr->host_addr)) {
+ remoteip_addr_info *info = apr_palloc(global_conf->pool, sizeof(*info));
+ info->addr = addr->host_addr;
+ info->source = cmd->server;
+ info->next = *add;
+ *add = info;
+ }
+ }
+
+ return NULL;
+}
+
+static int remoteip_hook_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp)
+{
+ remoteip_config_t *config = (remoteip_config_t *)
+ create_remoteip_server_config(pconf, NULL);
+ ap_set_module_config(ap_server_conf->module_config, &remoteip_module,
+ config);
+
+ return OK;
+}
+
+static int remoteip_hook_post_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ remoteip_config_t *conf;
+ remoteip_addr_info *info;
+ char buf[INET6_ADDRSTRLEN];
+
+ conf = ap_get_module_config(ap_server_conf->module_config,
+ &remoteip_module);
+
+ for (info = conf->proxy_protocol_enabled; info; info = info->next) {
+ apr_sockaddr_ip_getbuf(buf, sizeof(buf), info->addr);
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, APLOGNO(03492)
+ "RemoteIPProxyProtocol: enabled on %s:%hu", buf, info->addr->port);
+ }
+ for (info = conf->proxy_protocol_optional; info; info = info->next) {
+ apr_sockaddr_ip_getbuf(buf, sizeof(buf), info->addr);
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, APLOGNO(03493)
+ "RemoteIPProxyProtocol: optional on %s:%hu", buf, info->addr->port);
+ }
+ for (info = conf->proxy_protocol_disabled; info; info = info->next) {
+ apr_sockaddr_ip_getbuf(buf, sizeof(buf), info->addr);
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, APLOGNO(03494)
+ "RemoteIPProxyProtocol: disabled on %s:%hu", buf, info->addr->port);
+ }
+
+ return OK;
+}
+
static int remoteip_modify_request(request_rec *r)
{
conn_rec *c = r->connection;
remoteip_config_t *config = (remoteip_config_t *)
ap_get_module_config(r->server->module_config, &remoteip_module);
- remoteip_req_t *req = NULL;
+ remoteip_conn_config_t *conn_config = (remoteip_conn_config_t *)
+ ap_get_module_config(r->connection->conn_config, &remoteip_module);
+ remoteip_req_t *req = NULL;
apr_sockaddr_t *temp_sa;
apr_status_t rv;
@@ -237,10 +510,37 @@ static int remoteip_modify_request(request_rec *r)
*/
void *internal = NULL;
- if (!config->header_name) {
+ /* No header defined or results from our input filter */
+ if (!config->header_name && !conn_config) {
return DECLINED;
}
+ /* Easy parsing case - just position the data we already have from PROXY
+ protocol handling allowing it to take precedence and return
+ */
+ if (conn_config) {
+ /* We may have gotten here if processing was optional - check for that */
+ if (!conn_config->client_addr) {
+ if (config->pp_optional) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03495)
+ "RemoteIPProxyProtocol data is missing, but was optional. Allowing request.");
+ return OK;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(03496)
+ "RemoteIPProxyProtocol data is missing, but required! Aborting request.");
+ return HTTP_BAD_REQUEST;
+ }
+ }
+
+ r->useragent_addr = conn_config->client_addr;
+ r->useragent_ip = conn_config->client_ip;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "Using %s as client's IP from PROXY protocol", r->useragent_ip);
+ return OK;
+ }
+
if (config->proxymatch_ip) {
/* This indicates that a RemoteIPInternalProxy, RemoteIPInternalProxyList, RemoteIPTrustedProxy
or RemoteIPTrustedProxyList directive is configured.
@@ -424,6 +724,464 @@ static int remoteip_modify_request(request_rec *r)
return OK;
}
+static int remoteip_is_server_port(apr_port_t port)
+{
+ ap_listen_rec *lr;
+
+ for (lr = ap_listeners; lr; lr = lr->next) {
+ if (lr->bind_addr && lr->bind_addr->port == port) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Human readable format:
+ * PROXY {TCP4|TCP6|UNKNOWN} <client-ip-addr> <dest-ip-addr> <client-port> <dest-port><CR><LF>
+ */
+static remoteip_parse_status_t remoteip_process_v1_header(conn_rec *c,
+ remoteip_conn_config_t *conn_conf,
+ proxy_header *hdr, apr_size_t len,
+ apr_size_t *hdr_len)
+{
+ char *end, *word, *host, *valid_addr_chars, *saveptr;
+ char buf[sizeof(hdr->v1.line)];
+ apr_port_t port;
+ apr_status_t ret;
+ apr_int32_t family;
+
+#define GET_NEXT_WORD(field) \
+ word = apr_strtok(NULL, " ", &saveptr); \
+ if (!word) { \
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(03497) \
+ "RemoteIPProxyProtocol: no " field " found in header '%s'", \
+ hdr->v1.line); \
+ return HDR_ERROR; \
+ }
+
+ end = memchr(hdr->v1.line, '\r', len - 1);
+ if (!end || end[1] != '\n') {
+ return HDR_NEED_MORE; /* partial or invalid header */
+ }
+
+ *end = '\0';
+ *hdr_len = end + 2 - hdr->v1.line; /* skip header + CRLF */
+
+ /* parse in separate buffer so have the original for error messages */
+ strcpy(buf, hdr->v1.line);
+
+ apr_strtok(buf, " ", &saveptr);
+
+ /* parse family */
+ GET_NEXT_WORD("family")
+ if (strcmp(word, "UNKNOWN") == 0) {
+ conn_conf->client_addr = c->client_addr;
+ conn_conf->client_ip = c->client_ip;
+ return HDR_DONE;
+ }
+ else if (strcmp(word, "TCP4") == 0) {
+ family = APR_INET;
+ valid_addr_chars = "0123456789.";
+ }
+ else if (strcmp(word, "TCP6") == 0) {
+#if APR_HAVE_IPV6
+ family = APR_INET6;
+ valid_addr_chars = "0123456789abcdefABCDEF:";
+#else
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(03498)
+ "RemoteIPProxyProtocol: Unable to parse v6 address - APR is not compiled with IPv6 support",
+ word, hdr->v1.line);
+ return HDR_ERROR;
+#endif
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(03499)
+ "RemoteIPProxyProtocol: unknown family '%s' in header '%s'",
+ word, hdr->v1.line);
+ return HDR_ERROR;
+ }
+
+ /* parse client-addr */
+ GET_NEXT_WORD("client-address")
+
+ if (strspn(word, valid_addr_chars) != strlen(word)) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(03500)
+ "RemoteIPProxyProtocol: invalid client-address '%s' found in "
+ "header '%s'", word, hdr->v1.line);
+ return HDR_ERROR;
+ }
+
+ host = word;
+
+ /* parse dest-addr */
+ GET_NEXT_WORD("destination-address")
+
+ /* parse client-port */
+ GET_NEXT_WORD("client-port")
+ if (sscanf(word, "%hu", &port) != 1) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(03501)
+ "RemoteIPProxyProtocol: error parsing port '%s' in header '%s'",
+ word, hdr->v1.line);
+ return HDR_ERROR;
+ }
+
+ /* parse dest-port */
+ /* GET_NEXT_WORD("destination-port") - no-op since we don't care about it */
+
+ /* create a socketaddr from the info */
+ ret = apr_sockaddr_info_get(&conn_conf->client_addr, host, family, port, 0,
+ c->pool);
+ if (ret != APR_SUCCESS) {
+ conn_conf->client_addr = NULL;
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, ret, c, APLOGNO(03502)
+ "RemoteIPProxyProtocol: error converting family '%d', host '%s',"
+ " and port '%hu' to sockaddr; header was '%s'",
+ family, host, port, hdr->v1.line);
+ return HDR_ERROR;
+ }
+
+ conn_conf->client_ip = apr_pstrdup(c->pool, host);
+
+ return HDR_DONE;
+}
+
+/** Add our filter to the connection if it is requested
+ */
+static int remoteip_hook_pre_connection(conn_rec *c, void *csd)
+{
+ remoteip_config_t *conf;
+ remoteip_conn_config_t *conn_conf;
+ int optional;
+
+ conf = ap_get_module_config(ap_server_conf->module_config,
+ &remoteip_module);
+
+ /* Used twice - do the check only once */
+ optional = remoteip_addr_in_list(conf->proxy_protocol_optional, c->local_addr);
+
+ /* check if we're enabled for this connection */
+ if ((!remoteip_addr_in_list(conf->proxy_protocol_enabled, c->local_addr)
+ && !optional )
+ || remoteip_addr_in_list(conf->proxy_protocol_disabled, c->local_addr)) {
+ return DECLINED;
+ }
+
+ /* mod_proxy creates outgoing connections - we don't want those */
+ if (!remoteip_is_server_port(c->local_addr->port)) {
+ return DECLINED;
+ }
+
+ /* add our filter */
+ if (!ap_add_input_filter(remoteip_filter_name, NULL, NULL, c)) {
+ /* XXX: Shouldn't this WARN in log? */
+ return DECLINED;
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03503)
+ "RemoteIPProxyProtocol: enabled on connection to %s:%hu",
+ c->local_ip, c->local_addr->port);
+
+ /* this holds the resolved proxy info for this connection */
+ conn_conf = apr_pcalloc(c->pool, sizeof(*conn_conf));
+
+ /* Propagate the optional flag so the connection handler knows not to
+ abort if the header is mising. NOTE: This means we must check after
+ we read the request that the header was NOT optional, too.
+ */
+ conn_conf->proxy_protocol_optional = optional;
+
+ ap_set_module_config(c->conn_config, &remoteip_module, conn_conf);
+
+ return OK;
+}
+
+/* Binary format:
+ * <sig><cmd><proto><addr-len><addr>
+ * sig = \x0D \x0A \x0D \x0A \x00 \x0D \x0A \x51 \x55 \x49 \x54 \x0A
+ * cmd = <4-bits-version><4-bits-command>
+ * 4-bits-version = \x02
+ * 4-bits-command = {\x00|\x01} (\x00 = LOCAL: discard con info; \x01 = PROXY)
+ * proto = <4-bits-family><4-bits-protocol>
+ * 4-bits-family = {\x00|\x01|\x02|\x03} (AF_UNSPEC, AF_INET, AF_INET6, AF_UNIX)
+ * 4-bits-protocol = {\x00|\x01|\x02} (UNSPEC, STREAM, DGRAM)
+ */
+static remoteip_parse_status_t remoteip_process_v2_header(conn_rec *c,
+ remoteip_conn_config_t *conn_conf,
+ proxy_header *hdr)
+{
+ apr_status_t ret;
+
+ switch (hdr->v2.ver_cmd & 0xF) {
+ case 0x01: /* PROXY command */
+ switch (hdr->v2.fam) {
+ case 0x11: /* TCPv4 */
+ ret = apr_sockaddr_info_get(&conn_conf->client_addr, NULL,
+ APR_INET,
+ ntohs(hdr->v2.addr.ip4.src_port),
+ 0, c->pool);
+ if (ret != APR_SUCCESS) {
+ conn_conf->client_addr = NULL;
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, ret, c, APLOGNO(03504)
+ "RemoteIPPProxyProtocol: error creating sockaddr");
+ return HDR_ERROR;
+ }
+
+ conn_conf->client_addr->sa.sin.sin_addr.s_addr =
+ hdr->v2.addr.ip4.src_addr;
+ break;
+
+ case 0x21: /* TCPv6 */
+#if APR_HAVE_IPV6
+ ret = apr_sockaddr_info_get(&conn_conf->client_addr, NULL,
+ APR_INET6,
+ ntohs(hdr->v2.addr.ip6.src_port),
+ 0, c->pool);
+ if (ret != APR_SUCCESS) {
+ conn_conf->client_addr = NULL;
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, ret, c, APLOGNO(03505)
+ "RemoteIPProxyProtocol: error creating sockaddr");
+ return HDR_ERROR;
+ }
+ memcpy(&conn_conf->client_addr->sa.sin6.sin6_addr.s6_addr,
+ hdr->v2.addr.ip6.src_addr, 16);
+ break;
+#else
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(03506)
+ "RemoteIPProxyProtocol: APR is not compiled with IPv6 support");
+ return HDR_ERROR;
+#endif
+ default:
+ /* unsupported protocol, keep local connection address */
+ return HDR_DONE;
+ }
+ break; /* we got a sockaddr now */
+
+ case 0x00: /* LOCAL command */
+ /* keep local connection address for LOCAL */
+ return HDR_DONE;
+
+ default:
+ /* not a supported command */
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(03507)
+ "RemoteIPProxyProtocol: unsupported command %.2hx",
+ (unsigned short)hdr->v2.ver_cmd);
+ return HDR_ERROR;
+ }
+
+ /* got address - compute the client_ip from it */
+ ret = apr_sockaddr_ip_get(&conn_conf->client_ip, conn_conf->client_addr);
+ if (ret != APR_SUCCESS) {
+ conn_conf->client_addr = NULL;
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, ret, c, APLOGNO(03508)
+ "RemoteIPProxyProtocol: error converting address to string");
+ return HDR_ERROR;
+ }
+
+ return HDR_DONE;
+}
+
+/** Determine if this is a v1 or v2 PROXY header.
+ */
+static int remoteip_determine_version(conn_rec *c, const char *ptr)
+{
+ proxy_header *hdr = (proxy_header *) ptr;
+
+ /* assert len >= 14 */
+
+ if (memcmp(&hdr->v2, v2sig, sizeof(v2sig)) == 0 &&
+ (hdr->v2.ver_cmd & 0xF0) == 0x20) {
+ return 2;
+ }
+ else if (memcmp(hdr->v1.line, "PROXY ", 6) == 0) {
+ return 1;
+ }
+ else {
+ return -1;
+ }
+}
+
+/* Capture the first bytes on the protocol and parse the proxy protocol header.
+ * Removes itself when the header is complete.
+ */
+static apr_status_t remoteip_input_filter(ap_filter_t *f,
+ apr_bucket_brigade *bb_out,
+ ap_input_mode_t mode,
+ apr_read_type_e block,
+ apr_off_t readbytes)
+{
+ apr_status_t ret;
+ remoteip_filter_context *ctx = f->ctx;
+ remoteip_conn_config_t *conn_conf;
+ apr_bucket *b;
+ remoteip_parse_status_t psts;
+ const char *ptr;
+ apr_size_t len;
+
+ if (f->c->aborted) {
+ return APR_ECONNABORTED;
+ }
+
+ /* allocate/retrieve the context that holds our header */
+ if (!ctx) {
+ ctx = f->ctx = apr_palloc(f->c->pool, sizeof(*ctx));
+ ctx->rcvd = 0;
+ ctx->need = MIN_HDR_LEN;
+ ctx->version = 0;
+ ctx->mode = AP_MODE_READBYTES;
+ ctx->bb = apr_brigade_create(f->c->pool, f->c->bucket_alloc);
+ ctx->done = 0;
+ }
+
+ if (ctx->done) {
+ /* Note: because we're a connection filter we can't remove ourselves
+ * when we're done, so we have to stay in the chain and just go into
+ * passthrough mode.
+ */
+ return ap_get_brigade(f->next, bb_out, mode, block, readbytes);
+ }
+
+ conn_conf = ap_get_module_config(f->c->conn_config, &remoteip_module);
+
+ /* try to read a header's worth of data */
+ while (!ctx->done) {
+ if (APR_BRIGADE_EMPTY(ctx->bb)) {
+ ret = ap_get_brigade(f->next, ctx->bb, ctx->mode, block,
+ ctx->need - ctx->rcvd);
+ if (ret != APR_SUCCESS) {
+ return ret;
+ }
+ }
+ if (APR_BRIGADE_EMPTY(ctx->bb)) {
+ return APR_EOF;
+ }
+
+ while (!ctx->done && !APR_BRIGADE_EMPTY(ctx->bb)) {
+ b = APR_BRIGADE_FIRST(ctx->bb);
+
+ ret = apr_bucket_read(b, &ptr, &len, block);
+ if (APR_STATUS_IS_EAGAIN(ret) && block == APR_NONBLOCK_READ) {
+ return APR_SUCCESS;
+ }
+ if (ret != APR_SUCCESS) {
+ return ret;
+ }
+
+ memcpy(ctx->header + ctx->rcvd, ptr, len);
+ ctx->rcvd += len;
+
+ /* Remove instead of delete - we may put this bucket
+ back into bb_out if the header was optional and we
+ pass down the chain */
+ APR_BUCKET_REMOVE(b);
+ psts = HDR_NEED_MORE;
+
+ if (ctx->version == 0) {
+ /* reading initial chunk */
+ if (ctx->rcvd >= MIN_HDR_LEN) {
+ ctx->version = remoteip_determine_version(f->c, ctx->header);
+ if (ctx->version < 0) {
+ psts = HDR_MISSING;
+ }
+ else if (ctx->version == 1) {
+ ctx->mode = AP_MODE_GETLINE;
+ ctx->need = sizeof(proxy_v1);
+ }
+ else if (ctx->version == 2) {
+ ctx->need = MIN_V2_HDR_LEN;
+ }
+ }
+ }
+ else if (ctx->version == 1) {
+ psts = remoteip_process_v1_header(f->c, conn_conf,
+ (proxy_header *) ctx->header,
+ ctx->rcvd, &ctx->need);
+ }
+ else if (ctx->version == 2) {
+ if (ctx->rcvd >= MIN_V2_HDR_LEN) {
+ ctx->need = MIN_V2_HDR_LEN +
+ ntohs(((proxy_header *) ctx->header)->v2.len);
+ }
+ if (ctx->rcvd >= ctx->need) {
+ psts = remoteip_process_v2_header(f->c, conn_conf,
+ (proxy_header *) ctx->header);
+ }
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, f->c, APLOGNO(03509)
+ "RemoteIPProxyProtocol: internal error: unknown version "
+ "%d", ctx->version);
+ f->c->aborted = 1;
+ apr_bucket_delete(b);
+ apr_brigade_destroy(ctx->bb);
+ return APR_ECONNABORTED;
+ }
+
+ switch (psts) {
+ case HDR_MISSING:
+ if (conn_conf->proxy_protocol_optional) {
+ /* Same as DONE, but don't delete the bucket. Rather, put it
+ back into the brigade and move the request along the stack */
+ ctx->done = 1;
+ APR_BRIGADE_INSERT_HEAD(bb_out, b);
+ return ap_pass_brigade(f->next, ctx->bb);
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, f->c, APLOGNO(03510)
+ "RemoteIPProxyProtocol: no valid PROXY header found");
+ /* fall through to error case */
+ }
+ case HDR_ERROR:
+ f->c->aborted = 1;
+ apr_bucket_delete(b);
+ apr_brigade_destroy(ctx->bb);
+ return APR_ECONNABORTED;
+
+ case HDR_DONE:
+ apr_bucket_delete(b);
+ ctx->done = 1;
+ break;
+
+ case HDR_NEED_MORE:
+ /* It is safe to delete this bucket if we get here since we know
+ that we are definitely processing a header (we've read enough to
+ know if the signatures exist on the line) */
+ apr_bucket_delete(b);
+ break;
+ }
+ }
+ }
+
+ /* we only get here when done == 1 */
+ if (psts == HDR_DONE) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, APLOGNO(03511)
+ "RemoteIPProxyProtocol: received valid PROXY header: %s:%hu",
+ conn_conf->client_ip, conn_conf->client_addr->port);
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, APLOGNO(03512)
+ "RemoteIPProxyProtocol: PROXY header was missing");
+ }
+
+ if (ctx->rcvd > ctx->need || !APR_BRIGADE_EMPTY(ctx->bb)) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, f->c, APLOGNO(03513)
+ "RemoteIPProxyProtocol: internal error: have data left over; "
+ " need=%lu, rcvd=%lu, brigade-empty=%d", ctx->need,
+ ctx->rcvd, APR_BRIGADE_EMPTY(ctx->bb));
+ f->c->aborted = 1;
+ apr_brigade_destroy(ctx->bb);
+ return APR_ECONNABORTED;
+ }
+
+ /* clean up */
+ apr_brigade_destroy(ctx->bb);
+ ctx->bb = NULL;
+
+ /* now do the real read for the upper layer */
+ return ap_get_brigade(f->next, bb_out, mode, block, readbytes);
+}
+
static const command_rec remoteip_cmds[] =
{
AP_INIT_TAKE1("RemoteIPHeader", header_name_set, NULL, RSRC_CONF,
@@ -447,11 +1205,21 @@ static const command_rec remoteip_cmds[] =
RSRC_CONF | EXEC_ON_READ,
"The filename to read the list of internal proxies, "
"see the RemoteIPInternalProxy directive"),
+ AP_INIT_TAKE1("RemoteIPProxyProtocolEnable", remoteip_enable_proxy_protocol, NULL,
+ RSRC_CONF, "Enable proxy-protocol handling (`on', `off')"),
{ NULL }
};
static void register_hooks(apr_pool_t *p)
{
+ /* mod_ssl is CONNECTION + 5, so we want something higher (earlier);
+ * mod_reqtimeout is CONNECTION + 8, so we want something lower (later) */
+ ap_register_input_filter(remoteip_filter_name, remoteip_input_filter, NULL,
+ AP_FTYPE_CONNECTION + 7);
+
+ ap_hook_pre_config(remoteip_hook_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_post_config(remoteip_hook_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_pre_connection(remoteip_hook_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_post_read_request(remoteip_modify_request, NULL, NULL, APR_HOOK_FIRST);
}