| /* |
| * Proxy 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 <fcntl.h> |
| #include <unistd.h> |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| #include <sys/stat.h> |
| |
| #include <common/defaults.h> |
| #include <common/compat.h> |
| #include <common/config.h> |
| #include <common/time.h> |
| |
| #include <types/global.h> |
| #include <types/polling.h> |
| |
| #include <proto/client.h> |
| #include <proto/fd.h> |
| #include <proto/log.h> |
| #include <proto/proxy.h> |
| |
| |
| int listeners; /* # of listeners */ |
| struct proxy *proxy = NULL; /* list of all existing proxies */ |
| |
| /* |
| * This function returns a string containing the type of the proxy in a format |
| * suitable for error messages, from its capabilities. |
| */ |
| const char *proxy_type_str(struct proxy *proxy) |
| { |
| int cap = proxy->cap; |
| if ((cap & PR_CAP_LISTEN) == PR_CAP_LISTEN) |
| return "listener"; |
| else if (cap & PR_CAP_FE) |
| return "frontend"; |
| else if (cap & PR_CAP_BE) |
| return "backend"; |
| else if (cap & PR_CAP_RS) |
| return "ruleset"; |
| else |
| return "proxy"; |
| } |
| |
| |
| /* |
| * This function creates all proxy sockets. It should be done very early, |
| * typically before privileges are dropped. The sockets will be registered |
| * but not added to any fd_set, in order not to loose them across the fork(). |
| * The proxies also start in IDLE state, meaning that it will be |
| * maintain_proxies that will finally complete their loading. |
| * |
| * Its return value is composed from ERR_NONE, ERR_RETRYABLE and ERR_FATAL. |
| * Retryable errors will only be printed if <verbose> is not zero. |
| */ |
| int start_proxies(int verbose) |
| { |
| struct proxy *curproxy; |
| struct listener *listener; |
| int err = ERR_NONE; |
| int fd, pxerr; |
| |
| for (curproxy = proxy; curproxy != NULL; curproxy = curproxy->next) { |
| if (curproxy->state != PR_STNEW) |
| continue; /* already initialized */ |
| |
| pxerr = 0; |
| for (listener = curproxy->listen; listener != NULL; listener = listener->next) { |
| if (listener->fd != -1) |
| continue; /* already initialized */ |
| |
| if ((fd = socket(listener->addr.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) { |
| if (verbose) |
| Alert("cannot create listening socket for proxy %s. Aborting.\n", |
| curproxy->id); |
| err |= ERR_RETRYABLE; |
| pxerr |= 1; |
| continue; |
| } |
| |
| if (fd >= global.maxsock) { |
| Alert("socket(): not enough free sockets for proxy %s. Raise -n argument. Aborting.\n", |
| curproxy->id); |
| close(fd); |
| err |= ERR_FATAL; |
| pxerr |= 1; |
| break; |
| } |
| |
| if ((fcntl(fd, F_SETFL, O_NONBLOCK) == -1) || |
| (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, |
| (char *) &one, sizeof(one)) == -1)) { |
| Alert("cannot make socket non-blocking for proxy %s. Aborting.\n", |
| curproxy->id); |
| close(fd); |
| err |= ERR_FATAL; |
| pxerr |= 1; |
| break; |
| } |
| |
| if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(one)) == -1) { |
| Alert("cannot do so_reuseaddr for proxy %s. Continuing.\n", |
| curproxy->id); |
| } |
| |
| #ifdef SO_REUSEPORT |
| /* OpenBSD supports this. As it's present in old libc versions of Linux, |
| * it might return an error that we will silently ignore. |
| */ |
| setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (char *) &one, sizeof(one)); |
| #endif |
| if (bind(fd, |
| (struct sockaddr *)&listener->addr, |
| listener->addr.ss_family == AF_INET6 ? |
| sizeof(struct sockaddr_in6) : |
| sizeof(struct sockaddr_in)) == -1) { |
| if (verbose) |
| Alert("cannot bind socket for proxy %s. Aborting.\n", |
| curproxy->id); |
| close(fd); |
| err |= ERR_RETRYABLE; |
| pxerr |= 1; |
| continue; |
| } |
| |
| if (listen(fd, curproxy->maxconn) == -1) { |
| if (verbose) |
| Alert("cannot listen to socket for proxy %s. Aborting.\n", |
| curproxy->id); |
| close(fd); |
| err |= ERR_RETRYABLE; |
| pxerr |= 1; |
| continue; |
| } |
| |
| /* the socket is ready */ |
| listener->fd = fd; |
| |
| /* the function for the accept() event */ |
| fd_insert(fd); |
| fdtab[fd].cb[DIR_RD].f = &event_accept; |
| fdtab[fd].cb[DIR_WR].f = NULL; /* never called */ |
| fdtab[fd].cb[DIR_RD].b = fdtab[fd].cb[DIR_WR].b = NULL; |
| fdtab[fd].owner = (struct task *)curproxy; /* reference the proxy instead of a task */ |
| fdtab[fd].state = FD_STLISTEN; |
| fdtab[fd].ev = 0; |
| listeners++; |
| } |
| |
| if (!pxerr) { |
| curproxy->state = PR_STIDLE; |
| send_log(curproxy, LOG_NOTICE, "Proxy %s started.\n", curproxy->id); |
| } |
| } |
| |
| return err; |
| } |
| |
| |
| /* |
| * this function enables proxies when there are enough free sessions, |
| * or stops them when the table is full. It is designed to be called from the |
| * select_loop(). It returns the time left before next expiration event |
| * during stop time, TIME_ETERNITY otherwise. |
| */ |
| int maintain_proxies(void) |
| { |
| struct proxy *p; |
| struct listener *l; |
| int tleft; /* time left */ |
| |
| p = proxy; |
| tleft = TIME_ETERNITY; /* infinite time */ |
| |
| /* if there are enough free sessions, we'll activate proxies */ |
| if (actconn < global.maxconn) { |
| while (p) { |
| if (p->feconn < p->maxconn) { |
| if (p->state == PR_STIDLE) { |
| for (l = p->listen; l != NULL; l = l->next) { |
| EV_FD_SET(l->fd, DIR_RD); |
| } |
| p->state = PR_STRUN; |
| } |
| } |
| else { |
| if (p->state == PR_STRUN) { |
| for (l = p->listen; l != NULL; l = l->next) { |
| EV_FD_CLR(l->fd, DIR_RD); |
| } |
| p->state = PR_STIDLE; |
| } |
| } |
| p = p->next; |
| } |
| } |
| else { /* block all proxies */ |
| while (p) { |
| if (p->state == PR_STRUN) { |
| for (l = p->listen; l != NULL; l = l->next) { |
| EV_FD_CLR(l->fd, DIR_RD); |
| } |
| p->state = PR_STIDLE; |
| } |
| p = p->next; |
| } |
| } |
| |
| if (stopping) { |
| p = proxy; |
| while (p) { |
| if (p->state != PR_STSTOPPED) { |
| int t; |
| t = tv_ms_remain2(&now, &p->stop_time); |
| if (t == 0) { |
| Warning("Proxy %s stopped.\n", p->id); |
| send_log(p, LOG_WARNING, "Proxy %s stopped.\n", p->id); |
| |
| for (l = p->listen; l != NULL; l = l->next) { |
| fd_delete(l->fd); |
| listeners--; |
| } |
| p->state = PR_STSTOPPED; |
| } |
| else { |
| tleft = MINTIME(t, tleft); |
| } |
| } |
| p = p->next; |
| } |
| } |
| return tleft; |
| } |
| |
| |
| /* |
| * this function disables health-check servers so that the process will quickly be ignored |
| * by load balancers. Note that if a proxy was already in the PAUSED state, then its grace |
| * time will not be used since it would already not listen anymore to the socket. |
| */ |
| void soft_stop(void) |
| { |
| struct proxy *p; |
| |
| stopping = 1; |
| p = proxy; |
| tv_now(&now); /* else, the old time before select will be used */ |
| while (p) { |
| if (p->state != PR_STSTOPPED) { |
| Warning("Stopping proxy %s in %d ms.\n", p->id, p->grace); |
| send_log(p, LOG_WARNING, "Stopping proxy %s in %d ms.\n", p->id, p->grace); |
| tv_ms_add(&p->stop_time, &now, p->grace); |
| } |
| p = p->next; |
| } |
| } |
| |
| |
| /* |
| * Linux unbinds the listen socket after a SHUT_RD, and ignores SHUT_WR. |
| * Solaris refuses either shutdown(). |
| * OpenBSD ignores SHUT_RD but closes upon SHUT_WR and refuses to rebind. |
| * So a common validation path involves SHUT_WR && listen && SHUT_RD. |
| * If disabling at least one listener returns an error, then the proxy |
| * state is set to PR_STERROR because we don't know how to resume from this. |
| */ |
| void pause_proxy(struct proxy *p) |
| { |
| struct listener *l; |
| for (l = p->listen; l != NULL; l = l->next) { |
| if (shutdown(l->fd, SHUT_WR) == 0 && |
| listen(l->fd, p->maxconn) == 0 && |
| shutdown(l->fd, SHUT_RD) == 0) { |
| EV_FD_CLR(l->fd, DIR_RD); |
| if (p->state != PR_STERROR) |
| p->state = PR_STPAUSED; |
| } |
| else |
| p->state = PR_STERROR; |
| } |
| } |
| |
| /* |
| * This function temporarily disables listening so that another new instance |
| * can start listening. It is designed to be called upon reception of a |
| * SIGTTOU, after which either a SIGUSR1 can be sent to completely stop |
| * the proxy, or a SIGTTIN can be sent to listen again. |
| */ |
| void pause_proxies(void) |
| { |
| int err; |
| struct proxy *p; |
| |
| err = 0; |
| p = proxy; |
| tv_now(&now); /* else, the old time before select will be used */ |
| while (p) { |
| if (p->state != PR_STERROR && |
| p->state != PR_STSTOPPED && |
| p->state != PR_STPAUSED) { |
| Warning("Pausing proxy %s.\n", p->id); |
| send_log(p, LOG_WARNING, "Pausing proxy %s.\n", p->id); |
| pause_proxy(p); |
| if (p->state != PR_STPAUSED) { |
| err |= 1; |
| Warning("Proxy %s failed to enter pause mode.\n", p->id); |
| send_log(p, LOG_WARNING, "Proxy %s failed to enter pause mode.\n", p->id); |
| } |
| } |
| p = p->next; |
| } |
| if (err) { |
| Warning("Some proxies refused to pause, performing soft stop now.\n"); |
| send_log(p, LOG_WARNING, "Some proxies refused to pause, performing soft stop now.\n"); |
| soft_stop(); |
| } |
| } |
| |
| |
| /* |
| * This function reactivates listening. This can be used after a call to |
| * sig_pause(), for example when a new instance has failed starting up. |
| * It is designed to be called upon reception of a SIGTTIN. |
| */ |
| void listen_proxies(void) |
| { |
| struct proxy *p; |
| struct listener *l; |
| |
| p = proxy; |
| tv_now(&now); /* else, the old time before select will be used */ |
| while (p) { |
| if (p->state == PR_STPAUSED) { |
| Warning("Enabling proxy %s.\n", p->id); |
| send_log(p, LOG_WARNING, "Enabling proxy %s.\n", p->id); |
| |
| for (l = p->listen; l != NULL; l = l->next) { |
| if (listen(l->fd, p->maxconn) == 0) { |
| if (actconn < global.maxconn && p->feconn < p->maxconn) { |
| EV_FD_SET(l->fd, DIR_RD); |
| p->state = PR_STRUN; |
| } |
| else |
| p->state = PR_STIDLE; |
| } else { |
| int port; |
| |
| if (l->addr.ss_family == AF_INET6) |
| port = ntohs(((struct sockaddr_in6 *)(&l->addr))->sin6_port); |
| else |
| port = ntohs(((struct sockaddr_in *)(&l->addr))->sin_port); |
| |
| Warning("Port %d busy while trying to enable proxy %s.\n", |
| port, p->id); |
| send_log(p, LOG_WARNING, "Port %d busy while trying to enable proxy %s.\n", |
| port, p->id); |
| /* Another port might have been enabled. Let's stop everything. */ |
| pause_proxy(p); |
| break; |
| } |
| } |
| } |
| p = p->next; |
| } |
| } |
| |
| |
| /* |
| * Local variables: |
| * c-indent-level: 8 |
| * c-basic-offset: 8 |
| * End: |
| */ |