From: Lennart Poettering Date: Sat, 19 Jun 2010 02:25:28 +0000 (+0200) Subject: socket: enforce limit on number of concurrent connections X-Git-Tag: v1~142 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=commitdiff_plain;h=6cf6bbc2682c6df010d64ea7e49d93affa7282cd socket: enforce limit on number of concurrent connections --- diff --git a/fixme b/fixme index 15e3ef6b2..a366ea83a 100644 --- a/fixme +++ b/fixme @@ -1,7 +1,5 @@ * calendar time support in timer -* enforce max number of concurrent connection limit in sockets. - * complete dbus exposure * make conf parser work more like .desktop parsers @@ -51,11 +49,13 @@ * follow property change dbus spec +* make systemd bus activatable (?) + * pam module * selinux -* make systemd bus activatable +External: * systemd-sysvinit as package @@ -63,8 +63,6 @@ if [ $PPID -ne 1 && mountpoint /cgroup/systemd ] ; then echo "You suck!" ; fi -* remove MANAGER_SYSTEM - Regularly: * look for close() vs. close_nointr() vs. close_nointr_nofail() diff --git a/src/load-fragment.c b/src/load-fragment.c index d6a35f7ff..b7bb4d7c5 100644 --- a/src/load-fragment.c +++ b/src/load-fragment.c @@ -1516,6 +1516,7 @@ static int load_from_path(Unit *u, const char *path) { { "SocketMode", config_parse_mode, &u->socket.socket_mode, "Socket" }, { "KillMode", config_parse_kill_mode, &u->socket.kill_mode, "Socket" }, { "Accept", config_parse_bool, &u->socket.accept, "Socket" }, + { "MaxConnections", config_parse_unsigned, &u->socket.max_connections, "Socket" }, EXEC_CONTEXT_CONFIG_ITEMS(u->socket.exec_context, "Socket"), { "What", config_parse_string, &u->mount.parameters_fragment.what, "Mount" }, diff --git a/src/mount.c b/src/mount.c index c95581fd1..5c16f86d5 100644 --- a/src/mount.c +++ b/src/mount.c @@ -645,7 +645,7 @@ static void mount_enter_unmounting(Mount *m, bool success) { return; fail: - log_warning("%s failed to run umount exectuable: %s", UNIT(m)->meta.id, strerror(-r)); + log_warning("%s failed to run 'umount' task: %s", UNIT(m)->meta.id, strerror(-r)); mount_enter_mounted(m, false); } @@ -688,7 +688,7 @@ static void mount_enter_mounting(Mount *m) { return; fail: - log_warning("%s failed to run mount exectuable: %s", UNIT(m)->meta.id, strerror(-r)); + log_warning("%s failed to run 'mount' task: %s", UNIT(m)->meta.id, strerror(-r)); mount_enter_dead(m, false); } diff --git a/src/service.c b/src/service.c index b8687b7cf..4375cb5cb 100644 --- a/src/service.c +++ b/src/service.c @@ -157,6 +157,16 @@ static void service_close_socket_fd(Service *s) { s->socket_fd = -1; } +static void service_connection_unref(Service *s) { + assert(s); + + if (!s->socket) + return; + + socket_connection_unref(s->socket); + s->socket = NULL; +} + static void service_done(Unit *u) { Service *s = SERVICE(u); @@ -190,6 +200,7 @@ static void service_done(Unit *u) { } service_close_socket_fd(s); + service_connection_unref(s); unit_unwatch_timer(u, &s->timer_watch); } @@ -800,6 +811,11 @@ static int service_verify(Service *s) { return -EINVAL; } + if (s->exec_command[SERVICE_EXEC_START]->command_next) { + log_error("%s has more than one ExecStart setting. Refusing.", UNIT(s)->meta.id); + return -EINVAL; + } + if (s->type == SERVICE_DBUS && !s->bus_name) { log_error("%s is of type D-Bus but no D-Bus service name has been specified. Refusing.", UNIT(s)->meta.id); return -EINVAL; @@ -993,6 +1009,9 @@ static int service_get_sockets(Service *s, Set **_set) { assert(s); assert(_set); + if (s->socket_fd >= 0) + return 0; + /* Collects all Socket objects that belong to this * service. Note that a service might have multiple sockets * via multiple names. */ @@ -1038,6 +1057,9 @@ static int service_notify_sockets_dead(Service *s) { assert(s); + if (s->socket_fd >= 0) + return 0; + /* Notifies all our sockets when we die */ if ((r = service_get_sockets(s, &set)) < 0) return r; @@ -1107,8 +1129,19 @@ static void service_set_state(Service *s, ServiceState state) { if (state != SERVICE_START_PRE && state != SERVICE_START && - !(state == SERVICE_DEAD && UNIT(s)->meta.job)) + state != SERVICE_START_POST && + state != SERVICE_RUNNING && + state != SERVICE_RELOAD && + state != SERVICE_STOP && + state != SERVICE_STOP_SIGTERM && + state != SERVICE_STOP_SIGKILL && + state != SERVICE_STOP_POST && + state != SERVICE_FINAL_SIGTERM && + state != SERVICE_FINAL_SIGKILL && + !(state == SERVICE_DEAD && UNIT(s)->meta.job)) { service_close_socket_fd(s); + service_connection_unref(s); + } if (old_state != state) log_debug("%s changed %s -> %s", UNIT(s)->meta.id, service_state_to_string(old_state), service_state_to_string(state)); @@ -1194,6 +1227,9 @@ static int service_collect_fds(Service *s, int **fds, unsigned *n_fds) { assert(fds); assert(n_fds); + if (s->socket_fd >= 0) + return 0; + if ((r = service_get_sockets(s, &set)) < 0) return r; @@ -1255,7 +1291,7 @@ static int service_spawn( pid_t pid; int r; - int *fds = NULL; + int *fds = NULL, *fdsbuf = NULL; unsigned n_fds = 0; char **argv = NULL, **env = NULL; @@ -1263,12 +1299,20 @@ static int service_spawn( assert(c); assert(_pid); - if (pass_fds) { + if (pass_fds || + s->exec_context.std_input == EXEC_INPUT_SOCKET || + s->exec_context.std_output == EXEC_OUTPUT_SOCKET || + s->exec_context.std_error == EXEC_OUTPUT_SOCKET) { + if (s->socket_fd >= 0) { fds = &s->socket_fd; n_fds = 1; - } else if ((r = service_collect_fds(s, &fds, &n_fds)) < 0) - goto fail; + } else { + if ((r = service_collect_fds(s, &fdsbuf, &n_fds)) < 0) + goto fail; + + fds = fdsbuf; + } } if (timeout && s->timeout_usec) { @@ -1321,12 +1365,8 @@ static int service_spawn( if (r < 0) goto fail; - if (fds) { - if (s->socket_fd >= 0) - service_close_socket_fd(s); - else - free(fds); - } + if (fdsbuf) + free(fdsbuf); if ((r = unit_watch_pid(UNIT(s), pid)) < 0) /* FIXME: we need to do something here */ @@ -1756,7 +1796,7 @@ static void service_run_next(Service *s, bool success) { return; fail: - log_warning("%s failed to run spawn next task: %s", UNIT(s)->meta.id, strerror(-r)); + log_warning("%s failed to run next task: %s", UNIT(s)->meta.id, strerror(-r)); if (s->state == SERVICE_START_PRE) service_enter_signal(s, SERVICE_FINAL_SIGTERM, false); @@ -2524,7 +2564,7 @@ static void service_bus_query_pid_done( service_set_main_pid(s, pid); } -int service_set_socket_fd(Service *s, int fd) { +int service_set_socket_fd(Service *s, int fd, Socket *sock) { assert(s); assert(fd >= 0); @@ -2543,6 +2583,8 @@ int service_set_socket_fd(Service *s, int fd) { s->socket_fd = fd; s->got_socket_fd = true; + s->socket = sock; + return 0; } diff --git a/src/service.h b/src/service.h index 7b85771a3..6c1612d0f 100644 --- a/src/service.h +++ b/src/service.h @@ -137,13 +137,14 @@ struct Service { RateLimit ratelimit; int socket_fd; + struct Socket *socket; Watch timer_watch; }; extern const UnitVTable service_vtable; -int service_set_socket_fd(Service *s, int fd); +int service_set_socket_fd(Service *s, int fd, struct Socket *socket); const char* service_state_to_string(ServiceState i); ServiceState service_state_from_string(const char *s); diff --git a/src/socket.c b/src/socket.c index 874cc9169..fdb6f66b6 100644 --- a/src/socket.c +++ b/src/socket.c @@ -63,6 +63,8 @@ static void socket_init(Unit *u) { s->directory_mode = 0755; s->socket_mode = 0666; + s->max_connections = 64; + exec_context_init(&s->exec_context); s->control_command_id = _SOCKET_EXEC_COMMAND_INVALID; @@ -81,6 +83,7 @@ static void socket_unwatch_control_pid(Socket *s) { static void socket_done(Unit *u) { Socket *s = SOCKET(u); SocketPort *p; + Meta *i; assert(s); @@ -108,6 +111,14 @@ static void socket_done(Unit *u) { s->bind_to_device = NULL; unit_unwatch_timer(u, &s->timer_watch); + + /* Make sure no service instance refers to us anymore. */ + LIST_FOREACH(units_per_type, i, u->meta.manager->units_per_type[UNIT_SERVICE]) { + Service *service = (Service *) i; + + if (service->socket == s) + service->socket = NULL; + } } static bool have_non_accept_socket(Socket *s) { @@ -141,6 +152,11 @@ static int socket_verify(Socket *s) { return -EINVAL; } + if (s->accept && s->max_connections <= 0) { + log_error("%s's MaxConnection setting too small. Refusing.", UNIT(s)->meta.id); + return -EINVAL; + } + return 0; } @@ -307,8 +323,12 @@ static void socket_dump(Unit *u, FILE *f, const char *prefix) { if (s->accept) fprintf(f, - "%sAccepted: %u\n", - prefix, s->n_accepted); + "%sAccepted: %u\n" + "%sNConnections: %u\n" + "%sMaxConnections: %u\n", + prefix, s->n_accepted, + prefix, s->n_connections, + prefix, s->max_connections); LIST_FOREACH(port, p, s->ports) { @@ -734,7 +754,7 @@ static void socket_enter_stop_post(Socket *s, bool success) { return; fail: - log_warning("%s failed to run stop-post executable: %s", s->meta.id, strerror(-r)); + log_warning("%s failed to run 'stop-post' task: %s", s->meta.id, strerror(-r)); socket_enter_signal(s, SOCKET_FINAL_SIGTERM, false); } @@ -809,7 +829,7 @@ static void socket_enter_stop_pre(Socket *s, bool success) { return; fail: - log_warning("%s failed to run stop-pre executable: %s", s->meta.id, strerror(-r)); + log_warning("%s failed to run 'stop-pre' task: %s", s->meta.id, strerror(-r)); socket_enter_stop_post(s, false); } @@ -844,7 +864,7 @@ static void socket_enter_start_post(Socket *s) { if ((s->control_command = s->exec_command[SOCKET_EXEC_START_POST])) { if ((r = socket_spawn(s, s->control_command, &s->control_pid)) < 0) { - log_warning("%s failed to run start-post executable: %s", s->meta.id, strerror(-r)); + log_warning("%s failed to run 'start-post' task: %s", s->meta.id, strerror(-r)); goto fail; } @@ -877,7 +897,7 @@ static void socket_enter_start_pre(Socket *s) { return; fail: - log_warning("%s failed to run start-pre exectuable: %s", s->meta.id, strerror(-r)); + log_warning("%s failed to run 'start-pre' task: %s", s->meta.id, strerror(-r)); socket_enter_dead(s, false); } @@ -895,6 +915,12 @@ static void socket_enter_running(Socket *s, int cfd) { Unit *u; char *prefix, *instance, *name; + if (s->n_connections >= s->max_connections) { + log_warning("Too many incoming connections (%u)", s->n_connections); + close_nointr_nofail(cfd); + return; + } + if ((r = instance_from_socket(cfd, s->n_accepted++, &instance)) < 0) goto fail; @@ -919,11 +945,13 @@ static void socket_enter_running(Socket *s, int cfd) { if (r < 0) goto fail; - if ((r = service_set_socket_fd(SERVICE(u), cfd)) < 0) + if ((r = service_set_socket_fd(SERVICE(u), cfd, s)) < 0) goto fail; cfd = -1; + s->n_connections ++; + if ((r = manager_add_job(u->meta.manager, JOB_START, u, JOB_REPLACE, true, NULL)) < 0) goto fail; } @@ -958,7 +986,7 @@ static void socket_run_next(Socket *s, bool success) { return; fail: - log_warning("%s failed to run spawn next executable: %s", s->meta.id, strerror(-r)); + log_warning("%s failed to run next task: %s", s->meta.id, strerror(-r)); if (s->state == SOCKET_START_POST) socket_enter_stop_pre(s, false); @@ -1181,6 +1209,14 @@ static const char *socket_sub_state_to_string(Unit *u) { return socket_state_to_string(SOCKET(u)->state); } +static bool socket_check_gc(Unit *u) { + Socket *s = SOCKET(u); + + assert(u); + + return s->n_connections > 0; +} + static void socket_fd_event(Unit *u, int fd, uint32_t events, Watch *w) { Socket *s = SOCKET(u); int cfd = -1; @@ -1375,7 +1411,10 @@ int socket_collect_fds(Socket *s, int **fds, unsigned *n_fds) { void socket_notify_service_dead(Socket *s) { assert(s); - /* The service is dead. Dang. */ + /* The service is dead. Dang! + * + * This is strictly for one-instance-for-all-connections + * services. */ if (s->state == SOCKET_RUNNING) { log_debug("%s got notified about service death.", s->meta.id); @@ -1383,6 +1422,20 @@ void socket_notify_service_dead(Socket *s) { } } +void socket_connection_unref(Socket *s) { + assert(s); + + /* The service is dead. Yay! + * + * This is strictly for one-onstance-per-connection + * services. */ + + assert(s->n_connections > 0); + s->n_connections--; + + log_debug("%s: One connection closed, %u left.", s->meta.id, s->n_connections); +} + static const char* const socket_state_table[_SOCKET_STATE_MAX] = { [SOCKET_DEAD] = "dead", [SOCKET_START_PRE] = "start-pre", @@ -1429,6 +1482,8 @@ const UnitVTable socket_vtable = { .active_state = socket_active_state, .sub_state_to_string = socket_sub_state_to_string, + .check_gc = socket_check_gc, + .fd_event = socket_fd_event, .sigchld_event = socket_sigchld_event, .timer_event = socket_timer_event, diff --git a/src/socket.h b/src/socket.h index 5a2cd06d9..31b3870ef 100644 --- a/src/socket.h +++ b/src/socket.h @@ -103,6 +103,8 @@ struct Socket { bool accept; unsigned n_accepted; + unsigned n_connections; + unsigned max_connections; bool failure; Watch timer_watch; @@ -118,6 +120,9 @@ void socket_notify_service_dead(Socket *s); * any of the sockets of this socket */ int socket_add_one_mount_link(Socket *s, Mount *m); +/* Called from the service code when a per-connection service ended */ +void socket_connection_unref(Socket *s); + extern const UnitVTable socket_vtable; const char* socket_state_to_string(SocketState i);