diff --git a/CHANGES b/CHANGES index 3996481..9df8757 100644 --- a/CHANGES +++ b/CHANGES @@ -117,6 +117,11 @@ Features: network namespace. Tests: NETNS NETNS_EXEC + New address ACCEPT-FD (ACCEPT) expects a listening file descriptor + passed from parent, and accepts one or more connections for data + transfer. This can be used with "inetd mode" of systemd. + Test: ACCEPT_FD + Corrections: When a sub process (EXEC, SYSTEM) terminated with exit code other than 0, its last sent data might have been lost depending on timing of read/ diff --git a/doc/socat.yo b/doc/socat.yo index 114d82f..b29e92e 100644 --- a/doc/socat.yo +++ b/doc/socat.yo @@ -945,6 +945,18 @@ label(ADDRESS_SOCKET_SENDTO)dit(bf(tt(SOCKET-SENDTO::::< link(SOCKET-DATAGRAM)(ADDRESS_SOCKET_DATAGRAM), link(SOCKET-RECV)(ADDRESS_SOCKET_RECV) link(SOCKET-RECVFROM)(ADDRESS_SOCKET_RECVFROM) +label(ADDRESS_ACCEPT_FD)dit(bf(tt(ACCEPT-FD:))) + Expects a listening socket in and accepts one or (with option + link(fork)(OPTION_FORK)) more connections. This address type is useful under + systemd control with "inetd mode".nl() + Example: (link(example)(EXAMPLE_ADDRESS_ACCEPT_FD)) + Option groups: link(FD)(GROUP_FD),link(SOCKET)(GROUP_SOCKET)link(TCP)(GROUP_TCP),link(CHILD)(GROUP_CHILD),link(RETRY)(GROUP_RETRY) nl() + Useful options: + link(fork)(OPTION_FORK)), + link(range)(OPTION_RANGE), + link(sourceport)(OPTION_SOURCEPORT), + link(lowport)(OPTION_LOWPORT), + link(tcpwrap)(OPTION_TCPWRAPPERS), label(ADDRESS_SOCKS4)dit(bf(tt(SOCKS4:::))) Connects via [link(IP address)(TYPE_IP_ADDRESS)] to [link(IPv4 address)(TYPE_IPV4_ADDRESS)] @@ -4140,6 +4152,23 @@ socat - \ sends an SSDP (Simple Service Discovery Protocol) query to the local network and collects and outputs the answers received. + +label(EXAMPLE_ADDRESS_ACCEPT_FD) +mancommand(\.LP) +mancommand(\.nf) +mancommand(\fBsystemd-socket-activate -l 1077 --inetd socat ACCEPT:0,fork PIPE\fP) +mancommand(\.RE) +mancommand(\.fi) + +htmlcommand(
systemd-socket-activate -l 1077 --inetd socat ACCEPT:0,fork PIPE
) + +tt(systemd-socket-activate) is a program for testing tt(systemd) socket +activation of daemons. With tt(--inetd) it waits for a connection on the +specified port. It does not accept the connection but passes the listening file +descriptor as FDs 0 and 1. Socat() accepts the waiting connection and starts +data transfer. + + dit(bf(tt())) diff --git a/test.sh b/test.sh index a52b78d..944d6f1 100755 --- a/test.sh +++ b/test.sh @@ -6168,8 +6168,11 @@ if ! diff "$tref" "$tf" >"$tdiff"; then $PRINTF "$FAILED\n" echo "$CMD0 &" cat "${te}0" >&2 - echo "$CMD1" + echo "$CMD1 &" cat "${te}1" >&2 + echo "$CMD1" + cat "${te}2" >&2 + cat "$tdiff" >&2 numFAIL=$((numFAIL+1)) listFAIL="$listFAIL $N" else @@ -6178,6 +6181,8 @@ else if [ "$DEBUG" ]; then cat "${te}0" >&2; fi if [ "$VERBOSE" ]; then echo "$CMD1"; fi if [ "$DEBUG" ]; then cat "${te}1" >&2; fi + if [ "$VERBOSE" ]; then echo "$CMD1"; fi + if [ "$DEBUG" ]; then cat "${te}2" >&2; fi numOK=$((numOK+1)) fi # !(rc -ne 0) wait @@ -16648,6 +16653,70 @@ PORT=$((PORT+1)) N=$((N+1)) +# Test the ACCEPT-FD address +NAME=ACCEPT_FD +case "$TESTS" in +*%$N%*|*%functions%*|*%bugs%*|*%socket%*|*%systemd%*|*%accept%*|*%$NAME%*) +TEST="$NAME: ACCEPT-FD address" +# Start Socat with address ACCEPT-FD via systemd-socket-activate for echoing +# data. +# Connect with a client; the test succeeds when the client gets its data back. +if ! eval $NUMCOND; then :; +elif ! $(type systemd-socket-activate >/dev/null 2>&1); then + $PRINTF "test $F_n $TEST... ${YELLOW}systemd-socket-activate not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! F=$(testfeats IP4 TCP LISTEN); then + $PRINTF "test $F_n $TEST... ${YELLOW}Feature $F not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! A=$(testaddrs ACCEPT-FD PIPE STDIO TCP4); then + $PRINTF "test $F_n $TEST... ${YELLOW}Address $A not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! runsip4 >/dev/null; then + $PRINTF "test $F_n $TEST... ${YELLOW}IPv4 not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +else +tf="$td/test$N.stdout" +te="$td/test$N.stderr" +tdiff="$td/test$N.diff" +da="test$N $(date) $RANDOM" +newport tcp4 +CMD0="systemd-socket-activate -l $PORT --inetd $TRACE $SOCAT $opts ACCEPT-FD:0 PIPE" +CMD1="$TRACE $SOCAT $opts - TCP4:$LOCALHOST:$PORT" +printf "test $F_n $TEST... " $N +$CMD0 >/dev/null 2>"${te}0" & +pid0=$! +waittcp4port $PORT 1 +echo "$da" |$CMD1 >"${tf}1" 2>"${te}1" +rc1=$? +kill $pid0 2>/dev/null; wait +if echo "$da" |diff "${tf}1" - >$tdiff; then + $PRINTF "$OK\n" + if [ "$VERBOSE" ]; then echo "$CMD0 &"; fi + if [ "$DEBUG" ]; then cat "${te}0" >&2; fi + if [ "$VERBOSE" ]; then echo "$CMD1"; fi + if [ "$DEBUG" ]; then cat "${te}1" >&2; fi + numOK=$((numOK+1)) +else + $PRINTF "$FAILED\n" + echo "$CMD0 &" + cat "${te}0" >&2 + echo "$CMD1" + cat "${te}1" >&2 + cat $tdiff >&2 + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" + namesFAIL="$namesFAIL $NAME" +fi +fi # NUMCOND + ;; +esac +N=$((N+1)) + + # end of common tests ################################################################################## @@ -16840,7 +16909,7 @@ waitport $PORT 1 echo "$da" |$CMD1 >"${tf}1" 2>"${te}1" rc1=$? kill $pid0 2>/dev/null; wait -if [ !!! ]; then +if echo "$da" |diff "${tf}1" - >$tdiff !!!; then $PRINTF "$OK\n" if [ "$VERBOSE" ]; then echo "$CMD0 &"; fi if [ "$DEBUG" ]; then cat "${te}0" >&2; fi diff --git a/xio-exec.c b/xio-exec.c index 89be2a1..a59db4f 100644 --- a/xio-exec.c +++ b/xio-exec.c @@ -35,7 +35,7 @@ static int xioopen_exec(int argc, const char *argv[], struct opt *opts, int duptostderr; if (argc != 2) { - Error3("\"%s:%s\": wrong number of parameters (%d instead of 1)", argv[0], argv[1], argc-1); + Error2("\"%s\": wrong number of parameters (%d instead of 1)", argv[0], argc-1); } retropt_bool(opts, OPT_DASH, &dash); diff --git a/xio-fdnum.c b/xio-fdnum.c index 1299b43..71611d9 100644 --- a/xio-fdnum.c +++ b/xio-fdnum.c @@ -7,15 +7,21 @@ #include "xiosysincludes.h" #include "xioopen.h" +#include "xio-listen.h" + #include "xio-fdnum.h" #if WITH_FDNUM static int xioopen_fdnum(int argc, const char *argv[], struct opt *opts, int rw, xiofile_t *xfd, groups_t groups, int dummy1, int dummy2, int dummy3); +static int xioopen_accept_fd(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xfd, groups_t groups, int dummy1, int dummy2, int dummy3); -const struct addrdesc xioaddr_fd = { "FD", 3, xioopen_fdnum, GROUP_FD|GROUP_FIFO|GROUP_CHR|GROUP_BLK|GROUP_FILE|GROUP_SOCKET|GROUP_TERMIOS|GROUP_SOCK_UNIX|GROUP_SOCK_IP|GROUP_IPAPP, 0, 0, 0 HELP(":") }; +const struct addrdesc xioaddr_fd = { "FD", 1+XIO_RDWR, xioopen_fdnum, GROUP_FD|GROUP_FIFO|GROUP_CHR|GROUP_BLK|GROUP_FILE|GROUP_SOCKET|GROUP_TERMIOS|GROUP_SOCK_UNIX|GROUP_SOCK_IP|GROUP_IPAPP, 0, 0, 0 HELP(":") }; +#if WITH_LISTEN +const struct addrdesc xioaddr_accept_fd = { "ACCEPT-FD", 1+XIO_RDWR, xioopen_accept_fd, GROUP_FD|GROUP_SOCKET|GROUP_SOCK_UNIX|GROUP_SOCK_IP|GROUP_IPAPP|GROUP_CHILD|GROUP_RANGE|GROUP_RETRY, 0, 0, 0 HELP(":") }; +#endif /* WITH_LISTEN */ /* use some file descriptor and apply the options. Set the FD_CLOEXEC flag. */ @@ -28,7 +34,7 @@ static int xioopen_fdnum(int argc, const char *argv[], struct opt *opts, int result; if (argc != 2) { - Error3("%s:%s: wrong number of parameters (%d instead of 1)", argv[0], argv[1], argc-1); + Error2("%s: wrong number of parameters (%d instead of 1)", argv[0], argc-1); } numfd = strtoul(argv[1], &a1, 0); @@ -46,8 +52,55 @@ static int xioopen_fdnum(int argc, const char *argv[], struct opt *opts, return 0; } +#if WITH_LISTEN + +/* Use some file descriptor and apply the options. Set the FD_CLOEXEC flag. */ +static int xioopen_accept_fd( + int argc, + const char *argv[], + struct opt *opts, + int xioflags, + xiofile_t *xfd, + groups_t groups, + int dummy1, + int dummy2, + int dummy3) +{ + char *a1; + int rw = (xioflags&XIO_ACCMODE); + int numfd; + union sockaddr_union us; + socklen_t uslen = sizeof(union sockaddr_union); + int result; + + if (argc != 2) { + Error2("%s: wrong number of parameters (%d instead of 1)", argv[0], argc-1); + } + + numfd = strtoul(argv[1], &a1, 0); + if (*a1 != '\0') { + Error1("error in FD number \"%s\"", argv[1]); + } + /* we dont want to see these fds in child processes */ + if (Fcntl_l(numfd, F_SETFD, FD_CLOEXEC) < 0) { + Warn2("fcntl(%d, F_SETFD, FD_CLOEXEC): %s", numfd, strerror(errno)); + } + + if (Getsockname(numfd, (struct sockaddr *)&us, &uslen) < 0) { + Warn2("getsockname(fd=%d, ...): %s", numfd, strerror(errno)); + } + Notice2("using file descriptor %d accepting a connection for %s", numfd, ddirection[rw]); + xfd->stream.fd = numfd; + if ((result = _xioopen_accept_fd(&xfd->stream, xioflags, (struct sockaddr *)&us, uslen, opts, us.soa.sa_family, 0, 0)) < 0) { + return result; + } + return 0; +} + +#endif /* WITH_LISTEN */ #endif /* WITH_FDNUM */ + #if WITH_FD /* retrieve and apply options to a standard file descriptor. diff --git a/xio-fdnum.h b/xio-fdnum.h index 06f1766..9e3acdb 100644 --- a/xio-fdnum.h +++ b/xio-fdnum.h @@ -6,6 +6,7 @@ #define __xio_fdnum_h_included 1 extern const struct addrdesc xioaddr_fd; +extern const struct addrdesc xioaddr_accept_fd; extern int xioopen_fd(struct opt *opts, int rw, xiosingle_t *xfd, int numfd, int dummy2, int dummy3); diff --git a/xio-listen.c b/xio-listen.c index 855d012..aef31ee 100644 --- a/xio-listen.c +++ b/xio-listen.c @@ -27,7 +27,6 @@ const struct optdesc opt_range = { "range", NULL, OPT_RANGE, GROUP_R #endif const struct optdesc opt_accept_timeout = { "accept-timeout", "listen-timeout", OPT_ACCEPT_TIMEOUT, GROUP_LISTEN, PH_LISTEN, TYPE_TIMEVAL, OFUNC_OFFSET, XIO_OFFSETOF(para.socket.accept_timeout) }; - /* applies and consumes the following option: PH_INIT, PH_PASTSOCKET, PH_PREBIND, PH_BIND, PH_PASTBIND, PH_EARLY, @@ -106,45 +105,11 @@ int */ int _xioopen_listen(struct single *xfd, int xioflags, struct sockaddr *us, socklen_t uslen, struct opt *opts, int pf, int socktype, int proto, int level) { - struct sockaddr sa; - socklen_t salen; 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; - union sockaddr_union _sockname; - union sockaddr_union *pa = &_peername; /* peer address */ - union sockaddr_union *la = &_sockname; /* local address */ - socklen_t pas = sizeof(_peername); /* peer address size */ - socklen_t las = sizeof(_sockname); /* local address size */ - int result; - - retropt_bool(opts, OPT_FORK, &dofork); - - if (dofork) { - if (!(xioflags & XIO_MAYFORK)) { - Error("option fork not allowed here"); - return STAT_NORETRY; - } - 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) { - xiosetchilddied(); /* set SIGCHLD handler */ - } - if ((xfd->fd = xiosocket(opts, pf?pf:us->sa_family, socktype, proto, level)) < 0) { return STAT_RETRYLATER; } @@ -175,13 +140,6 @@ int _xioopen_listen(struct single *xfd, int xioflags, struct sockaddr *us, sockl } } #endif - /* under some circumstances (e.g., TCP listen on port 0) bind() fills empty - fields that we want to know. */ - salen = sizeof(sa); - if (Getsockname(xfd->fd, us, &uslen) < 0) { - Warn4("getsockname(%d, %p, {%d}): %s", - xfd->fd, &us, uslen, strerror(errno)); - } applyopts(xfd->fd, opts, PH_PASTBIND); #if WITH_UNIX @@ -197,6 +155,70 @@ int _xioopen_listen(struct single *xfd, int xioflags, struct sockaddr *us, sockl } #endif /* WITH_UNIX */ + applyopts(xfd->fd, opts, PH_PRELISTEN); + retropt_int(opts, OPT_BACKLOG, &backlog); + applyopts(xfd->fd, opts, PH_LISTEN); + if (Listen(xfd->fd, backlog) < 0) { + Error3("listen(%d, %d): %s", xfd->fd, backlog, strerror(errno)); + return STAT_RETRYLATER; + } + return _xioopen_accept_fd(xfd, xioflags, us, uslen, opts, pf, proto,level); +} + +int _xioopen_accept_fd( + struct single *xfd, + int xioflags, + struct sockaddr *us, + socklen_t uslen, + struct opt *opts, + int pf, + int proto, + int level) +{ + struct sockaddr sa; + socklen_t salen; + char *rangename; + bool dofork = false; + int maxchildren = 0; + char infobuff[256]; + char lisname[256]; + union sockaddr_union _peername; + union sockaddr_union _sockname; + union sockaddr_union *pa = &_peername; /* peer address */ + union sockaddr_union *la = &_sockname; /* local address */ + socklen_t pas = sizeof(_peername); /* peer address size */ + socklen_t las = sizeof(_sockname); /* local address size */ + int result; + + retropt_bool(opts, OPT_FORK, &dofork); + + if (dofork) { + if (!(xioflags & XIO_MAYFORK)) { + Error("option fork not allowed here"); + return STAT_NORETRY; + } + 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 (dofork) { + xiosetchilddied(); /* set SIGCHLD handler */ + } + + /* Under some circumstances (e.g., TCP listen on port 0) bind() fills empty + fields that we want to know. */ + salen = sizeof(sa); + if (Getsockname(xfd->fd, us, &uslen) < 0) { + Warn4("getsockname(%d, %p, {%d}): %s", + xfd->fd, &us, uslen, strerror(errno)); + } + #if WITH_IP4 /*|| WITH_IP6*/ if (retropt_string(opts, OPT_RANGE, &rangename) >= 0) { if (xioparserange(rangename, pf, &xfd->para.socket.range, @@ -221,14 +243,6 @@ int _xioopen_listen(struct single *xfd, int xioflags, struct sockaddr *us, sockl retropt_bool(opts, OPT_LOWPORT, &xfd->para.socket.ip.lowport); #endif /* WITH_TCP || WITH_UDP */ - applyopts(xfd->fd, opts, PH_PRELISTEN); - retropt_int(opts, OPT_BACKLOG, &backlog); - applyopts(xfd->fd, opts, PH_LISTEN); - if (Listen(xfd->fd, backlog) < 0) { - Error3("listen(%d, %d): %s", xfd->fd, backlog, strerror(errno)); - return STAT_RETRYLATER; - } - if (xioparms.logopt == 'm') { Info("starting accept loop, switching to syslog"); diag_set('y', xioparms.syslogfac); xioparms.logopt = 'y'; diff --git a/xio-listen.h b/xio-listen.h index 028d157..8b712c6 100644 --- a/xio-listen.h +++ b/xio-listen.h @@ -20,5 +20,6 @@ int int _xioopen_listen(struct single *fd, int xioflags, struct sockaddr *us, socklen_t uslen, struct opt *opts, int pf, int socktype, int proto, int level); +extern int _xioopen_accept_fd(struct single *xfd, int xioflags, struct sockaddr *us, socklen_t uslen, struct opt *opts, int pf, int proto, int level); #endif /* !defined(__xio_listen_h_included) */ diff --git a/xio-socket.c b/xio-socket.c index 9af7104..e2397b8 100644 --- a/xio-socket.c +++ b/xio-socket.c @@ -1847,7 +1847,7 @@ int xioparsenetwork( char *addrname; const char *maskname; if ((maskname = strchr(rangename, ':')) == NULL) { - Error1("syntax error in range \"%s\": use :", rangename); + Error1("syntax error in range \"%s\" of unspecified address family: use :", rangename); return STAT_NORETRY; } ++maskname; /* skip ':' */ diff --git a/xioopen.c b/xioopen.c index 3aeed05..b3646ee 100644 --- a/xioopen.c +++ b/xioopen.c @@ -33,6 +33,10 @@ const struct addrname addressnames[] = { { "ABSTRACT-RECVFROM", &xioaddr_abstract_recvfrom }, { "ABSTRACT-SENDTO", &xioaddr_abstract_sendto }, #endif /* defined(WITH_UNIX) && defined(WITH_ABSTRACT_UNIXSOCKET) */ +#if WITH_LISTEN + { "ACCEPT", &xioaddr_accept_fd }, + { "ACCEPT-FD", &xioaddr_accept_fd }, +#endif #if WITH_CREAT { "CREAT", &xioaddr_creat }, { "CREATE", &xioaddr_creat },