On 12/30/2016 03:20 PM, [email protected] wrote:
> Author: druggeri
> Date: Fri Dec 30 14:20:48 2016
> New Revision: 1776575
>
> URL: http://svn.apache.org/viewvc?rev=1776575&view=rev
> Log:
> Merge new PROXY protocol code into mod_remoteip
>
> Modified:
> httpd/httpd/trunk/docs/log-message-tags/next-number
> httpd/httpd/trunk/docs/manual/mod/mod_remoteip.xml
> httpd/httpd/trunk/modules/metadata/mod_remoteip.c
>
> ==============================================================================
> --- httpd/httpd/trunk/modules/metadata/mod_remoteip.c (original)
> +++ httpd/httpd/trunk/modules/metadata/mod_remoteip.c Fri Dec 30 14:20:48 2016
> @@ -427,6 +730,464 @@ static int remoteip_modify_request(reque
> 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;
> + }
Why is the c->local_addr->port set to a port we are listening on in case of
proxy connections?
> +
> + /* 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",
> + 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)) {
What about the case of an non blocking read where the upstream filter returns
an empty brigade
and APR_SUCCESS. This is equal to returning EAGAIN.
> + 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);
See below. We need to restore all buckets. What if the original read was
speculative?
> + return ap_pass_brigade(f->next, ctx->bb);
Why using ap_pass_brigade on f->next? We are an input filter and f->next is our
upstream input filter.
> + }
> + 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) */
No. We might not even received MIN_HDR_LEN data so far and thus did not check
for the signature
so far. We need to safe all the buckets that we might need to restore in a
separate bucket brigade,
that we need to be set aside before doing the next ap_get_brigade to avoid
killing transient buckets.
> + 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,
Regards
Rüdiger