New address SOCKETPAIR for echoing datagrams
diff --git a/CHANGES b/CHANGES
index 10e5c00..3996481 100644
--- a/CHANGES
+++ b/CHANGES
@@ -13,6 +13,12 @@
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
diff --git a/Makefile.in b/Makefile.in
index a54a954..95fcca9 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -45,7 +45,7 @@
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 @@
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 \
diff --git a/config.h.in b/config.h.in
index a87bf21..67e566c 100644
--- a/config.h.in
+++ b/config.h.in
@@ -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
diff --git a/configure.ac b/configure.ac
index bf04260..7dced77 100644
--- a/configure.ac
+++ b/configure.ac
@@ -219,6 +219,14 @@
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
diff --git a/doc/socat.yo b/doc/socat.yo
index b28e55f..114d82f 100644
--- a/doc/socat.yo
+++ b/doc/socat.yo
@@ -682,13 +682,22 @@
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
diff --git a/socat.c b/socat.c
index 6f25707..e2b1bd8 100644
--- a/socat.c
+++ b/socat.c
@@ -540,6 +540,11 @@
#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
diff --git a/test.sh b/test.sh
index 0fea81b..a52b78d 100755
--- a/test.sh
+++ b/test.sh
@@ -16546,6 +16546,108 @@
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
##################################################################################
diff --git a/xio-pipe.c b/xio-pipe.c
index c4c8ceb..5b7d077 100644
--- a/xio-pipe.c
+++ b/xio-pipe.c
@@ -9,6 +9,8 @@
#include "xio-named.h"
+#include "xio-pipe.h"
+
#if WITH_PIPE
@@ -40,6 +42,7 @@
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);
diff --git a/xio-pipe.h b/xio-pipe.h
index 34d95fa..1ea8ba9 100644
--- a/xio-pipe.h
+++ b/xio-pipe.h
@@ -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) */
diff --git a/xio-socket.c b/xio-socket.c
index 81b827a..9af7104 100644
--- a/xio-socket.c
+++ b/xio-socket.c
@@ -202,7 +202,6 @@
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 };
diff --git a/xio-socketpair.c b/xio-socketpair.c
new file mode 100644
index 0000000..49396af
--- /dev/null
+++ b/xio-socketpair.c
@@ -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 */
diff --git a/xio-socketpair.h b/xio-socketpair.h
new file mode 100644
index 0000000..2f92f70
--- /dev/null
+++ b/xio-socketpair.h
@@ -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) */
diff --git a/xio.h b/xio.h
index 1d72fa3..6c3d057 100644
--- a/xio.h
+++ b/xio.h
@@ -61,6 +61,7 @@
#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 @@
union {
struct {
int fdout; /* use fd for output */
+ int socktype;
} bipipe;
#if _WITH_SOCKET
struct {
@@ -242,8 +244,8 @@
} 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
diff --git a/xiomodes.h b/xiomodes.h
index 7a20547..8ab1b9a 100644
--- a/xiomodes.h
+++ b/xiomodes.h
@@ -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"
diff --git a/xioopen.c b/xioopen.c
index 1a87ac8..3aeed05 100644
--- a/xioopen.c
+++ b/xioopen.c
@@ -193,6 +193,9 @@
{ "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 },
diff --git a/xioopts.h b/xioopts.h
index c3f1359..509809e 100644
--- a/xioopts.h
+++ b/xioopts.h
@@ -152,7 +152,6 @@
#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 */
diff --git a/xioread.c b/xioread.c
index 36bbe0b..1399012 100644
--- a/xioread.c
+++ b/xioread.c
@@ -134,7 +134,27 @@
#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 @@
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};
diff --git a/xiowrite.c b/xiowrite.c
index 143e1ac..769a3fd 100644
--- a/xiowrite.c
+++ b/xiowrite.c
@@ -114,7 +114,11 @@
#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",