From 11d1e9e11fb9919ce18c65c20cb20df65ace99ea Mon Sep 17 00:00:00 2001 From: Gerhard Rieger Date: Thu, 26 Oct 2023 19:12:38 +0200 Subject: [PATCH] Use PACKET_IGNORE_OUTGOING when available; a few corrections and renamings for raw sockets and ancillary messages --- CHANGES | 6 +++ VERSION | 2 +- config.h.in | 4 +- configure.ac | 6 +-- doc/socat.yo | 2 +- sysincludes.h | 4 +- test.sh | 117 +++++++++++++++++++++++++++++++++++++++++++----- xio-interface.c | 5 ++- xio-socket.c | 12 +++-- xio-socket.h | 1 + xioclose.c | 5 ++- xioread.c | 63 +++++++++++++++++++------- 12 files changed, 185 insertions(+), 42 deletions(-) diff --git a/CHANGES b/CHANGES index 55dcaba..97a0cbf 100644 --- a/CHANGES +++ b/CHANGES @@ -108,6 +108,10 @@ Porting: Removed Config/ because its contents have not been maintained for many years. + Try to not receive outgoing packets on raw (PF_PACKET) sockets - use + PACKET_IGNORE_OUTGOING socket options when available. + Test: INTERFACE_IGNOREOUTGOING + Testing: Removed obselete parts from test.sh @@ -117,6 +121,8 @@ Documentation: Added doc for option ipv6-join-group (ipv6-add-membership) Thanks to Martin Buck for sending the patch. + Renamed xiogetpacketsrc() to xiogetancillary() + ####################### V 1.7.4.5 (not released): Corrections: diff --git a/VERSION b/VERSION index ceaf471..7fd3594 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -"1.7.4.5+" +"1.7.4.5+vlans" diff --git a/config.h.in b/config.h.in index 6ca2507..dfeaab8 100644 --- a/config.h.in +++ b/config.h.in @@ -285,8 +285,8 @@ /* Define if you have the header file. */ #undef HAVE_LINUX_VM_SOCKETS_H -/* Define if you have the header file. */ -#undef HAVE_NETPACKET_PACKET_H +/* Define if you have the header file. */ +#undef HAVE_LINUX_IF_PACKET_H /* Define if you have the header file. */ #undef HAVE_NETINET_IF_ETHER_H diff --git a/configure.ac b/configure.ac index 60e269e..42ac783 100644 --- a/configure.ac +++ b/configure.ac @@ -321,10 +321,10 @@ AC_ARG_ENABLE(interface, [ --disable-interface disable network interface suppo esac], [AC_MSG_RESULT(yes); WITH_INTERFACE=1 ]) if test "$WITH_INTERFACE"; then - AC_CHECK_HEADER(netpacket/packet.h, - AC_DEFINE(HAVE_NETPACKET_PACKET_H), + AC_CHECK_HEADER(linux/if_packet.h, + AC_DEFINE(HAVE_LINUX_IF_PACKET_H), [WITH_INTERFACE=; - AC_MSG_WARN([include file netpacket/packet.h not found, disabling interface])]) + AC_MSG_WARN([include file linux/if_packet.h not found, disabling interface])]) fi if test "$WITH_INTERFACE"; then AC_CHECK_HEADER(netinet/if_ether.h, diff --git a/doc/socat.yo b/doc/socat.yo index 93f201a..3e8ce58 100644 --- a/doc/socat.yo +++ b/doc/socat.yo @@ -389,7 +389,7 @@ label(ADDRESS_IP_SENDTO)dit(bf(tt(IP-SENDTO::))) label(ADDRESS_INTERFACE)dit(bf(tt(INTERFACE:))) Communicates with a network connected on an interface using raw packets including link level data. link()(TYPE_INTERFACE) is the name of - the network interface. Currently only available on Linux. + the network interface. Currently only available on Linux.nl() Option groups: link(FD)(GROUP_FD),link(SOCKET)(GROUP_SOCKET) nl() Useful options: link(pf)(OPTION_PROTOCOL_FAMILY), diff --git a/sysincludes.h b/sysincludes.h index 4d1078f..01acdb3 100644 --- a/sysincludes.h +++ b/sysincludes.h @@ -141,8 +141,8 @@ #if HAVE_LINUX_ERRQUEUE_H #include /* struct sock_extended_err */ #endif -#if HAVE_NETPACKET_PACKET_H -#include +#if HAVE_LINUX_IF_PACKET_H +#include #endif #if HAVE_NETINET_IF_ETHER_H #include diff --git a/test.sh b/test.sh index 9fe7522..c873df6 100755 --- a/test.sh +++ b/test.sh @@ -760,7 +760,8 @@ testaddrs () { local a A; for a in $@; do A=$(echo "$a" |tr 'a-z' 'A-Z') - if ! $SOCAT $A /dev/null 2>&1 &1 /dev/ listFAIL="$listFAIL $N" else $PRINTF "$OK\n" - if [ -n "$debug" ]; then - grep " $LEVELS " "${te}0"; echo; grep " $LEVELS " "${te}1"; - fi + if [ "$VERBOSE" ]; then echo "$CMD0 &"; fi + if [ "$DEBUG" ]; then grep " $LEVELS " "${te}0" >&2; fi + if [ "$VERBOSE" ]; then echo "echo XYZ |$CMD1"; fi + if [ "$DEBUG" ]; then grep " $LEVELS " "${te}1" >&2; fi numOK=$((numOK+1)) fi else # option is not supported @@ -9716,9 +9718,10 @@ elif ! expr "$(cat "$tf")" : "$SCM_VALUE\$" >/dev/null; then listFAIL="$listFAIL $N" else $PRINTF "$OK\n" - if [ -n "$debug" ]; then - cat "${te}0"; echo; cat "${te}1"; - fi + if [ "$VERBOSE" ]; then echo "$CMD0 &"; fi + if [ "$DEBUG" ]; then cat "${te}0" >&2; fi + if [ "$VERBOSE" ]; then echo "{ echo XYZ; sleep 0.1; } |$CMD1"; fi + if [ "$DEBUG" ]; then cat "${te}1" >&2; fi numOK=$((numOK+1)) fi else # option is not supported @@ -13148,7 +13151,6 @@ fi fi ;; # NUMCOND, feats esac N=$((N+1)) -set +vx # test the DTLS server feature NAME=OPENSSL_DTLS_SERVER @@ -15590,6 +15592,99 @@ esac N=$((N+1)) +# Socats INTERFACE address has to ignore outgoing packets if possible. +# On Linux is uses socket option PACKET_IGNORE_OUTGOING or it queries per +# packet the PACKET_OUTGOING flag of struct sockaddr_ll.sll_pkttype +NAME=INTERFACE_IGNOREOUTGOING +case "$TESTS" in +*%$N%*|*%functions%*|*%interface%*|*%tun%*|*%root%*|*%$NAME%*) +TEST="$NAME: INTERFACE ignores outgoing packets" +#idea: create a TUN interface and hook with INTERFACE. +# Send a packet out the interface, should not be seen by INTERFACE +if ! eval $NUMCOND; then :; +elif [ $(id -u) -ne 0 -a "$withroot" -eq 0 ]; then + $PRINTF "test $F_n $TEST... ${YELLOW}must be root${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! $(type ping >/dev/null 2>&1); then + $PRINTF "test $F_n $TEST... ${YELLOW}ping not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! feat=$(testfeats TUN STDIO INTERFACE); then + $PRINTF "test $F_n $TEST... ${YELLOW}$feat not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! A=$(testaddrs - TUN STDIO INTERFACE); 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 iff-up tun-type tun-name ) >/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" +tl="$td/test$N.lock" +da="$(date) $RANDOM" +TUNNET=10.255.255 +TUNNAME=tun9 +CMD0="$TRACE $SOCAT $opts -L $tl TUN:$TUNNET.1/24,iff-up=1,tun-type=tun,tun-name=$TUNNAME -" +CMD1="$TRACE $SOCAT $opts -u INTERFACE:$TUNNAME -" +CMD2="ping -c 1 -w 1 -b $TUNNET.255" +printf "test $F_n $TEST... " $N +sleep 1 |$CMD0 2>"${te}0" >/dev/null & +pid0="$!" +#waitinterface "$TUNNAME" +usleep $MICROS +$CMD1 >"${tf}1" 2>"${te}1" & +pid1="$!" +usleep $MICROS +$CMD2 2>"${te}2" 1>&2 +kill $pid1 2>/dev/null +usleep $MICROS +kill $pid0 2>/dev/null +wait +if [ $? -ne 0 ]; then + $PRINTF "$FAILED: $TRACE $SOCAT:\n" + echo "$CMD0 &" + cat "${te}0" >&2 + echo "$CMD1" + cat "${te}1" >&2 + echo "$CMD2" + cat "${te}2" >&2 + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif test -s "${tf}1"; then + $PRINTF "$FAILED\n" + echo "$CMD0 &" + cat "${te}0" >&2 + echo "$CMD1" + cat "${te}1" >&2 + echo "$CMD2" + cat "${te}2" >&2 + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" +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 "$CMD2"; fi + if [ "$DEBUG" ]; then cat "${te}2" >&2; fi + numOK=$((numOK+1)) +fi +fi ;; # NUMCOND, feats +esac +PORT=$((PORT+1)) +N=$((N+1)) + # end of common tests ################################################################################## @@ -15694,9 +15789,11 @@ if [ "$OPT_EXPECT_FAIL" ]; then grep "^< " "$td/failed.diff" |awk '{print($2);}' >"$td/ok.unexp" ln -s "$td/ok.unexp" . echo "OK unexpected: $(cat "$td/ok.unexp" |xargs echo)" +else + touch "$td/failed.diff" fi -listFAIL=$(cat "$td/failed.lst" |xargs echo) -numFAIL="$(wc -l "$td/failed.lst" |awk '{print($1);}')" +#listFAIL=$(cat "$td/failed.lst" |xargs echo) +#numFAIL="$(wc -l "$td/failed.lst" |awk '{print($1);}')" ! test -s "$td/failed.unexp" exit diff --git a/xio-interface.c b/xio-interface.c index cfd467b..d0d10c1 100644 --- a/xio-interface.c +++ b/xio-interface.c @@ -111,13 +111,14 @@ int _xioopen_interface(const char *ifname, _xiointerface_apply_iff(xfd->fd, ifname, xfd->para.interface.iff_opts); #ifdef PACKET_IGNORE_OUTGOING - /* Raw socket may even provide packets that are outbound - we are not + /* Raw socket might also provide packets that are outbound - we are not interested in these and disable this "feature" in kernel if possible */ if (Setsockopt(xfd->fd, SOL_PACKET, PACKET_IGNORE_OUTGOING, &one, sizeof(one)) < 0) { Warn2("setsockopt(%d, SOL_PACKET, PACKET_IGNORE_OUTGOING, {1}): %s", xfd->fd, strerror(errno)); } -#endif +#endif /*defined(PACKET_IGNORE_OUTGOING) */ + return 0; } diff --git a/xio-socket.c b/xio-socket.c index 4497c1e..543122e 100644 --- a/xio-socket.c +++ b/xio-socket.c @@ -25,6 +25,7 @@ #endif /* WITH_IP6 */ #include "xio-ip.h" #include "xio-listen.h" +#include "xio-interface.h" #include "xio-ipapp.h" /*! not clean */ #include "xio-tcpwrap.h" @@ -733,7 +734,7 @@ int xiogetpacketinfo(int fd) #if HAVE_STRUCT_MSGHDR_MSGCONTROLLEN msgh.msg_controllen = sizeof(ctrlbuff); #endif - if (xiogetpacketsrc(fd, + if (xiogetancillary(fd, &msgh, MSG_ERRQUEUE #ifdef MSG_TRUNC @@ -1078,7 +1079,7 @@ int _xioopen_dgram_sendto(/* them is already in xfd->peersa */ applyopts_named(us->un.sun_path, opts, PH_LATE); } #endif - applyopts(xfd->fd, opts, PH_LATE); + /*applyopts(xfd->fd, opts, PH_LATE);*/ /* xfd->dtype = DATA_RECVFROM; *//* no, the caller must set this (ev _SKIPIP) */ Notice1("successfully prepared local socket %s", @@ -1244,7 +1245,7 @@ int _xioopen_dgram_recvfrom(struct single *xfd, int xioflags, #if HAVE_STRUCT_MSGHDR_MSGCONTROLLEN msgh.msg_controllen = sizeof(ctrlbuff); #endif - while ((rc = xiogetpacketsrc(xfd->fd, + while ((rc = xiogetancillary(xfd->fd, &msgh, MSG_PEEK #ifdef MSG_TRUNC @@ -1431,8 +1432,9 @@ int retropt_socket_pf(struct opt *opts, int *pf) { In msgh the msg_name pointer must refer to an (empty) sockaddr storage. Returns STAT_OK on success, or STAT_RETRYLATER when an error occurred, including EINTR. + (recvmsg() retrieves just meta info, not the data) */ -int xiogetpacketsrc(int fd, struct msghdr *msgh, int flags) { +int xiogetancillary(int fd, struct msghdr *msgh, int flags) { char peekbuff[1]; #if HAVE_STRUCT_IOVEC struct iovec iovec; @@ -1473,6 +1475,8 @@ int xiodopacketinfo(struct msghdr *msgh, bool withlog, bool withenv) { char valbuff[256], *valp; char envbuff[256], *envp; + Info3("ancillary message in xiodopacketinfo(): len="F_Zu", level=%d, type=%d", + cmsg->cmsg_len, cmsg->cmsg_level, cmsg->cmsg_type); if (withlog) { xiodump(CMSG_DATA(cmsg), cmsg->cmsg_len-((char *)CMSG_DATA(cmsg)-(char *)cmsg), diff --git a/xio-socket.h b/xio-socket.h index 2d36132..31d51ed 100644 --- a/xio-socket.h +++ b/xio-socket.h @@ -84,6 +84,7 @@ extern char *xiogetifname(int ind, char *val, int ins); extern int retropt_socket_pf(struct opt *opts, int *pf); +extern int xiogetancillary(int fd, struct msghdr *msgh, int flags); extern int xioopen_connect(struct single *fd, union sockaddr_union *us, size_t uslen, diff --git a/xioclose.c b/xioclose.c index 3bbfca7..2a236d1 100644 --- a/xioclose.c +++ b/xioclose.c @@ -81,7 +81,10 @@ int xioclose1(struct single *pipe) { #if WITH_INTERFACE case END_INTERFACE: { - _xiointerface_set_iff(pipe->fd, pipe->para.interface.name, pipe->para.interface.save_iff); + if (pipe->para.interface.name[0] != '\0') { + _xiointerface_set_iff(pipe->fd, pipe->para.interface.name, + pipe->para.interface.save_iff); + } if (Close(pipe->fd) < 0) { Info2("close(%d): %s", pipe->fd, strerror(errno)); } break; } diff --git a/xioread.c b/xioread.c index b5ec837..c3d2da0 100644 --- a/xioread.c +++ b/xioread.c @@ -42,7 +42,7 @@ ssize_t xioread(xiofile_t *file, void *buff, size_t bufsiz) { if (pipe->readbytes) { if (pipe->actbytes == 0) { - Info("xioread(): readbytes consumed, inserting EOF"); + Info1("xioread(%d, ...): readbytes consumed, inserting EOF", pipe->fd); return 0; /* EOF by count */ } @@ -135,6 +135,10 @@ ssize_t xioread(xiofile_t *file, void *buff, size_t bufsiz) { #if _WITH_SOCKET case XIOREAD_RECV: 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)", + __func__); #if WITH_RAWIP || WITH_UDP || WITH_UNIX struct msghdr msgh = {0}; union sockaddr_union from = {{0}}; @@ -151,8 +155,7 @@ ssize_t xioread(xiofile_t *file, void *buff, size_t bufsiz) { #if HAVE_STRUCT_MSGHDR_MSGCONTROLLEN msgh.msg_controllen = sizeof(ctrlbuff); #endif - - while ((rc = xiogetpacketsrc(pipe->fd, &msgh, + while ((rc = xiogetancillary(pipe->fd, &msgh, MSG_PEEK #ifdef MSG_TRUNC |MSG_TRUNC @@ -161,6 +164,9 @@ ssize_t xioread(xiofile_t *file, void *buff, size_t bufsiz) { errno == EINTR) ; if (rc < 0) return -1; + /* Note: we do not call xiodopacketinfo() and xiocheckpeer() here because + that already happened in xioopen() / _xioopen_dgram_recvfrom() ... */ + do { bytes = Recvfrom(pipe->fd, buff, bufsiz, 0, &from.soa, &fromlen); @@ -175,16 +181,22 @@ ssize_t xioread(xiofile_t *file, void *buff, size_t bufsiz) { errno = _errno; return -1; } - /* on packet type we also receive outgoing packets, this is not desired - */ -#if defined(PF_PACKET) && defined(PACKET_OUTGOING) + +#if defined(PF_PACKET) && !defined(PACKET_IGNORE_OUTGOING) && defined(PACKET_OUTGOING) + /* In future versions there may be an option that controls receiving of + outgoing packets, but currently it is hardcoded that we try to avoid + them - either by once setting socket option PACKET_IGNORE_OUTGOING + when available, otherwise by checking flag PACKET_OUTGOING per packet. + */ if (from.soa.sa_family == PF_PACKET) { - if ((from.ll.sll_pkttype & PACKET_OUTGOING) - == 0) { - errno = EAGAIN; return -1; + if ((from.ll.sll_pkttype & PACKET_OUTGOING) != 0) { + Info2("%s(fd=%d): ignoring outgoing packet", __func__, pipe->fd); + errno = EAGAIN; + return -1; } + Debug2("%s(fd=%d): packet is not outgoing - process it", __func__, pipe->fd); } -#endif /* defined(PF_PACKET) && defined(PACKET_OUTGOING) */ +#endif /* defined(PF_PACKET) && !defined(PACKET_IGNORE_OUTGOING) && defined(PACKET_OUTGOING) */ Notice2("received packet with "F_Zu" bytes from %s", bytes, @@ -336,19 +348,24 @@ ssize_t xioread(xiofile_t *file, void *buff, size_t bufsiz) { continue; } #endif -#else /* !WITH_RAWIP */ +#else /* !(WITH_RAWIP || WITH_UDP || WITH_UNIX) */ Fatal("address requires raw sockets, but they are not compiled in"); return -1; -#endif /* !WITH_RAWIP || WITH_UDP || WITH_UNIX */ +#endif /* !(WITH_RAWIP || WITH_UDP || WITH_UNIX) */ } else /* ~XIOREAD_RECV_FROM */ { - union sockaddr_union from; socklen_t fromlen = sizeof(from); - char infobuff[256]; + /* 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}; + union sockaddr_union from = {{ 0 }}; + socklen_t fromlen = sizeof(from); + char infobuff[256]; char ctrlbuff[1024]; /* ancillary messages */ int rc; - socket_init(pipe->para.socket.la.soa.sa_family, &from); + Debug1("%s(): XIOREAD_RECV and not XIOREAD_RECV_FROM (peer checks to be done)", + __func__); + /* get source address */ msgh.msg_name = &from; msgh.msg_namelen = fromlen; @@ -358,7 +375,7 @@ ssize_t xioread(xiofile_t *file, void *buff, size_t bufsiz) { #if HAVE_STRUCT_MSGHDR_MSGCONTROLLEN msgh.msg_controllen = sizeof(ctrlbuff); #endif - while ((rc = xiogetpacketsrc(pipe->fd, &msgh, + while ((rc = xiogetancillary(pipe->fd, &msgh, MSG_PEEK #ifdef MSG_TRUNC |MSG_TRUNC @@ -390,9 +407,23 @@ ssize_t xioread(xiofile_t *file, void *buff, size_t bufsiz) { errno = _errno; return -1; } + +#if defined(PF_PACKET) && !defined(PACKET_IGNORE_OUTGOING) && defined(PACKET_OUTGOING) + /* For remarks see similar section above */ + if (from.soa.sa_family == PF_PACKET) { + if ((from.ll.sll_pkttype & PACKET_OUTGOING) != 0) { + Info2("%s(fd=%d): ignoring outgoing packet", __func__, pipe->fd); + errno = EAGAIN; + return -1; + } + Debug2("%s(fd=%d): packet is not outgoing - process it", __func__, pipe->fd); + } +#endif /* defined(PF_PACKET) && !defined(PACKET_IGNORE_OUTGOING) && defined(PACKET_OUTGOING) */ + Notice2("received packet with "F_Zu" bytes from %s", bytes, sockaddr_info(&from.soa, fromlen, infobuff, sizeof(infobuff))); + if (bytes == 0) { if (!pipe->para.socket.null_eof) { errno = EAGAIN; return -1;