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 >