blob: e4babba0813450174e189f46e49910b0da16c884 [file] [log] [blame]
/*
* Client-side variables and functions.
*
* Copyright 2000-2006 Willy Tarreau <w@1wt.eu>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
*/
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <common/compat.h>
#include <common/config.h>
#include <common/time.h>
#include <types/backend.h>
#include <types/buffers.h>
#include <types/global.h>
#include <types/httperr.h>
#include <types/polling.h>
#include <types/proxy.h>
#include <types/server.h>
#include <types/session.h>
#include <proto/buffers.h>
#include <proto/client.h>
#include <proto/fd.h>
#include <proto/log.h>
#include <proto/hdr_idx.h>
#include <proto/proto_http.h>
#include <proto/stream_sock.h>
#include <proto/task.h>
/*
* FIXME: This should move to the STREAM_SOCK code then split into TCP and HTTP.
*/
/*
* this function is called on a read event from a listen socket, corresponding
* to an accept. It tries to accept as many connections as possible.
* It returns 0.
*/
int event_accept(int fd) {
struct proxy *p = (struct proxy *)fdtab[fd].owner;
struct session *s;
struct http_req *hreq;
struct task *t;
int cfd;
int max_accept;
if (global.nbproc > 1)
max_accept = 8; /* let other processes catch some connections too */
else
max_accept = -1;
while (p->feconn < p->maxconn && max_accept--) {
struct sockaddr_storage addr;
socklen_t laddr = sizeof(addr);
if ((cfd = accept(fd, (struct sockaddr *)&addr, &laddr)) == -1) {
switch (errno) {
case EAGAIN:
case EINTR:
case ECONNABORTED:
return 0; /* nothing more to accept */
case ENFILE:
send_log(p, LOG_EMERG,
"Proxy %s reached system FD limit at %d. Please check system tunables.\n",
p->id, maxfd);
return 0;
case EMFILE:
send_log(p, LOG_EMERG,
"Proxy %s reached process FD limit at %d. Please check 'ulimit-n' and restart.\n",
p->id, maxfd);
return 0;
case ENOBUFS:
case ENOMEM:
send_log(p, LOG_EMERG,
"Proxy %s reached system memory limit at %d sockets. Please check system tunables.\n",
p->id, maxfd);
return 0;
default:
return 0;
}
}
if ((s = pool_alloc(session)) == NULL) { /* disable this proxy for a while */
Alert("out of memory in event_accept().\n");
MY_FD_CLR(fd, StaticReadEvent);
p->state = PR_STIDLE;
close(cfd);
return 0;
}
/* if this session comes from a known monitoring system, we want to ignore
* it as soon as possible, which means closing it immediately for TCP.
*/
s->flags = 0;
if (addr.ss_family == AF_INET &&
p->mon_mask.s_addr &&
(((struct sockaddr_in *)&addr)->sin_addr.s_addr & p->mon_mask.s_addr) == p->mon_net.s_addr) {
if (p->mode == PR_MODE_TCP) {
close(cfd);
pool_free(session, s);
continue;
}
s->flags |= SN_MONITOR;
}
if ((t = pool_alloc(task)) == NULL) { /* disable this proxy for a while */
Alert("out of memory in event_accept().\n");
MY_FD_CLR(fd, StaticReadEvent);
p->state = PR_STIDLE;
close(cfd);
pool_free(session, s);
return 0;
}
s->cli_addr = addr;
if (cfd >= global.maxsock) {
Alert("accept(): not enough free sockets. Raise -n argument. Giving up.\n");
close(cfd);
pool_free(task, t);
pool_free(session, s);
return 0;
}
if ((fcntl(cfd, F_SETFL, O_NONBLOCK) == -1) ||
(setsockopt(cfd, IPPROTO_TCP, TCP_NODELAY,
(char *) &one, sizeof(one)) == -1)) {
Alert("accept(): cannot set the socket in non blocking mode. Giving up\n");
close(cfd);
pool_free(task, t);
pool_free(session, s);
return 0;
}
if (p->options & PR_O_TCP_CLI_KA)
setsockopt(cfd, SOL_SOCKET, SO_KEEPALIVE, (char *) &one, sizeof(one));
t->wq = NULL;
t->rqnext = NULL;
t->state = TASK_IDLE;
t->process = process_session;
t->context = s;
s->task = t;
s->be = s->fe = p;
s->cli_state = (p->mode == PR_MODE_HTTP) ? CL_STHEADERS : CL_STDATA; /* no HTTP headers for non-HTTP proxies */
s->srv_state = SV_STIDLE;
s->req = s->rep = NULL; /* will be allocated later */
s->cli_fd = cfd;
s->srv_fd = -1;
s->srv = NULL;
s->pend_pos = NULL;
s->conn_retries = p->beprm->conn_retries;
/* FIXME: the logs are horribly complicated now, because they are
* defined in <p>, <p->beprm>, and later <be> and <be->beprm>.
*/
if (s->flags & SN_MONITOR)
s->logs.logwait = 0;
else
s->logs.logwait = p->to_log;
s->logs.tv_accept = now;
s->logs.t_request = -1;
s->logs.t_queue = -1;
s->logs.t_connect = -1;
s->logs.t_data = -1;
s->logs.t_close = 0;
s->logs.uri = NULL;
s->logs.cli_cookie = NULL;
s->logs.srv_cookie = NULL;
s->logs.status = -1;
s->logs.bytes_in = s->logs.bytes_out = 0;
s->logs.prx_queue_size = 0; /* we get the number of pending conns before us */
s->logs.srv_queue_size = 0; /* we will get this number soon */
s->data_source = DATA_SRC_NONE;
s->uniq_id = totalconn;
p->cum_feconn++; /* cum_beconn will be increased once assigned */
hreq = &s->hreq;
hreq->req.cap = NULL;
hreq->rsp.cap = NULL;
hreq->hdr_idx.v = NULL;
hreq->hdr_idx.size = hreq->hdr_idx.used = 0;
if (p->mode == PR_MODE_HTTP) {
hreq->req.hdr_state = HTTP_MSG_RQBEFORE; /* at the very beginning of the request */
hreq->req.sol = hreq->req.eol = NULL;
hreq->req.sor = hreq->req.eoh = 0; /* relative to the buffer */
hreq->auth_hdr.len = -1;
hreq->hdr_idx.size = MAX_HTTP_HDR;
if (p->fiprm->nb_req_cap > 0) {
if ((hreq->req.cap =
pool_alloc_from(p->fiprm->req_cap_pool, p->fiprm->nb_req_cap*sizeof(char *)))
== NULL) { /* no memory */
close(cfd); /* nothing can be done for this fd without memory */
pool_free(task, t);
pool_free(session, s);
return 0;
}
memset(hreq->req.cap, 0, p->fiprm->nb_req_cap*sizeof(char *));
}
if (p->fiprm->nb_rsp_cap > 0) {
if ((hreq->rsp.cap =
pool_alloc_from(p->fiprm->rsp_cap_pool, p->fiprm->nb_rsp_cap*sizeof(char *)))
== NULL) { /* no memory */
if (hreq->req.cap != NULL)
pool_free_to(p->fiprm->req_cap_pool, hreq->req.cap);
close(cfd); /* nothing can be done for this fd without memory */
pool_free(task, t);
pool_free(session, s);
return 0;
}
memset(hreq->rsp.cap, 0, p->fiprm->nb_rsp_cap*sizeof(char *));
}
if ((hreq->hdr_idx.v =
pool_alloc_from(p->hdr_idx_pool, hreq->hdr_idx.size*sizeof(*hreq->hdr_idx.v)))
== NULL) { /* no memory */
if (hreq->rsp.cap != NULL)
pool_free_to(p->fiprm->rsp_cap_pool, hreq->rsp.cap);
if (hreq->req.cap != NULL)
pool_free_to(p->fiprm->req_cap_pool, hreq->req.cap);
close(cfd); /* nothing can be done for this fd without memory */
pool_free(task, t);
pool_free(session, s);
return 0;
}
hdr_idx_init(&hreq->hdr_idx);
}
if ((p->mode == PR_MODE_TCP || p->mode == PR_MODE_HTTP)
&& (p->logfac1 >= 0 || p->logfac2 >= 0)) {
struct sockaddr_storage sockname;
socklen_t namelen = sizeof(sockname);
if (addr.ss_family != AF_INET ||
!(s->fe->options & PR_O_TRANSP) ||
get_original_dst(cfd, (struct sockaddr_in *)&sockname, &namelen) == -1)
getsockname(cfd, (struct sockaddr *)&sockname, &namelen);
if (p->to_log) {
/* we have the client ip */
if (s->logs.logwait & LW_CLIP)
if (!(s->logs.logwait &= ~LW_CLIP))
sess_log(s);
}
else if (s->cli_addr.ss_family == AF_INET) {
char pn[INET_ADDRSTRLEN], sn[INET_ADDRSTRLEN];
if (inet_ntop(AF_INET, (const void *)&((struct sockaddr_in *)&sockname)->sin_addr,
sn, sizeof(sn)) &&
inet_ntop(AF_INET, (const void *)&((struct sockaddr_in *)&s->cli_addr)->sin_addr,
pn, sizeof(pn))) {
send_log(p, LOG_INFO, "Connect from %s:%d to %s:%d (%s/%s)\n",
pn, ntohs(((struct sockaddr_in *)&s->cli_addr)->sin_port),
sn, ntohs(((struct sockaddr_in *)&sockname)->sin_port),
p->id, (p->mode == PR_MODE_HTTP) ? "HTTP" : "TCP");
}
}
else {
char pn[INET6_ADDRSTRLEN], sn[INET6_ADDRSTRLEN];
if (inet_ntop(AF_INET6, (const void *)&((struct sockaddr_in6 *)&sockname)->sin6_addr,
sn, sizeof(sn)) &&
inet_ntop(AF_INET6, (const void *)&((struct sockaddr_in6 *)&s->cli_addr)->sin6_addr,
pn, sizeof(pn))) {
send_log(p, LOG_INFO, "Connect from %s:%d to %s:%d (%s/%s)\n",
pn, ntohs(((struct sockaddr_in6 *)&s->cli_addr)->sin6_port),
sn, ntohs(((struct sockaddr_in6 *)&sockname)->sin6_port),
p->id, (p->mode == PR_MODE_HTTP) ? "HTTP" : "TCP");
}
}
}
if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) {
struct sockaddr_in sockname;
socklen_t namelen = sizeof(sockname);
int len;
if (addr.ss_family != AF_INET ||
!(s->fe->options & PR_O_TRANSP) ||
get_original_dst(cfd, (struct sockaddr_in *)&sockname, &namelen) == -1)
getsockname(cfd, (struct sockaddr *)&sockname, &namelen);
if (s->cli_addr.ss_family == AF_INET) {
char pn[INET_ADDRSTRLEN];
inet_ntop(AF_INET,
(const void *)&((struct sockaddr_in *)&s->cli_addr)->sin_addr,
pn, sizeof(pn));
len = sprintf(trash, "%08x:%s.accept(%04x)=%04x from [%s:%d]\n",
s->uniq_id, p->id, (unsigned short)fd, (unsigned short)cfd,
pn, ntohs(((struct sockaddr_in *)&s->cli_addr)->sin_port));
}
else {
char pn[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6,
(const void *)&((struct sockaddr_in6 *)(&s->cli_addr))->sin6_addr,
pn, sizeof(pn));
len = sprintf(trash, "%08x:%s.accept(%04x)=%04x from [%s:%d]\n",
s->uniq_id, p->id, (unsigned short)fd, (unsigned short)cfd,
pn, ntohs(((struct sockaddr_in6 *)(&s->cli_addr))->sin6_port));
}
write(1, trash, len);
}
if ((s->req = pool_alloc(buffer)) == NULL) { /* no memory */
if (hreq->hdr_idx.v != NULL)
pool_free_to(p->hdr_idx_pool, hreq->hdr_idx.v);
if (hreq->rsp.cap != NULL)
pool_free_to(p->fiprm->rsp_cap_pool, hreq->rsp.cap);
if (hreq->req.cap != NULL)
pool_free_to(p->fiprm->req_cap_pool, hreq->req.cap);
close(cfd); /* nothing can be done for this fd without memory */
pool_free(task, t);
pool_free(session, s);
return 0;
}
buffer_init(s->req);
s->req->rlim += BUFSIZE;
if (s->cli_state == CL_STHEADERS) /* reserve some space for header rewriting */
s->req->rlim -= MAXREWRITE;
s->req->rto = s->fe->clitimeout;
s->req->wto = s->be->beprm->srvtimeout;
s->req->cto = s->be->beprm->srvtimeout;
if ((s->rep = pool_alloc(buffer)) == NULL) { /* no memory */
pool_free(buffer, s->req);
if (hreq->hdr_idx.v != NULL)
pool_free_to(p->hdr_idx_pool, hreq->hdr_idx.v);
if (hreq->rsp.cap != NULL)
pool_free_to(p->fiprm->rsp_cap_pool, hreq->rsp.cap);
if (hreq->req.cap != NULL)
pool_free_to(p->fiprm->req_cap_pool, hreq->req.cap);
close(cfd); /* nothing can be done for this fd without memory */
pool_free(task, t);
pool_free(session, s);
return 0;
}
buffer_init(s->rep);
s->rep->rto = s->be->beprm->srvtimeout;
s->rep->wto = s->fe->clitimeout;
s->rep->cto = 0;
fdtab[cfd].owner = t;
fdtab[cfd].state = FD_STREADY;
fdtab[cfd].cb[DIR_RD].f = &stream_sock_read;
fdtab[cfd].cb[DIR_RD].b = s->req;
fdtab[cfd].cb[DIR_WR].f = &stream_sock_write;
fdtab[cfd].cb[DIR_WR].b = s->rep;
if ((p->mode == PR_MODE_HTTP && (s->flags & SN_MONITOR)) ||
(p->mode == PR_MODE_HEALTH && (p->options & PR_O_HTTP_CHK))) {
/* Either we got a request from a monitoring system on an HTTP instance,
* or we're in health check mode with the 'httpchk' option enabled. In
* both cases, we return a fake "HTTP/1.0 200 OK" response and we exit.
*/
struct chunk msg = { .str = "HTTP/1.0 200 OK\r\n\r\n", .len = 19 };
client_retnclose(s, &msg); /* forge a 200 response */
}
else if (p->mode == PR_MODE_HEALTH) { /* health check mode, no client reading */
struct chunk msg = { .str = "OK\n", .len = 3 };
client_retnclose(s, &msg); /* forge an "OK" response */
}
else {
MY_FD_SET(cfd, StaticReadEvent);
}
#if defined(DEBUG_FULL) && defined(ENABLE_EPOLL)
if (PrevReadEvent) {
assert(!(MY_FD_ISSET(cfd, PrevReadEvent)));
assert(!(MY_FD_ISSET(cfd, PrevWriteEvent)));
}
#endif
fd_insert(cfd);
tv_eternity(&s->req->rex);
tv_eternity(&s->req->wex);
tv_eternity(&s->req->cex);
tv_eternity(&s->rep->rex);
tv_eternity(&s->rep->wex);
if (s->fe->clitimeout) {
if (MY_FD_ISSET(cfd, StaticReadEvent))
tv_delayfrom(&s->req->rex, &now, s->fe->clitimeout);
if (MY_FD_ISSET(cfd, StaticWriteEvent))
tv_delayfrom(&s->rep->wex, &now, s->fe->clitimeout);
}
tv_min(&t->expire, &s->req->rex, &s->rep->wex);
task_queue(t);
if (p->mode != PR_MODE_HEALTH)
task_wakeup(&rq, t);
p->feconn++; /* beconn will be increased later */
if (p->feconn > p->feconn_max)
p->feconn_max = p->feconn;
actconn++;
totalconn++;
// fprintf(stderr, "accepting from %p => %d conn, %d total, task=%p\n", p, actconn, totalconn, t);
} /* end of while (p->feconn < p->maxconn) */
return 0;
}
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*/