diff --git a/CHANGES b/CHANGES index a25a489..aecf384 100644 --- a/CHANGES +++ b/CHANGES @@ -129,6 +129,10 @@ porting: corrections for OpenEmbedded, especially termios SHIFT values and ISPEED/OSPEED. Thanks to John Faith for providing the patch +new features: + added option max-children that limits the number of concurrent child + processes. Thanks to Sam Liddicott for providing the patch. + ####################### V 2.0.0-b7: security: diff --git a/doc/socat.yo b/doc/socat.yo index d9eaca1..531a5e1 100644 --- a/doc/socat.yo +++ b/doc/socat.yo @@ -710,6 +710,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), @@ -995,6 +996,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), @@ -2349,6 +2351,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 eb497e6..3f63806 100755 --- a/test.sh +++ b/test.sh @@ -10977,6 +10977,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)) + + # socat up to 1.7.2.0 and 2.0.0-b4 had a bug in xioscan_readline() that could # be exploited # to overflow a heap based buffer (socat security advisory 3) diff --git a/xio-listen.c b/xio-listen.c index f1ab523..0559890 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 }; @@ -115,6 +116,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; @@ -135,6 +137,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) { @@ -286,12 +295,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->rfd); + 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); @@ -317,6 +339,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->rfd) < 0) { diff --git a/xio-listen.h b/xio-listen.h index 24303c1..53946d6 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-2012 */ /* 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 dc54209..3335a51 100644 --- a/xio.h +++ b/xio.h @@ -604,6 +604,8 @@ struct threadarg_struct { 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 d821343..e80cd9c 100644 --- a/xioinitialize.c +++ b/xioinitialize.c @@ -17,7 +17,7 @@ static int xioinitialized; xiofile_t *sock[XIO_MAXSOCK]; int (*xiohook_newchild)(void); /* xio calls this function in every new child process */ - +int num_child = 0; /* call this function before calling any other xio function. With xioflags, you have to set the features that xio can make use of. @@ -215,6 +215,7 @@ int xio_forked_inchild(void) { for (i=0; i