new option max-children that limits the number of concurrent child processes

This commit is contained in:
Gerhard Rieger 2012-11-16 18:07:52 +01:00
parent 3d7521d251
commit 46313470b8
10 changed files with 99 additions and 2 deletions

View file

@ -129,6 +129,10 @@ porting:
corrections for OpenEmbedded, especially termios SHIFT values and corrections for OpenEmbedded, especially termios SHIFT values and
ISPEED/OSPEED. Thanks to John Faith for providing the patch 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: ####################### V 2.0.0-b7:
security: security:

View file

@ -710,6 +710,7 @@ label(ADDRESS_SCTP_LISTEN)dit(bf(tt(SCTP-LISTEN:<port>)))
link(range)(OPTION_RANGE), link(range)(OPTION_RANGE),
link(tcpwrap)(OPTION_TCPWRAPPERS), link(tcpwrap)(OPTION_TCPWRAPPERS),
link(pf)(OPTION_PROTOCOL_FAMILY), link(pf)(OPTION_PROTOCOL_FAMILY),
link(max-children)(OPTION_MAX_CHILDREN),
link(backlog)(OPTION_BACKLOG), link(backlog)(OPTION_BACKLOG),
link(sctp-maxseg)(OPTION_SCTP_MAXSEG), link(sctp-maxseg)(OPTION_SCTP_MAXSEG),
link(sctp-nodelay)(OPTION_SCTP_NODELAY), link(sctp-nodelay)(OPTION_SCTP_NODELAY),
@ -995,6 +996,7 @@ label(ADDRESS_TCP_LISTEN)dit(bf(tt(TCP-LISTEN:<port>)))
link(range)(OPTION_RANGE), link(range)(OPTION_RANGE),
link(tcpwrap)(OPTION_TCPWRAPPERS), link(tcpwrap)(OPTION_TCPWRAPPERS),
link(pf)(OPTION_PROTOCOL_FAMILY), link(pf)(OPTION_PROTOCOL_FAMILY),
link(max-children)(OPTION_MAX_CHILDREN),
link(backlog)(OPTION_BACKLOG), link(backlog)(OPTION_BACKLOG),
link(mss)(OPTION_MSS), link(mss)(OPTION_MSS),
link(su)(OPTION_SUBSTUSER), link(su)(OPTION_SUBSTUSER),
@ -2349,6 +2351,9 @@ startdit()
label(OPTION_BACKLOG)dit(bf(tt(backlog=<count>))) label(OPTION_BACKLOG)dit(bf(tt(backlog=<count>)))
Sets the backlog value passed with the code(listen()) system call to <count> Sets the backlog value passed with the code(listen()) system call to <count>
[link(int)(TYPE_INT)]. Default is 5. [link(int)(TYPE_INT)]. Default is 5.
label(OPTION_MAX_CHILDREN)dit(bf(tt(max-children=<count>)))
Limits the number of concurrent child processes [link(int)(TYPE_INT)].
Default is no limit.
enddit() enddit()
startdit()enddit()nl() startdit()enddit()nl()

48
test.sh
View file

@ -10977,6 +10977,54 @@ PORT=$((PORT+1))
N=$((N+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 # socat up to 1.7.2.0 and 2.0.0-b4 had a bug in xioscan_readline() that could
# be exploited # be exploited
# to overflow a heap based buffer (socat security advisory 3) # to overflow a heap based buffer (socat security advisory 3)

View file

@ -19,6 +19,7 @@
/***** LISTEN options *****/ /***** LISTEN options *****/
const struct optdesc opt_backlog = { "backlog", NULL, OPT_BACKLOG, GROUP_LISTEN, PH_LISTEN, TYPE_INT, OFUNC_SPEC }; 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_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) #if (WITH_UDP || WITH_TCP)
const struct optdesc opt_range = { "range", NULL, OPT_RANGE, GROUP_RANGE, PH_ACCEPT, TYPE_STRING, OFUNC_SPEC }; 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 */ int backlog = 5; /* why? 1 seems to cause problems under some load */
char *rangename; char *rangename;
bool dofork = false; bool dofork = false;
int maxchildren = 0;
char infobuff[256]; char infobuff[256];
char lisname[256]; char lisname[256];
union sockaddr_union _peername; union sockaddr_union _peername;
@ -135,6 +137,13 @@ int _xioopen_listen(struct single *xfd, int xioflags, struct sockaddr *us, sockl
xfd->flags |= XIO_DOESFORK; 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 (applyopts_single(xfd, opts, PH_INIT) < 0) return -1;
if (dofork) { if (dofork) {
@ -286,12 +295,25 @@ int _xioopen_listen(struct single *xfd, int xioflags, struct sockaddr *us, sockl
if (dofork) { if (dofork) {
pid_t pid; /* mostly int; only used with fork */ 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) { if ((pid = xio_fork(false, level==E_ERROR?level:E_WARN)) < 0) {
Close(xfd->rfd); Close(xfd->rfd);
Sigprocmask(SIG_UNBLOCK, &mask_sigchld, NULL);
return STAT_RETRYLATER; return STAT_RETRYLATER;
} }
if (pid == 0) { /* child */ if (pid == 0) { /* child */
pid_t cpid = Getpid(); pid_t cpid = Getpid();
Sigprocmask(SIG_UNBLOCK, &mask_sigchld, NULL);
Info1("just born: client process "F_pid, cpid); Info1("just born: client process "F_pid, cpid);
xiosetenvulong("PID", cpid, 1); xiosetenvulong("PID", cpid, 1);
@ -317,6 +339,15 @@ int _xioopen_listen(struct single *xfd, int xioflags, struct sockaddr *us, sockl
if (Close(ps) < 0) { if (Close(ps) < 0) {
Info2("close(%d): %s", ps, strerror(errno)); 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"); Info("still listening");
} else { } else {
if (Close(xfd->rfd) < 0) { if (Close(xfd->rfd) < 0) {

View file

@ -1,5 +1,5 @@
/* source: xio-listen.h */ /* 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 */ /* Published under the GNU General Public License V.2, see file COPYING */
#ifndef __xio_listen_h_included #ifndef __xio_listen_h_included
@ -7,6 +7,7 @@
extern const struct optdesc opt_backlog; extern const struct optdesc opt_backlog;
extern const struct optdesc opt_fork; extern const struct optdesc opt_fork;
extern const struct optdesc opt_max_children;
extern const struct optdesc opt_range; extern const struct optdesc opt_range;
int int

2
xio.h
View file

@ -604,6 +604,8 @@ struct threadarg_struct {
extern const char *PIPESEP; extern const char *PIPESEP;
extern xiofile_t *sock[XIO_MAXSOCK]; /*!!!*/ extern xiofile_t *sock[XIO_MAXSOCK]; /*!!!*/
extern int num_child;
/* return values of xioopensingle */ /* return values of xioopensingle */
#define STAT_OK 0 #define STAT_OK 0
#define STAT_WARNING 1 #define STAT_WARNING 1

View file

@ -17,7 +17,7 @@ static int xioinitialized;
xiofile_t *sock[XIO_MAXSOCK]; xiofile_t *sock[XIO_MAXSOCK];
int (*xiohook_newchild)(void); /* xio calls this function in every new child int (*xiohook_newchild)(void); /* xio calls this function in every new child
process */ process */
int num_child = 0;
/* call this function before calling any other xio function. /* call this function before calling any other xio function.
With xioflags, you have to set the features that xio can make use of. 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<NUMUNKNOWN; ++i) { for (i=0; i<NUMUNKNOWN; ++i) {
diedunknown[i] = 0; diedunknown[i] = 0;
} }
num_child = 0;
xiodroplocks(); xiodroplocks();
#if WITH_FIPS #if WITH_FIPS
if (xio_reset_fips_mode() != 0) { if (xio_reset_fips_mode() != 0) {
@ -273,6 +274,7 @@ pid_t xio_fork(bool subchild, int level) {
return 0; return 0;
} }
num_child++;
/* parent process */ /* parent process */
Notice1("forked off child process "F_pid, pid); Notice1("forked off child process "F_pid, pid);
/* gdb recommends to have env controlled sleep after fork */ /* gdb recommends to have env controlled sleep after fork */

View file

@ -865,6 +865,8 @@ const struct optname optionnames[] = {
IF_ANY ("lseek64-set", &opt_lseek64_set) IF_ANY ("lseek64-set", &opt_lseek64_set)
#endif #endif
IF_TUN ("master", &opt_iff_master) IF_TUN ("master", &opt_iff_master)
IF_LISTEN ("max-children", &opt_max_children)
IF_LISTEN ("maxchildren", &opt_max_children)
#ifdef TCP_MAXSEG #ifdef TCP_MAXSEG
IF_TCP ("maxseg", &opt_tcp_maxseg) IF_TCP ("maxseg", &opt_tcp_maxseg)
IF_TCP ("maxseg-late", &opt_tcp_maxseg_late) IF_TCP ("maxseg-late", &opt_tcp_maxseg_late)

View file

@ -436,6 +436,7 @@ enum e_optcode {
OPT_IXON, /* termios.c_iflag */ OPT_IXON, /* termios.c_iflag */
OPT_LOCKFILE, OPT_LOCKFILE,
OPT_LOWPORT, OPT_LOWPORT,
OPT_MAX_CHILDREN,
#ifdef NLDLY #ifdef NLDLY
# ifdef NL0 # ifdef NL0
OPT_NL0, /* termios.c_oflag */ OPT_NL0, /* termios.c_oflag */

View file

@ -124,6 +124,7 @@ void childdied(int signum
errno = _errno; errno = _errno;
return; return;
} }
if (num_child) num_child--;
#if 0 #if 0
/*! indent */ /*! indent */
/* check if it was a registered child process */ /* check if it was a registered child process */