/* Pure Load Balancer - (C)opyleft 2003 Jedi/Sector One */ #include #define DEFINE_GLOBALS #include "plb.h" #include "parser.h" #include "plb_globals.h" #include "plb_p.h" #ifdef WITH_DMALLOC # include #endif static int init_pool_add(Server **serverpool_last, Server **serverpool_previous, const char * const host, const char * const port) { struct addrinfo hints, *res; int on; plb_log(LL_NOTIFY, "Adding [%s]:[%s] to the server pool", host, port); *serverpool_previous = *serverpool_last; if ((*serverpool_last = malloc(sizeof **serverpool_last)) == NULL) { plb_log(LL_ERROR, "Out of memory to fill the server pool : [%s]", strerror(errno)); return -1; } (*serverpool_last)->next = NULL; if (*serverpool_previous == NULL) { serverpool_head = *serverpool_last; } else { (*serverpool_previous)->next = *serverpool_last; } memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_addr = NULL; if ((on = getaddrinfo(host, port, &hints, &res)) != 0 || (res->ai_family != AF_INET && res->ai_family != AF_INET6)) { plb_log(LL_ERROR, "Unable to add [%s]:[%s] to the server pool : [%s]", host, port, gai_strerror(on)); return -1; } (*serverpool_last)->ai_family = res->ai_family; if (res->ai_addrlen > sizeof (*serverpool_last)->ai_addr) { plb_log(LL_FATAL, "ai_addr overflow"); return -1; } memcpy(&(*serverpool_last)->ai_addr, res->ai_addr, res->ai_addrlen); (*serverpool_last)->ai_addrlen = res->ai_addrlen; (*serverpool_last)->status = server_retry; (*serverpool_last)->cleanup_fd = -1; if (((*serverpool_last)->name = strdup(host)) == NULL) { plb_log(LL_ERROR, "Out of memory when adding [%s]:[%s]", host, port); return -1; } return 0; } static int init_pool(const char * const poolstr_, const char * const port) { char *poolstr; Server *serverpool_last = NULL; Server *serverpool_previous; char *poolstrbegtok; char *poolstrtokpnt; char savechar; if ((poolstr = strdup(poolstr_)) == NULL) { plb_log(LL_FATAL, "Out of memory to duplicate the server pool list"); return -1; } poolstrbegtok = poolstrtokpnt = poolstr; for (;;) { while (*poolstrtokpnt != 0 && !isspace((unsigned int) *poolstrtokpnt)) { poolstrtokpnt++; } savechar = *poolstrtokpnt; *poolstrtokpnt = 0; if (init_pool_add(&serverpool_last, &serverpool_previous, poolstrbegtok, port) != 0) { free(poolstr); return -1; } if (savechar == 0) { break; } poolstrtokpnt++; while (*poolstrtokpnt != 0 && isspace((unsigned int) *poolstrtokpnt)) { poolstrtokpnt++; } if (*poolstrtokpnt == 0) { break; } poolstrbegtok = poolstrtokpnt; } if ((serverpool_current = serverpool_head) == NULL) { free(poolstr); return -1; } free(poolstr); return 0; } int parse(const char * const file) { if (generic_parser(file, plb_config_keywords) != 0) { plb_log(LL_FATAL, "Invalid configuration file : [%s]", file); return -1; } if (cfg_bind_ipv6 != NULL) { if (atoi(cfg_bind_ipv6) > 0) { bind_ipv6 = 1; } } if (cfg_protocol != NULL) { if (strcasecmp(cfg_protocol, "HTTP") == 0) { protocol = PROTOCOL_HTTP; } else if (strcasecmp(cfg_protocol, "SMTP") == 0) { protocol = PROTOCOL_SMTP; } else { plb_log(LL_FATAL, "Unknown protocol : [%s]", cfg_protocol); return -1; } } if (cfg_timeout_header_client_read != NULL) { timeout_header_client_read.tv_sec = (time_t) strtoul(cfg_timeout_header_client_read, NULL, 10); } if (cfg_timeout_header_client_write != NULL) { timeout_header_client_write.tv_sec = (time_t) strtoul(cfg_timeout_header_client_write, NULL, 10); } if (cfg_timeout_header_server_read != NULL) { timeout_header_server_read.tv_sec = (time_t) strtoul(cfg_timeout_header_server_read, NULL, 10); } if (cfg_timeout_header_server_write != NULL) { timeout_header_server_write.tv_sec = (time_t) strtoul(cfg_timeout_header_server_write, NULL, 10); } if (cfg_timeout_forward_client_read != NULL) { timeout_forward_client_read.tv_sec = (time_t) strtoul(cfg_timeout_forward_client_read, NULL, 10); } if (cfg_timeout_forward_client_write != NULL) { timeout_forward_client_write.tv_sec = (time_t) strtoul(cfg_timeout_forward_client_write, NULL, 10); } if (cfg_timeout_forward_server_read != NULL) { timeout_forward_server_read.tv_sec = (time_t) strtoul(cfg_timeout_forward_server_read, NULL, 10); } if (cfg_timeout_forward_server_write != NULL) { timeout_forward_server_write.tv_sec = (time_t) strtoul(cfg_timeout_forward_server_write, NULL, 10); } if (cfg_timeout_cleanup != NULL) { timeout_cleanup.tv_sec = (time_t) strtoul(cfg_timeout_cleanup, NULL, 10); } if (cfg_max_clients != NULL) { max_clients = (int) strtoul(cfg_max_clients, NULL, 10); } if (cfg_server_retry != NULL) { server_retry = (unsigned int) strtoul(cfg_server_retry, NULL, 10); } if (cfg_backlog != NULL) { backlog = (int) strtoul(cfg_backlog, NULL, 10); } if (cfg_log_level != NULL) { log_level = (LogLevel) (int) strtoul(cfg_log_level, NULL, 10); } /* TODO: check ranges */ return 0; } static void usage(void) { puts("Usage: plb [-B|--daemonize] [-c|--config ]\n" " [-d|--loglevel ] [-h --help]\n" " [-l|--logfile ] [-v|--version]"); } static void version(void) { puts("Pure Load Balancer v" VERSION ", compiled on " __DATE__); } static int sigterm(void) { return -1; } static RETSIGTYPE sigterm_(int sig) { event_sigcb = sigterm; event_gotsig = sig; } static void set_signals(void) { struct sigaction sa; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sa.sa_handler = SIG_IGN; (void) sigaction(SIGPIPE, &sa, NULL); #ifdef SIGURG (void) sigaction(SIGURG, &sa, NULL); #endif sa.sa_flags = 0; sa.sa_handler = sigterm_; (void) sigaction(SIGTERM, &sa, NULL); (void) sigaction(SIGHUP, &sa, NULL); (void) sigaction(SIGQUIT, &sa, NULL); (void) sigaction(SIGINT, &sa, NULL); #ifdef SIGXCPU (void) sigaction(SIGXCPU, &sa, NULL); #endif } int main(int argc, char *argv[]) { struct addrinfo hints, *res; struct event ev; struct event cleanup_ev; int option_index = 0; int on; int listenfd; int fodder; int want_daemonization = 0; while ((fodder = getopt_long(argc, argv, GETOPT_OPTIONS, long_options, &option_index)) != -1) { switch (fodder) { case 'B': want_daemonization = 1; break; case 'c': if ((config_file = strdup(optarg)) == NULL) { plb_log(LL_FATAL, "Out of memory to store the config file name"); return 1; } break; case 'd': log_level = (LogLevel) (int) strtoul(optarg, NULL, 10); break; case 'g': if ((cfg_pid_file = strdup(optarg)) == NULL) { plb_log(LL_FATAL, "Out of memory to store the pid file name"); return 1; } break; case 'h': usage(); return 0; case 'l': if (strcmp(optarg, "-") == 0) { cfg_log_file = NULL; } else if ((cfg_log_file = strdup(optarg)) == NULL) { plb_log(LL_FATAL, "Out of memory to store the log file name"); return 1; } break; case 'v': version(); return 0; default: plb_log(LL_ERROR, "Unrecognized command-line switch"); return 1; } } init_timeouts(); if (parse(config_file) != 0) { return 1; } if (protocol == PROTOCOL_HTTP) { protocol_header_end = HTTP_HEADER_END; protocol_header_end_len = sizeof HTTP_HEADER_END - (size_t) 1U; } else if (protocol == PROTOCOL_SMTP) { protocol_header_end = SMTP_HEADER_END; protocol_header_end_len = sizeof SMTP_HEADER_END - (size_t) 1U; } else { plb_log(LL_FATAL, "Unknown protocol"); return 1; } if (cfg_log_file != NULL && plb_open_log(cfg_log_file) != 0) { plb_log(LL_FATAL, "Unable to access the log file, exiting"); return 1; } plb_log(LL_NOTIFY, "PLB " VERSION " has been started"); if (init_pool(cfg_servers_ip, cfg_servers_port) != 0) { plb_log(LL_FATAL, "No server pool defined"); return 1; } if ((clients = malloc((size_t) max_clients * sizeof *clients)) == NULL) { plb_log(LL_FATAL, "Out of memory : max_clients too high?"); return 1; } memset(&hints, 0, sizeof hints); hints.ai_flags = AI_PASSIVE; hints.ai_family = bind_ipv6 > 0 ? AF_INET6 : AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_addr = NULL; if ((on = getaddrinfo(cfg_listen_ip, cfg_listen_port, &hints, &res)) != 0 || (res->ai_family != AF_INET && res->ai_family != AF_INET6)) { plb_log(LL_FATAL, "Unable to get the local address [%s]:[%s] : [%s]", cfg_listen_ip, cfg_listen_port, gai_strerror(on)); return 1; } on = 1; if ((listenfd = socket(res->ai_family, SOCK_STREAM, IPPROTO_TCP)) == -1 || setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof on) != 0 || bind(listenfd, (struct sockaddr *) res->ai_addr, (socklen_t) res->ai_addrlen) != 0 || listen(listenfd, backlog) != 0) { plb_log(LL_FATAL, "Unable to listen to [%s]:[%s] : [%s]", cfg_listen_ip, cfg_listen_port, strerror(errno)); freeaddrinfo(res); return 1; } freeaddrinfo(res); if (want_daemonization != 0 && daemonize() != 0) { plb_log(LL_FATAL, "Unable to switch to background"); return 1; } if (update_pid_file(cfg_pid_file) != 0) { plb_log(LL_FATAL, "Unable to create the pid file"); return 1; } if (plb_drop_caps(cfg_user, cfg_group, cfg_chroot_dir) != 0) { plb_log(LL_FATAL, "Unable to drop privileges"); _exit(1); } set_signals(); event_init(); event_set(&ev, listenfd, EV_READ | EV_PERSIST, new_client, &ev); event_add(&ev, NULL); evtimer_set(&cleanup_ev, periodic_cleanup, &cleanup_ev); evtimer_add(&cleanup_ev, &timeout_cleanup); plb_log(LL_NOTIFY, "Server ready, now accepting connections on [%s]:[%s]", cfg_listen_ip, cfg_listen_port); event_dispatch(); free(clients); plb_close_log(); if (delete_pid_file() != 0) { plb_log(LL_NOTIFY, "Failed to remove the pid file"); } return 0; }