New address SOCKETPAIR for echoing datagrams

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

View file

@ -13,6 +13,12 @@ Features:
The number of warnings has been reduced, e.g.removing a non existing
file does in most cases no longer log a warning.
Added address type internal SOCKETPAIR. This is similar to the unnamed
PIPE address (only for internal echoing) but it provides datagram mode
(the default) and thus keeps packet boundaries.
Tests: SOCKETPAIR_STREAM SOCKETPAIR_DATAGRAM SOCKETPAIR_SEQPACKET
SOCKETPAIR_BOUNDARIES
New option -S <mask> controls catching and logging of signals that are
not internally used by Socat.
Tests: SIGTERM_NOLOG SIG31_LOG

View file

@ -45,7 +45,7 @@ XIOSRCS = xioinitialize.c xiohelp.c xioparam.c xiodiag.c xioopen.c xioopts.c \
xiosignal.c xiosigchld.c xioread.c xiowrite.c \
xiolayer.c xioshutdown.c xioclose.c xioexit.c \
xio-process.c xio-fd.c xio-fdnum.c xio-stdio.c xio-pipe.c \
xio-gopen.c xio-creat.c xio-file.c xio-named.c \
xio-socketpair.c xio-gopen.c xio-creat.c xio-file.c xio-named.c \
xio-socket.c xio-interface.c xio-listen.c xio-unix.c xio-vsock.c \
xio-ip.c xio-ip4.c xio-ip6.c xio-ipapp.c xio-tcp.c \
xio-sctp.c xio-rawip.c \
@ -64,7 +64,7 @@ HFILES = sycls.h sslcls.h error.h dalan.h procan.h filan.h hostan.h sysincludes.
xioconfig.h mytypes.h xioopts.h xiodiag.h xiohelp.h xiosysincludes.h \
xiomodes.h xiolayer.h xio-process.h xio-fd.h xio-fdnum.h xio-stdio.h \
xio-named.h xio-file.h xio-creat.h xio-gopen.h xio-pipe.h \
xio-socket.h xio-interface.h xio-listen.h xio-unix.h xio-vsock.h \
xio-socketpair.h xio-socket.h xio-interface.h xio-listen.h xio-unix.h xio-vsock.h \
xio-ip.h xio-ip4.h xio-ip6.h xio-rawip.h \
xio-ipapp.h xio-tcp.h xio-udp.h xio-sctp.h \
xio-socks.h xio-proxy.h xio-progcall.h xio-exec.h \

View file

@ -692,6 +692,7 @@
#undef WITH_GOPEN
#undef WITH_TERMIOS
#undef WITH_PIPE
#undef WITH_SOCKETPAIR
#undef WITH_UNIX
#undef WITH_ABSTRACT_UNIXSOCKET
#undef WITH_IP4

View file

@ -219,6 +219,14 @@ AC_ARG_ENABLE(pipe, [ --disable-pipe disable pipe support],
esac],
[AC_DEFINE(WITH_PIPE) AC_MSG_RESULT(yes)])
AC_MSG_CHECKING(whether to include explicit socketpair support)
AC_ARG_ENABLE(socketpair, [ --disable-socketpair disable socketpair support],
[case "$enableval" in
no) AC_MSG_RESULT(no);;
*) AC_DEFINE(WITH_SOCKETPAIR) AC_MSG_RESULT(yes);;
esac],
[AC_DEFINE(WITH_SOCKETPAIR) AC_MSG_RESULT(yes)])
AC_MSG_CHECKING(whether to include explicit termios support)
AC_ARG_ENABLE(termios, [ --disable-termios disable termios support],
[case "$enableval" in

View file

@ -682,13 +682,22 @@ label(ADDRESS_NAMED_PIPE)dit(bf(tt(PIPE:<filename>)))
See also: link(unnamed pipe)(ADDRESS_UNNAMED_PIPE)
label(ADDRESS_UNNAMED_PIPE)dit(bf(tt(PIPE)))
Creates an unnamed pipe and uses it for reading and writing. It works as an
echo, because everything written
to it appeares immediately as read data.nl()
echo, because everything written to it appeares immediately as read
data.nl()
Note: When socat tries to write more bytes than the pipe can queue (Linux
2.4: 2048 bytes), socat might block. Consider, e.g., using
option code(-b 2048) nl()
Option groups: link(FD)(GROUP_FD) nl()
See also: link(named pipe)(ADDRESS_NAMED_PIPE)
See also: link(named pipe)(ADDRESS_NAMED_PIPE), link(SOCKETPAIR)(ADDRESS_SOCKETPAIR)
label(ADDRESS_SOCKETPAIR)dit(bf(tt(SOCKETPAIR)))
Creates a socketpair and uses it for reading and writing. It works as an
echo, because everything written to it appeares immediately as read
data. The default socket type is datagram, so it keeps packet boundaries.
nl()
Option groups: link(FD)(GROUP_FD) nl()
Useful options:
link(socktype)(OPTION_SO_TYPE)nl()
See also: link(unnamed pipe)(ADDRESS_UNNAMED_PIPE)
label(ADDRESS_PROXY_CONNECT)dit(bf(tt(PROXY:<proxy>:<hostname>:<port>)))
Connects to an HTTP proxy server on port 8080 using TCP/IP version 4 or 6
depending on address specification, name resolution, or option

View file

@ -540,6 +540,11 @@ void socat_version(FILE *fd) {
#else
fputs(" #undef WITH_PIPE\n", fd);
#endif
#ifdef WITH_SOCKETPAIR
fprintf(fd, " #define WITH_SOCKETPAIR %d\n", WITH_SOCKETPAIR);
#else
fputs(" #undef WITH_SOCKETPAIR\n", fd);
#endif
#ifdef WITH_UNIX
fprintf(fd, " #define WITH_UNIX %d\n", WITH_UNIX);
#else

102
test.sh
View file

@ -16546,6 +16546,108 @@ esac
N=$((N+1))
NAME=SOCKETPAIR_STREAM
case "$TESTS" in
*%$N%*|*%functions%*|*%stdio%*|*%socketpair%*|*%$NAME%*)
TEST="$NAME: stdio and internal socketpair with stream"
testecho "$N" "$TEST" "STDIO" "SOCKETPAIR" "$opts"
esac
N=$((N+1))
NAME=SOCKETPAIR_DATAGRAM
case "$TESTS" in
*%$N%*|*%functions%*|*%stdio%*|*%socketpair%*|*%$NAME%*)
TEST="$NAME: stdio and internal socketpair with datagram"
testecho "$N" "$TEST" "STDIO" "SOCKETPAIR,socktype=2" "$opts"
esac
N=$((N+1))
NAME=SOCKETPAIR_SEQPACKET
case "$TESTS" in
*%$N%*|*%functions%*|*%stdio%*|*%socketpair%*|*%$NAME%*)
TEST="$NAME: stdio and internal socketpair with seqpacket"
testecho "$N" "$TEST" "STDIO" "SOCKETPAIR,socktype=$SOCK_SEQPACKET" "$opts"
esac
N=$((N+1))
# Test if SOCKETPAIR address with SOCK_DGRAM keeps packet boundaries
NAME=SOCKETPAIR_BOUNDARIES
case "$TESTS" in
*%$N%*|*%functions%*|*%socketpair%*|*%udp%*|*%udp4%*|*%ip4%*|*%dgram%*|*%$NAME%*)
TEST="$NAME: Internal socketpair keeps packet boundaries"
# Start a UDP4-DATAGRAM process that echoes data with datagram SOCKETPAIR;
# a client sends two packets with 24 and ~18 bytes using a UDP4-DATAGRAM. The
# client truncates packets to size 24, so when a large merged packet comes from
# server some data will be lost. If the original data is received, the test
# succeeded.
if ! eval $NUMCOND; then :;
elif ! F=$(testfeats STDIO IP4 UDP SOCKETPAIR); then
$PRINTF "test $F_n $TEST... ${YELLOW}Feature $F not available in $SOCAT${NORMAL}\n" $N
numCANT=$((numCANT+1))
listCANT="$listCANT $N"
elif ! A=$(testaddrs - STDIO UDP4-DATAGRAM SOCKETPAIR); then
$PRINTF "test $F_n $TEST... ${YELLOW}Address $A not available in $SOCAT${NORMAL}\n" $N
numCANT=$((numCANT+1))
listCANT="$listCANT $N"
elif ! o=$(testoptions bind socktype ) >/dev/null; then
$PRINTF "test $F_n $TEST... ${YELLOW}Option $o not available in $SOCAT${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"
ts1p=$PORT; PORT=$((PORT+1))
ts2p=$PORT; PORT=$((PORT+1))
da="test$N $(date) $RANDOM"
CMD1="$TRACE $SOCAT $opts -T 0.2 UDP4-DATAGRAM:$LOCALHOST:$ts2p,bind=$LOCALHOST:$ts1p SOCKETPAIR,socktype=$SOCK_DGRAM"
CMD2="$TRACE $SOCAT $opts -b 24 -t 0.2 -T 0.3 - UDP4-DATAGRAM:$LOCALHOST:$ts1p,bind=$LOCALHOST:$ts2p"
printf "test $F_n $TEST... " $N
export SOCAT_TRANSFER_WAIT=0.2
$CMD1 2>"${te}1" &
pid1="$!"
unset SOCAT_TRANSFER_WAIT
waitudp4port $ts1p 1
{ echo -n "${da:0:20}"; usleep 100000; echo "${da:20}"; } |$CMD2 >>"$tf" 2>>"${te}2"
rc2="$?"
kill "$pid1" 2>/dev/null; wait;
if [ "$rc2" -ne 0 ]; then
$PRINTF "$FAILED (rc2=$rc2): $TRACE $SOCAT:\n"
echo "$CMD1 &"
cat "${te}1" >&2
echo "$CMD2"
cat "${te}2" >&2
numFAIL=$((numFAIL+1))
listFAIL="$listFAIL $N"
elif ! echo "$da" |diff - "$tf" >"$tdiff"; then
$PRINTF "$FAILED\n"
echo "$CMD1 &"
cat "${te}1" >&2
echo "$CMD2"
cat "${te}2" >&2
echo diff:
cat "$tdiff"
numFAIL=$((numFAIL+1))
listFAIL="$listFAIL $N"
else
$PRINTF "$OK\n"
if [ "$VERBOSE" ]; then echo "$CMD1"; fi
if [ "$DEBUG" ]; then cat "${te}1" >&2; fi
if [ "$VERBOSE" ]; then echo "$CMD2 &"; fi
if [ "$DEBUG" ]; then cat "${te}2" >&2; fi
numOK=$((numOK+1))
fi
fi # NUMCOND
;;
esac
PORT=$((PORT+1))
N=$((N+1))
# end of common tests
##################################################################################

View file

@ -9,6 +9,8 @@
#include "xio-named.h"
#include "xio-pipe.h"
#if WITH_PIPE
@ -40,6 +42,7 @@ static int xioopen_fifo_unnamed(xiofile_t *sock, struct opt *opts) {
sock->stream.dtype = XIODATA_PIPE;
sock->stream.fd = filedes[0];
sock->stream.para.bipipe.fdout = filedes[1];
sock->stream.para.bipipe.socktype = SOCK_STREAM; /* due to socketpair reuse */
applyopts_cloexec(sock->stream.fd, opts);
applyopts_cloexec(sock->stream.para.bipipe.fdout, opts);

View file

@ -7,6 +7,4 @@
extern const struct addrdesc xioaddr_pipe;
extern int xioopen_fifo_unnamed(char *arg, xiofile_t *sock);
#endif /* !defined(__xio_pipe_h_included) */

View file

@ -202,7 +202,6 @@ const struct optdesc opt_siocspgrp = { "siocspgrp", NULL, OPT_SIOCSPGRP, GRO
const struct optdesc opt_bind = { "bind", NULL, OPT_BIND, GROUP_SOCKET, PH_BIND, TYPE_STRING,OFUNC_SPEC };
const struct optdesc opt_connect_timeout = { "connect-timeout", NULL, OPT_CONNECT_TIMEOUT, GROUP_SOCKET, PH_PASTSOCKET, TYPE_TIMEVAL, OFUNC_OFFSET, XIO_OFFSETOF(para.socket.connect_timeout) };
const struct optdesc opt_protocol_family = { "protocol-family", "pf", OPT_PROTOCOL_FAMILY, GROUP_SOCKET, PH_PRESOCKET, TYPE_STRING, OFUNC_SPEC };
const struct optdesc opt_protocol = { "protocol", NULL, OPT_PROTOCOL, GROUP_SOCKET, PH_PRESOCKET, TYPE_STRING, OFUNC_SPEC };
/* generic setsockopt() options */
const struct optdesc opt_setsockopt = { "setsockopt", "sockopt", OPT_SETSOCKOPT_BIN, GROUP_SOCKET,PH_CONNECTED, TYPE_INT_INT_BIN, OFUNC_SOCKOPT_GENERIC, 0, 0 };

102
xio-socketpair.c Normal file
View file

@ -0,0 +1,102 @@
/* source: xio-socketpair.c */
/* Copyright Gerhard Rieger and contributors (see file CHANGES) */
/* Published under the GNU General Public License V.2, see file COPYING */
/* this file contains the source for opening addresses of socketpair type */
#include "xiosysincludes.h"
#include "xioopen.h"
#include "xio-named.h"
#include "xio-socketpair.h"
#if WITH_SOCKETPAIR
static int xioopen_socketpair(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_socketpair = { "SOCKETPAIR", 3, xioopen_socketpair, GROUP_FD|GROUP_SOCKET, 0, 0, 0 HELP(":<filename>") };
/* open a socketpair */
static int xioopen_socketpair(
int argc,
const char *argv[],
struct opt *opts,
int xioflags,
xiofile_t *xfd,
groups_t groups ,
int dummy1,
int dummy2,
int dummy3)
{
struct single *sfd;
struct opt *opts2;
int pf = PF_UNIX;
int protocol = 0;
int filedes[2];
int numleft;
int result;
if (argc != 1) {
Error2("%s: wrong number of parameters (%d instead of 1)", argv[0], argc-1);
}
sfd = &xfd->stream;
sfd->para.bipipe.socktype = SOCK_DGRAM;
if (applyopts_single(sfd, opts, PH_INIT) < 0) return -1;
applyopts(-1, opts, PH_INIT);
retropt_int(opts, OPT_PROTOCOL_FAMILY, &pf);
retropt_int(opts, OPT_SO_TYPE, &sfd->para.bipipe.socktype);
retropt_int(opts, OPT_SO_PROTOTYPE, &protocol);
if (Socketpair(pf, sfd->para.bipipe.socktype, protocol, filedes) != 0) {
Error5("socketpair(%d, %d, %d, %p): %s", pf, sfd->para.bipipe.socktype, protocol, filedes, strerror(errno));
return -1;
}
Info2("socketpair({%d,%d})", filedes[0], filedes[1]);
sfd->tag = XIO_TAG_RDWR;
if (sfd->para.bipipe.socktype == SOCK_STREAM) {
sfd->dtype = XIOREAD_STREAM|XIOWRITE_PIPE;
} else {
sfd->dtype = XIOREAD_RECV|XIOREAD_RECV_NOCHECK|XIOWRITE_PIPE;
}
sfd->fd = filedes[0];
sfd->para.bipipe.fdout = filedes[1];
applyopts_cloexec(sfd->fd, opts);
applyopts_cloexec(sfd->para.bipipe.fdout, opts);
/* one-time and input-direction options, no second application */
retropt_bool(opts, OPT_IGNOREEOF, &sfd->ignoreeof);
/* here we copy opts! */
if ((opts2 = copyopts(opts, GROUP_SOCKET)) == NULL) {
return STAT_NORETRY;
}
/* apply options to first FD */
if ((result = applyopts(sfd->fd, opts, PH_ALL)) < 0) {
return result;
}
if ((result = applyopts_single(sfd, opts, PH_ALL)) < 0) {
return result;
}
/* apply options to second FD */
if ((result = applyopts(sfd->para.bipipe.fdout, opts2, PH_ALL)) < 0)
{
return result;
}
if ((numleft = leftopts(opts)) > 0) {
Error1("%d option(s) could not be used", numleft);
showleft(opts);
}
Notice("writing to and reading from unnamed socketpair");
return 0;
}
#endif /* WITH_SOCKETPAIR */

10
xio-socketpair.h Normal file
View file

@ -0,0 +1,10 @@
/* source: xio-socketpair.h */
/* Copyright Gerhard Rieger and contributors (see file CHANGES) */
/* Published under the GNU General Public License V.2, see file COPYING */
#ifndef __xio_socketpair_h_included
#define __xio_socketpair_h_included 1
const extern struct addrdesc xioaddr_socketpair;
#endif /* !defined(__xio_socketpair_h_included) */

4
xio.h
View file

@ -61,6 +61,7 @@ struct opt;
#define XIOREAD_RECV_ONESHOT 0x0008 /* give EOF after first packet */
#define XIOREAD_RECV_SKIPIP 0x0010 /* recv, skip IPv4 header */
#define XIOREAD_RECV_FROM 0x0020 /* remember peer for replying */
#define XIOREAD_RECV_NOCHECK 0x0040 /* do not check peer */
/* combinations */
#define XIODATA_MASK (XIODATA_READMASK|XIODATA_WRITEMASK)
@ -211,6 +212,7 @@ typedef struct single {
union {
struct {
int fdout; /* use fd for output */
int socktype;
} bipipe;
#if _WITH_SOCKET
struct {
@ -242,8 +244,8 @@ typedef struct single {
} socket;
#endif /* _WITH_SOCKET */
struct {
pid_t pid; /* child PID, with EXEC: */
int fdout; /* use fd for output if two pipes */
pid_t pid; /* child PID, with EXEC: */
struct timeval sitout_eio;
} exec;
#if WITH_READLINE

View file

@ -15,6 +15,7 @@
#include "xio-creat.h"
#include "xio-gopen.h"
#include "xio-pipe.h"
#include "xio-socketpair.h"
#if _WITH_SOCKET
#include "xio-socket.h"
#include "xio-listen.h"

View file

@ -193,6 +193,9 @@ const struct addrname addressnames[] = {
{ "SOCKET-RECVFROM", &xioaddr_socket_recvfrom },
{ "SOCKET-SENDTO", &xioaddr_socket_sendto },
#endif
#if WITH_SOCKETPAIR
{ "SOCKETPAIR", &xioaddr_socketpair },
#endif
#if WITH_SOCKS4
{ "SOCKS", &xioaddr_socks4_connect },
{ "SOCKS4", &xioaddr_socks4_connect },

View file

@ -152,7 +152,6 @@ enum e_func {
#define GROUP_FILE GROUP_REG
#define GROUP_SOCKET 0x00000020
#define GROUP_READLINE 0x00000040
#define GROUP_NAMED 0x00000100 /* file system entry */
#define GROUP_OPEN 0x00000200 /* flags for open() */
#define GROUP_EXEC 0x00000400 /* program or script execution */

View file

@ -134,7 +134,27 @@ ssize_t xioread(xiofile_t *file, void *buff, size_t bufsiz) {
#if _WITH_SOCKET
case XIOREAD_RECV:
if (pipe->dtype & XIOREAD_RECV_FROM) {
if (pipe->dtype & XIOREAD_RECV_NOCHECK) {
/* No need to check peer address */
do {
bytes =
Recv(pipe->fd, buff, bufsiz, 0);
} while (bytes < 0 && errno == EINTR);
if (bytes < 0) {
_errno = errno;
Error3("recvfrom(%d, %p, "F_Zu", 0", pipe->fd, buff, bufsiz);
errno = _errno;
return -1;
}
Notice1("received packet with "F_Zu" bytes", bytes);
if (bytes == 0) {
if (!pipe->para.socket.null_eof) {
errno = EAGAIN; return -1;
}
return bytes;
}
} else if (pipe->dtype & XIOREAD_RECV_FROM) {
/* Receiving packets in addresses of RECVFROM types, the sender address
has already been determined in OPEN phase. */
Debug1("%s(): XIOREAD_RECV and XIOREAD_RECV_FROM (peer checks already done)",
@ -375,7 +395,7 @@ ssize_t xioread(xiofile_t *file, void *buff, size_t bufsiz) {
return -1;
#endif /* !(WITH_RAWIP || WITH_UDP || WITH_UNIX) */
} else /* ~XIOREAD_RECV_FROM */ {
} else /* ~(XIOREAD_RECV_FROM|XIOREAD_RECV_FROM) */ {
/* Receiving packets without planning to answer to the sender, but we
might need sender info for some checks, thus we use recvfrom() */
struct msghdr msgh = {0};

View file

@ -114,7 +114,11 @@ ssize_t xiowrite(xiofile_t *file, const void *buff, size_t bytes) {
#endif /* _WITH_SOCKET */
case XIOWRITE_PIPE:
writt = Write(pipe->para.bipipe.fdout, buff, bytes);
if (pipe->para.bipipe.socktype == SOCK_STREAM) {
writt = Write(pipe->para.bipipe.fdout, buff, bytes);
} else {
writt = Send(pipe->para.bipipe.fdout, buff, bytes, 0);
}
_errno = errno;
if (writt < 0) {
Error4("write(%d, %p, "F_Zu"): %s",