New address ACCEPT-FD

This commit is contained in:
Gerhard Rieger 2023-09-30 18:39:13 +02:00
parent c311542e11
commit ebacb7c4e8
10 changed files with 232 additions and 56 deletions

View file

@ -117,6 +117,11 @@ Features:
network namespace. network namespace.
Tests: NETNS NETNS_EXEC 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: Corrections:
When a sub process (EXEC, SYSTEM) terminated with exit code other than 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/ 0, its last sent data might have been lost depending on timing of read/

View file

@ -945,6 +945,18 @@ label(ADDRESS_SOCKET_SENDTO)dit(bf(tt(SOCKET-SENDTO:<domain>:<type>:<protocol>:<
link(SOCKET-DATAGRAM)(ADDRESS_SOCKET_DATAGRAM), link(SOCKET-DATAGRAM)(ADDRESS_SOCKET_DATAGRAM),
link(SOCKET-RECV)(ADDRESS_SOCKET_RECV) link(SOCKET-RECV)(ADDRESS_SOCKET_RECV)
link(SOCKET-RECVFROM)(ADDRESS_SOCKET_RECVFROM) link(SOCKET-RECVFROM)(ADDRESS_SOCKET_RECVFROM)
label(ADDRESS_ACCEPT_FD)dit(bf(tt(ACCEPT-FD:<fdnum>)))
Expects a listening socket in <fdnum> 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:<socks-server>:<host>:<port>))) label(ADDRESS_SOCKS4)dit(bf(tt(SOCKS4:<socks-server>:<host>:<port>)))
Connects via <socks-server> [link(IP address)(TYPE_IP_ADDRESS)] Connects via <socks-server> [link(IP address)(TYPE_IP_ADDRESS)]
to <host> [link(IPv4 address)(TYPE_IPV4_ADDRESS)] to <host> [link(IPv4 address)(TYPE_IPV4_ADDRESS)]
@ -4140,6 +4152,23 @@ socat - &#x5C;
sends an SSDP (Simple Service Discovery Protocol) query to the local network sends an SSDP (Simple Service Discovery Protocol) query to the local network
and collects and outputs the answers received. 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(<hr><div class="shell">systemd-socket-activate -l 1077 --inetd socat ACCEPT:0,fork PIPE</div>)
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())) dit(bf(tt()))

73
test.sh
View file

@ -6168,8 +6168,11 @@ if ! diff "$tref" "$tf" >"$tdiff"; then
$PRINTF "$FAILED\n" $PRINTF "$FAILED\n"
echo "$CMD0 &" echo "$CMD0 &"
cat "${te}0" >&2 cat "${te}0" >&2
echo "$CMD1" echo "$CMD1 &"
cat "${te}1" >&2 cat "${te}1" >&2
echo "$CMD1"
cat "${te}2" >&2
cat "$tdiff" >&2
numFAIL=$((numFAIL+1)) numFAIL=$((numFAIL+1))
listFAIL="$listFAIL $N" listFAIL="$listFAIL $N"
else else
@ -6178,6 +6181,8 @@ else
if [ "$DEBUG" ]; then cat "${te}0" >&2; fi if [ "$DEBUG" ]; then cat "${te}0" >&2; fi
if [ "$VERBOSE" ]; then echo "$CMD1"; fi if [ "$VERBOSE" ]; then echo "$CMD1"; fi
if [ "$DEBUG" ]; then cat "${te}1" >&2; 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)) numOK=$((numOK+1))
fi # !(rc -ne 0) fi # !(rc -ne 0)
wait wait
@ -16648,6 +16653,70 @@ PORT=$((PORT+1))
N=$((N+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 # end of common tests
################################################################################## ##################################################################################
@ -16840,7 +16909,7 @@ wait<something>port $PORT 1
echo "$da" |$CMD1 >"${tf}1" 2>"${te}1" echo "$da" |$CMD1 >"${tf}1" 2>"${te}1"
rc1=$? rc1=$?
kill $pid0 2>/dev/null; wait kill $pid0 2>/dev/null; wait
if [ !!! ]; then if echo "$da" |diff "${tf}1" - >$tdiff !!!; then
$PRINTF "$OK\n" $PRINTF "$OK\n"
if [ "$VERBOSE" ]; then echo "$CMD0 &"; fi if [ "$VERBOSE" ]; then echo "$CMD0 &"; fi
if [ "$DEBUG" ]; then cat "${te}0" >&2; fi if [ "$DEBUG" ]; then cat "${te}0" >&2; fi

View file

@ -35,7 +35,7 @@ static int xioopen_exec(int argc, const char *argv[], struct opt *opts,
int duptostderr; int duptostderr;
if (argc != 2) { 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); retropt_bool(opts, OPT_DASH, &dash);

View file

@ -7,15 +7,21 @@
#include "xiosysincludes.h" #include "xiosysincludes.h"
#include "xioopen.h" #include "xioopen.h"
#include "xio-listen.h"
#include "xio-fdnum.h" #include "xio-fdnum.h"
#if WITH_FDNUM #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_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(":<num>") }; 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(":<fdnum>") };
#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(":<fdnum>") };
#endif /* WITH_LISTEN */
/* use some file descriptor and apply the options. Set the FD_CLOEXEC flag. */ /* 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; int result;
if (argc != 2) { 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); numfd = strtoul(argv[1], &a1, 0);
@ -46,8 +52,55 @@ static int xioopen_fdnum(int argc, const char *argv[], struct opt *opts,
return 0; 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 */ #endif /* WITH_FDNUM */
#if WITH_FD #if WITH_FD
/* retrieve and apply options to a standard file descriptor. /* retrieve and apply options to a standard file descriptor.

View file

@ -6,6 +6,7 @@
#define __xio_fdnum_h_included 1 #define __xio_fdnum_h_included 1
extern const struct addrdesc xioaddr_fd; 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); extern int xioopen_fd(struct opt *opts, int rw, xiosingle_t *xfd, int numfd, int dummy2, int dummy3);

View file

@ -27,7 +27,6 @@ const struct optdesc opt_range = { "range", NULL, OPT_RANGE, GROUP_R
#endif #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) }; 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: applies and consumes the following option:
PH_INIT, PH_PASTSOCKET, PH_PREBIND, PH_BIND, PH_PASTBIND, PH_EARLY, 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, 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 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 */ int backlog = 5; /* why? 1 seems to cause problems under some load */
char *rangename;
bool dofork = false;
int maxchildren = 0;
char infobuff[256]; 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 (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) { if ((xfd->fd = xiosocket(opts, pf?pf:us->sa_family, socktype, proto, level)) < 0) {
return STAT_RETRYLATER; return STAT_RETRYLATER;
} }
@ -175,13 +140,6 @@ int _xioopen_listen(struct single *xfd, int xioflags, struct sockaddr *us, sockl
} }
} }
#endif #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); applyopts(xfd->fd, opts, PH_PASTBIND);
#if WITH_UNIX #if WITH_UNIX
@ -197,6 +155,70 @@ int _xioopen_listen(struct single *xfd, int xioflags, struct sockaddr *us, sockl
} }
#endif /* WITH_UNIX */ #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 WITH_IP4 /*|| WITH_IP6*/
if (retropt_string(opts, OPT_RANGE, &rangename) >= 0) { if (retropt_string(opts, OPT_RANGE, &rangename) >= 0) {
if (xioparserange(rangename, pf, &xfd->para.socket.range, 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); retropt_bool(opts, OPT_LOWPORT, &xfd->para.socket.ip.lowport);
#endif /* WITH_TCP || WITH_UDP */ #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') { if (xioparms.logopt == 'm') {
Info("starting accept loop, switching to syslog"); Info("starting accept loop, switching to syslog");
diag_set('y', xioparms.syslogfac); xioparms.logopt = 'y'; diag_set('y', xioparms.syslogfac); xioparms.logopt = 'y';

View file

@ -20,5 +20,6 @@ int
int _xioopen_listen(struct single *fd, int xioflags, int _xioopen_listen(struct single *fd, int xioflags,
struct sockaddr *us, socklen_t uslen, struct sockaddr *us, socklen_t uslen,
struct opt *opts, int pf, int socktype, int proto, int level); 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) */ #endif /* !defined(__xio_listen_h_included) */

View file

@ -1847,7 +1847,7 @@ int xioparsenetwork(
char *addrname; char *addrname;
const char *maskname; const char *maskname;
if ((maskname = strchr(rangename, ':')) == NULL) { if ((maskname = strchr(rangename, ':')) == NULL) {
Error1("syntax error in range \"%s\": use <addr>:<mask>", rangename); Error1("syntax error in range \"%s\" of unspecified address family: use <addr>:<mask>", rangename);
return STAT_NORETRY; return STAT_NORETRY;
} }
++maskname; /* skip ':' */ ++maskname; /* skip ':' */

View file

@ -33,6 +33,10 @@ const struct addrname addressnames[] = {
{ "ABSTRACT-RECVFROM", &xioaddr_abstract_recvfrom }, { "ABSTRACT-RECVFROM", &xioaddr_abstract_recvfrom },
{ "ABSTRACT-SENDTO", &xioaddr_abstract_sendto }, { "ABSTRACT-SENDTO", &xioaddr_abstract_sendto },
#endif /* defined(WITH_UNIX) && defined(WITH_ABSTRACT_UNIXSOCKET) */ #endif /* defined(WITH_UNIX) && defined(WITH_ABSTRACT_UNIXSOCKET) */
#if WITH_LISTEN
{ "ACCEPT", &xioaddr_accept_fd },
{ "ACCEPT-FD", &xioaddr_accept_fd },
#endif
#if WITH_CREAT #if WITH_CREAT
{ "CREAT", &xioaddr_creat }, { "CREAT", &xioaddr_creat },
{ "CREATE", &xioaddr_creat }, { "CREATE", &xioaddr_creat },