From: Timo Sirainen Date: Mon, 23 Sep 2013 01:25:16 +0000 (+0300) Subject: master: Added reuse_port setting to inet_listeners, which enables SO_REUSEPORT if... X-Git-Tag: 2.2.6~1 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=5d4c793b4e3dbc07f08daa4465594b1857f80725;p=thirdparty%2Fdovecot%2Fcore.git master: Added reuse_port setting to inet_listeners, which enables SO_REUSEPORT if available. After forking a new service process, a new listener socket is created for each such inet_listener. Linux v3.9+ added SO_REUSEPORT feature, which should distribute clients more uniformly to the processes. I'm not sure if this makes any difference in BSDs. At least in Linux v3.9 there was still a bug that if the number of listening processes changed, some TCP handshakes might not finish. I don't see if this has already been fixed, so this is probably safe to use only for services whose process count doesn't change (e.g. process_min_avail is set high enough). --- diff --git a/src/imap-login/imap-login-settings.c b/src/imap-login/imap-login-settings.c index fe3c7122e2..0b1ffc2f3d 100644 --- a/src/imap-login/imap-login-settings.c +++ b/src/imap-login/imap-login-settings.c @@ -11,8 +11,8 @@ /* */ static struct inet_listener_settings imap_login_inet_listeners_array[] = { - { "imap", "", 143, FALSE }, - { "imaps", "", 993, TRUE } + { .name = "imap", .address = "", .port = 143 }, + { .name = "imaps", .address = "", .port = 993, .ssl = TRUE } }; static struct inet_listener_settings *imap_login_inet_listeners[] = { &imap_login_inet_listeners_array[0], diff --git a/src/lib-master/service-settings.h b/src/lib-master/service-settings.h index 2dcdc7dbb6..b97aacceb8 100644 --- a/src/lib-master/service-settings.h +++ b/src/lib-master/service-settings.h @@ -31,6 +31,7 @@ struct inet_listener_settings { const char *address; unsigned int port; bool ssl; + bool reuse_port; }; ARRAY_DEFINE_TYPE(inet_listener_settings, struct inet_listener_settings *); diff --git a/src/master/master-settings.c b/src/master/master-settings.c index 45e56ac126..2ea707abee 100644 --- a/src/master/master-settings.c +++ b/src/master/master-settings.c @@ -64,6 +64,7 @@ static const struct setting_define inet_listener_setting_defines[] = { DEF(SET_STR, address), DEF(SET_UINT, port), DEF(SET_BOOL, ssl), + DEF(SET_BOOL, reuse_port), SETTING_DEFINE_LIST_END }; @@ -72,7 +73,8 @@ static const struct inet_listener_settings inet_listener_default_settings = { .name = "", .address = "", .port = 0, - .ssl = FALSE + .ssl = FALSE, + .reuse_port = FALSE }; static const struct setting_parser_info inet_listener_setting_parser_info = { diff --git a/src/master/service-listen.c b/src/master/service-listen.c index 4f149c81bf..37cdc917c8 100644 --- a/src/master/service-listen.c +++ b/src/master/service-listen.c @@ -189,9 +189,11 @@ systemd_listen_fd(const struct ip_addr *ip, unsigned int port, int *fd_r) static int service_inet_listener_listen(struct service_listener *l) { struct service *service = l->service; + enum net_listen_flags flags = 0; const struct inet_listener_settings *set = l->set.inetset.set; unsigned int port = set->port; int fd; + #ifdef HAVE_SYSTEMD if (systemd_listen_fd(&l->set.inetset.ip, port, &fd) < 0) return -1; @@ -199,13 +201,16 @@ static int service_inet_listener_listen(struct service_listener *l) if (fd == -1) #endif { - fd = net_listen(&l->set.inetset.ip, &port, - service_get_backlog(service)); + if (set->reuse_port) + flags |= NET_LISTEN_FLAG_REUSEPORT; + fd = net_listen_full(&l->set.inetset.ip, &port, &flags, + service_get_backlog(service)); if (fd < 0) { service_error(service, "listen(%s, %u) failed: %m", l->inet_address, set->port); return errno == EADDRINUSE ? 0 : -1; } + l->reuse_port = (flags & NET_LISTEN_FLAG_REUSEPORT) != 0; } net_set_nonblock(fd, TRUE); fd_close_on_exec(fd, TRUE); @@ -214,6 +219,19 @@ static int service_inet_listener_listen(struct service_listener *l) return 1; } +int service_listener_listen(struct service_listener *l) +{ + switch (l->type) { + case SERVICE_LISTENER_UNIX: + return service_unix_listener_listen(l); + case SERVICE_LISTENER_FIFO: + return service_fifo_listener_listen(l); + case SERVICE_LISTENER_INET: + return service_inet_listener_listen(l); + } + i_unreached(); +} + static int service_listen(struct service *service) { struct service_listener *const *listeners; @@ -225,18 +243,7 @@ static int service_listen(struct service *service) if (l->fd != -1) continue; - switch (l->type) { - case SERVICE_LISTENER_UNIX: - ret2 = service_unix_listener_listen(l); - break; - case SERVICE_LISTENER_FIFO: - ret2 = service_fifo_listener_listen(l); - break; - case SERVICE_LISTENER_INET: - ret2 = service_inet_listener_listen(l); - break; - } - + ret2 = service_listener_listen(l); if (ret2 < ret) ret = ret2; } diff --git a/src/master/service-listen.h b/src/master/service-listen.h index dc792faa8b..ffd88bd5fa 100644 --- a/src/master/service-listen.h +++ b/src/master/service-listen.h @@ -13,4 +13,6 @@ int services_listen(struct service_list *service_list); int services_listen_using(struct service_list *new_service_list, struct service_list *old_service_list); +int service_listener_listen(struct service_listener *l); + #endif diff --git a/src/master/service-process.c b/src/master/service-process.c index e737d4ed96..6d2d22246e 100644 --- a/src/master/service-process.c +++ b/src/master/service-process.c @@ -23,6 +23,7 @@ #include "dup2-array.h" #include "service.h" #include "service-anvil.h" +#include "service-listen.h" #include "service-log.h" #include "service-process-notify.h" #include "service-process.h" @@ -34,6 +35,24 @@ #include #include +static void service_reopen_inet_listeners(struct service *service) +{ + struct service_listener *const *listeners; + unsigned int i, count; + int old_fd; + + listeners = array_get(&service->listeners, &count); + for (i = 0; i < count; i++) { + if (!listeners[i]->reuse_port || listeners[i]->fd == -1) + continue; + + old_fd = listeners[i]->fd; + listeners[i]->fd = -1; + if (service_listener_listen(listeners[i]) < 0) + listeners[i]->fd = old_fd; + } +} + static void service_dup_fds(struct service *service) { @@ -305,6 +324,7 @@ struct service_process *service_process_create(struct service *service) if (pid == 0) { /* child */ service_process_setup_environment(service, uid); + service_reopen_inet_listeners(service); service_dup_fds(service); drop_privileges(service); process_exec(service->executable, NULL); diff --git a/src/master/service.h b/src/master/service.h index 3ed10b4e3e..79aa65f9ef 100644 --- a/src/master/service.h +++ b/src/master/service.h @@ -38,6 +38,8 @@ struct service_listener { struct ip_addr ip; } inetset; } set; + + bool reuse_port; }; struct service { diff --git a/src/pop3-login/pop3-login-settings.c b/src/pop3-login/pop3-login-settings.c index f69f2ec1cc..584e508559 100644 --- a/src/pop3-login/pop3-login-settings.c +++ b/src/pop3-login/pop3-login-settings.c @@ -11,8 +11,8 @@ /* */ static struct inet_listener_settings pop3_login_inet_listeners_array[] = { - { "pop3", "", 110, FALSE }, - { "pop3s", "", 995, TRUE } + { .name = "pop3", .address = "", .port = 110 }, + { .name = "pop3s", .address = "", .port = 995, .ssl = TRUE } }; static struct inet_listener_settings *pop3_login_inet_listeners[] = { &pop3_login_inet_listeners_array[0],