diff --git a/CHANGES b/CHANGES index c0ecd6b..c3281f9 100644 --- a/CHANGES +++ b/CHANGES @@ -87,6 +87,10 @@ porting: minor corrections to docu and test.sh resulting from local compilation on Openmoko SHR +new features: + added option max-children that limits the number of concurrent child + processes. Thanks to Sam Liddicott for providing the patch. + ####################### V 1.7.1.3: security: diff --git a/doc/socat.yo b/doc/socat.yo index 5027eb6..82be39c 100644 --- a/doc/socat.yo +++ b/doc/socat.yo @@ -659,6 +659,7 @@ label(ADDRESS_SCTP_LISTEN)dit(bf(tt(SCTP-LISTEN:))) link(range)(OPTION_RANGE), link(tcpwrap)(OPTION_TCPWRAPPERS), link(pf)(OPTION_PROTOCOL_FAMILY), + link(max-children)(OPTION_MAX_CHILDREN), link(backlog)(OPTION_BACKLOG), link(sctp-maxseg)(OPTION_SCTP_MAXSEG), link(sctp-nodelay)(OPTION_SCTP_NODELAY), @@ -931,6 +932,7 @@ label(ADDRESS_TCP_LISTEN)dit(bf(tt(TCP-LISTEN:))) link(range)(OPTION_RANGE), link(tcpwrap)(OPTION_TCPWRAPPERS), link(pf)(OPTION_PROTOCOL_FAMILY), + link(max-children)(OPTION_MAX_CHILDREN), link(backlog)(OPTION_BACKLOG), link(mss)(OPTION_MSS), link(su)(OPTION_SUBSTUSER), @@ -2282,6 +2284,9 @@ startdit() label(OPTION_BACKLOG)dit(bf(tt(backlog=))) Sets the backlog value passed with the code(listen()) system call to [link(int)(TYPE_INT)]. Default is 5. +label(OPTION_MAX_CHILDREN)dit(bf(tt(max-children=))) + Limits the number of concurrent child processes [link(int)(TYPE_INT)]. + Default is no limit. enddit() startdit()enddit()nl() diff --git a/test.sh b/test.sh index c7b695d..a89743d 100755 --- a/test.sh +++ b/test.sh @@ -10553,6 +10553,54 @@ PORT=$((PORT+1)) N=$((N+1)) +# test the max-children option +NAME=MAXCHILDREN +case "$TESTS" in +*%functions%*|*%socket%*|*%$NAME%*) +TEST="$NAME: max-children option" +# start a listen process with max-children=1; connect with a client, let it +# sleep some time before sending data; connect with second client that sends +# data immediately. If max-children is working correctly the first data should +# arrive first because the second process has to wait. +if ! eval $NUMCOND; then :; else +ts="$td/test$N.sock" +tf="$td/test$N.stdout" +te="$td/test$N.stderr" +tdiff="$td/test$N.diff" +da="test$N $(date) $RANDOM" +CMD0="$SOCAT $opts -U FILE:$tf,o-trunc,o-creat,o-append UNIX-L:$ts,fork,max-children=1" +CMD1="$SOCAT $opts -u - UNIX-CONNECT:$ts" +printf "test $F_n $TEST... " $N +$CMD0 >/dev/null 2>"${te}0" & +pid0=$! +waitunixport $ts 1 +(sleep 2; echo "$da 1") |$CMD1 >"${tf}1" 2>"${te}1" & +pid1=$! +sleep 1 +echo "$da 2" |$CMD1 >"${tf}2" 2>"${te}2" +rc2=$? +sleep 2 +kill $pid0 $pid1 2>/dev/null; wait +if echo -e "$da 1\n$da 2" |diff $tf - >$tdiff; then + $PRINTF "$OK\n" + numOK=$((numOK+1)) +else + $PRINTF "$FAILED\n" + echo "$CMD0 &" + echo "(sleep 2; echo \"$da 1\") |$CMD1" + echo "echo \"$da 2\" |$CMD1" + cat "${te}0" + cat "${te}1" + cat "${te}2" + cat "$tdiff" + numFAIL=$((numFAIL+1)) +fi +fi # NUMCOND + ;; +esac +N=$((N+1)) + + ############################################################################### # here come tests that might affect your systems integrity. Put normal tests # before this paragraph. diff --git a/xio-listen.c b/xio-listen.c index 60ab4a4..68afaa7 100644 --- a/xio-listen.c +++ b/xio-listen.c @@ -19,6 +19,7 @@ /***** LISTEN options *****/ const struct optdesc opt_backlog = { "backlog", NULL, OPT_BACKLOG, GROUP_LISTEN, PH_LISTEN, TYPE_INT, OFUNC_SPEC }; const struct optdesc opt_fork = { "fork", NULL, OPT_FORK, GROUP_CHILD, PH_PASTACCEPT, TYPE_BOOL, OFUNC_SPEC }; +const struct optdesc opt_max_children = { "max-children", NULL, OPT_MAX_CHILDREN, GROUP_CHILD, PH_PASTACCEPT, TYPE_INT, OFUNC_SPEC }; /**/ #if (WITH_UDP || WITH_TCP) const struct optdesc opt_range = { "range", NULL, OPT_RANGE, GROUP_RANGE, PH_ACCEPT, TYPE_STRING, OFUNC_SPEC }; @@ -114,6 +115,7 @@ int _xioopen_listen(struct single *xfd, int xioflags, struct sockaddr *us, sockl int backlog = 5; /* why? 1 seems to cause problems under some load */ char *rangename; bool dofork = false; + int maxchildren = 0; char infobuff[256]; char lisname[256]; union sockaddr_union _peername; @@ -134,6 +136,13 @@ int _xioopen_listen(struct single *xfd, int xioflags, struct sockaddr *us, sockl xfd->flags |= XIO_DOESFORK; } + retropt_int(opts, OPT_MAX_CHILDREN, &maxchildren); + + if (! dofork && maxchildren) { + Error("option max-children not allowed without option fork"); + return STAT_NORETRY; + } + if (applyopts_single(xfd, opts, PH_INIT) < 0) return -1; if (dofork) { @@ -278,12 +287,25 @@ int _xioopen_listen(struct single *xfd, int xioflags, struct sockaddr *us, sockl if (dofork) { pid_t pid; /* mostly int; only used with fork */ + sigset_t mask_sigchld; + + /* we must prevent that the current packet triggers another fork; + therefore we wait for a signal from the recent child: USR1 + indicates that is has consumed the last packet; CHLD means it has + terminated */ + /* block SIGCHLD and SIGUSR1 until parent is ready to react */ + sigemptyset(&mask_sigchld); + sigaddset(&mask_sigchld, SIGCHLD); + Sigprocmask(SIG_BLOCK, &mask_sigchld, NULL); + if ((pid = xio_fork(false, level==E_ERROR?level:E_WARN)) < 0) { Close(xfd->fd); + Sigprocmask(SIG_UNBLOCK, &mask_sigchld, NULL); return STAT_RETRYLATER; } if (pid == 0) { /* child */ pid_t cpid = Getpid(); + Sigprocmask(SIG_UNBLOCK, &mask_sigchld, NULL); Info1("just born: client process "F_pid, cpid); xiosetenvulong("PID", cpid, 1); @@ -308,6 +330,15 @@ int _xioopen_listen(struct single *xfd, int xioflags, struct sockaddr *us, sockl if (Close(ps) < 0) { Info2("close(%d): %s", ps, strerror(errno)); } + + /* now we are ready to handle signals */ + Sigprocmask(SIG_UNBLOCK, &mask_sigchld, NULL); + + while (maxchildren) { + if (num_child < maxchildren) break; + Notice("maxchildren are active, waiting"); + while (!Sleep(UINT_MAX)) ; /* any signal lets us continue */ + } Info("still listening"); } else { if (Close(xfd->fd) < 0) { diff --git a/xio-listen.h b/xio-listen.h index 24303c1..1c4a66d 100644 --- a/xio-listen.h +++ b/xio-listen.h @@ -1,5 +1,5 @@ /* source: xio-listen.h */ -/* Copyright Gerhard Rieger 2001-2006 */ +/* Copyright Gerhard Rieger 2001-2011 */ /* Published under the GNU General Public License V.2, see file COPYING */ #ifndef __xio_listen_h_included @@ -7,6 +7,7 @@ extern const struct optdesc opt_backlog; extern const struct optdesc opt_fork; +extern const struct optdesc opt_max_children; extern const struct optdesc opt_range; int diff --git a/xio.h b/xio.h index 6bd6b66..85bbace 100644 --- a/xio.h +++ b/xio.h @@ -378,6 +378,8 @@ struct opt { extern const char *PIPESEP; extern xiofile_t *sock[XIO_MAXSOCK]; +extern int num_child; + /* return values of xioopensingle */ #define STAT_OK 0 #define STAT_WARNING 1 diff --git a/xioinitialize.c b/xioinitialize.c index 1591294..7f7c058 100644 --- a/xioinitialize.c +++ b/xioinitialize.c @@ -15,7 +15,7 @@ static int xioinitialized; xiofile_t *sock[XIO_MAXSOCK]; int (*xiohook_newchild)(void); /* xio calls this function from a new child process */ - +int num_child = 0; /* returns 0 on success or != if an error occurred */ int xioinitialize(void) { @@ -181,6 +181,7 @@ int xio_forked_inchild(void) { for (i=0; i