From 28f1a563050e79d034dae7836570966a35a34e0d Mon Sep 17 00:00:00 2001 From: Gerhard Rieger Date: Sat, 11 Nov 2023 18:17:25 +0100 Subject: [PATCH] Added socat-mux.sh for n-to-1 / 1-to-n communications --- CHANGES | 4 ++ Makefile.in | 4 +- socat-mux.sh | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++ test.sh | 112 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 234 insertions(+), 1 deletion(-) create mode 100755 socat-mux.sh diff --git a/CHANGES b/CHANGES index b1e2861..352eabc 100644 --- a/CHANGES +++ b/CHANGES @@ -201,6 +201,10 @@ Features: drive socks through TLS, or to use TLS over a serial line. Tests: SOCAT_CHAIN_SOCKS4 SOCAT_CHAIN_SSL_PTY + Added script socat-mux.sh that performs n-to-1 / 1-to-n communications + using two Socat instances with multicasting. + Tests: SOCAT_MUX + 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/Makefile.in b/Makefile.in index cf9f263..a01fb0a 100644 --- a/Makefile.in +++ b/Makefile.in @@ -76,7 +76,7 @@ HFILES = sycls.h sslcls.h error.h dalan.h procan.h filan.h hostan.h sysincludes. DOCFILES = README README.FIPS CHANGES FILES EXAMPLES PORTING SECURITY DEVELOPMENT doc/socat.yo doc/socat.1 doc/socat.html FAQ BUGREPORTS COPYING COPYING.OpenSSL doc/dest-unreach.css doc/socat-openssltunnel.html doc/socat-multicast.html doc/socat-tun.html doc/socat-genericsocket.html -SHFILES = socat-chain.sh \ +SHFILES = socat-chain.sh socat-mux.sh \ daemon.sh mail.sh ftp.sh readline.sh \ socat_buildscript_for_android.sh TESTFILES = test.sh socks4echo.sh proxyecho.sh readline-test.sh \ @@ -133,6 +133,7 @@ install: progs $(srcdir)/doc/socat.1 $(INSTALL) -m 755 socat $(DESTDIR)$(BINDEST)/socat1 ln -s socat1 $(DESTDIR)$(BINDEST)/socat $(INSTALL) -m 755 socat-chain.sh $(DESTDIR)$(BINDEST) + $(INSTALL) -m 755 socat-mux.sh $(DESTDIR)$(BINDEST) $(INSTALL) -m 755 procan $(DESTDIR)$(BINDEST) $(INSTALL) -m 755 filan $(DESTDIR)$(BINDEST) mkdir -p $(DESTDIR)$(MANDEST)/man1 @@ -143,6 +144,7 @@ uninstall: rm -f $(DESTDIR)$(BINDEST)/socat rm -f $(DESTDIR)$(BINDEST)/socat1 rm -f $(DESTDIR)$(BINDEST)/socat-chain.sh + rm -f $(DESTDIR)$(BINDEST)/socat-mux.sh rm -f $(DESTDIR)$(BINDEST)/procan rm -f $(DESTDIR)$(BINDEST)/filan rm -f $(DESTDIR)$(MANDEST)/man1/socat.1 diff --git a/socat-mux.sh b/socat-mux.sh new file mode 100755 index 0000000..7a13f27 --- /dev/null +++ b/socat-mux.sh @@ -0,0 +1,115 @@ +#! /usr/bin/env bash +# Copyright Gerhard Rieger and contributors (see file CHANGES) +# Published under the GNU General Public License V.2, see file COPYING + +# Shell script to build a many-to-one, one-to-all communication +# It starts two Socat instances that communicate via IPv4 broadcast, +# the first of which forks a child process for each connected client. + +# Example: + +# Consider a serial device connected to the Internet on TCP port 1234, it +# accepts only one connection at a time. +# On a proxy/relay server run this script: +# socat-mux.sh \ +# TCP-L:1234,reuseaddr,fork \ +# TCP::1234 +# Now connect with an arbitrary number of clients to TCP::1234; +# data sent by the device goes to all clients, data from any client is sent to +# the device. + +ECHO="echo -e" + +usage () { + $ECHO "Usage: $0 " + $ECHO "Example:" + $ECHO " $0 TCP4-L:1234,reuseaddr,fork TCP:10.2.3.4:12345" + $ECHO "Clients may connect to port 1234; data sent by any client is forwarded to 10.2.3.4," + $ECHO "data provided by 10.2.3.4 is sent to ALL clients" + $ECHO " :" + $ECHO "\t-h\tShow this help text and exit" + $ECHO "\t-V\tShow Socat commands" + $ECHO "\t-q\tSuppress most messages" + $ECHO "\t-d*\tOptions beginning with -d are passed to Socat processes" + $ECHO "\t-l*\tOptions beginning with -l are passed to Socat processes" + $ECHO "\t-b|-S|-t|-T|-l \tThese options are passed to Socat processes" +} + +VERBOSE= QUIET= OPTS= +while [ "$1" ]; do + case "X$1" in + X-h) usage; exit ;; + X-V) VERBOSE=1 ;; + X-q) QUIET=1; OPTS="-d0" ;; + X-d*|X-l?*) OPTS="$OPTS $1" ;; + X-b|X-S|X-t|X-T|X-l) OPT=$1; shift; OPTS="$OPTS $OPT $1" ;; + X-) break ;; + X-*) echo "$0: Unknown option \"$1\"" >&2 + usage >&2 + exit 1 ;; + *) break ;; + esac + shift +done + +LISTENER="$1" +TARGET="$2" + +if [ -z "$LISTENER" -o -z "$TARGET" ]; then + echo "$0: Missing parameter(s)" >&2 + usage >&2 + exit 1 +fi + +shopt -s nocasematch +if ! [[ "$LISTENER" =~ .*,fork ]] || [[ "$LISTENER" =~ .*,fork, ]]; then + LISTENER="$LISTENER,fork" +fi + +case "$0" in + */*) SOCAT=${0%/*}/socat ;; + *) SOCAT=socat ;; +esac + +PORT1=$($SOCAT -d -d -T 0.000001 UDP4-RECV:0 /dev/null 2>&1 |grep bound |sed 's/.*:\([1-9][0-9]*\)$/\1/') +PORT2=$($SOCAT -d -d -T 0.000001 UDP4-RECV:0 /dev/null 2>&1 |grep bound |sed 's/.*:\([1-9][0-9]*\)$/\1/') +if [ -z "$PORT1" -o -z "$PORT2" ]; then + echo "$0: Failed to determine free UDP ports" >&2 + exit 1 +fi +if [ "$PORT1" = "$PORT2" ]; then # seen on etch + PORT2=$((PORT1+1)) +fi + +IFADDR=127.0.0.1 +BCADDR=127.255.255.255 + + +pid1= pid2= +trap '[ "$pid1" ] && kill $pid1 2>/dev/null; [ "$pid2" ] && kill $pid2 2>/dev/null' EXIT + +set -bm +trap 'if kill -n 0 $pid1 2>/dev/null; then [ -z "$QUIET" ] && echo "$0: socat-listener exited with rc=$?" >&2; kill $pid1; else [ -z "$QUIET" ] && echo "$0: socat-multiplexer exited with rc=$?" >&2; kill $pid2 2>/dev/null; fi; exit 1' SIGCHLD + +if [ "$VERBOSE" ]; then + $ECHO "$SOCAT -lp muxfwd $OPTS \\ + \"$TARGET\" \\ + \"UDP4-DATAGRAM:$BCADDR:$PORT2,bind=$IFADDR:$PORT1,so-broadcast\" &" +fi +$SOCAT -lp muxfwd $OPTS \ + "$TARGET" \ + "UDP4-DATAGRAM:$BCADDR:$PORT2,bind=$IFADDR:$PORT1,so-broadcast" & +pid1=$! + +if [ "$VERBOSE" ]; then + $ECHO "$SOCAT -lp muxlst $OPTS \\ + \"$LISTENER\" \\ + \"UDP4-DATAGRAM:$IFADDR:$PORT1,bind=:$PORT2,so-broadcast,so-reuseaddr\" &" +fi +$SOCAT -lp muxlst $OPTS \ + "$LISTENER" \ + "UDP4-DATAGRAM:$IFADDR:$PORT1,bind=:$PORT2,so-broadcast,so-reuseaddr" & +pid2=$! + +wait +#wait -f diff --git a/test.sh b/test.sh index f91973e..e10f6bf 100755 --- a/test.sh +++ b/test.sh @@ -18986,6 +18986,118 @@ esac N=$((N+1)) +# Test the socat-mux.sh script +# Requires lo/lo0 to have broadcast address 127.255.255.255 +NAME=SOCAT_MUX +case "$TESTS" in +*%$N%*|*%functions%*|*%script%*|*%socat-mux%*|*%socket%*|*%udp%*|*%broadcast%*|*%$NAME%*) +TEST="$NAME: test the socat-mux.sh script" +# Start a simple TCP server +# Start socat-mux.sh to connect to this server +# Connect with two clients to mux, send different data records from both. +# Check if both client received both records in order. +if ! eval $NUMCOND; then : +# Remove unneeded checks, adapt lists of the remaining ones +elif ! cond=$(checkconds \ + "" \ + "" \ + "" \ + "IP4 UDP TCP LISTEN STDIO PIPE" \ + "TCP4-LISTEN TCP4-CONNECT PIPE STDIO UDP-DATAGRAM" \ + "so-reuseaddr" \ + "udp4 tcp4" ); then + $PRINTF "test $F_n $TEST... ${YELLOW}$cond${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" + namesCANT="$namesCANT $NAME" +else + tf="$td/test$N.stdout" + te="$td/test$N.stderr" + tdiff="$td/test$N.diff" + newport tcp4 + PORT0=$PORT + newport tcp4 + PORT1=$PORT + CMD0="$TRACE $SOCAT $opts -lp server TCP-LISTEN:$PORT0 PIPE" + CMD1="./socat-mux.sh TCP-LISTEN:$PORT1 TCP-CONNECT:$LOCALHOST:$PORT0" + CMD2="$TRACE $SOCAT $opts -lp client STDIO TCP:$LOCALHOST:$PORT1" + da_a="test$N $(date) $RANDOM" + da_b="test$N $(date) $RANDOM" + printf "test $F_n $TEST... " $N + $CMD0 >/dev/null 2>"${te}0" & + pid0=$! + waittcp4port $PORT0 1 + $CMD1 >/dev/null 2>"${te}1" & + pid1=$! + waittcp4port $PORT1 1 + { sleep 1; echo "$da_a"; sleep 2; } "${tf}2a" 2>"${te}2a" & + pid2a=$! + { sleep 2; echo "$da_b"; sleep 1; } |$CMD2 >"${tf}2b" 2>"${te}2b" + rc2b=$? + kill $pid0 $(childpids $pid1) $pid1 2>/dev/null + wait 2>/dev/null + kill $pid0 2>/dev/null; wait + if [ "$rc2b" -ne 0 ]; then + $PRINTF "$FAILED (rc2b=$rc2b)\n" + echo "$CMD0 &" + cat "${te}0" >&2 + echo "$CMD1 &" + cat "${te}1" >&2 + echo "{ sleep 1; echo \"\$da_a\"; sleep 2; } |$CMD2" + cat "${te}2a" >&2 + echo "{ sleep 2; echo \"\$da_b\"; sleep 1; } |$CMD2" + cat "${te}2b" >&2 + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" + namesFAIL="$namesFAIL $NAME" + elif ! $ECHO "$da_a\n$da_b" |diff - "${tf}2a" >${tdiff}_a; then + $PRINTF "$FAILED (diff a)\n" + echo "$CMD0 &" + cat "${te}0" >&2 + echo "$CMD1 &" + cat "${te}1" >&2 + echo "{ sleep 1; echo \"\$da_a\"; sleep 2; } |$CMD2" + cat "${te}2a" >&2 + echo "{ sleep 2; echo \"\$da_b\"; sleep 1; } |$CMD2" + cat "${te}2b" >&2 + echo "// diff a:" >&2 + cat "${tdiff}_a" >&2 + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" + namesFAIL="$namesFAIL $NAME" + elif ! $ECHO "$da_a\n$da_b" |diff - "${tf}2b" >${tdiff}_b; then + $PRINTF "$FAILED (diff b)\n" + echo "$CMD0 &" + cat "${te}0" >&2 + echo "$CMD1 &" + cat "${te}1" >&2 + echo "{ sleep 1; echo \"\$da_a\"; sleep 2; } |$CMD2" + cat "${te}2a" >&2 + echo "{ sleep 2; echo \"\$da_b\"; sleep 2; } |$CMD2" + cat "${te}2b" >&2 + echo "// diff b:" >&2 + cat "${tdiff}_b" >&2 + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" + namesFAIL="$namesFAIL $NAME" + else + $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 + if [ "$VERBOSE" ]; then echo "{ sleep 1; echo \"\$da_a\"; sleep 2; } |$CMD2"; fi + if [ "$DEBUG" ]; then cat "${te}2a" >&2; fi + if [ "$VERBOSE" ]; then echo "{ sleep 2; echo \"\$da_b\"; sleep 1; } |$CMD2"; fi + if [ "$DEBUG" ]; then cat "${te}2b" >&2; fi + numOK=$((numOK+1)) + fi +fi # NUMCOND + ;; +esac +N=$((N+1)) + + # end of common tests ##################################################################################