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