Index: usr.sbin/inetd/inetd.8 =================================================================== RCS file: /cvsroot/src/usr.sbin/inetd/inetd.8,v retrieving revision 1.68 diff -p -u -r1.68 inetd.8 --- usr.sbin/inetd/inetd.8 24 May 2024 21:55:13 -0000 1.68 +++ usr.sbin/inetd/inetd.8 22 Dec 2025 07:19:26 -0000 @@ -133,7 +133,7 @@ The fields of the configuration file are .Pp .Bd -unfilled -offset indent -compact [listen-addr:]service-spec -socket-type[:accept-filter] +socket-type[,accept-max][:accept-filter] protocol[,sndbuf=size][,rcvbuf=size] wait/nowait[:max] user[:group] @@ -220,16 +220,23 @@ or depending on whether the socket is a stream, datagram, raw, reliably delivered message, or sequenced packet socket. .Pp -Optionally, for Internet services, an accept filter +Optionally, for Internet services, an accept limit +and an accept filter (see .Xr accept_filter 9 ) -can be specified by appending a colon to +can be specified. +The accept limit is appended with a comma followed by a decimal number, +the accept filter is appended with a colon to .Em socket-type , followed by the name of the desired accept filter. In this case .Nm -will not see new connections for the specified service until the accept -filter decides they are ready to be handled. +will not see new connections for the specified service until the +number of running instances is below the accept limit and the +accept filter decides they are ready to be handled. +The accept limit is ignored for services marked as +.Dq wait, +because the socket is handed over to the server program. .\" XXX: do accept filters work for AF_UNIX sockets? nobody probably .\" cares, but... .Pp @@ -475,6 +482,10 @@ is specified and is .Li udp{4,6} or .Li tcp{4,6} . +.It Sy accept_max +Equivalent to +.Em accept-max +in positional notation. .It Sy acceptfilter An accept filter, equivalent to .Em accept Index: usr.sbin/inetd/inetd.c =================================================================== RCS file: /cvsroot/src/usr.sbin/inetd/inetd.c,v retrieving revision 1.141 diff -p -u -r1.141 inetd.c --- usr.sbin/inetd/inetd.c 10 Aug 2022 08:37:53 -0000 1.141 +++ usr.sbin/inetd/inetd.c 22 Dec 2025 07:19:26 -0000 @@ -288,6 +288,12 @@ static struct kevent *allocchange(void); static int get_line(int, char *, int); static void spawn(struct servtab *, int); +static void suspend_sep(struct servtab *); +static void resume_sep(struct servtab *); + +static void add_child(struct servtab *, pid_t); +static void remove_child(struct servtab *, pid_t); + struct biltin { const char *bi_service; /* internally provided service name */ int bi_socktype; /* type of socket supported */ @@ -405,7 +411,7 @@ main(int argc, char *argv[]) } for (;;) { - int ctrl; + int ctrl; struct kevent eventbuf[64], *ev; struct servtab *sep; @@ -449,12 +455,16 @@ main(int argc, char *argv[]) DPRINTF(SERV_FMT ": accept, ctrl fd %d", SERV_PARAMS(sep), ctrl); if (ctrl < 0) { - if (errno != EINTR) + if (errno != EINTR && errno != EBADF) syslog(LOG_WARNING, SERV_FMT ": accept: %m", SERV_PARAMS(sep)); continue; } + sep->se_accept_count++; + if (sep->se_accept_max != SERVTAB_UNSPEC_SIZE_T && + sep->se_accept_count >= sep->se_accept_max) + suspend_sep(sep); } else ctrl = sep->se_fd; spawn(sep, ctrl); @@ -481,11 +491,18 @@ spawn(struct servtab *sep, int ctrl) pid = fork(); if (pid < 0) { syslog(LOG_ERR, "fork: %m"); - if (sep->se_wait == 0 && sep->se_socktype == SOCK_STREAM) + if (sep->se_wait == 0 && sep->se_socktype == SOCK_STREAM) { + sep->se_accept_count--; + if (sep->se_accept_max != SERVTAB_UNSPEC_SIZE_T && + sep->se_accept_count < sep->se_accept_max) + resume_sep(sep); close(ctrl); + } sleep(1); return; } + if (pid != 0 && sep->se_accept_children != NULL) + add_child(sep, pid); if (pid != 0 && sep->se_wait != 0) { struct kevent *ev; @@ -633,6 +650,39 @@ run_service(int ctrl, struct servtab *se } static void +add_child(struct servtab *sep, pid_t pid) +{ + if (sep->se_accept_max != SERVTAB_UNSPEC_SIZE_T && + sep->se_accept_count <= sep->se_accept_max && + sep->se_accept_count > 0) + sep->se_accept_children[sep->se_accept_count-1] = pid; +} + +static void +remove_child(struct servtab *sep, pid_t pid) +{ + size_t i, tail; + + for (i=0; ise_accept_count; ++i) { + if (sep->se_accept_children[i] != pid) + continue; + + if (sep->se_accept_max != SERVTAB_UNSPEC_SIZE_T && + sep->se_accept_count <= sep->se_accept_max) + resume_sep(sep); + + sep->se_accept_count--; + tail = sep->se_accept_count - i; + + memmove(&sep->se_accept_children[i], + &sep->se_accept_children[i+1], + tail * sizeof(pid_t)); + + break; + } +} + +static void reapchild(void) { int status; @@ -644,7 +694,7 @@ reapchild(void) if (pid <= 0) break; DPRINTF("%d reaped, status %#x", pid, status); - for (sep = servtab; sep != NULL; sep = sep->se_next) + for (sep = servtab; sep != NULL; sep = sep->se_next) { if (sep->se_wait == pid) { struct kevent *ev; @@ -663,6 +713,10 @@ reapchild(void) DPRINTF("restored %s, fd %d", sep->se_service, sep->se_fd); } + + if (sep->se_accept_children != NULL) + remove_child(sep, pid); + } } } @@ -799,7 +853,7 @@ setsockopt(fd, SOL_SOCKET, opt, &on, (so return; } if (sep->se_socktype == SOCK_STREAM) - listen(sep->se_fd, 10); + listen(sep->se_fd, 128); /* for internal dgram, setsockopt() is required for recvfromto() */ if (sep->se_socktype == SOCK_DGRAM && sep->se_bi != NULL) { @@ -821,6 +875,18 @@ setsockopt(fd, SOL_SOCKET, opt, &on, (so } } + /* Allocate list of children */ + if (sep->se_accept_max != SERVTAB_UNSPEC_SIZE_T && + sep->se_accept_max > 0) { + sep->se_accept_children = calloc(sep->se_accept_max, + sizeof(pid_t)); + if (sep->se_accept_children == NULL) { + syslog(LOG_ERR, SERV_FMT ": accept_max too large: %m", + SERV_PARAMS(sep)); + sep->se_accept_max = SERVTAB_UNSPEC_SIZE_T; + } + } + /* Set the accept filter, if specified. To be done after listen.*/ if (sep->se_accf.af_name[0] != 0 && setsockopt(sep->se_fd, SOL_SOCKET, SO_ACCEPTFILTER, &sep->se_accf, @@ -856,6 +922,58 @@ close_sep(struct servtab *sep) } } +/* + * Suspend service by ignoring connect events + */ +static void +suspend_sep(struct servtab *sep) +{ + struct kevent *ev; + + if (sep->se_accept_count == sep->se_accept_max) { + ev = allocchange(); + EV_SET(ev, sep->se_fd, EVFILT_READ, EV_DISABLE, 0, 0, + (intptr_t)sep); + } + + if (sep->se_accept_mark == 0) + syslog(LOG_ERR, SERV_FMT + ": accept_max reached (%zu);" + "service suspended", + SERV_PARAMS(sep), + sep->se_accept_max); + + if (sep->se_accept_max > 0) + sep->se_accept_mark = sep->se_accept_max - 1; + else + sep->se_accept_mark = 0; +} + +/* + * Resume service by listening to connect events + */ +static void +resume_sep(struct servtab *sep) +{ + struct kevent *ev; + + if (sep->se_accept_count == sep->se_accept_max) { + ev = allocchange(); + EV_SET(ev, sep->se_fd, EVFILT_READ, EV_ENABLE, 0, 0, + (intptr_t)sep); + } + + if (sep->se_accept_mark == 0 || + sep->se_accept_count < sep->se_accept_mark) { + syslog(LOG_ERR, SERV_FMT + ": fell below accept_max (%zu);" + "service resumed", + SERV_PARAMS(sep), + sep->se_accept_max); + sep->se_accept_mark = 0; + } +} + void register_rpc(struct servtab *sep) { Index: usr.sbin/inetd/inetd.h =================================================================== RCS file: /cvsroot/src/usr.sbin/inetd/inetd.h,v retrieving revision 1.7 diff -p -u -r1.7 inetd.h --- usr.sbin/inetd/inetd.h 16 Dec 2025 18:24:46 -0000 1.7 +++ usr.sbin/inetd/inetd.h 22 Dec 2025 07:19:26 -0000 @@ -190,6 +190,10 @@ struct servtab { size_t se_ip_max; /* max # of instances of this service per ip per minute */ SLIST_HEAD(iplist, rl_ip_node) se_rl_ip_list; /* per-address (IP) rate limiting */ time_t se_time; /* start of se_count and ip_max counts, in seconds from arbitrary point */ + size_t se_accept_max; /* max # of connections to accept */ + size_t se_accept_count; /* number of accepted connections */ + pid_t *se_accept_children; /* identify child */ + size_t se_accept_mark; /* mark when to enable messages */ /* TODO convert to using SLIST */ struct servtab *se_next; @@ -255,6 +259,7 @@ int parse_wait(struct servtab *, int); int parse_server(struct servtab *, const char *); void parse_socktype(char *, struct servtab *); void parse_accept_filter(char *, struct servtab *); +void parse_accept_max(char *, struct servtab *); char *nextline(FILE *); char *newstr(const char *); Index: usr.sbin/inetd/parse.c =================================================================== RCS file: /cvsroot/src/usr.sbin/inetd/parse.c,v retrieving revision 1.5 diff -p -u -r1.5 parse.c --- usr.sbin/inetd/parse.c 10 Aug 2022 08:37:53 -0000 1.5 +++ usr.sbin/inetd/parse.c 22 Dec 2025 07:19:26 -0000 @@ -184,6 +184,7 @@ config(void) SWAP(service_type, cp->se_type, sep->se_type); SWAP(size_t, cp->se_service_max, sep->se_service_max); SWAP(size_t, cp->se_ip_max, sep->se_ip_max); + SWAP(size_t, cp->se_accept_max, sep->se_accept_max); #undef SWAP if (isrpcservice(sep)) unregister_rpc(sep); @@ -572,6 +573,9 @@ more: /* continue parsing v1 */ parse_socktype(arg, sep); if (sep->se_socktype == SOCK_STREAM) { + parse_accept_max(arg, sep); + } + if (sep->se_socktype == SOCK_STREAM) { parse_accept_filter(arg, sep); } if (sep->se_hostaddr == NULL) { @@ -1128,6 +1132,32 @@ parse_accept_filter(char *arg, struct se } void +parse_accept_max(char *arg, struct servtab *sep) +{ + char *cp1; + int rstatus; + + if (strncmp(arg, "stream,", sizeof("stream,") - 1) != 0) + return; + cp1 = arg + (sizeof("stream,") - 1); + + sep->se_accept_max = (size_t)strtou(cp1, NULL, 10, 0, + SERVTAB_COUNT_MAX, &rstatus); + if (rstatus != 0) { + if (rstatus != ERANGE) { + /* For compatibility w/ atoi parsing */ + sep->se_accept_max = 0; + } + + WRN("Improper \"accept_max\" value '%s', " + "using '%zu' instead: %s", + cp1, + sep->se_accept_max, + strerror(rstatus)); + } +} + +void parse_socktype(char* arg, struct servtab* sep) { /* stream socket may have an accept filter, only check first chars */ @@ -1156,6 +1186,7 @@ init_servtab(void) */ .se_service_max = SERVTAB_UNSPEC_SIZE_T, .se_ip_max = SERVTAB_UNSPEC_SIZE_T, + .se_accept_max = SERVTAB_UNSPEC_SIZE_T, .se_wait = SERVTAB_UNSPEC_VAL, .se_socktype = SERVTAB_UNSPEC_VAL, .se_rl_ip_list = SLIST_HEAD_INITIALIZER(se_ip_list_head) Index: usr.sbin/inetd/parse_v2.c =================================================================== RCS file: /cvsroot/src/usr.sbin/inetd/parse_v2.c,v retrieving revision 1.7 diff -p -u -r1.7 parse_v2.c --- usr.sbin/inetd/parse_v2.c 8 Feb 2024 20:51:25 -0000 1.7 +++ usr.sbin/inetd/parse_v2.c 22 Dec 2025 07:19:26 -0000 @@ -75,6 +75,7 @@ static hresult filter_handler(struct ser static hresult group_handler(struct servtab *, vlist); static hresult service_max_handler(struct servtab *, vlist); static hresult ip_max_handler(struct servtab *, vlist); +static hresult accept_max_handler(struct servtab *, vlist); static hresult protocol_handler(struct servtab *, vlist); static hresult recv_buf_handler(struct servtab *, vlist); static hresult send_buf_handler(struct servtab *, vlist); @@ -124,6 +125,7 @@ static struct key_handler { { "exec", exec_handler }, { "args", args_handler }, { "ip_max", ip_max_handler }, + { "accept_max", accept_max_handler }, #ifdef IPSEC { "ipsec", ipsec_handler } #endif @@ -1002,6 +1004,44 @@ ip_max_handler(struct servtab *sep, vlis return KEY_HANDLER_SUCCESS; } +/* Set max connections to accept */ +static hresult +accept_max_handler(struct servtab *sep, vlist values) +{ + char *count_str; + int rstatus; + + if (sep->se_accept_max != SERVTAB_UNSPEC_SIZE_T) { + TMD("accept_max"); + return KEY_HANDLER_FAILURE; + } + + count_str = next_value(values); + + if (count_str == NULL) { + TFA("accept_max"); + return KEY_HANDLER_FAILURE; + } + + size_t count = (size_t)strtou(count_str, NULL, 10, 0, + SERVTAB_COUNT_MAX, &rstatus); + + if (rstatus != 0) { + ERR("Invalid accept_max '%s': %s", count_str, + strerror(rstatus)); + return KEY_HANDLER_FAILURE; + } + + if (next_value(values) != NULL) { + TMA("accept_max"); + return KEY_HANDLER_FAILURE; + } + + sep->se_accept_max = count; + + return KEY_HANDLER_SUCCESS; +} + /* Set user to execute as */ static hresult user_handler(struct servtab *sep, vlist values)