/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License.
*/
/* * Opaque structure containing infos for CONNECT-ing an origin server through a * remote (forward) proxy. Saved in the (opaque) proxy_conn_rec::forward pointer * field for backend connections kept alive, allowing for reuse when subsequent * requests should be routed through the same remote proxy.
*/ typedefstruct { constchar *proxy_auth; /* Proxy authorization */ constchar *target_host; /* Target/origin hostname */
apr_port_t target_port; /* Target/origin port */
} remote_connect_info;
/* * Opaque structure containing a refcounted and TTL'ed address.
*/ typedefstruct proxy_address {
apr_sockaddr_t *addr; /* Remote address info */ constchar *hostname; /* Remote host name */
apr_port_t hostport; /* Remote host port */
apr_uint32_t refcount; /* Number of conns and/or worker using it */
apr_uint32_t expiry; /* Expiry timestamp (seconds to proxy_start_time) */
} proxy_address;
/* special case handling */ if (!dlen) { /* XXX: APR_ENOSPACE would be better */ return APR_EGENERAL;
} if (!src) {
*dst = '\0'; return APR_SUCCESS;
}
thenil = apr_cpystrn(dst, src, dlen);
thelen = thenil - dst; if (src[thelen] == '\0') { return APR_SUCCESS;
} return APR_EGENERAL;
}
/* already called in the knowledge that the characters are hex digits */
PROXY_DECLARE(int) ap_proxy_hex2c(constchar *x)
{ int i;
#if !APR_CHARSET_EBCDIC int ch = x[0];
if (apr_isdigit(ch)) {
i = ch - '0';
} elseif (apr_isupper(ch)) {
i = ch - ('A' - 10);
} else {
i = ch - ('a' - 10);
}
i <<= 4;
ch = x[1]; if (apr_isdigit(ch)) {
i += ch - '0';
} elseif (apr_isupper(ch)) {
i += ch - ('A' - 10);
} else {
i += ch - ('a' - 10);
} return i; #else/*APR_CHARSET_EBCDIC*/ /* * we assume that the hex value refers to an ASCII character * so convert to EBCDIC so that it makes sense locally; * * example: * * client specifies %20 in URL to refer to a space char; * at this point we're called with EBCDIC "20"; after turning * EBCDIC "20" into binary 0x20, we then need to assume that 0x20 * represents an ASCII char and convert 0x20 to EBCDIC, yielding * 0x40
*/ char buf[1];
/* * Convert a URL-encoded string to canonical form. * It decodes characters which need not be encoded, * and encodes those which must be encoded, and does not touch * those which must not be touched.
*/
PROXY_DECLARE(char *)ap_proxy_canonenc_ex(apr_pool_t *p, constchar *x, int len, enum enctype t, int flags, int proxyreq)
{ int i, j, ch; char *y; char *allowed; /* characters which should not be encoded */ char *reserved; /* characters which much not be en/de-coded */ int forcedec = flags & PROXY_CANONENC_FORCEDEC; int noencslashesenc = flags & PROXY_CANONENC_NOENCODEDSLASHENCODING;
/* * N.B. in addition to :@&=, this allows ';' in an http path * and '?' in an ftp path -- this may be revised * * Also, it makes a '+' character in a search string reserved, as * it may be form-encoded. (Although RFC 1738 doesn't allow this - * it only permits ; / ? : @ = & as reserved chars.)
*/ if (t == enc_path) {
allowed = "~$-_.+!*'(),;:@&=";
} elseif (t == enc_search) {
allowed = "$-_.!*'(),;:@&=";
} elseif (t == enc_user) {
allowed = "$-_.+!*'(),;@&=";
} elseif (t == enc_fpath) {
allowed = "$-_.+!*'(),?:@&=";
} else { /* if (t == enc_parm) */
allowed = "$-_.+!*'(),?/:@&=";
}
for (i = 0, j = 0; i < len; i++, j++) { /* always handle '/' first */
ch = x[i]; if (strchr(reserved, ch)) {
y[j] = ch; continue;
} /* * decode it if not already done. do not decode reverse proxied URLs * unless specifically forced
*/ if ((forcedec || noencslashesenc
|| (proxyreq && proxyreq != PROXYREQ_REVERSE)) && ch == '%') { if (!apr_isxdigit(x[i + 1]) || !apr_isxdigit(x[i + 2])) { return NULL;
}
ch = ap_proxy_hex2c(&x[i + 1]); if (ch != 0 && strchr(reserved, ch)) { /* keep it encoded */
y[j++] = x[i++];
y[j++] = x[i++];
y[j] = x[i]; continue;
} if (noencslashesenc && !forcedec && (proxyreq == PROXYREQ_REVERSE)) { /* * In the reverse proxy case when we only want to keep encoded * slashes untouched revert back to '%' which will cause * '%' to be encoded in the following.
*/
ch = '%';
} else {
i += 2;
}
} /* recode it, if necessary */ if (!apr_isalnum(ch) && !strchr(allowed, ch)) {
ap_proxy_c2hex(ch, &y[j]);
j += 2;
} else {
y[j] = ch;
}
}
y[j] = '\0'; return y;
}
/* * Convert a URL-encoded string to canonical form. * It decodes characters which need not be encoded, * and encodes those which must be encoded, and does not touch * those which must not be touched.
*/
PROXY_DECLARE(char *)ap_proxy_canonenc(apr_pool_t *p, constchar *x, int len, enum enctype t, int forcedec, int proxyreq)
{ int flags;
/* * Parses network-location. * urlp on input the URL; on output the path, after the leading / * user NULL if no user/password permitted * password holder for password * host holder for host * port port number; only set if one is supplied. * * Returns an error string.
*/
PROXY_DECLARE(char *)
ap_proxy_canon_netloc(apr_pool_t *p, char **const urlp, char **userp, char **passwordp, char **hostp, apr_port_t *port)
{ char *addr, *scope_id, *strp, *host, *url = *urlp; char *user = NULL, *password = NULL;
apr_port_t tmp_port;
apr_status_t rv;
user = ap_proxy_canonenc(p, user, strlen(user), enc_user, 1, 0); if (user == NULL) { return"Bad %-escape in URL (username)";
}
} if (userp != NULL) {
*userp = user;
} if (passwordp != NULL) {
*passwordp = password;
}
/* * Parse the host string to separate host portion from optional port. * Perform range checking on port.
*/
rv = apr_parse_addr_port(&addr, &scope_id, &tmp_port, host, p); if (rv != APR_SUCCESS || addr == NULL || scope_id != NULL) { return"Invalid host/port";
} if (tmp_port != 0) { /* only update caller's port if port was specified */
*port = tmp_port;
}
ap_str_tolower(addr); /* DNS names are case-insensitive */
*urlp = url;
*hostp = addr;
return NULL;
}
staticint proxyerror_core(request_rec *r, int statuscode, constchar *message,
apr_status_t rv)
{
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00898) "%s returned by %s", message, r->uri);
apr_table_setn(r->notes, "error-notes",
apr_pstrcat(r->pool, "The proxy server could not handle the request<p>" "Reason: <strong>", ap_escape_html(r->pool, message), "</strong></p>",
NULL));
/* Allow "error-notes" string to be printed by ap_send_error_response() */
apr_table_setn(r->notes, "verbose-error-to", "*");
return host; /* ought to return the port, too */
}
/* Return TRUE if addr represents an IP address (or an IP network address) */
PROXY_DECLARE(int) ap_proxy_is_ipaddr(struct dirconn_entry *This, apr_pool_t *p)
{ constchar *addr = This->name; long ip_addr[4]; int i, quads; long bits;
/* * if the address is given with an explicit netmask, use that * Due to a deficiency in apr_inet_addr(), it is impossible to parse * "partial" addresses (with less than 4 quads) correctly, i.e. * 192.168.123 is parsed as 192.168.0.123, which is not what I want. * I therefore have to parse the IP address manually: * if (proxy_readmask(This->name, &This->addr.s_addr, &This->mask.s_addr) == 0) * addr and mask were set by proxy_readmask() * return 1;
*/
/* * Parse IP addr manually, optionally allowing * abbreviated net addresses like 192.168.
*/
/* Iterate over up to 4 (dotted) quads. */ for (quads = 0; quads < 4 && *addr != '\0'; ++quads) { char *tmp;
/* Return TRUE if addr represents an IP address (or an IP network address) */ staticint proxy_match_ipaddr(struct dirconn_entry *This, request_rec *r)
{ int i, ip_addr[4]; struct in_addr addr, *ip; constchar *host = proxy_get_host_of_request(r);
/* Try to deal with multiple IP addr's for a host */ /* FIXME: This needs to be able to deal with IPv6 */ while (reqaddr) {
ip = (struct in_addr *) reqaddr->ipaddr_ptr; if (This->addr.s_addr == (ip->s_addr & This->mask.s_addr)) { #if DEBUGGING
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00910) "3)IP-Match: %s[%s] <-> ", host, inet_ntoa(*ip));
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00911) "%s/", inet_ntoa(This->addr));
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00912) "%s", inet_ntoa(This->mask)); #endif return 1;
} #if DEBUGGING else {
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00913) "3)IP-NoMatch: %s[%s] <-> ", host, inet_ntoa(*ip));
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00914) "%s/", inet_ntoa(This->addr));
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00915) "%s", inet_ntoa(This->mask));
} #endif
reqaddr = reqaddr->next;
}
}
return 0;
}
/* Return TRUE if addr represents a domain name */
PROXY_DECLARE(int) ap_proxy_is_domainname(struct dirconn_entry *This, apr_pool_t *p)
{ char *addr = This->name; int i;
/* Domain name must start with a '.' */ if (addr[0] != '.') { return 0;
}
/* rfc1035 says DNS names must consist of "[-a-zA-Z0-9]" and '.' */ for (i = 0; apr_isalnum(addr[i]) || addr[i] == '-' || addr[i] == '.'; ++i) { continue;
}
#if 0 if (addr[i] == ':') {
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(03234) "@@@@ handle optional port in proxy_is_domainname()"); /* @@@@ handle optional port */
} #endif
if (addr[i] != '\0') { return 0;
}
/* Strip trailing dots */ for (i = strlen(addr) - 1; i > 0 && addr[i] == '.'; --i) {
addr[i] = '\0';
}
/* Return TRUE if host "host" is in domain "domain" */ staticint proxy_match_domainname(struct dirconn_entry *This, request_rec *r)
{ constchar *host = proxy_get_host_of_request(r); int d_len = strlen(This->name), h_len;
if (host == NULL) { /* some error was logged already */ return 0;
}
h_len = strlen(host);
/* @@@ do this within the setup? */ /* Ignore trailing dots in domain comparison: */ while (d_len > 0 && This->name[d_len - 1] == '.') {
--d_len;
} while (h_len > 0 && host[h_len - 1] == '.') {
--h_len;
} return h_len > d_len
&& strncasecmp(&host[h_len - d_len], This->name, d_len) == 0;
}
/* Return TRUE if host represents a host name */
PROXY_DECLARE(int) ap_proxy_is_hostname(struct dirconn_entry *This, apr_pool_t *p)
{ struct apr_sockaddr_t *addr; char *host = This->name; int i;
/* Host names must not start with a '.' */ if (host[0] == '.') { return 0;
} /* rfc1035 says DNS names must consist of "[-a-zA-Z0-9]" and '.' */ for (i = 0; apr_isalnum(host[i]) || host[i] == '-' || host[i] == '.'; ++i);
/* Return TRUE if addr is to be matched as a word */
PROXY_DECLARE(int) ap_proxy_is_word(struct dirconn_entry *This, apr_pool_t *p)
{
This->matcher = proxy_match_word; return 1;
}
/* XXX FIXME: conf->noproxies->elts is part of an opaque structure */ for (j = 0; j < conf->noproxies->nelts; j++) { struct noproxy_entry *npent = (struct noproxy_entry *) conf->noproxies->elts; struct apr_sockaddr_t *conf_addr;
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "checking remote machine [%s] against [%s]",
hostname, npent[j].name); if (ap_strstr_c(hostname, npent[j].name) || npent[j].name[0] == '*') {
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(00916) "connect to remote machine %s blocked: name %s " "matched", hostname, npent[j].name); return HTTP_FORBIDDEN;
}
/* No IP address checks if no IP address was passed in, * i.e. the forward address proxy case, where this server does
* not resolve the hostname. */ if (!addr) continue;
/* * XXX FIXME: Make sure this handled the ambiguous case of the :<PORT> * after the hostname * XXX FIXME: Ensure the /uri component is a case sensitive match
*/ if (r->proxyreq != PROXYREQ_REVERSE) { return url;
}
l1_orig = strlen(url); if (conf->interpolate_env == 1) {
rconf = ap_get_module_config(r->request_config, &proxy_module);
ent = (struct proxy_alias *)rconf->raliases->elts;
} else {
ent = (struct proxy_alias *)conf->raliases->elts;
} for (i = 0; i < conf->raliases->nelts; i++) {
proxy_server_conf *sconf = (proxy_server_conf *)
ap_get_module_config(r->server->module_config, &proxy_module);
proxy_balancer *balancer; constchar *real = ent[i].real;
/* Restore the url length, if it had been changed by the code below */
l1 = l1_orig;
/* * First check if mapping against a balancer and see * if we have such a entity. If so, then we need to * find the particulars of the actual worker which may * or may not be the right one... basically, we need * to find which member actually handled this request.
*/ if (ap_proxy_valid_balancer_name((char *)real, 0) &&
(balancer = ap_proxy_get_balancer(r->pool, sconf, real, 1))) { int n, l3 = 0;
proxy_worker **worker = (proxy_worker **)balancer->workers->elts; constchar *urlpart = ap_strchr_c(real + sizeof(BALANCER_PREFIX) - 1, '/'); if (urlpart) { if (!urlpart[1])
urlpart = NULL; else
l3 = strlen(urlpart);
} /* The balancer comparison is a bit trickier. Given the context * BalancerMember balancer://alias http://example.com/foo * ProxyPassReverse /bash balancer://alias/bar * translate url http://example.com/foo/bar/that to /bash/that
*/ for (n = 0; n < balancer->workers->nelts; n++) {
l2 = strlen((*worker)->s->name_ex); if (urlpart) { /* urlpart (l3) assuredly starts with its own '/' */ if ((*worker)->s->name_ex[l2 - 1] == '/')
--l2; if (l1 >= l2 + l3
&& strncasecmp((*worker)->s->name_ex, url, l2) == 0
&& strncmp(urlpart, url + l2, l3) == 0) {
u = apr_pstrcat(r->pool, ent[i].fake, &url[l2 + l3],
NULL); return ap_is_url(u) ? u : ap_construct_url(r->pool, u, r);
}
} elseif (l1 >= l2 && strncasecmp((*worker)->s->name_ex, url, l2) == 0) { /* edge case where fake is just "/"... avoid double slash */ if ((ent[i].fake[0] == '/') && (ent[i].fake[1] == 0) && (url[l2] == '/')) {
u = apr_pstrdup(r->pool, &url[l2]);
} else {
u = apr_pstrcat(r->pool, ent[i].fake, &url[l2], NULL);
} return ap_is_url(u) ? u : ap_construct_url(r->pool, u, r);
}
worker++;
}
} else { constchar *part = url;
l2 = strlen(real); if (real[0] == '/') {
part = ap_strstr_c(url, "://"); if (part) {
part = ap_strchr_c(part+3, '/'); if (part) {
l1 = strlen(part);
} else {
part = url;
}
} else {
part = url;
}
} if (l2 > 0 && l1 >= l2 && strncasecmp(real, part, l2) == 0) {
u = apr_pstrcat(r->pool, ent[i].fake, &part[l2], NULL); return ap_is_url(u) ? u : ap_construct_url(r->pool, u, r);
}
}
}
return url;
}
/* * Cookies are a bit trickier to match: we've got two substrings to worry * about, and we can't just find them with strstr 'cos of case. Regexp * matching would be an easy fix, but for better consistency with all the * other matches we'll refrain and use apr_strmatch to find path=/domain= * and stick to plain strings for the config values.
*/
PROXY_DECLARE(constchar *) ap_proxy_cookie_reverse_map(request_rec *r,
proxy_dir_conf *conf, constchar *str)
{
proxy_req_conf *rconf = ap_get_module_config(r->request_config,
&proxy_module); struct proxy_alias *ent;
apr_size_t len = strlen(str); constchar *newpath = NULL; constchar *newdomain = NULL; constchar *pathp; constchar *domainp; constchar *pathe = NULL; constchar *domaine = NULL;
apr_size_t l1, l2, poffs = 0, doffs = 0; int i; int ddiff = 0; int pdiff = 0; char *tmpstr, *tmpstr_orig, *token, *last, *ret;
if (r->proxyreq != PROXYREQ_REVERSE) { return str;
}
/* * Find the match and replacement, but save replacing until we've done * both path and domain so we know the new strlen
*/
tmpstr_orig = tmpstr = apr_pstrdup(r->pool, str); while ((token = apr_strtok(tmpstr, ";", &last))) { /* skip leading spaces */ while (apr_isspace(*token)) {
++token;
}
/* * verifies that the balancer name conforms to standards.
*/
PROXY_DECLARE(int) ap_proxy_valid_balancer_name(char *name, int i)
{ if (!i)
i = sizeof(BALANCER_PREFIX)-1; return (!ap_cstr_casecmpn(name, BALANCER_PREFIX, i));
}
/* * NOTE: The default method is byrequests - if it doesn't * exist, that's OK at this time. We check when we share and sync
*/
lbmethod = ap_lookup_provider(PROXY_LBMETHOD, "byrequests", "0");
(*balancer)->lbmethod = lbmethod;
/* Generate a pseudo-UUID from the PRNG to use as a nonce for * the lifetime of the process. uuid.data is a char array so
* this is an adequate substitute for apr_uuid_get(). */
ap_random_insecure_bytes(uuid.data, sizeof uuid.data);
apr_uuid_format(nonce, &uuid);
rv = PROXY_STRNCPY(balancer->s->nonce, nonce);
} return rv;
}
if (!storage) {
ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s, APLOGNO(00918) "no provider for %s", balancer->s->name); return APR_EGENERAL;
} /* * for each balancer we need to init the global * mutex and then attach to the shared worker shm
*/ if (!balancer->gmutex) {
ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s, APLOGNO(00919) "no mutex %s", balancer->s->name); return APR_EGENERAL;
}
/* Re-open the mutex for the child. */
rv = apr_global_mutex_child_init(&(balancer->gmutex),
apr_global_mutex_lockfile(balancer->gmutex),
p); if (rv != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00920) "Failed to reopen mutex %s in child",
balancer->s->name); return rv;
}
/* Process lbsets in order, only replacing unusable workers in a given lbset * with available spares from the same lbset. Hot standbys will be used as a * last resort when all other workers and spares are unavailable.
*/ for (cur_lbset = 0; !best_worker && (cur_lbset <= max_lbset); cur_lbset++) {
unusable_workers = 0;
apr_array_clear(spares);
apr_array_clear(standbys);
for (i = 0; i < balancer->workers->nelts; i++) {
worker = APR_ARRAY_IDX(balancer->workers, i, proxy_worker *);
if (worker->s->lbset > max_lbset) {
max_lbset = worker->s->lbset;
}
if (worker->s->lbset != cur_lbset) { continue;
}
/* A draining worker that is neither a spare nor a standby should be * considered unusable to be replaced by spares.
*/ if (PROXY_WORKER_IS_DRAINING(worker)) { if (!PROXY_WORKER_IS_SPARE(worker) && !PROXY_WORKER_IS_STANDBY(worker)) {
unusable_workers++;
}
continue;
}
/* If the worker is in error state run retry on that worker. It will * be marked as operational if the retry timeout is elapsed. The * worker might still be unusable, but we try anyway.
*/ if (!PROXY_WORKER_IS_USABLE(worker)) {
ap_proxy_retry_worker("BALANCER", worker, r->server);
}
/* Check if any spares are best. */ for (i = 0; (i < spares->nelts) && (i < unusable_workers); i++) {
worker = APR_ARRAY_IDX(spares, i, proxy_worker *);
if (is_best(worker, best_worker, baton)) {
best_worker = worker;
}
}
/* If no workers are available, use the standbys. */ if (!best_worker) { for (i = 0; i < standbys->nelts; i++) {
worker = APR_ARRAY_IDX(standbys, i, proxy_worker *);
static apr_status_t conn_pool_cleanup(void *theworker)
{ /* Signal that the child is exiting */
((proxy_worker *)theworker)->cp = NULL; return APR_SUCCESS;
}
/* * Alloc from the same pool as worker. * proxy_conn_pool is permanently attached to the worker.
*/
cp = (proxy_conn_pool *)apr_pcalloc(p, sizeof(proxy_conn_pool));
worker->cp = cp;
/* * We need a first pool (cp->pool) to maintain the connections attached to * the worker and a second one (cp->dns_pool) to maintain the DNS addresses * in use (TTL'ed, refcounted). New connections are created as/on a subpool * of cp->pool and new addresses as/on a subpool of cp->dns_pool, such that * both leaks (the subpools can be destroyed when the connections and/or * addresses are over) and race conditions (the creation/destruction of * subpools is protected by the parent pool's mutex) can be avoided. * * cp->dns_pool is created before cp->pool because when a connection on the * latter is destroyed it might destroy an address on the former, so when * the base pools are destroyed (e.g. child exit) we thusly make sure that * cp->dns_pool and its subpools are still alive when cp->pool gets killed. * * Both cp->dns_pool and cp->pool have their own allocator/mutex too since * acquiring connections and addresses don't need to contend.
*/
cp->dns_pool = make_conn_subpool(p, "proxy_worker_dns", s);
cp->pool = make_conn_subpool(p, "proxy_worker_cp", s);
/* When p is cleaning up the child is exiting, signal that to e.g. avoid * destroying the subpools explicitely in connection_destructor() when * they have been destroyed already by the reslist cleanup.
*/
apr_pool_pre_cleanup_register(p, worker, conn_pool_cleanup);
}
/* * Create another subpool that manages the data for the * socket and the connection member of the proxy_conn_rec struct as we * destroy this data more frequently than other data in the proxy_conn_rec * struct like hostname and addr (at least in the case where we have * keepalive connections that timed out). * * XXX: this is really needed only when worker->s->is_address_reusable, * otherwise conn->scpool = conn->pool would be fine. For now we * can't change it since it's (kind of) part of the API.
*/
apr_pool_create(&conn->scpool, p);
apr_pool_tag(conn->scpool, "proxy_conn_scpool");
/* Sanity check: Did we already return the pooled connection? */ if (conn->inreslist) {
ap_log_perror(APLOG_MARK, APLOG_ERR, 0, conn->pool, APLOGNO(00923) "Pooled connection 0x%pp for worker %s has been" " already returned to the connection pool.", conn,
ap_proxy_worker_name(conn->pool, worker)); return;
}
if (conn->r) {
apr_pool_destroy(conn->r->pool);
conn->r = NULL;
}
/* determine if the connection should be cleared, closed or reused */ if (!worker->s->is_address_reusable) {
apr_pool_t *p = conn->pool;
apr_pool_clear(p);
conn = connection_make(p, worker);
} elseif (!conn->sock
|| (conn->connection
&& conn->connection->keepalive == AP_CONN_CLOSE)
|| !ap_proxy_connection_reusable(conn)) {
socket_cleanup(conn);
} elseif (conn->is_ssl) { /* The current ssl section/dir config of the conn is not necessarily * the one it will be reused for, so while the conn is in the reslist * reset its ssl config to the worker's, until a new user sets its own * ssl config eventually in proxy_connection_create() and so on.
*/
ap_proxy_ssl_engine(conn->connection, worker->section_config, 1);
}
/* * If we have an existing SSL connection it might be possible that the * server sent some SSL message we have not read so far (e.g. an SSL * shutdown message if the server closed the keepalive connection while * the connection was held unused in our pool). * So ensure that if present (=> APR_NONBLOCK_READ) it is read and * processed. We don't expect any data to be in the returned brigade.
*/ if (conn->sock && conn->connection) {
rv = ap_get_brigade(conn->connection->input_filters, conn->tmp_bb,
AP_MODE_READBYTES, APR_NONBLOCK_READ,
HUGE_STRING_LEN); if (!APR_BRIGADE_EMPTY(conn->tmp_bb)) {
apr_off_t len;
/* * Create a subpool for each connection * This keeps the memory consumption constant * when it's recycled or destroyed.
*/
apr_pool_create(&p, pool);
apr_pool_tag(p, "proxy_conn_pool");
conn = connection_make(p, worker);
conn->inreslist = 1;
/* Compare to the provided default (if any) */ return (dflt && ap_cstr_casecmp(dflt, upgrade) == 0);
}
/* * Taken from ap_strcmp_match() : * Match = 0, NoMatch = 1, Abort = -1, Inval = -2 * Based loosely on sections of wildmat.c by Rich Salz * Hmmm... shouldn't this really go component by component? * * Adds handling of the "\<any>" => "<any>" unescaping.
*/ staticint ap_proxy_strcmp_ematch(constchar *str, constchar *expected)
{
apr_size_t x, y;
for (x = 0, y = 0; expected[y]; ++y, ++x) { if (expected[y] == '$' && apr_isdigit(expected[y + 1])) { do {
y += 2;
} while (expected[y] == '$' && apr_isdigit(expected[y + 1])); if (!expected[y]) return 0; while (str[x]) { int ret; if ((ret = ap_proxy_strcmp_ematch(&str[x++], &expected[y])) != 1) return ret;
} return -1;
} elseif (!str[x]) { return -1;
} elseif (expected[y] == '\\' && !expected[++y]) { /* NUL is an invalid char! */ return -2;
} if (str[x] != expected[y]) return 1;
} /* We got all the way through the worker path without a difference */ return 0;
}
staticint worker_matches(proxy_worker *worker, constchar *url, apr_size_t url_len,
apr_size_t min_match, apr_size_t *max_match, unsignedint mask)
{
apr_size_t name_len = strlen(worker->s->name_ex); if (name_len <= url_len
&& name_len > *max_match /* min_match is the length of the scheme://host part only of url, * so it's used as a fast path to avoid the match when url is too * small, but it's irrelevant when the worker host contains globs * (i.e. ->is_host_matchable).
*/
&& (worker->s->is_name_matchable
? ((mask & AP_PROXY_WORKER_IS_MATCH)
&& (worker->s->is_host_matchable || name_len >= min_match)
&& !ap_proxy_strcmp_ematch(url, worker->s->name_ex))
: ((mask & AP_PROXY_WORKER_IS_PREFIX)
&& (name_len >= min_match)
&& !strncmp(url, worker->s->name_ex, name_len)))) {
*max_match = name_len; return 1;
} return 0;
}
/* Default to lookup for both _PREFIX and _MATCH workers */ if (!(mask & (AP_PROXY_WORKER_IS_PREFIX | AP_PROXY_WORKER_IS_MATCH))) {
mask |= AP_PROXY_WORKER_IS_PREFIX | AP_PROXY_WORKER_IS_MATCH;
}
/* * We need to find the start of the path and * therefore we know the length of the scheme://hostname/ * part to we can force-lowercase everything up to * the start of the path.
*/
c = ap_strchr_c(c+3, '/'); if (c) { char *pathstart;
pathstart = url_copy + (c - url);
*pathstart = '\0';
ap_str_tolower(url_copy);
min_match = strlen(url_copy);
*pathstart = '/';
} else {
ap_str_tolower(url_copy);
min_match = strlen(url_copy);
}
/* * Do a "longest match" on the worker name to find the worker that * fits best to the URL, but keep in mind that we must have at least * a minimum matching of length min_match such that * scheme://hostname[:port] matches between worker and url.
*/ if (balancer) {
proxy_worker **worker = (proxy_worker **)balancer->workers->elts; for (i = 0; i < balancer->workers->nelts; i++, worker++) { if (worker_matches(*worker, url_copy, url_len,
min_match, &max_match, mask)) {
max_worker = *worker;
}
}
} else {
proxy_worker *worker = (proxy_worker *)conf->workers->elts; for (i = 0; i < conf->workers->nelts; i++, worker++) { if (worker_matches(worker, url_copy, url_len,
min_match, &max_match, mask)) {
max_worker = worker;
}
}
}
/* * To create a worker from scratch first we define the * specifics of the worker; this is all local data. * We then allocate space for it if data needs to be * shared. This allows for dynamic addition during * config and runtime.
*/
PROXY_DECLARE(char *) ap_proxy_define_worker_ex(apr_pool_t *p,
proxy_worker **worker,
proxy_balancer *balancer,
proxy_server_conf *conf, constchar *url, unsignedint mask)
{
apr_status_t rv;
proxy_worker_shared *wshared; constchar *ptr = NULL, *sockpath = NULL, *pdollars = NULL;
apr_port_t port_of_scheme; int address_not_reusable = 0;
apr_uri_t uri;
/* * Look to see if we are using UDS: * require format: unix:/path/foo/bar.sock|http://ignored/path2/ * This results in talking http to the socket at /path/foo/bar.sock
*/ if (!ap_cstr_casecmpn(url, "unix:", 5)
&& (ptr = ap_strchr_c(url + 5, '|'))) {
rv = apr_uri_parse(p, apr_pstrmemdup(p, url, ptr - url), &uri); if (rv == APR_SUCCESS) {
sockpath = ap_runtime_dir_relative(p, uri.path);
ptr++; /* so we get the scheme for the uds */
} else {
ptr = url;
}
} else {
ptr = url;
}
if (mask & AP_PROXY_WORKER_IS_MATCH) { /* apr_uri_parse() will accept the '$' sign anywhere in the URL but * in the :port part, and we don't want scheme://host:port$1$2/path * to fail (e.g. "ProxyPassMatch ^/(a|b)(/.*)? http://host:port$2"). * So we trim all the $n from the :port and prepend them in uri.path * afterward for apr_uri_unparse() to restore the original URL below. * If a dollar substitution is found in the hostname[:port] part of * the URL, reusing address and connections in the same worker is not * possible (the current implementation of active connections cache * handles/assumes a single origin server:port per worker only), so * we set address_not_reusable here during parsing to take that into * account in the worker settings below.
*/ #define IS_REF(x) (x[0] == '$' && apr_isdigit(x[1])) constchar *pos = ap_strstr_c(ptr, "://"); if (pos) {
pos += 3; while (*pos && *pos != ':' && *pos != '/') { if (*pos == '$') {
address_not_reusable = 1;
}
pos++;
} if (*pos == ':') {
pos++; while (*pos && !IS_REF(pos) && *pos != '/') {
pos++;
} if (IS_REF(pos)) { struct iovec vec[2]; constchar *path = pos + 2; while (*path && *path != '/') {
path++;
}
pdollars = apr_pstrmemdup(p, pos, path - pos);
vec[0].iov_base = (void *)ptr;
vec[0].iov_len = pos - ptr;
vec[1].iov_base = (void *)path;
vec[1].iov_len = strlen(path);
ptr = apr_pstrcatv(p, vec, 2, NULL);
address_not_reusable = 1;
}
}
} #undef IS_REF
}
/* Normalize the url (worker name) */
rv = apr_uri_parse(p, ptr, &uri); if (rv != APR_SUCCESS) { return apr_pstrcat(p, "Unable to parse URL: ", url, NULL);
} if (!uri.scheme) { return apr_pstrcat(p, "URL must be absolute!: ", url, NULL);
} if (!uri.hostname || !*uri.hostname) { if (sockpath) { /* allow for unix:/path|http: */
uri.hostname = "localhost";
} else { return apr_pstrcat(p, "URL must be absolute!: ", url, NULL);
}
} else {
ap_str_tolower(uri.hostname);
}
ap_str_tolower(uri.scheme);
port_of_scheme = ap_proxy_port_of_scheme(uri.scheme); if (uri.port && uri.port == port_of_scheme) {
uri.port = 0;
} if (pdollars) { /* Restore/prepend pdollars into the path. */
uri.path = apr_pstrcat(p, pdollars, uri.path, NULL);
}
ptr = apr_uri_unparse(p, &uri, APR_URI_UNP_REVEALPASSWORD);
/* * Workers can be associated w/ balancers or on their * own; ie: the generic reverse-proxy or a worker * in a simple ProxyPass statement. eg: * * ProxyPass / http://www.example.com * * in which case the worker goes in the conf slot.
*/ if (balancer) {
proxy_worker **runtime; /* recall that we get a ptr to the ptr here */
runtime = apr_array_push(balancer->workers);
*worker = *runtime = apr_palloc(p, sizeof(proxy_worker)); /* right to left baby */ /* we've updated the list of workers associated with
* this balancer *locally* */
balancer->wupdated = apr_time_now();
} elseif (conf) {
*worker = apr_array_push(conf->workers);
} else { /* we need to allocate space here */
*worker = apr_palloc(p, sizeof(proxy_worker));
}
memset(*worker, 0, sizeof(proxy_worker));
/* right here we just want to tuck away the worker info. * if called during config, we don't have shm setup yet,
* so just note the info for later. */ if (mask & AP_PROXY_WORKER_IS_MALLOCED)
wshared = ap_malloc(sizeof(proxy_worker_shared)); /* will be freed ap_proxy_share_worker */ else
wshared = apr_palloc(p, sizeof(proxy_worker_shared));
memset(wshared, 0, sizeof(proxy_worker_shared));
if (PROXY_STRNCPY(wshared->name_ex, ptr) != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(10366) "Alert! worker name (%s) too long; truncated to: %s", ptr, wshared->name_ex);
} if (PROXY_STRNCPY(wshared->name, ptr) != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(010118) "worker name (%s) too long; truncated for legacy modules that do not use " "proxy_worker_shared->name_ex: %s", ptr, wshared->name);
} if (PROXY_STRNCPY(wshared->scheme, uri.scheme) != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(010117) "Alert! worker scheme (%s) too long; truncated to: %s", uri.scheme, wshared->scheme);
} if (PROXY_STRNCPY(wshared->hostname_ex, uri.hostname) != APR_SUCCESS) { return apr_psprintf(p, "worker hostname (%s) too long", uri.hostname);
} if (PROXY_STRNCPY(wshared->hostname, uri.hostname) != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(010118) "worker hostname (%s) too long; truncated for legacy modules that do not use " "proxy_worker_shared->hostname_ex: %s", uri.hostname, wshared->hostname);
}
wshared->port = (uri.port) ? uri.port : port_of_scheme;
wshared->flush_packets = flush_off;
wshared->flush_wait = PROXY_FLUSH_WAIT;
wshared->address_ttl = (address_not_reusable) ? 0 : -1;
wshared->is_address_reusable = (address_not_reusable == 0);
wshared->disablereuse = (address_not_reusable != 0);
wshared->lbfactor = 100;
wshared->passes = 1;
wshared->fails = 1;
wshared->interval = apr_time_from_sec(HCHECK_WATHCHDOG_DEFAULT_INTERVAL);
wshared->smax = -1;
wshared->hash.def = ap_proxy_hashfunc(wshared->name_ex, PROXY_HASHFUNC_DEFAULT);
wshared->hash.fnv = ap_proxy_hashfunc(wshared->name_ex, PROXY_HASHFUNC_FNV);
wshared->was_malloced = (mask & AP_PROXY_WORKER_IS_MALLOCED) != 0; if (mask & AP_PROXY_WORKER_IS_MATCH) {
wshared->is_name_matchable = 1;
wshared->is_host_matchable = (address_not_reusable != 0);
/* Before AP_PROXY_WORKER_IS_MATCH (< 2.4.47), a regex worker with * dollar substitution was never matched against any actual URL, thus * the requests fell through the generic worker. Now if a ProyPassMatch * matches, a worker (and its parameters) is always used to determine * the properties of the connection with the origin server. So for * instance the same "timeout=" will be enforced for all the requests * matched by the same ProyPassMatch worker, which is an improvement * compared to the global/vhost [Proxy]Timeout applied by the generic * worker. Likewise, address and connection reuse is the default for * a ProyPassMatch worker with no dollar substitution, just like a * "normal" worker. However to avoid DNS and connection reuse compat * issues, connection reuse is disabled by default if there is any * substitution in the uri-path (an explicit enablereuse=on can still * opt-in), and reuse is even disabled definitively for substitutions * happening in the hostname[:port] (is_address_reusable was unset * above so it will prevent enablereuse=on to apply anyway).
*/ if (ap_strchr_c(wshared->name, '$')) {
wshared->disablereuse = 1;
}
} if (sockpath) { if (PROXY_STRNCPY(wshared->uds_path, sockpath) != APR_SUCCESS) { return apr_psprintf(p, "worker uds path (%s) too long", sockpath);
}
/* * Create an already defined worker and free up memory
*/
PROXY_DECLARE(apr_status_t) ap_proxy_share_worker(proxy_worker *worker, proxy_worker_shared *shm, int i)
{ char *action = "copying"; if (!shm || !worker->s) return APR_EINVAL;
if ((worker->s->hash.def != shm->hash.def) ||
(worker->s->hash.fnv != shm->hash.fnv)) {
memcpy(shm, worker->s, sizeof(proxy_worker_shared)); if (worker->s->was_malloced)
free(worker->s); /* was malloced in ap_proxy_define_worker */
} else {
action = "re-using";
}
worker->s = shm;
worker->s->index = i;
if (APLOGdebug(ap_server_conf)) {
apr_pool_t *pool;
apr_pool_create(&pool, ap_server_conf->process->pool);
apr_pool_tag(pool, "proxy_worker_name");
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(02338) "%s shm[%d] (0x%pp) for worker: %s", action, i, (void *)shm,
ap_proxy_worker_name(pool, worker)); if (pool) {
apr_pool_destroy(pool);
}
} return APR_SUCCESS;
}
if (worker->s->status & PROXY_WORKER_INITIALIZED) { /* The worker is already initialized */
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00924) "worker %s shared already initialized",
ap_proxy_worker_name(p, worker));
} else {
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00925) "initializing worker %s shared",
ap_proxy_worker_name(p, worker)); /* Set default parameters */ if (!worker->s->retry_set) {
worker->s->retry = apr_time_from_sec(PROXY_WORKER_DEFAULT_RETRY);
} /* Consistently set address and connection reusabilty: when reuse * is disabled by configuration, or when the address is known already * to not be reusable for this worker (in any case, thus ignore/force * DisableReuse).
*/ if (!worker->s->address_ttl || (!worker->s->address_ttl_set
&& worker->s->disablereuse)) {
worker->s->is_address_reusable = 0;
} if (!worker->s->is_address_reusable && !worker->s->disablereuse) { /* Explicit enablereuse=on can't work in this case, warn user. */ if (worker->s->disablereuse_set) {
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(10400) "enablereuse/disablereuse ignored for worker %s",
ap_proxy_worker_name(p, worker));
}
worker->s->disablereuse = 1;
}
/* * When mod_http2 is loaded we might have more threads since it has * its own pool of processing threads.
*/
ap_mpm_query(AP_MPMQ_MAX_THREADS, &max_threads);
get_h2_num_workers = APR_RETRIEVE_OPTIONAL_FN(http2_get_num_workers); if (get_h2_num_workers) {
get_h2_num_workers(s, &minw, &maxw); /* So now the max is: * max_threads-1 threads for HTTP/1 each requiring one connection * + one thread for HTTP/2 requiring maxw connections
*/
max_threads = max_threads - 1 + maxw;
} if (max_threads > 1) { /* Default hmax is max_threads to scale with the load and never * wait for an idle connection to proceed.
*/ if (worker->s->hmax == 0) {
worker->s->hmax = max_threads;
} if (worker->s->smax == -1 || worker->s->smax > worker->s->hmax) {
worker->s->smax = worker->s->hmax;
} /* Set min to be lower than smax */ if (worker->s->min > worker->s->smax) {
worker->s->min = worker->s->smax;
}
} else { /* This will suppress the apr_reslist creation */
worker->s->min = worker->s->smax = worker->s->hmax = 0;
}
}
/* What if local is init'ed and shm isn't?? Even possible? */ if (worker->local_status & PROXY_WORKER_INITIALIZED) {
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00926) "worker %s local already initialized",
ap_proxy_worker_name(p, worker));
} else {
apr_global_mutex_lock(proxy_mutex); /* Check again after we got the lock if we are still uninitialized */ if (!(AP_VOLATILIZE_T(unsignedint, worker->local_status) & PROXY_WORKER_INITIALIZED)) {
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00927) "initializing worker %s local",
ap_proxy_worker_name(p, worker)); /* Now init local worker data */ #if APR_HAS_THREADS if (worker->tmutex == NULL) {
rv = apr_thread_mutex_create(&(worker->tmutex), APR_THREAD_MUTEX_DEFAULT, p); if (rv != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00928) "can not create worker thread mutex");
apr_global_mutex_unlock(proxy_mutex); return rv;
}
} #endif if (worker->cp == NULL)
init_conn_pool(p, worker, s); if (worker->cp == NULL) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00929) "can not create connection pool");
apr_global_mutex_unlock(proxy_mutex); return APR_EGENERAL;
}
staticint ap_proxy_retry_worker(constchar *proxy_function, proxy_worker *worker,
server_rec *s)
{ if (worker->s->status & PROXY_WORKER_IN_ERROR) { if (PROXY_WORKER_IS(worker, PROXY_WORKER_STOPPED)) {
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(3305) "%s: Won't retry worker (%s:%d): stopped",
proxy_function, worker->s->hostname_ex,
(int)worker->s->port); return DECLINED;
} if ((worker->s->status & PROXY_WORKER_IGNORE_ERRORS)
|| apr_time_now() > worker->s->error_time + worker->s->retry) {
++worker->s->retries;
worker->s->status &= ~PROXY_WORKER_IN_ERROR;
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00932) "%s: worker for (%s:%d) has been marked for retry",
proxy_function, worker->s->hostname_ex,
(int)worker->s->port); return OK;
} else {
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00933) "%s: too soon to retry worker for (%s:%d)",
proxy_function, worker->s->hostname_ex,
(int)worker->s->port); return DECLINED;
}
} else { return OK;
}
}
/* * In the case of the reverse proxy, we need to see if we * were passed a UDS url (eg: from mod_proxy) and adjust uds_path * as required.
*/ staticint fixup_uds_filename(request_rec *r)
{ char *uds_url = r->filename + 6, *origin_url;
/* The hostname part of the URL is not mandated for UDS though * the canon_handler hooks will require it. ProxyPass URLs are * fixed at load time by adding "localhost" automatically in the * worker URL, but SetHandler "proxy:unix:/udspath|scheme:[//]" * URLs are not so we have to fix it here the same way.
*/ if (!col[1]) { /* origin_url is "scheme:" */
r->filename = apr_pstrcat(r->pool, "proxy:",
origin_url, "//localhost",
NULL);
} /* For a SetHandler "proxy:..." in a <Location "/path">, the "/path" * is appended to r->filename, hence the below origin_url cases too:
*/ elseif (col[1] == '/' && (col[2] != '/'/* "scheme:/path" */
|| col[3] == '/'/* "scheme:///path" */
|| !col[3])) { /* "scheme://" */ char *scheme = origin_url;
*col = '\0'; /* nul terminate scheme */ if (col[2] != '/') {
origin_url = col + 1;
} else {
origin_url = col + 3;
}
r->filename = apr_pstrcat(r->pool, "proxy:",
scheme, "://localhost",
origin_url, NULL);
} else { /* origin_url is normal "scheme://host/path", can overwrite * the UDS part of r->filename in place.
*/
memmove(uds_url, origin_url, strlen(origin_url) + 1);
} return OK;
}
PROXY_DECLARE(constchar *) ap_proxy_interpolate(request_rec *r, constchar *str)
{ /* Interpolate an env str in a configuration string * Syntax ${var} --> value_of(var) * Method: replace one var, and recurse on remainder of string * Nothing clever here, and crap like nested vars may do silly things * but we'll at least avoid sending the unwary into a loop
*/ constchar *start; constchar *end; constchar *var; constchar *val; constchar *firstpart;
start = ap_strstr_c(str, "${"); if (start == NULL) { return str;
}
end = ap_strchr_c(start+2, '}'); if (end == NULL) { return str;
} /* OK, this is syntax we want to interpolate. Is there such a var ? */
var = apr_pstrmemdup(r->pool, start+2, end-(start+2));
val = apr_table_get(r->subprocess_env, var);
firstpart = apr_pstrmemdup(r->pool, str, (start-str));
/* Put the UDS path appart if any (and not already stripped) */ if (r->proxyreq == PROXYREQ_REVERSE) {
access_status = fixup_uds_filename(r); if (ap_is_HTTP_ERROR(access_status)) { return access_status;
}
}
/* Keep this after fixup_uds_filename() */
url = apr_pstrdup(r->pool, r->filename + 6);
if ((dconf->interpolate_env == 1) && (r->proxyreq == PROXYREQ_REVERSE)) { /* create per-request copy of reverse proxy conf, * and interpolate vars in it
*/
proxy_req_conf *rconf = apr_palloc(r->pool, sizeof(proxy_req_conf));
ap_set_module_config(r->request_config, &proxy_module, rconf);
rconf->raliases = proxy_vars(r, dconf->raliases);
rconf->cookie_paths = proxy_vars(r, dconf->cookie_paths);
rconf->cookie_domains = proxy_vars(r, dconf->cookie_domains);
}
/* canonicalise each specific scheme */ if ((access_status = proxy_run_canon_handler(r, url))) { return access_status;
}
p = strchr(url, ':'); if (p == NULL || p == url) return HTTP_BAD_REQUEST;
return OK; /* otherwise; we've done the best we can */
}
while (backend_addr && !connected) { if ((rv = apr_socket_create(newsock, backend_addr->family,
SOCK_STREAM, 0, r->pool)) != APR_SUCCESS) {
loglevel = backend_addr->next ? APLOG_DEBUG : APLOG_ERR;
ap_log_rerror(APLOG_MARK, loglevel, rv, r, APLOGNO(00935) "%s: error creating fam %d socket for target %s",
proxy_function, backend_addr->family, backend_name); /* * this could be an IPv6 address from the DNS but the * local machine won't give us an IPv6 socket; hopefully the * DNS returned an additional address to try
*/
backend_addr = backend_addr->next; continue;
}
if (conf->recv_buffer_size > 0 &&
(rv = apr_socket_opt_set(*newsock, APR_SO_RCVBUF,
conf->recv_buffer_size))) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00936) "apr_socket_opt_set(SO_RCVBUF): Failed to set " "ProxyReceiveBufferSize, using default");
}
/* Set a timeout on the socket */ if (conf->timeout_set) {
apr_socket_timeout_set(*newsock, conf->timeout);
} else {
apr_socket_timeout_set(*newsock, r->server->timeout);
}
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "%s: fam %d socket created to connect to %s",
proxy_function, backend_addr->family, backend_name);
if (conf->source_address) {
apr_sockaddr_t *local_addr; /* Make a copy since apr_socket_bind() could change * conf->source_address, which we don't want.
*/
local_addr = apr_pmemdup(r->pool, conf->source_address, sizeof(apr_sockaddr_t));
local_addr->pool = r->pool;
rv = apr_socket_bind(*newsock, local_addr); if (rv != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00938) "%s: failed to bind socket to local address",
proxy_function);
}
}
/* make the connection out of the socket */
rv = apr_socket_connect(*newsock, backend_addr);
/* if an error occurred, loop round and try again */ if (rv != APR_SUCCESS) {
apr_socket_close(*newsock);
loglevel = backend_addr->next ? APLOG_DEBUG : APLOG_ERR;
ap_log_rerror(APLOG_MARK, loglevel, rv, r, APLOGNO(00939) "%s: attempt to connect to %pI (%s) failed",
proxy_function, backend_addr, backend_name);
backend_addr = backend_addr->next; continue;
}
connected = 1;
} return connected ? 0 : 1;
}
if (!PROXY_WORKER_IS_USABLE(worker)) { /* Retry the worker */
ap_proxy_retry_worker(proxy_function, worker, s);
if (!PROXY_WORKER_IS_USABLE(worker)) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00940) "%s: disabled connection for (%s:%d)",
proxy_function, worker->s->hostname_ex,
(int)worker->s->port); return HTTP_SERVICE_UNAVAILABLE;
}
}
if (worker->s->hmax && worker->cp->res) {
rv = apr_reslist_acquire(worker->cp->res, (void **)conn);
} else { /* create the new connection if the previous was destroyed */ if (!worker->cp->conn) {
rv = connection_constructor((void **)conn, worker, worker->cp->pool);
} else {
*conn = worker->cp->conn;
worker->cp->conn = NULL;
rv = APR_SUCCESS;
}
}
if (rv != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00941) "%s: failed to acquire connection for (%s:%d)",
proxy_function, worker->s->hostname_ex,
(int)worker->s->port); return HTTP_SERVICE_UNAVAILABLE;
}
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00942) "%s: has acquired connection for (%s:%d)",
proxy_function, worker->s->hostname_ex,
(int)worker->s->port);
(*conn)->worker = worker;
(*conn)->inreslist = 0;
return OK;
}
PROXY_DECLARE(int) ap_proxy_release_connection(constchar *proxy_function,
proxy_conn_rec *conn,
server_rec *s)
{
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00943) "%s: has released connection for (%s:%d)",
proxy_function, conn->worker->s->hostname_ex,
(int)conn->worker->s->port);
connection_cleanup(conn);
return OK;
}
static APR_INLINE void proxy_address_inc(proxy_address *address)
{
apr_uint32_t old = apr_atomic_inc32(&address->refcount);
ap_assert(old > 0 && old < APR_UINT32_MAX);
}
static APR_INLINE void proxy_address_dec(proxy_address *address)
{ /* Use _add32(, -1) since _dec32()'s returned value does not help */
apr_uint32_t old = apr_atomic_add32(&address->refcount, -1);
ap_assert(old > 0); if (old == 1) {
apr_pool_destroy(address->addr->pool);
}
}
/* * Worker can have the single constant backend adress. * The single DNS lookup is used once per worker. * If dynamic change is needed then set the addr to NULL * inside dynamic config to force the lookup. * The worker's addressTTL parameter may also be configured * to perform the DNS lookups only when the TTL expires, * or each time if that TTL is zero.
*/ if (!worker->s->is_address_reusable) {
conn->hostname = apr_pstrdup(conn->pool, hostname);
conn->port = hostport;
if (flags & PROXY_DETERMINE_ADDRESS_CHECK) { /* The caller wants to check if the address changed, return * APR_EEXIST if not, otherwise fall through to update the * worker's for everyone to switch.
*/ if (!conn->addr) { /* Need something to compare with */ return APR_EINVAL;
}
rv = worker_address_resolve(worker, &addr,
hostname, hostport,
proxy_function, r, s); if (rv != APR_SUCCESS) { return rv;
} if (proxy_addrs_equal(conn->addr, addr)) {
apr_pool_destroy(addr->pool); return APR_EEXIST;
}
}
AP_DEBUG_ASSERT(ttl != 0); if (ttl > 0) { /* TODO: use a monotonic clock here */
now = apr_time_sec(apr_time_now() - *proxy_start_time);
}
/* Addresses are refcounted, destroyed when their refcount reaches 0. * * One ref is taken by worker->address as the worker's current/latest * address, it's dropped when that address expires/changes (see below). * The other refs are taken by the connections when using/switching to * the current worker address (also below), they are dropped when the * conns are destroyed (by the reslist though it should never happen * if hmax is greater than the number of threads) OR for an expired * conn->address when it's replaced by the new worker->address below. * * Dereferencing worker->address requires holding the worker mutex or * some concurrent connection processing might change/destroy it at any * time. So only conn->address is safe to dereference anywhere (unless * NULL..) since it has at least the lifetime of the connection.
*/ if (!addr) {
address = worker_address_get(worker);
} if (!address
|| conn->address != address
|| apr_atomic_read32(&address->expiry) <= now) {
PROXY_THREAD_LOCK(worker);
/* Re-check while locked, might be a new address already */ if (!addr) {
address = worker_address_get(worker);
} if (!address || apr_atomic_read32(&address->expiry) <= now) { if (!addr) {
rv = worker_address_resolve(worker, &addr,
hostname, hostport,
proxy_function, r, s); if (rv != APR_SUCCESS) {
PROXY_THREAD_UNLOCK(worker); return rv;
}
/* Recompute "now" should the DNS be slow * TODO: use a monotonic clock here
*/
now = apr_time_sec(apr_time_now() - *proxy_start_time);
}
if (ttl > 0) { /* We keep each worker's expiry date shared accross all the * children so that they update their address at the same * time, regardless of whether a specific child forced an * address to expire at some point (for connect() issues).
*/
address->expiry = apr_atomic_read32(&worker->s->address_expiry); if (address->expiry <= now) {
apr_uint32_t prev, next = (now + ttl) - (now % ttl); do {
prev = apr_atomic_cas32(&worker->s->address_expiry,
next, address->expiry); if (prev == address->expiry) {
address->expiry = next; break;
}
address->expiry = prev;
} while (prev <= now);
}
} else { /* Never expires */
address->expiry = APR_UINT32_MAX;
}
/* One ref is for worker->address in any case */ if (worker->address || worker->cp->addr) {
apr_atomic_set32(&address->refcount, 1);
} else { /* Set worker->cp->addr once for compat with third-party * modules. This addr never changed before and can't change * underneath users now because of some TTL configuration. * So we take one more ref for worker->cp->addr to remain * allocated forever (though it might not be up to date..). * Modules should use conn->addr instead of worker->cp-addr * to get the actual address used by each conn, determined * at connect() time.
*/
apr_atomic_set32(&address->refcount, 2);
worker->cp->addr = address->addr;
}
/* Publish the changes. The old worker address (if any) is no * longer used by this worker, it will be destroyed now if the * worker is the last user (refcount == 1) or by the last conn * using it (refcount > 1).
*/
worker_address_set(worker, address);
}
/* Take the ref for conn->address (before dropping the mutex so to * let no chance for this address be killed before it's used!)
*/
proxy_address_inc(address);
PROXY_THREAD_UNLOCK(worker);
/* Release the old conn address */ if (conn->address) { /* On Windows and OS/2, apr_socket_connect() called from * ap_proxy_connect_backend() does a simple pointer copy of * its given conn->addr[->next] into conn->sock->remote_addr. * Thus conn->addr cannot be freed if the conn->sock should be * kept alive (same new and old addresses) and the old address * is still in conn->sock->remote_addr. In this case we rather * delay the release of the old address by moving the cleanup * to conn->scpool such that it runs when the socket is closed. * In any other case, including other platforms, just release * the old address now since conn->sock->remote_addr is either * obsolete (socket forcibly closed) or a copy on conn->scpool * already (not a dangling pointer).
*/ int keep_addr_alive = 0,
keep_conn_alive = (conn->sock && conn->addr &&
proxy_addrs_equal(conn->addr,
address->addr)); if (keep_conn_alive) { #ifdefined(WIN32) || defined(OS2)
apr_sockaddr_t *remote_addr = NULL;
apr_socket_addr_get(&remote_addr, APR_REMOTE, conn->sock); for (addr = conn->addr; addr; addr = addr->next) { if (addr == remote_addr) {
keep_addr_alive = 1; break;
}
} #else /* Nothing to do, keep_addr_alive = 0 */ #endif
} elseif (conn->sock && (r ? APLOGrdebug(r) : APLOGdebug(s))) {
apr_sockaddr_t *local_addr = NULL;
apr_sockaddr_t *remote_addr = NULL;
apr_socket_addr_get(&local_addr, APR_LOCAL, conn->sock);
apr_socket_addr_get(&remote_addr, APR_REMOTE, conn->sock); if (r) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(10481) "%s: closing connection to %s (%pI<>%pI) on " "address change", proxy_function, hostname,
local_addr, remote_addr);
} else {
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10482) "%s: closing connection to %s (%pI<>%pI) on " "address change", proxy_function, hostname,
local_addr, remote_addr);
}
} if (keep_addr_alive) {
apr_pool_cleanup_kill(conn->pool, conn->address,
proxy_address_cleanup);
apr_pool_cleanup_register(conn->scpool, conn->address,
proxy_address_cleanup,
apr_pool_cleanup_null);
} else {
apr_pool_cleanup_run(conn->pool, conn->address,
proxy_address_cleanup); if (!keep_conn_alive) {
conn_cleanup(conn);
}
}
}
/* Use the new address */
apr_pool_cleanup_register(conn->pool, address,
proxy_address_cleanup,
apr_pool_cleanup_null);
conn->address = address;
conn->hostname = address->hostname;
conn->port = address->hostport;
conn->addr = address->addr;
}
}
/* * Break up the URL to determine the host to connect to
*/
/* we break the URL into host, port, uri */ if (APR_SUCCESS != apr_uri_parse(p, *url, uri)) { return ap_proxyerror(r, HTTP_BAD_REQUEST,
apr_pstrcat(p,"URI cannot be parsed: ", *url,
NULL));
}
if (!uri->hostname) { return ap_proxyerror(r, HTTP_BAD_REQUEST,
apr_pstrcat(p,"URI has no hostname: ", *url,
NULL));
}
if (!uri->port) {
uri->port = ap_proxy_port_of_scheme(uri->scheme);
}
/* Close a possible existing socket if we are told to do so */ if (conn->close) {
socket_cleanup(conn);
}
/* * allocate these out of the specified connection pool * The scheme handler decides if this is permanent or * short living pool.
*/ /* Unless we are connecting the backend via a (forward Proxy)Remote, we * have to use the original form of the URI (non absolute), but this is * also the case via a remote proxy using the CONNECT method since the * original request (and URI) is to be embedded in the body.
*/ if (!proxyname || conn->is_ssl) {
*url = apr_pstrcat(p, uri->path, uri->query ? "?" : "",
uri->query ? uri->query : "",
uri->fragment ? "#" : "",
uri->fragment ? uri->fragment : "", NULL);
} /* * Figure out if our passed in proxy_conn_rec has a usable * address cached. * * TODO: Handle this much better... * * XXX: If generic workers are ever address-reusable, we need * to check host and port on the conn and be careful about * spilling the cached addr from the worker.
*/
uds_path = (*worker->s->uds_path
? worker->s->uds_path
: apr_table_get(r->notes, "uds_path")); if (uds_path) { if (!conn->uds_path || strcmp(conn->uds_path, uds_path) != 0) {
apr_pool_t *pool = conn->pool; if (conn->uds_path) {
conn_cleanup(conn); if (!conn->uds_pool) {
apr_pool_create(&conn->uds_pool, worker->cp->dns_pool);
}
pool = conn->uds_pool;
} /* * In UDS cases, some structs are NULL. Protect from de-refs * and provide info for logging at the same time.
*/ #if APR_HAVE_SOCKADDR_UN
apr_sockaddr_info_get(&conn->addr, uds_path, APR_UNIX, 0, 0, pool); if (conn->addr && conn->addr->hostname) {
conn->uds_path = conn->addr->hostname;
} else {
conn->uds_path = apr_pstrdup(pool, uds_path);
} #else
apr_sockaddr_info_get(&conn->addr, NULL, APR_UNSPEC, 0, 0, pool);
conn->uds_path = apr_pstrdup(pool, uds_path); #endif
conn->hostname = apr_pstrdup(pool, uri->hostname);
conn->port = uri->port;
}
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02545) "%s: has determined UDS as %s (for %s:%hu)",
uri->scheme, conn->uds_path, conn->hostname, conn->port);
} else {
remote_connect_info *connect_info = NULL; constchar *hostname = uri->hostname;
apr_port_t hostport = uri->port;
if (proxyname) {
hostname = proxyname;
hostport = proxyport;
/* * If we have a remote proxy and the protocol is HTTPS, * then we need to prepend a HTTP CONNECT request before * sending our actual HTTPS requests.
*/ if (conn->is_ssl) { constchar *proxy_auth;
/* Do we want to pass Proxy-Authorization along? * If we haven't used it, then YES * If we have used it then MAYBE: RFC2616 says we MAY propagate it. * So let's make it configurable by env. * The logic here is the same used in mod_proxy_http.
*/
proxy_auth = apr_table_get(r->notes, "proxy-basic-creds"); if (proxy_auth == NULL
&& (r->user == NULL /* we haven't yet authenticated */
|| apr_table_get(r->subprocess_env, "Proxy-Chain-Auth"))) {
proxy_auth = apr_table_get(r->headers_in, "Proxy-Authorization");
} if (proxy_auth != NULL && proxy_auth[0] == '\0') {
proxy_auth = NULL;
}
/* Save our real backend data for using it later during HTTP CONNECT */
connect_info = conn->forward; if (!connect_info /* reset connect info if they changed */
|| connect_info->target_port != uri->port
|| ap_cstr_casecmp(connect_info->target_host, uri->hostname) != 0
|| (connect_info->proxy_auth != NULL) != (proxy_auth != NULL)
|| (connect_info->proxy_auth != NULL && proxy_auth != NULL &&
strcmp(connect_info->proxy_auth, proxy_auth) != 0)) {
apr_pool_t *fwd_pool = conn->pool; if (worker->s->is_address_reusable) { if (conn->fwd_pool) {
apr_pool_clear(conn->fwd_pool);
} else {
apr_pool_create(&conn->fwd_pool, conn->pool);
}
fwd_pool = conn->fwd_pool;
}
connect_info = apr_pcalloc(fwd_pool, sizeof(*connect_info));
connect_info->target_host = apr_pstrdup(fwd_pool, uri->hostname);
connect_info->target_port = uri->port; if (proxy_auth) {
connect_info->proxy_auth = apr_pstrdup(fwd_pool, proxy_auth);
}
conn->forward = NULL;
}
}
}
/* Close the connection if the remote proxy or origin server don't match */ if (conn->forward != connect_info
|| (conn->hostname
&& (conn->port != hostport
|| ap_cstr_casecmp(conn->hostname, hostname) != 0))) {
conn_cleanup(conn);
conn->forward = connect_info;
}
/* Resolve the connection address with the determined hostname/port */ if (ap_proxy_determine_address(uri->scheme, conn, hostname, hostport,
0, r, NULL)) { return HTTP_INTERNAL_SERVER_ERROR;
}
}
/* Get the server port for the Via headers */
server_port = ap_get_server_port(r);
AP_DEBUG_ASSERT(server_portstr_size > 0); if (ap_is_default_port(server_port, r)) {
server_portstr[0] = '\0';
} else {
apr_snprintf(server_portstr, server_portstr_size, ":%d",
server_port);
}
/* check if ProxyBlock directive on this host */ if (OK != ap_proxy_checkproxyblock2(r, conf, uri->hostname,
proxyname ? NULL : conn->addr)) { return ap_proxyerror(r, HTTP_FORBIDDEN, "Connect to remote machine blocked");
} /* * When SSL is configured, determine the hostname (SNI) for the request * and save it in conn->ssl_hostname. Close any reused connection whose * SNI differs.
*/ if (conn->is_ssl) {
proxy_dir_conf *dconf; constchar *ssl_hostname; /* * In the case of ProxyPreserveHost on use the hostname of * the request if present otherwise use the one from the * backend request URI.
*/
dconf = ap_get_module_config(r->per_dir_config, &proxy_module); if (dconf->preserve_host) {
ssl_hostname = r->hostname;
} elseif (conn->forward) {
ssl_hostname = ((remote_connect_info *)conn->forward)->target_host;
} else {
ssl_hostname = conn->hostname;
} /* * Close if a SNI is in use but this request requires no or * a different one, or no SNI is in use but one is required.
*/ if ((conn->ssl_hostname && (!ssl_hostname ||
strcasecmp(conn->ssl_hostname,
ssl_hostname) != 0)) ||
(!conn->ssl_hostname && ssl_hostname && conn->sock)) {
socket_cleanup(conn);
} if (conn->ssl_hostname == NULL) {
conn->ssl_hostname = apr_pstrdup(conn->scpool, ssl_hostname);
}
}
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00947) "connecting %s to %pI (%s:%hu)", *url,
conn->addr, conn->hostname, conn->port); return OK;
}
do {
status = apr_poll(&pfds[0], 1, &nfds, 0);
} while (APR_STATUS_IS_EINTR(status));
if (status == APR_SUCCESS && nfds == 1 &&
pfds[0].rtnevents == APR_POLLIN) {
apr_sockaddr_t unused;
apr_size_t len = 1; char buf[1]; /* The socket might be closed in which case * the poll will return POLLIN. * If there is no data available the socket * is closed.
*/
status = apr_socket_recvfrom(&unused, socket, APR_MSG_PEEK,
&buf[0], &len); if (status == APR_SUCCESS && len) return 1; else return 0;
} elseif (APR_STATUS_IS_EAGAIN(status) || APR_STATUS_IS_TIMEUP(status)) { return 1;
} return 0;
/* save timeout */
apr_socket_timeout_get(sock, ¤t_timeout); /* set no timeout */
apr_socket_timeout_set(sock, 0);
socket_status = apr_socket_recv(sock, test_buffer, &buffer_len); /* put back old timeout */
apr_socket_timeout_set(sock, current_timeout); if (APR_STATUS_IS_EOF(socket_status)
|| APR_STATUS_IS_ECONNRESET(socket_status)) { return 0;
} else { return 1;
}
} #endif/* USE_ALTERNATE_IS_CONNECTED */
/* * Send a HTTP CONNECT request to a remote proxy. * The proxy is given by "backend", the target server * is contained in the "forward" member of "backend".
*/ static apr_status_t send_http_connect(proxy_conn_rec *backend,
server_rec *s)
{ int status;
apr_size_t nbytes;
apr_size_t left; int complete = 0; char buffer[HUGE_STRING_LEN]; char drain_buffer[HUGE_STRING_LEN];
remote_connect_info *connect_info = backend->forward; int len = 0;
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00948) "CONNECT: sending the CONNECT request for %s:%d " "to the remote proxy %pI (%s)",
connect_info->target_host, connect_info->target_port,
backend->addr, backend->hostname); /* Create the CONNECT request */
nbytes = apr_snprintf(buffer, sizeof(buffer), "CONNECT %s:%d HTTP/1.0" CRLF,
connect_info->target_host, connect_info->target_port); /* Add proxy authorization from the configuration, or initial
* request if necessary */ if (connect_info->proxy_auth != NULL) {
nbytes += apr_snprintf(buffer + nbytes, sizeof(buffer) - nbytes, "Proxy-Authorization: %s" CRLF,
connect_info->proxy_auth);
} /* Set a reasonable agent and send everything */
nbytes += apr_snprintf(buffer + nbytes, sizeof(buffer) - nbytes, "Proxy-agent: %s" CRLF CRLF,
ap_get_server_banner());
ap_xlate_proto_to_ascii(buffer, nbytes);
apr_socket_send(backend->sock, buffer, &nbytes);
/* Receive the whole CONNECT response */
left = sizeof(buffer) - 1; /* Read until we find the end of the headers or run out of buffer */ do {
nbytes = left;
status = apr_socket_recv(backend->sock, buffer + len, &nbytes);
len += nbytes;
left -= nbytes;
buffer[len] = '\0'; if (strstr(buffer + len - nbytes, CRLF_ASCII CRLF_ASCII) != NULL) {
ap_xlate_proto_from_ascii(buffer, len);
complete = 1; break;
}
} while (status == APR_SUCCESS && left > 0); /* Drain what's left */ if (!complete) {
nbytes = sizeof(drain_buffer) - 1; while (status == APR_SUCCESS && nbytes) {
status = apr_socket_recv(backend->sock, drain_buffer, &nbytes);
drain_buffer[nbytes] = '\0';
nbytes = sizeof(drain_buffer) - 1; if (strstr(drain_buffer, CRLF_ASCII CRLF_ASCII) != NULL) { break;
}
}
}
/* Check for HTTP_OK response status */ if (status == APR_SUCCESS) { unsignedint major, minor; /* Only scan for three character status code */ char code_str[4];
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00949) "send_http_connect: response from the remote proxy: %s",
buffer);
/* Extract the returned code */ if (sscanf(buffer, "HTTP/%u.%u %3s", &major, &minor, code_str) == 3) {
status = atoi(code_str); if (status == HTTP_OK) {
status = APR_SUCCESS;
} else {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00950) "send_http_connect: the remote proxy returned code is '%s'",
code_str);
status = APR_INCOMPLETE;
}
}
}
return(status);
}
/* TODO: In APR 2.x: Extend apr_sockaddr_t to possibly be a path !!! */
PROXY_DECLARE(apr_status_t) ap_proxy_connect_uds(apr_socket_t *sock, constchar *uds_path,
apr_pool_t *p)
{ #if APR_HAVE_SYS_UN_H
apr_status_t rv;
apr_os_sock_t rawsock;
apr_interval_time_t t; struct sockaddr_un *sa;
apr_socklen_t addrlen, pathlen;
if (!PROXY_WORKER_IS_USABLE(worker)) { /* * The worker is in error likely done by a different thread / process * e.g. for a timeout or bad status. We should respect this and should * not continue with a connection via this worker even if we got one.
*/
rv = APR_EINVAL;
} elseif (conn->connection) { /* We have a conn_rec, check the full filter stack for things like * SSL alert/shutdown, filters aside data...
*/
rv = ap_check_pipeline(conn->connection, conn->tmp_bb,
max_blank_lines);
apr_brigade_cleanup(conn->tmp_bb); if (rv == APR_SUCCESS) { /* Some data available, the caller might not want them. */ if (flags & PROXY_CHECK_CONN_EMPTY) {
rv = APR_ENOTEMPTY;
}
} elseif (APR_STATUS_IS_EAGAIN(rv)) { /* Filter chain is OK and empty, yet we can't determine from * ap_check_pipeline (actually ap_core_input_filter) whether * an empty non-blocking read is EAGAIN or EOF on the socket * side (it's always SUCCESS), so check it explicitly here.
*/ if (ap_proxy_is_socket_connected(conn->sock)) {
rv = APR_SUCCESS;
} else {
rv = APR_EPIPE;
}
}
} elseif (conn->sock) { /* For modules working with sockets directly, check it. */ if (!ap_proxy_is_socket_connected(conn->sock)) {
rv = APR_EPIPE;
}
} else {
rv = APR_ENOSOCKET;
}
if (rv == APR_SUCCESS) { if (APLOGtrace2(server)) {
apr_sockaddr_t *local_addr = NULL;
apr_socket_addr_get(&local_addr, APR_LOCAL, conn->sock);
ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, server, "%s: reusing backend connection %pI<>%pI",
scheme, local_addr, conn->addr);
}
} elseif (conn->sock) { /* This clears conn->scpool (and associated data), so backup and * restore any ssl_hostname for this connection set earlier by * ap_proxy_determine_connection().
*/ char ssl_hostname[PROXY_WORKER_RFC1035_NAME_SIZE]; if (rv == APR_EINVAL
|| !conn->ssl_hostname
|| PROXY_STRNCPY(ssl_hostname, conn->ssl_hostname)) {
ssl_hostname[0] = '\0';
}
socket_cleanup(conn); if (rv != APR_ENOTEMPTY) {
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, APLOGNO(00951) "%s: backend socket is disconnected.", scheme);
} else {
ap_log_error(APLOG_MARK, APLOG_INFO, 0, server, APLOGNO(03408) "%s: reusable backend connection is not empty: " "forcibly closed", scheme);
}
if (ssl_hostname[0]) {
conn->ssl_hostname = apr_pstrdup(conn->scpool, ssl_hostname);
}
}
return rv;
}
PROXY_DECLARE(int) ap_proxy_connect_backend(constchar *proxy_function,
proxy_conn_rec *conn,
proxy_worker *worker,
server_rec *s)
{
apr_status_t rv; int loglevel;
remote_connect_info *connect_info = conn->forward;
apr_sockaddr_t *backend_addr; /* the local address to use for the outgoing connection */
apr_sockaddr_t *local_addr;
apr_socket_t *newsock; void *sconf = s->module_config; int address_reusable = worker->s->is_address_reusable; int did_dns_lookup = 0;
proxy_server_conf *conf =
(proxy_server_conf *) ap_get_module_config(sconf, &proxy_module);
/* We'll set conn->addr to the address actually connect()ed, so if the * network connection is not reused (per ap_proxy_check_connection() * above) we need to reset conn->addr to the first resolved address * and try to connect it first.
*/ if (conn->address && rv != APR_SUCCESS) {
conn->addr = conn->address->addr;
}
backend_addr = conn->addr;
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02823) "%s: connection established with Unix domain socket " "%s (%s:%hu)",
proxy_function,
conn->uds_path,
conn->hostname, conn->port);
} else #endif
{ if ((rv = apr_socket_create(&newsock, backend_addr->family,
SOCK_STREAM, APR_PROTO_TCP,
conn->scpool)) != APR_SUCCESS) {
loglevel = backend_addr->next ? APLOG_DEBUG : APLOG_ERR;
ap_log_error(APLOG_MARK, loglevel, rv, s, APLOGNO(00952) "%s: error creating fam %d socket to %pI for " "(%s:%hu)",
proxy_function,
backend_addr->family, backend_addr,
conn->hostname, conn->port); /* * this could be an IPv6 address from the DNS but the * local machine won't give us an IPv6 socket; hopefully the * DNS returned an additional address to try
*/
backend_addr = backend_addr->next; continue;
}
conn->connection = NULL;
if (worker->s->recv_buffer_size > 0 &&
(rv = apr_socket_opt_set(newsock, APR_SO_RCVBUF,
worker->s->recv_buffer_size))) {
ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00953) "apr_socket_opt_set(SO_RCVBUF): Failed to set " "ProxyReceiveBufferSize, using default");
}
/* Set a timeout for connecting to the backend on the socket */ if (worker->s->conn_timeout_set) {
apr_socket_timeout_set(newsock, worker->s->conn_timeout);
} elseif (worker->s->timeout_set) {
apr_socket_timeout_set(newsock, worker->s->timeout);
} elseif (conf->timeout_set) {
apr_socket_timeout_set(newsock, conf->timeout);
} else {
apr_socket_timeout_set(newsock, s->timeout);
} /* Set a keepalive option */ if (worker->s->keepalive) { if ((rv = apr_socket_opt_set(newsock,
APR_SO_KEEPALIVE, 1)) != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00955) "apr_socket_opt_set(SO_KEEPALIVE): Failed to set" " Keepalive");
}
}
ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, s, "%s: fam %d socket created for %pI (%s:%hu)",
proxy_function, backend_addr->family, backend_addr,
conn->hostname, conn->port);
if (conf->source_address_set) {
local_addr = apr_pmemdup(conn->scpool, conf->source_address, sizeof(apr_sockaddr_t));
local_addr->pool = conn->scpool;
rv = apr_socket_bind(newsock, local_addr); if (rv != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00956) "%s: failed to bind socket to local address",
proxy_function);
}
}
/* make the connection out of the socket */
rv = apr_socket_connect(newsock, backend_addr);
/* if an error occurred, loop round and try again */ if (rv != APR_SUCCESS) {
apr_socket_close(newsock);
loglevel = backend_addr->next ? APLOG_DEBUG : APLOG_ERR;
ap_log_error(APLOG_MARK, loglevel, rv, s, APLOGNO(00957) "%s: attempt to connect to %pI (%s:%hu) failed",
proxy_function, backend_addr,
conn->hostname, conn->port);
backend_addr = backend_addr->next; /* * If we run out of resolved IP's when connecting and if * we cache the resolution in the worker the resolution * might have changed. Hence try a DNS lookup to see if this * helps.
*/ if (!backend_addr && address_reusable && !did_dns_lookup) { /* Issue a new DNS lookup to check if the address changed, * in which case (SUCCESS) restart the loop with the new * one(s), otherwise leave (nothing we can do about it).
*/ if (ap_proxy_determine_address(proxy_function, conn,
conn->hostname, conn->port,
PROXY_DETERMINE_ADDRESS_CHECK,
NULL, s) == APR_SUCCESS) {
backend_addr = conn->addr;
}
/* * In case of an error backend_addr will be NULL which * is enough to leave the loop. If successful we'll retry * the new addresses only once.
*/
did_dns_lookup = 1;
} continue;
}
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02824) "%s: connection established with %pI (%s:%hu)",
proxy_function, backend_addr,
conn->hostname, conn->port);
/* Set the actual sockaddr we are connected to */
conn->addr = backend_addr;
}
/* Set a timeout on the socket */ if (worker->s->timeout_set) {
apr_socket_timeout_set(newsock, worker->s->timeout);
} elseif (conf->timeout_set) {
apr_socket_timeout_set(newsock, conf->timeout);
} else {
apr_socket_timeout_set(newsock, s->timeout);
}
conn->sock = newsock;
/* * For HTTP CONNECT we need to prepend CONNECT request before * sending our actual HTTPS requests.
*/ if (connect_info) {
rv = send_http_connect(conn, s); /* If an error occurred, loop round and try again */ if (rv != APR_SUCCESS) {
conn->sock = NULL;
apr_socket_close(newsock);
loglevel = backend_addr->next ? APLOG_DEBUG : APLOG_ERR;
ap_log_error(APLOG_MARK, loglevel, rv, s, APLOGNO(00958) "%s: attempt to connect to %s:%hu " "via http CONNECT through %pI (%s:%hu) failed",
proxy_function,
connect_info->target_host, connect_info->target_port,
backend_addr, conn->hostname, conn->port);
backend_addr = backend_addr->next; continue;
}
}
}
if (PROXY_WORKER_IS_USABLE(worker)) { /* * Put the entire worker to error state if * the PROXY_WORKER_IGNORE_ERRORS flag is not set. * Although some connections may be alive * no further connections to the worker could be made
*/ if (rv != APR_SUCCESS) { if (!(worker->s->status & PROXY_WORKER_IGNORE_ERRORS)) {
worker->s->error_time = apr_time_now();
worker->s->status |= PROXY_WORKER_IN_ERROR;
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00959) "ap_proxy_connect_backend disabling worker for (%s:%d) " "for %" APR_TIME_T_FMT "s",
worker->s->hostname_ex, (int)worker->s->port,
apr_time_sec(worker->s->retry));
}
} else { if (worker->s->retries) { /* * A worker came back. So here is where we need to * either reset all params to initial conditions or * apply some sort of aging
*/
}
worker->s->error_time = 0;
worker->s->retries = 0;
}
} else { /* * The worker is in error likely done by a different thread / process * e.g. for a timeout or bad status. We should respect this and should * not continue with a connection via this worker even if we got one.
*/
rv = APR_EINVAL;
} if (rv != APR_SUCCESS) {
socket_cleanup(conn); return DECLINED;
} return OK;
}
if (conn->connection) { if (conn->is_ssl) { /* on reuse, reinit the SSL connection dir config with the current * r->per_dir_config, the previous one was reset on release.
*/
ap_proxy_ssl_engine(conn->connection, per_dir_config, 1);
} return OK;
}
if (conn->sock) {
bucket_alloc = apr_bucket_alloc_create(conn->scpool);
conn->tmp_bb = apr_brigade_create(conn->scpool, bucket_alloc); /* * The socket is now open, create a new backend server connection
*/
conn->connection = ap_run_create_connection(conn->scpool, s, conn->sock,
0, NULL, bucket_alloc);
} if (!conn->connection) { /* * the peer reset the connection already; ap_run_create_connection() * closed the socket
*/
ap_log_error(APLOG_MARK, APLOG_ERR, 0,
s, APLOGNO(00960) "%s: an error occurred creating a " "new connection to %pI (%s)%s",
proxy_function, backend_addr, conn->hostname,
conn->sock ? "" : " (not connected)");
rc = HTTP_INTERNAL_SERVER_ERROR; goto cleanup;
}
/* For ssl connection to backend */ if (conn->is_ssl) { if (!ap_proxy_ssl_engine(conn->connection, per_dir_config, 1)) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0,
s, APLOGNO(00961) "%s: failed to enable ssl support " "for %pI (%s)", proxy_function,
backend_addr, conn->hostname);
rc = HTTP_INTERNAL_SERVER_ERROR; goto cleanup;
} if (conn->ssl_hostname) { /* Set a note on the connection about what CN is requested, * such that mod_ssl can check if it is requested to do so.
*/
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, conn->connection, "%s: set SNI to %s for (%s)", proxy_function,
conn->ssl_hostname, conn->hostname);
apr_table_setn(conn->connection->notes, "proxy-request-hostname",
conn->ssl_hostname);
}
} else { /* TODO: See if this will break FTP */
ap_proxy_ssl_engine(conn->connection, per_dir_config, 0);
}
/* * save the timeout of the socket because core_pre_connection * will set it to base_server->timeout * (core TimeOut directive).
*/
apr_socket_timeout_get(conn->sock, ¤t_timeout); /* set up the connection filters */
rc = ap_run_pre_connection(conn->connection, conn->sock); if (rc != OK && rc != DONE) {
conn->connection->aborted = 1;
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00963) "%s: pre_connection setup failed (%d)",
proxy_function, rc); goto cleanup;
}
apr_socket_timeout_set(conn->sock, current_timeout);
/* Shutdown the connection before closing it (eg. SSL connections * need to be close-notify-ed).
*/
apr_pool_pre_cleanup_register(conn->scpool, conn, connection_shutdown);
int ap_proxy_lb_workers(void)
{ /* * Since we can't resize the scoreboard when reconfiguring, we * have to impose a limit on the number of workers, we are * able to reconfigure to.
*/ if (!lb_workers_limit)
lb_workers_limit = proxy_lb_workers + PROXY_DYNAMIC_BALANCER_LIMIT; return lb_workers_limit;
}
static APR_INLINE int error_code_overridden(constint *elts, int nelts, int code)
{ int min = 0; int max = nelts - 1;
AP_DEBUG_ASSERT(max >= 0);
while (min < max) { int mid = (min + max) / 2; int val = elts[mid];
if (val < code) {
min = mid + 1;
} elseif (val > code) {
max = mid - 1;
} else { return 1;
}
}
return elts[min] == code;
}
PROXY_DECLARE(int) ap_proxy_should_override(proxy_dir_conf *conf, int code)
{ if (!conf->error_override) return 0;
if (apr_is_empty_array(conf->error_override_codes)) return ap_is_HTTP_ERROR(code);
/* Since error_override_codes is sorted, apply binary search. */ return error_code_overridden((int *)conf->error_override_codes->elts,
conf->error_override_codes->nelts,
code);
}
r->no_cache = 1; /* * If this is a subrequest, then prevent also caching of the main * request.
*/ if (r->main)
r->main->no_cache = 1;
e = ap_bucket_error_create(HTTP_BAD_GATEWAY, NULL, c->pool,
c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(brigade, e);
e = apr_bucket_eos_create(c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(brigade, e);
}
/* * Provide a string hashing function for the proxy. * We offer 2 methods: one is the APR model but we * also provide our own, based on either FNV or SDBM. * The reason is in case we want to use both to ensure no * collisions.
*/
PROXY_DECLARE(unsignedint)
ap_proxy_hashfunc(constchar *str, proxy_hash_t method)
{ if (method == PROXY_HASHFUNC_APR) {
apr_ssize_t slen = strlen(str); return apr_hashfunc_default(str, &slen);
} elseif (method == PROXY_HASHFUNC_FNV) { /* FNV model */ unsignedint hash; constunsignedint fnv_prime = 0x811C9DC5; for (hash = 0; *str; str++) {
hash *= fnv_prime;
hash ^= (*str);
} return hash;
} else { /* method == PROXY_HASHFUNC_DEFAULT */ /* SDBM model */ unsignedint hash; for (hash = 0; *str; str++) {
hash = (*str) + (hash << 6) + (hash << 16) - hash;
} return hash;
}
}
PROXY_DECLARE(apr_status_t) ap_proxy_set_wstatus(char c, int set, proxy_worker *w)
{ unsignedint *status = &w->s->status; char flag = toupper(c);
proxy_wstat_t *pwt = proxy_wstat_tbl; while (pwt->bit) { if (flag == pwt->flag) { if (set)
*status |= pwt->bit; else
*status &= ~(pwt->bit); return APR_SUCCESS;
}
pwt++;
} return APR_EINVAL;
}
PROXY_DECLARE(char *) ap_proxy_parse_wstatus(apr_pool_t *p, proxy_worker *w)
{ char *ret = ""; unsignedint status = w->s->status;
proxy_wstat_t *pwt = proxy_wstat_tbl; while (pwt->bit) { if (status & pwt->bit)
ret = apr_pstrcat(p, ret, pwt->name, NULL);
pwt++;
} if (!*ret) {
ret = "??? ";
} if (PROXY_WORKER_IS_USABLE(w))
ret = apr_pstrcat(p, ret, "Ok ", NULL); return ret;
}
/* * Look thru the list of workers in shm * and see which one(s) we are lacking... * again, the cast to unsigned int is safe * since our upper limit is always max_workers * which is int.
*/ for (index = 0; index < b->max_workers; index++) { int found;
apr_status_t rv; if ((rv = storage->dptr(b->wslot, (unsignedint)index, (void *)&shm)) != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s, APLOGNO(00965) "worker slotmem_dptr failed"); return APR_EGENERAL;
} /* account for possible "holes" in the slotmem * (eg: slots 0-2 are used, but 3 isn't, but 4-5 is)
*/ if (!shm->hash.def || !shm->hash.fnv) continue;
found = 0;
workers = (proxy_worker **)b->workers->elts; for (i = 0; i < b->workers->nelts; i++, workers++) {
proxy_worker *worker = *workers; if (worker->hash.def == shm->hash.def && worker->hash.fnv == shm->hash.fnv) {
found = 1;
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02402) "re-grabbing shm[%d] (0x%pp) for worker: %s", i, (void *)shm,
ap_proxy_worker_name(conf->pool, worker)); break;
}
} if (!found) {
proxy_worker **runtime; /* XXX: a thread mutex is maybe enough here */
apr_global_mutex_lock(proxy_mutex);
runtime = apr_array_push(b->workers);
*runtime = apr_pcalloc(conf->pool, sizeof(proxy_worker));
apr_global_mutex_unlock(proxy_mutex);
(*runtime)->hash = shm->hash;
(*runtime)->balancer = b;
(*runtime)->s = shm;
do { while (*val == ',' || *val == ';') {
val++;
}
name = ap_get_token(x->pool, &val, 0); if (!strcasecmp(name, "close")) {
x->closed = 1;
} if (!x->first) {
x->first = name;
} else { constchar **elt; if (!x->array) {
x->array = apr_array_make(x->pool, 4, sizeof(char *));
}
elt = apr_array_push(x->array);
*elt = name;
}
} while (*val);
return 1;
}
/** * Remove all headers referred to by the Connection header.
*/ staticint ap_proxy_clear_connection(request_rec *r, apr_table_t *headers)
{ constchar **name;
header_connection x;
apr_table_do(find_conn_headers, &x, headers, "Connection", NULL); if (x.first) { /* fast path - no memory allocated for one header */
apr_table_unset(headers, "Connection");
apr_table_unset(headers, x.first);
} if (x.array) { /* two or more headers */ while ((name = apr_array_pop(x.array))) {
apr_table_unset(headers, *name);
}
}
/* * HTTP "Ping" test? Easiest is 100-Continue. However: * To be compliant, we only use 100-Continue for requests with bodies. * We also make sure we won't be talking HTTP/1.0 as well.
*/ if (apr_table_get(r->subprocess_env, "force-proxy-request-1.0")) {
force10 = 1;
} elseif (apr_table_get(r->notes, "proxy-100-continue")
|| PROXY_SHOULD_PING_100_CONTINUE(worker, r)) {
do_100_continue = 1;
} if (force10 || apr_table_get(r->subprocess_env, "proxy-nokeepalive")) { if (origin) {
origin->keepalive = AP_CONN_CLOSE;
}
p_conn->close = 1;
}
/* * Make a copy on r->headers_in for the request we make to the backend, * modify the copy in place according to our configuration and connection * handling, use it to fill in the forwarded headers' brigade, and finally * restore the saved/original ones in r->headers_in. * * Note: We need to take r->pool for apr_table_copy as the key / value * pairs in r->headers_in have been created out of r->pool and * p might be (and actually is) a longer living pool. * This would trigger the bad pool ancestry abort in apr_table_copy if * apr is compiled with APR_POOL_DEBUG. * * icing: if p indeed lives longer than r->pool, we should allocate * all new header values from r->pool as well and avoid leakage.
*/
r->headers_in = apr_table_copy(r->pool, saved_headers_in);
/* Return the original Transfer-Encoding and/or Content-Length values * then drop the headers, they must be set by the proxy handler based * on the actual body being forwarded.
*/ if ((*old_te_val = (char *)apr_table_get(r->headers_in, "Transfer-Encoding"))) {
apr_table_unset(r->headers_in, "Transfer-Encoding");
} if ((*old_cl_val = (char *)apr_table_get(r->headers_in, "Content-Length"))) {
apr_table_unset(r->headers_in, "Content-Length");
}
/* Clear out hop-by-hop request headers not to forward */ if (ap_proxy_clear_connection(r, r->headers_in) < 0) {
rc = HTTP_BAD_REQUEST; goto cleanup;
}
/* RFC2616 13.5.1 says we should strip these */
apr_table_unset(r->headers_in, "Keep-Alive");
apr_table_unset(r->headers_in, "Upgrade");
apr_table_unset(r->headers_in, "Trailer");
apr_table_unset(r->headers_in, "TE");
/* Compute Host header */ if (dconf->preserve_host == 0) { if (!uri->hostname) {
rc = HTTP_BAD_REQUEST; goto cleanup;
} if (ap_strchr_c(uri->hostname, ':')) { /* if literal IPv6 address */ if (uri->port_str && uri->port != DEFAULT_HTTP_PORT) {
host = apr_pstrcat(r->pool, "[", uri->hostname, "]:",
uri->port_str, NULL);
} else {
host = apr_pstrcat(r->pool, "[", uri->hostname, "]", NULL);
}
} else { if (uri->port_str && uri->port != DEFAULT_HTTP_PORT) {
host = apr_pstrcat(r->pool, uri->hostname, ":",
uri->port_str, NULL);
} else {
host = uri->hostname;
}
}
apr_table_setn(r->headers_in, "Host", host);
} else { /* don't want to use r->hostname as the incoming header might have a * port attached, let's use the original header.
*/
host = saved_host; if (!host) {
host = r->server->server_hostname;
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01092) "no HTTP 0.9 request (with no host line) " "on incoming request and preserve host set " "forcing hostname to be %s for uri %s",
host, r->uri);
apr_table_setn(r->headers_in, "Host", host);
}
}
/* handle Via */ if (conf->viaopt == via_block) { /* Block all outgoing Via: headers */
apr_table_unset(r->headers_in, "Via");
} elseif (conf->viaopt != via_off) { constchar *server_name = ap_get_server_name(r); /* If USE_CANONICAL_NAME_OFF was configured for the proxy virtual host, * then the server name returned by ap_get_server_name() is the * origin server name (which does make too much sense with Via: headers) * so we use the proxy vhost's name instead.
*/ if (server_name == r->hostname)
server_name = r->server->server_hostname; /* Create a "Via:" request header entry and merge it */ /* Generate outgoing Via: header with/without server comment: */
apr_table_mergen(r->headers_in, "Via",
(conf->viaopt == via_full)
? apr_psprintf(p, "%d.%d %s%s (%s)",
HTTP_VERSION_MAJOR(r->proto_num),
HTTP_VERSION_MINOR(r->proto_num),
server_name, server_portstr,
AP_SERVER_BASEVERSION)
: apr_psprintf(p, "%d.%d %s%s",
HTTP_VERSION_MAJOR(r->proto_num),
HTTP_VERSION_MINOR(r->proto_num),
server_name, server_portstr)
);
}
/* Use HTTP/1.1 100-Continue as quick "HTTP ping" test * to backend
*/ if (do_100_continue) { /* Add the Expect header if not already there. */ if (!(val = apr_table_get(r->headers_in, "Expect"))
|| (ap_cstr_casecmp(val, "100-Continue") != 0 /* fast path */
&& !ap_find_token(r->pool, val, "100-Continue"))) {
apr_table_mergen(r->headers_in, "Expect", "100-Continue");
}
} else { /* XXX: we should strip the 100-continue token only from the * Expect header, but are there others actually used anywhere?
*/
apr_table_unset(r->headers_in, "Expect");
}
/* X-Forwarded-*: handling * * XXX Privacy Note: * ----------------- * * These request headers are only really useful when the mod_proxy * is used in a reverse proxy configuration, so that useful info * about the client can be passed through the reverse proxy and on * to the backend server, which may require the information to * function properly. * * In a forward proxy situation, these options are a potential * privacy violation, as information about clients behind the proxy * are revealed to arbitrary servers out there on the internet. * * The HTTP/1.1 Via: header is designed for passing client * information through proxies to a server, and should be used in * a forward proxy configuration instead of X-Forwarded-*. See the * ProxyVia option for details.
*/ if (dconf->add_forwarded_headers) { if (PROXYREQ_REVERSE == r->proxyreq) { /* Add X-Forwarded-For: so that the upstream has a chance to * determine, where the original request came from.
*/
apr_table_mergen(r->headers_in, "X-Forwarded-For",
r->useragent_ip);
/* Add X-Forwarded-Host: so that upstream knows what the * original request hostname was.
*/ if (saved_host) {
apr_table_mergen(r->headers_in, "X-Forwarded-Host",
saved_host);
}
/* Add X-Forwarded-Server: so that upstream knows what the * name of this proxy server is (if there are more than one) * XXX: This duplicates Via: - do we strictly need it?
*/
apr_table_mergen(r->headers_in, "X-Forwarded-Server",
r->server->server_hostname);
}
}
/* Do we want to strip Proxy-Authorization ? * If we haven't used it, then NO * If we have used it then MAYBE: RFC2616 says we MAY propagate it. * So let's make it configurable by env.
*/ if (r->user != NULL /* we've authenticated */
&& !apr_table_get(r->subprocess_env, "Proxy-Chain-Auth")) {
apr_table_unset(r->headers_in, "Proxy-Authorization");
}
/* run hook to fixup the request we are about to send */
proxy_run_fixups(r);
/* We used to send `Host: ` always first, so let's keep it that * way. No telling which legacy backend is relying on this. * If proxy_run_fixups() changed the value, use it (though removal * is ignored).
*/
val = apr_table_get(r->headers_in, "Host"); if (val) {
apr_table_unset(r->headers_in, "Host");
host = val;
}
buf = apr_pstrcat(p, "Host: ", host, CRLF, NULL);
ap_xlate_proto_to_ascii(buf, strlen(buf));
e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(header_brigade, e);
/* Append the (remaining) headers to the brigade */
headers_in_array = apr_table_elts(r->headers_in);
headers_in = (const apr_table_entry_t *) headers_in_array->elts; for (counter = 0; counter < headers_in_array->nelts; counter++) { if (headers_in[counter].key == NULL
|| headers_in[counter].val == NULL) { continue;
}
/* Prefetch max_read bytes * * This helps us avoid any election of C-L v.s. T-E * request bodies, since we are willing to keep in * memory this much data, in any case. This gives * us an instant C-L election if the body is of some * reasonable size.
*/
temp_brigade = apr_brigade_create(p, input_brigade->bucket_alloc);
/* Account for saved input, if any. */
apr_brigade_length(input_brigade, 0, bytes_read);
/* Ensure we don't hit a wall where we have a buffer too small for * ap_get_brigade's filters to fetch us another bucket, surrender * once we hit 80 bytes (an arbitrary value) less than max_read.
*/ while (*bytes_read < max_read - 80
&& (APR_BRIGADE_EMPTY(input_brigade)
|| !APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade)))) {
status = ap_get_brigade(r->input_filters, temp_brigade,
AP_MODE_READBYTES, block,
max_read - *bytes_read); /* ap_get_brigade may return success with an empty brigade * for a non-blocking read which would block
*/ if (block == APR_NONBLOCK_READ
&& ((status == APR_SUCCESS && APR_BRIGADE_EMPTY(temp_brigade))
|| APR_STATUS_IS_EAGAIN(status))) { break;
} if (status != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01095) "prefetch request body failed to %pI (%s)" " from %s (%s)", backend->addr,
backend->hostname ? backend->hostname : "",
c->client_ip, c->remote_host ? c->remote_host : ""); return ap_map_http_request_error(status, HTTP_BAD_REQUEST);
}
/* * Save temp_brigade in input_brigade. (At least) in the SSL case * temp_brigade contains transient buckets whose data would get * overwritten during the next call of ap_get_brigade in the loop. * ap_save_brigade ensures these buckets to be set aside. * Calling ap_save_brigade with NULL as filter is OK, because * input_brigade already has been created and does not need to get * created by ap_save_brigade.
*/
status = ap_save_brigade(NULL, &input_brigade, &temp_brigade, p); if (status != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01096) "processing prefetched request body failed" " to %pI (%s) from %s (%s)", backend->addr,
backend->hostname ? backend->hostname : "",
c->client_ip, c->remote_host ? c->remote_host : ""); return HTTP_INTERNAL_SERVER_ERROR;
}
}
do { if (APR_BRIGADE_EMPTY(input_brigade)) {
rv = ap_proxy_read_input(r, backend, input_brigade,
HUGE_STRING_LEN); if (rv != OK) { return rv;
}
}
/* If this brigade contains EOS, either stop or remove it. */ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) {
seen_eos = 1;
}
apr_brigade_length(input_brigade, 1, &bytes);
if (*bytes_spooled + bytes > max_mem_spool) { /* can't spool any more in memory; write latest brigade to disk */ if (tmpfile == NULL) { constchar *temp_dir; char *template;
status = apr_temp_dir_get(&temp_dir, p); if (status != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01089) "search for temporary directory failed"); return HTTP_INTERNAL_SERVER_ERROR;
}
apr_filepath_merge(&template, temp_dir, "modproxy.tmp.XXXXXX",
APR_FILEPATH_NATIVE, p);
status = apr_file_mktemp(&tmpfile, template, 0, p); if (status != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01090) "creation of temporary file in directory " "%s failed", temp_dir); return HTTP_INTERNAL_SERVER_ERROR;
}
} for (e = APR_BRIGADE_FIRST(input_brigade);
e != APR_BRIGADE_SENTINEL(input_brigade);
e = APR_BUCKET_NEXT(e)) { constchar *data;
apr_size_t bytes_read, bytes_written;
apr_bucket_read(e, &data, &bytes_read, APR_BLOCK_READ);
status = apr_file_write_full(tmpfile, data, bytes_read, &bytes_written); if (status != APR_SUCCESS) { constchar *tmpfile_name;
/* * Save input_brigade in body_brigade. (At least) in the SSL case * input_brigade contains transient buckets whose data would get * overwritten during the next call of ap_get_brigade in the loop. * ap_save_brigade ensures these buckets to be set aside. * Calling ap_save_brigade with NULL as filter is OK, because * body_brigade already has been created and does not need to get * created by ap_save_brigade.
*/
status = ap_save_brigade(NULL, &body_brigade, &input_brigade, p); if (status != APR_SUCCESS) { return HTTP_INTERNAL_SERVER_ERROR;
}
}
*bytes_spooled += bytes;
} while (!seen_eos);
APR_BRIGADE_CONCAT(input_brigade, body_brigade); if (tmpfile) {
apr_brigade_insert_file(input_brigade, tmpfile, 0, fsize, p);
} if (apr_table_get(r->subprocess_env, "proxy-sendextracrlf")) {
e = apr_bucket_immortal_create(CRLF_ASCII, 2, bucket_alloc);
APR_BRIGADE_INSERT_TAIL(input_brigade, e);
} if (tmpfile) { /* We dropped metadata buckets when spooling to tmpfile, * terminate with EOS to allow for flushing in a one go.
*/
e = apr_bucket_eos_create(bucket_alloc);
APR_BRIGADE_INSERT_TAIL(input_brigade, e);
} return OK;
}
if (flush) {
apr_bucket *e = apr_bucket_flush_create(bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, e);
}
apr_brigade_length(bb, 0, &transferred); if (transferred != -1)
p_conn->worker->s->transferred += transferred;
status = ap_pass_brigade(origin->output_filters, bb); /* Cleanup the brigade now to avoid buckets lifetime
* issues in case of error returned below. */
apr_brigade_cleanup(bb); if (status != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01084) "pass request body failed to %pI (%s)",
p_conn->addr, p_conn->hostname); if (origin->aborted) { constchar *ssl_note;
apr_brigade_cleanup(to); for (e = APR_BRIGADE_FIRST(from);
e != APR_BRIGADE_SENTINEL(from);
e = APR_BUCKET_NEXT(e)) { if (!APR_BUCKET_IS_METADATA(e)) {
apr_bucket_read(e, &data, &bytes, APR_BLOCK_READ); new = apr_bucket_transient_create(data, bytes, bucket_alloc);
APR_BRIGADE_INSERT_TAIL(to, new);
} elseif (APR_BUCKET_IS_FLUSH(e)) { new = apr_bucket_flush_create(bucket_alloc);
APR_BRIGADE_INSERT_TAIL(to, new);
} elseif (APR_BUCKET_IS_EOS(e)) { new = apr_bucket_eos_create(bucket_alloc);
APR_BRIGADE_INSERT_TAIL(to, new);
} else {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(03304) "Unhandled bucket type of type %s in" " ap_proxy_buckets_lifetime_transform", e->type->name);
rv = APR_EGENERAL;
}
} return rv;
}
/* An arbitrary large value to address pathological case where we keep * reading from one side only, without scheduling the other direction for * too long. This can happen with large MTU and small read buffers, like * micro-benchmarking huge files bidirectional transfer with client, proxy * and backend on localhost for instance. Though we could just ignore the * case and let the sender stop by itself at some point when/if it needs to * receive data, or the receiver stop when/if it needs to send...
*/ #define PROXY_TRANSFER_MAX_READS 10000
/* * Compat: since FLUSH_EACH is default (and zero) for legacy reasons, we * pretend it's no FLUSH_AFTER nor YIELD_PENDING flags, the latter because * flushing would defeat the purpose of checking for pending data (hence * determine whether or not the output chain/stack is full for stopping).
*/ if (!(flags & (AP_PROXY_TRANSFER_FLUSH_AFTER |
AP_PROXY_TRANSFER_YIELD_PENDING))) {
flush_each = 1;
}
for (;;) {
apr_brigade_cleanup(bb_i);
rv = ap_get_brigade(c_i->input_filters, bb_i, AP_MODE_READBYTES,
APR_NONBLOCK_READ, bsize); if (rv != APR_SUCCESS) { if (!APR_STATUS_IS_EAGAIN(rv) && !APR_STATUS_IS_EOF(rv)) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(03308) "ap_proxy_transfer_between_connections: " "error on %s - ap_get_brigade",
name); if (rv == APR_INCOMPLETE) { /* Don't return APR_INCOMPLETE, it'd mean "should yield" * for the caller, while it means "incomplete body" here * from ap_http_filter(), which is an error.
*/
rv = APR_EGENERAL;
}
} break;
}
if (c_o->aborted) {
apr_brigade_cleanup(bb_i);
flags &= ~AP_PROXY_TRANSFER_FLUSH_AFTER;
rv = APR_EPIPE; break;
} if (APR_BRIGADE_EMPTY(bb_i)) { break;
} #ifdef DEBUGGING
len = -1;
apr_brigade_length(bb_i, 0, &len);
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03306) "ap_proxy_transfer_between_connections: " "read %" APR_OFF_T_FMT " bytes from %s", len, name); #endif if (sent) {
*sent = 1;
}
ap_proxy_buckets_lifetime_transform(r, bb_i, bb_o); if (flush_each) {
apr_bucket *b; /* * Do not use ap_fflush here since this would cause the flush * bucket to be sent in a separate brigade afterwards which * causes some filters to set aside the buckets from the first * brigade and process them when FLUSH arrives in the second * brigade. As set asides of our transformed buckets involve * memory copying we try to avoid this. If we have the flush * bucket in the first brigade they directly process the * buckets without setting them aside.
*/
b = apr_bucket_flush_create(bb_o->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb_o, b);
}
rv = ap_pass_brigade(c_o->output_filters, bb_o);
apr_brigade_cleanup(bb_o); if (rv != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(03307) "ap_proxy_transfer_between_connections: " "error on %s - ap_pass_brigade",
name);
flags &= ~AP_PROXY_TRANSFER_FLUSH_AFTER; break;
}
/* Yield if the output filters stack is full? This is to avoid * blocking and give the caller a chance to POLLOUT async.
*/ if ((flags & AP_PROXY_TRANSFER_YIELD_PENDING)
&& ap_filter_should_yield(c_o->output_filters)) { int rc = ap_filter_output_pending(c_o); if (rc == OK) {
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "ap_proxy_transfer_between_connections: " "yield (output pending)");
rv = APR_INCOMPLETE; break;
} if (rc != DECLINED) {
rv = AP_FILTER_ERROR; break;
}
}
/* Yield if we keep hold of the thread for too long? This gives * the caller a chance to schedule the other direction too.
*/ if ((flags & AP_PROXY_TRANSFER_YIELD_MAX_READS)
&& ++num_reads > PROXY_TRANSFER_MAX_READS) {
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "ap_proxy_transfer_between_connections: " "yield (max reads)");
rv = APR_SUCCESS; break;
}
}
if (flags & AP_PROXY_TRANSFER_FLUSH_AFTER) {
ap_fflush(c_o->output_filters, bb_o);
apr_brigade_cleanup(bb_o);
}
apr_brigade_cleanup(bb_i);
/* Defaults to the largest timeout of both connections */
tunnel->timeout = (client_timeout >= 0 && client_timeout > origin_timeout ?
client_timeout : origin_timeout);
/* Bidirectional non-HTTP stream will confuse mod_reqtimeoout */
ap_remove_input_filter_byhandle(c_i->input_filters, "reqtimeout");
/* The input/output filter stacks should contain connection filters only */
r->input_filters = r->proto_input_filters = c_i->input_filters;
r->output_filters = r->proto_output_filters = c_i->output_filters;
/* Won't be reused after tunneling */
c_i->keepalive = AP_CONN_CLOSE;
c_o->keepalive = AP_CONN_CLOSE;
/* Disable half-close forwarding for this request? */ if (apr_table_get(r->subprocess_env, "proxy-nohalfclose")) {
tunnel->nohalfclose = 1;
}
if (tunnel->client->pfd->desc_type == APR_POLL_SOCKET) { /* Both ends are sockets, the poll strategy is: * - poll both sides POLLOUT * - when one side is writable, remove the POLLOUT * and add POLLIN to the other side. * - tunnel arriving data, remove POLLIN from the source * again and add POLLOUT to the receiving side * - on EOF on read, remove the POLLIN from that side
* Repeat until both sides are down */
tunnel->client->pfd->reqevents = APR_POLLOUT | APR_POLLERR;
tunnel->origin->pfd->reqevents = APR_POLLOUT | APR_POLLERR; if ((rv = apr_pollset_add(tunnel->pollset, tunnel->origin->pfd)) ||
(rv = apr_pollset_add(tunnel->pollset, tunnel->client->pfd))) { return rv;
}
} elseif (tunnel->client->pfd->desc_type == APR_POLL_FILE) { /* Input is a PIPE fd, the poll strategy is: * - always POLLIN on origin * - use socket strategy described above for client only * otherwise the same
*/
tunnel->client->pfd->reqevents = 0;
tunnel->origin->pfd->reqevents = APR_POLLIN | APR_POLLHUP |
APR_POLLOUT | APR_POLLERR; if ((rv = apr_pollset_add(tunnel->pollset, tunnel->origin->pfd))) { return rv;
}
} else { /* input is already closed, unsual, but we know nothing about
* the tunneled protocol. */
tunnel->client->down_in = 1;
tunnel->origin->pfd->reqevents = APR_POLLIN | APR_POLLHUP; if ((rv = apr_pollset_add(tunnel->pollset, tunnel->origin->pfd))) { return rv;
}
}
rv = ap_proxy_transfer_between_connections(tunnel->r,
in->c, out->c,
in->bb, out->bb,
in->name, &sent,
tunnel->read_buf_size,
AP_PROXY_TRANSFER_YIELD_PENDING |
AP_PROXY_TRANSFER_YIELD_MAX_READS); if (sent && out == tunnel->client) {
tunnel->replied = 1;
} if (rv != APR_SUCCESS) { if (APR_STATUS_IS_INCOMPLETE(rv)) { /* Pause POLLIN while waiting for POLLOUT on the other * side, hence avoid filling the output filters even * more to avoid blocking there.
*/
ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, tunnel->r, "proxy: %s: %s wait writable",
tunnel->scheme, out->name);
} elseif (APR_STATUS_IS_EOF(rv)) { /* Stop POLLIN and wait for POLLOUT (flush) on the * other side to shut it down.
*/
ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, tunnel->r, "proxy: %s: %s read shutdown",
tunnel->scheme, in->name); if (tunnel->nohalfclose) { /* No half-close forwarding, we are done both ways as * soon as one side shuts down.
*/ return DONE;
}
in->down_in = 1;
} else { /* Real failure, bail out */ return HTTP_INTERNAL_SERVER_ERROR;
}
del_pollset(tunnel->pollset, in->pfd, APR_POLLIN); if (out->pfd->desc_type == APR_POLL_SOCKET) { /* if the output is a SOCKET, we can stop polling the input
* until the output signals POLLOUT again. */
add_pollset(tunnel->pollset, out->pfd, APR_POLLOUT);
} else { /* We can't use POLLOUT in this direction for the only * APR_POLL_FILE case we have so far (mod_h2's "signal" pipe), * we assume that the client's ouput filters chain will block/flush * if necessary (i.e. no pending data), hence that the origin
* is EOF when reaching here. This direction is over. */
ap_assert(in->down_in && APR_STATUS_IS_EOF(rv));
ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, tunnel->r, "proxy: %s: %s write shutdown",
tunnel->scheme, out->name);
out->down_out = 1;
}
}
/* We want to write if we asked for POLLOUT and got: * - POLLOUT: the socket is ready for write; * - !POLLIN: the socket is in error state (POLLERR) so we let * the user know by failing the write and log, OR the socket * is shutdown for read already (POLLHUP) so we have to * shutdown for write.
*/ if ((tc->pfd->reqevents & APR_POLLOUT)
&& ((pfd->rtnevents & APR_POLLOUT)
|| !(tc->pfd->reqevents & APR_POLLIN)
|| !(pfd->rtnevents & (APR_POLLIN | APR_POLLHUP)))) { struct proxy_tunnel_conn *out = tc, *in = tc->other;
rc = ap_filter_output_pending(out->c); if (rc == OK) { /* Keep polling out (only) */ continue;
} if (rc != DECLINED) { /* Real failure, bail out */
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10221) "proxy: %s: %s flushing failed (%i)",
scheme, out->name, rc);
status = rc; goto done;
}
/* No more pending data. If the other side is not readable * anymore it's time to shutdown for write (this direction * is over). Otherwise back to normal business.
*/
del_pollset(pollset, out->pfd, APR_POLLOUT); if (in->down_in) {
ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "proxy: %s: %s write shutdown",
scheme, out->name);
apr_socket_shutdown(out->pfd->desc.s, 1);
out->down_out = 1;
} else {
ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "proxy: %s: %s resume writable",
scheme, out->name);
add_pollset(pollset, in->pfd, APR_POLLIN);
/* Flush any pending input data now, we don't know when * the next POLLIN will trigger and retaining data might * deadlock the underlying protocol. We don't check for * pending data first with ap_filter_input_pending() since * the read from proxy_tunnel_forward() is nonblocking * anyway and returning OK if there's no data.
*/
rc = proxy_tunnel_forward(tunnel, in); if (rc != OK) {
status = rc; goto done;
}
}
}
/* We want to read if we asked for POLLIN|HUP and got: * - POLLIN|HUP: the socket is ready for read or EOF (POLLHUP); * - !POLLOUT: the socket is in error state (POLLERR) so we let * the user know by failing the read and log.
*/ if ((tc->pfd->reqevents & APR_POLLIN)
&& ((pfd->rtnevents & (APR_POLLIN | APR_POLLHUP))
|| !(pfd->rtnevents & APR_POLLOUT))) {
rc = proxy_tunnel_forward(tunnel, tc); if (rc != OK) {
status = rc; goto done;
}
}
}
} while (!client->down_out || !origin->down_out);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.