Let me take a look... afaict, this is a copy of what was donated, which has been working for numerous people. But that doesn't mean it can't have bugs ;)
> On Jan 16, 2017, at 7:20 AM, Ruediger Pluem <rpl...@apache.org> wrote: > > Anyone? > > Regards > > Rüdiger > > On 01/10/2017 12:39 PM, Ruediger Pluem wrote: >> >> >> On 12/30/2016 03:20 PM, drugg...@apache.org 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 >>