chiark / gitweb /
socket: enforce limit on number of concurrent connections
[elogind.git] / src / socket.c
index 19f1d2209736a6afe878d6ac44ae2cb73488008a..fdb6f66b6fd79c16470a7976875367ca644ec339 100644 (file)
@@ -49,7 +49,7 @@ static const UnitActiveState state_translation_table[_SOCKET_STATE_MAX] = {
         [SOCKET_STOP_POST] = UNIT_DEACTIVATING,
         [SOCKET_FINAL_SIGTERM] = UNIT_DEACTIVATING,
         [SOCKET_FINAL_SIGKILL] = UNIT_DEACTIVATING,
-        [SOCKET_MAINTAINANCE] = UNIT_INACTIVE,
+        [SOCKET_MAINTENANCE] = UNIT_INACTIVE,
 };
 
 static void socket_init(Unit *u) {
@@ -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) {
 
@@ -707,7 +727,7 @@ static void socket_enter_dead(Socket *s, bool success) {
         if (!success)
                 s->failure = true;
 
-        socket_set_state(s, s->failure ? SOCKET_MAINTAINANCE : SOCKET_DEAD);
+        socket_set_state(s, s->failure ? SOCKET_MAINTENANCE : SOCKET_DEAD);
 }
 
 static void socket_enter_signal(Socket *s, SocketState state, bool success);
@@ -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);
@@ -995,12 +1023,12 @@ static int socket_start(Unit *u) {
                 /* If the service is alredy actvie we cannot start the
                  * socket */
                 if (s->service->state != SERVICE_DEAD &&
-                    s->service->state != SERVICE_MAINTAINANCE &&
+                    s->service->state != SERVICE_MAINTENANCE &&
                     s->service->state != SERVICE_AUTO_RESTART)
                         return -EBUSY;
         }
 
-        assert(s->state == SOCKET_DEAD || s->state == SOCKET_MAINTAINANCE);
+        assert(s->state == SOCKET_DEAD || s->state == SOCKET_MAINTENANCE);
 
         s->failure = false;
         socket_enter_start_pre(s);
@@ -1047,7 +1075,7 @@ static int socket_serialize(Unit *u, FILE *f, FDSet *fds) {
         unit_serialize_item_format(u, f, "n-accepted", "%u", s->n_accepted);
 
         if (s->control_pid > 0)
-                unit_serialize_item_format(u, f, "control-pid", "%u", (unsigned) s->control_pid);
+                unit_serialize_item_format(u, f, "control-pid", "%lu", (unsigned long) s->control_pid);
 
         if (s->control_command_id >= 0)
                 unit_serialize_item(u, f, "control-command", socket_exec_command_to_string(s->control_command_id));
@@ -1110,12 +1138,12 @@ static int socket_deserialize_item(Unit *u, const char *key, const char *value,
                 else
                         s->n_accepted += k;
         } else if (streq(key, "control-pid")) {
-                unsigned pid;
+                pid_t pid;
 
-                if ((r = safe_atou(value, &pid)) < 0 || pid <= 0)
+                if ((r = parse_pid(value, &pid)) < 0)
                         log_debug("Failed to parse control-pid value %s", value);
                 else
-                        s->control_pid = (pid_t) pid;
+                        s->control_pid = pid;
         } else if (streq(key, "control-command")) {
                 SocketExecCommand id;
 
@@ -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;
@@ -1228,12 +1264,14 @@ static void socket_sigchld_event(Unit *u, pid_t pid, int code, int status) {
         assert(s);
         assert(pid >= 0);
 
-        success = is_clean_exit(code, status);
-        s->failure = s->failure || !success;
+        if (pid != s->control_pid)
+                return;
 
-        assert(s->control_pid == pid);
         s->control_pid = 0;
 
+        success = is_clean_exit(code, status);
+        s->failure = s->failure || !success;
+
         if (s->control_command)
                 exec_status_fill(&s->control_command->exec_status, pid, code, status);
 
@@ -1329,7 +1367,7 @@ static void socket_timer_event(Unit *u, uint64_t elapsed, Watch *w) {
                 break;
 
         case SOCKET_FINAL_SIGKILL:
-                log_warning("%s still around after SIGKILL (2). Entering maintainance mode.", u->meta.id);
+                log_warning("%s still around after SIGKILL (2). Entering maintenance mode.", u->meta.id);
                 socket_enter_dead(s, false);
                 break;
 
@@ -1373,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);
@@ -1381,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",
@@ -1393,7 +1448,7 @@ static const char* const socket_state_table[_SOCKET_STATE_MAX] = {
         [SOCKET_STOP_POST] = "stop-post",
         [SOCKET_FINAL_SIGTERM] = "final-sigterm",
         [SOCKET_FINAL_SIGKILL] = "final-sigkill",
-        [SOCKET_MAINTAINANCE] = "maintainance"
+        [SOCKET_MAINTENANCE] = "maintenance"
 };
 
 DEFINE_STRING_TABLE_LOOKUP(socket_state, SocketState);
@@ -1427,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,