diff --git a/ChangeLog b/ChangeLog index 01affff..b330a52 100644 --- a/ChangeLog +++ b/ChangeLog @@ -12,6 +12,12 @@ vNEXT: `msg_probe_error` to `msg_probe_info`: beware if you happen to use them... + Close unused file descriptors upon forking a worker + for a connection (in sslh-ev and sslh-select). In + particular, this prevents forked processes to + inherit the listen sockets and block a new main + process from starting. + v2.3.0: Added `max_connections` setting to `listen` and `protocol` configuration; see the diff --git a/collection.c b/collection.c index 8680915..d62c627 100644 --- a/collection.c +++ b/collection.c @@ -28,6 +28,7 @@ /* Info to keep track of all connections */ struct cnx_collection { gap_array* fd2cnx; /* Array indexed by file descriptor to things in cnx[] */ + int max_fd; }; /* Allocates and initialises a new collection of connections with at least @@ -42,6 +43,7 @@ cnx_collection* collection_init(int len) memset(collection, 0, sizeof(*collection)); collection->fd2cnx = gap_init(len); + collection->max_fd = 0; return collection; } @@ -54,10 +56,17 @@ void collection_destroy(cnx_collection* collection) free(collection); } +int collection_max_fd(cnx_collection* collection) +{ + return collection->max_fd; +} + /* Points the file descriptor to the specified connection index */ int collection_add_fd(cnx_collection* collection, struct connection* cnx, int fd) { gap_set(collection->fd2cnx, fd, cnx); + if (fd > collection->max_fd) + collection->max_fd = fd; return 0; } @@ -74,8 +83,7 @@ struct connection* collection_alloc_cnx_from_fd(struct cnx_collection* collectio cnx->state = ST_PROBING; cnx->probe_timeout = time(NULL) + cfg.timeout; - gap_set(collection->fd2cnx, fd, cnx); - + collection_add_fd(collection, cnx, fd); return cnx; } diff --git a/collection.h b/collection.h index 8d6ebb2..5ca6a4f 100644 --- a/collection.h +++ b/collection.h @@ -9,6 +9,7 @@ void collection_destroy(cnx_collection* collection); struct connection* collection_alloc_cnx_from_fd(cnx_collection* collection, int fd); int collection_add_fd(cnx_collection* collection, struct connection* cnx, int fd); +int collection_max_fd(cnx_collection* collection); /* Remove a connection from the collection */ int collection_remove_cnx(cnx_collection* collection, struct connection *cnx); diff --git a/processes.c b/processes.c index 27a10d6..ae77226 100644 --- a/processes.c +++ b/processes.c @@ -196,7 +196,7 @@ static int next_power_of_two(int n) return n + 1; } -void loop_init(struct loop_info* loop, int num_addr_listen) +void loop_init(struct loop_info* loop, struct listen_endpoint* listen_sockets, int num_addr_listen) { memset(loop, 0, sizeof(*loop)); loop->collection = collection_init(0); @@ -206,6 +206,7 @@ void loop_init(struct loop_info* loop, int num_addr_listen) udp_init(loop); tcp_init(); + loop->listen_sockets = listen_sockets; loop->num_addr_listen = num_addr_listen; int max_forks = next_power_of_two(max_forking_connections()); diff --git a/processes.h b/processes.h index 20566cc..8d20ca4 100644 --- a/processes.h +++ b/processes.h @@ -23,6 +23,8 @@ struct loop_info { hash* pid2proto; /* to follow which forked PID is processing what protocol for connection count */ watchers* watchers; + + struct listen_endpoint* listen_sockets; int num_addr_listen; /* How many listen endpoints do we have here */ cnx_collection* collection; /* Collection of connections linked to this loop */ @@ -32,7 +34,7 @@ void cnx_read_process(struct loop_info* fd_info, int fd); struct connection* cnx_accept_process(struct loop_info* fd_info, struct listen_endpoint* listen_socket); int tidy_connection(struct connection *cnx, struct loop_info* fd_info); -void loop_init(struct loop_info* loop, int num_addr_listen); +void loop_init(struct loop_info* loop, struct listen_endpoint* listen_sockets, int num_addr_listen); void remember_child_data(struct loop_info* fd_info, struct connection* cnx, pid_t pid); @@ -47,4 +49,6 @@ void watchers_del_write(watchers* w, int fd); void watcher_sigchld(struct loop_info* fd_info, struct connection* cnx, pid_t pid); +void close_listen_endpoints(struct loop_info* loop); + #endif diff --git a/sslh-ev.c b/sslh-ev.c index f9b82ed..3e7de94 100644 --- a/sslh-ev.c +++ b/sslh-ev.c @@ -207,13 +207,12 @@ void watcher_sigchld(struct loop_info* fd_info, struct connection* cnx, pid_t pi ev_child_start(EV_DEFAULT_ cw); } - void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen) { struct loop_info ev_info; loop = EV_DEFAULT; - loop_init(&ev_info, num_addr_listen); + loop_init(&ev_info, listen_sockets, num_addr_listen); watchers_init(&ev_info.watchers, listen_sockets, num_addr_listen); ev_set_userdata(EV_A_ &ev_info); diff --git a/sslh-select.c b/sslh-select.c index 376ff6a..5dc81f0 100644 --- a/sslh-select.c +++ b/sslh-select.c @@ -204,7 +204,7 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen) int i, res; setup_sigalrm(); - loop_init(&fd_info, num_addr_listen); + loop_init(&fd_info, listen_sockets, num_addr_listen); watchers_init(&fd_info.watchers, listen_sockets, num_addr_listen); diff --git a/tcp-listener.c b/tcp-listener.c index 4840a58..14858c2 100644 --- a/tcp-listener.c +++ b/tcp-listener.c @@ -296,6 +296,37 @@ static void connect_proxy(struct connection *cnx) } +/* This ensures the kernel will close the listening socket when the + * parent process closes; otherwise, the listening socket is kept + * around by the inactive file descriptor in the child */ +void close_listen_endpoints(struct loop_info* loop) +{ + struct listen_endpoint* listen_sockets = loop->listen_sockets; + watchers* w = loop->watchers; + + for (int i = 0; i < loop->num_addr_listen; i++) { + int fd = listen_sockets[i].socketfd; + watchers_del_read(w, fd); + watchers_del_write(w, fd); + close(fd); + } +} + + +/* Close all connections except specified. + * This saves file descriptors and allocated memory when forking */ +void tidy_other_connections(struct loop_info* fd_info, struct connection* keep_cnx) +{ + struct connection* cnx; + cnx_collection* cnx_coll = fd_info->collection; + + for (int i = 0; i < collection_max_fd(cnx_coll); i++) { + cnx = collection_get_cnx_from_fd(cnx_coll, i); + if (cnx && (cnx != keep_cnx)) + tidy_connection(cnx, fd_info); + } +} + /* Forks a shoveler process for one protocol */ void fork_shoveling_process(struct loop_info* fd_info, struct connection* cnx) { pid_t pid; @@ -307,8 +338,8 @@ void fork_shoveling_process(struct loop_info* fd_info, struct connection* cnx) { switch (pid = fork()) { case 0: /* child */ - /* TODO: close all file descriptors except 2 */ - /* free(cnx); */ + close_listen_endpoints(fd_info); + tidy_other_connections(fd_info, cnx); connect_proxy(cnx); exit(0); case -1: print_message(msg_system_error, "fork failed: err %d: %s\n", errno, strerror(errno));