diff --git a/redsocks.c b/redsocks.c index 225d065..7a3c55f 100644 --- a/redsocks.c +++ b/redsocks.c @@ -65,6 +65,7 @@ static parser_entry redsocks_entries[] = { .key = "login", .type = pt_pchar }, { .key = "password", .type = pt_pchar }, { .key = "listenq", .type = pt_uint16 }, + { .key = "max_accept_backoff", .type = pt_uint16 }, { } }; @@ -119,6 +120,7 @@ static int redsocks_onenter(parser_section *section) * Linux: sysctl net.core.somaxconn * FreeBSD: sysctl kern.ipc.somaxconn */ instance->config.listenq = SOMAXCONN; + instance->config.max_backoff_ms = 60000; for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) entry->addr = @@ -130,6 +132,7 @@ static int redsocks_onenter(parser_section *section) (strcmp(entry->key, "login") == 0) ? (void*)&instance->config.login : (strcmp(entry->key, "password") == 0) ? (void*)&instance->config.password : (strcmp(entry->key, "listenq") == 0) ? (void*)&instance->config.listenq : + (strcmp(entry->key, "max_accept_backoff") == 0) ? (void*)&instance->config.max_backoff_ms : NULL; section->data = instance; return 0; @@ -167,6 +170,10 @@ static int redsocks_onexit(parser_section *section) err = "no `type` for redsocks"; } + if (!err && !instance->config.max_backoff_ms) { + err = "`max_accept_backoff` must be positive, 0 ms is too low"; + } + if (err) parser_error(section->context, err); @@ -561,6 +568,19 @@ void redsocks_connect_relay(redsocks_client *client) } } +static void redsocks_accept_backoff(int fd, short what, void *_arg) +{ + redsocks_instance *self = _arg; + + /* Isn't it already deleted? EV_PERSIST has nothing common with timeouts in + * old libevent... On the other hand libevent does not return any error. */ + if (tracked_event_del(&self->accept_backoff) != 0) + log_errno(LOG_ERR, "event_del"); + + if (tracked_event_add(&self->listener, NULL) != 0) + log_errno(LOG_ERR, "event_add"); +} + static void redsocks_accept_client(int fd, short what, void *_arg) { redsocks_instance *self = _arg; @@ -576,9 +596,27 @@ static void redsocks_accept_client(int fd, short what, void *_arg) // working with client_fd client_fd = accept(fd, (struct sockaddr*)&clientaddr, &addrlen); if (client_fd == -1) { - log_errno(LOG_WARNING, "accept"); + /* Different systems use different `errno` value to signal different + * `lack of file descriptors` conditions. Here are most of them. */ + if (errno == ENFILE || errno == EMFILE || errno == ENOBUFS || errno == ENOMEM) { + // FIXME: should I log on every attempt? + self->accept_backoff_ms = (self->accept_backoff_ms << 1) + 1; + if (self->accept_backoff_ms > self->config.max_backoff_ms) + self->accept_backoff_ms = self->config.max_backoff_ms; + int delay = (random() % self->accept_backoff_ms) + 1; + log_errno(LOG_WARNING, "accept: out of file descriptos, backing off for %u ms", delay); + struct timeval tvdelay = { delay / 1000, (delay % 1000) * 1000 }; + if (tracked_event_del(&self->listener) != 0) + log_errno(LOG_ERR, "event_del"); + if (tracked_event_add(&self->accept_backoff, &tvdelay) != 0) + log_errno(LOG_ERR, "event_add"); + } + else { + log_errno(LOG_WARNING, "accept"); + } goto fail; } + self->accept_backoff_ms = 0; // socket is really bound now (it could be bound to 0.0.0.0) addrlen = sizeof(myaddr); @@ -742,6 +780,8 @@ static int redsocks_init_instance(redsocks_instance *instance) } tracked_event_set(&instance->listener, fd, EV_READ | EV_PERSIST, redsocks_accept_client, instance); + tracked_event_set(&instance->accept_backoff, -1, 0, redsocks_accept_backoff, instance); + error = tracked_event_add(&instance->listener, NULL); if (error) { log_errno(LOG_ERR, "event_add"); @@ -781,6 +821,9 @@ static void redsocks_fini_instance(redsocks_instance *instance) { if (instance->listener.inserted) if (tracked_event_del(&instance->listener) != 0) log_errno(LOG_WARNING, "event_del"); + if (instance->accept_backoff.inserted) + if (tracked_event_del(&instance->accept_backoff) != 0) + log_errno(LOG_WARNING, "event_del"); if (close(EVENT_FD(&instance->listener.ev)) != 0) log_errno(LOG_WARNING, "close"); memset(&instance->listener, 0, sizeof(instance->listener)); diff --git a/redsocks.conf.example b/redsocks.conf.example index b27ef8a..d33fba6 100644 --- a/redsocks.conf.example +++ b/redsocks.conf.example @@ -45,7 +45,13 @@ redsocks { // listen() queue length. Default value is SOMAXCONN and it should be // good enough for most of us. - // listenq = 128; + // listenq = 128; // SOMAXCONN equals 128 on my Linux box. + + // `max_accept_backoff` is a delay to retry `accept()` after accept + // failure (e.g. due to lack of file descriptors). It's measured in + // milliseconds and maximal value is 65535. Setting it to zero leads to + // busy-loop. + // max_accept_backoff = 60000; // `ip' and `port' are IP and tcp-port of proxy-server // You can also use hostname instead of IP, only one (random) diff --git a/redsocks.h b/redsocks.h index eae0eae..3a38c62 100644 --- a/redsocks.h +++ b/redsocks.h @@ -29,6 +29,7 @@ typedef struct redsocks_config_t { char *type; char *login; char *password; + uint16_t max_backoff_ms; // backoff capped by 65 seconds is enough :) uint16_t listenq; } redsocks_config; @@ -41,6 +42,8 @@ typedef struct redsocks_instance_t { list_head list; redsocks_config config; struct tracked_event listener; + struct tracked_event accept_backoff; + uint16_t accept_backoff_ms; list_head clients; relay_subsys *relay_ss; } redsocks_instance;