From 254958a34d5e538a33713655cbbdac56d647c336 Mon Sep 17 00:00:00 2001 From: Gerhard Rieger Date: Sun, 1 Oct 2023 22:16:20 +0200 Subject: [PATCH] Added option res-nsaddr --- CHANGES | 3 +++ VERSION | 2 +- config.h.in | 1 + configure.ac | 6 +++++ doc/socat.yo | 33 ++++++++++++++++++++--- test.sh | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++ xio-ip.c | 60 ++++++++++++++++++++++++++++++++++++----- xio-ip.h | 1 + xio.h | 5 ++++ xiohelp.c | 2 +- xioopts.c | 56 +++++++++++++++++++++++++++++++++++++++ xioopts.h | 2 ++ 12 files changed, 234 insertions(+), 12 deletions(-) diff --git a/CHANGES b/CHANGES index 287ec70..1df4df9 100644 --- a/CHANGES +++ b/CHANGES @@ -140,6 +140,9 @@ Features: number of times to retransmit. Disable them and the old res-* opts with: ./configure --disable-resolve + Added option res-nsaddr that overrides /etc/resolv.conf nameserver + address based on an undocumented resolver feature. + 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/VERSION b/VERSION index ceaf471..f09dcc3 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -"1.7.4.5+" +"1.7.4.5+20230721" diff --git a/config.h.in b/config.h.in index 6a36b51..ac19ce7 100644 --- a/config.h.in +++ b/config.h.in @@ -680,6 +680,7 @@ #undef HAVE_RES_RETRANS #undef HAVE_RES_RETRY +#undef HAVE_RES_NSADDR_LIST #undef HAVE_SETGRENT #undef HAVE_GETGRENT diff --git a/configure.ac b/configure.ac index e2edebc..89388df 100644 --- a/configure.ac +++ b/configure.ac @@ -2156,6 +2156,12 @@ AC_TRY_COMPILE([#include ], [AC_MSG_RESULT(yes); AC_DEFINE(HAVE_RES_RETRY, 1)], [AC_MSG_RESULT(no)]) +AC_MSG_CHECKING(for _res.nsaddr_list) +AC_TRY_COMPILE([#include ], +[_res.nsaddr_list[0].sin_family == 0], +[AC_MSG_RESULT(yes); + AC_DEFINE(HAVE_RES_NSADDR_LIST, 1)], + [AC_MSG_RESULT(no)]) dnl "tcpd" "tcpwrappers" diff --git a/doc/socat.yo b/doc/socat.yo index dcd3d13..dd4d830 100644 --- a/doc/socat.yo +++ b/doc/socat.yo @@ -2397,23 +2397,39 @@ label(OPTOIN_IP_TRANSPARENT) dit(bf(tt(ip-transparent))) Sets the IP_TRANSPARENT socket option. This option might require root privilege. +enddit() + +startdit()enddit()nl() + +label(OPTIONS_RESOLVER)em(bf(Resolver options)) + +These options temporarily change the behaviour of hostname resolution. The +options of form code(ai-*) affect behaviour of the code(getaddrinfo()) function that +includes code(/etc/hosts) and NIS based lookups. + +The addresses of form code(res-*) only affect DNS lookups, and only when the +result is not cached in code(nscd). These options might not work on all +operating systems or libc implementations. + +startdit() label(OPTION_AI_ADDRCONFIG) -dit(bf(tt(ai-addrconfig[=0|1]))), dit(bf(tt(addrconfig[=0|1]))) +dit(bf(tt(ai-addrconfig[=0|1]))) dit(bf(tt(addrconfig[=0|1]))) Sets or unsets the AI_ADDRCONFIG flag to prevent name resolution to address families that are not available on the computer (e.g. IPv6). Default value is 1 in case the resolver does not get an address family hint from Socat address or defaults. label(OPTION_AI_PASSIVE) -dit(bf(tt(ai-passive[=0|1]))), dit(bf(tt(passive[=0|1]))) - Sets of unsets the AI_ADDRCONFIG flag for code(getaddrinfo()) calls. +dit(bf(tt(ai-passive[=0|1]))) dit(bf(tt(passive[=0|1]))) + Sets of unsets the AI_PASSIVE flag for code(getaddrinfo()) calls. Default is 1 for LISTEN, RECV, and RECVFROM type addresses, and with link(bind)(OPTION_BIND) option. label(OPTION_AI_V4MAPPED) -dit(bf(tt(ai-v4mapped[=0|1]))), dit(bf(tt(v4mapped[=0|1]))) +dit(bf(tt(ai-v4mapped[=0|1]))) dit(bf(tt(v4mapped[=0|1]))) Sets or unsets the AI_V4MAPPED flag for code(getaddrinfo()). With socat() addresses requiring IPv6 addresses, this resolves IPv4 addresses to the approriate IPv6 address [::ffff:*:*]. For IPv6 Socat addresses, the default is 1. + label(OPTION_RES_DEBUG)dit(bf(tt(res-debug))) label(OPTION_RES_AAONLY)dit(bf(tt(res-aaonly))) label(OPTION_RES_USEVC)dit(bf(tt(res-usevc))) @@ -2437,6 +2453,12 @@ label(OPTION_RES_RETRANS)dit(bf(tt(res-retrans=))) label(OPTION_RES_RETRY)dit(bf(tt(res-retry=))) Sets the number of retransmits of the DNS resolver (based on an undocumented feature). +label(OPTION_RES_NSADDR)dit(bf(tt(res-nsaddr=[:]))) + Tries to overwrite nameserver settings loaded from /etc/resolv.conf by + writing the given IPv4 address into the undocumented + code(_res:nsaddr_list[0]) field. + code(/etc/hosts) is still checked by resolver. Please note that glibc's + code(nscd) is always queried first when it is running! enddit() startdit()enddit()nl() @@ -2707,6 +2729,7 @@ label(OPTION_ACCEPT_TIMEOUT)dit(bf(tt(accept-timeout=))) End waiting for a connection after [link(timeval)(TYPE_TIMEVAL)] with error status. enddit() + startdit()enddit()nl() @@ -2826,6 +2849,7 @@ label(GROUP_SHELL) Options for link(address SHELL)(ADDRESS_SHELL) +startdit() label(OPTION_SHELL)dit(bf(tt(shell=))) Overwrites use the default shell with the named executable, e.g. tt(/bin/dash). Also sets the tt(SHELL) environment variable. @@ -3217,6 +3241,7 @@ measures are required to get the VLAN information, see NOEXPAND(packet(7)) PACKE and to optionally insert the tag into the packet again, use option link(retrieve-vlan)(OPTION_RETRIEVE_VLAN) when you need this. +startdit() label(OPTION_RETRIEVE_VLAN)dit(bf(tt(retrieve-vlan))) On packets incoming on raw sockets, retrieve the VLAN information and insert it into the packets for further processing (Linux) diff --git a/test.sh b/test.sh index 6e6c98d..a2255db 100755 --- a/test.sh +++ b/test.sh @@ -17287,6 +17287,80 @@ esac N=$((N+1)) +# Test the res-nsaddr (resolver, dns) option +NAME=RES_NSADDR +case "$TESTS" in +*%$N%*|*%functions%*|*%resolv%*|*%ip4%*|*%tcp4%*|*%socket%*|*%$NAME%*) +TEST="$NAME: test the res-nsaddr option" +# Start a supplementary Socat instance that will receive the DNS query. +# Run main Socat process, opening an IPv4 socket with option res-nsaddr +# directed to the aux process. +# When the supplementary Socat instance received the query the test succeeded. +if ! eval $NUMCOND; then :; +elif ! F=$(testfeats STDIO IP4 UDP TCP); then + $PRINTF "test $F_n $TEST... ${YELLOW}Feature $F not configured in $SOCAT${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! A=$(testaddrs STDIO TCP4 UDP-RECVFROM); 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 res-nsaddr) >/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 on host${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="$(echo test$N $(date) $RANDOM |tr ' :' '-')" +echo "$da" >"$td/test$N.da" +newport udp4 # or whatever proto, or drop this line +CMD0="$TRACE $SOCAT $opts -u UDP-RECVFROM:$PORT -" +CMD1="$TRACE $SOCAT $opts - TCP4:$da:0,res-nsaddr=$LOCALHOST:$PORT" +printf "test $F_n $TEST... " $N +$CMD0 >/dev/null 2>"${te}0" >"${tf}0" & +pid0=$! +waitudp4port $PORT 1 +$CMD1 >"${tf}1" 2>"${te}1" +rc1=$? +kill $pid0 2>/dev/null; wait +if grep "$da" "${tf}0" >/dev/null; 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)) +elif pgrep -u root nscd >/dev/null 2>&1; then + $PRINTF "${YELLOW}FAILED (due to nscd?)${NORMAL}\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 + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" + namesCAnT="$namesCANT $NAME" +else + $PRINTF "$FAILED (query not received)\n" + echo "$CMD0 &" + cat "${te}0" >&2 + echo "$CMD1" + cat "${te}1" >&2 + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" + namesFAIL="$namesFAIL $NAME" +fi +fi # NUMCOND + ;; +esac +N=$((N+1)) + + # end of common tests ################################################################################## @@ -17502,6 +17576,7 @@ elif [ ??? ]; then if [ "$DEBUG" ]; then cat "${te}1" >&2; fi numCANT=$((numCANT+1)) listCANT="$listCANT $N" + namesCANT="$namesCANT $NAME" else $PRINTF "$OK\n" if [ "$VERBOSE" ]; then echo "$CMD0 &"; fi diff --git a/xio-ip.c b/xio-ip.c index 04e796c..9256d84 100644 --- a/xio-ip.c +++ b/xio-ip.c @@ -116,6 +116,9 @@ const struct optdesc opt_res_retrans = { "res-retrans", "retrans", OPT_RES_RE #if HAVE_RES_RETRY const struct optdesc opt_res_retry = { "res-retry", NULL, OPT_RES_RETRY, GROUP_SOCK_IP, PH_OFFSET, TYPE_INT, OFUNC_OFFSET, XIO_OFFSETOF(para.socket.ip.res.retry), XIO_SIZEOF(para.socket.ip.res.retry), RES_MAXRETRY }; #endif +#if HAVE_RES_NSADDR_LIST +const struct optdesc opt_res_nsaddr = { "res-nsaddr", "dns", OPT_RES_NSADDR, GROUP_SOCK_IP, PH_OFFSET, TYPE_IP4SOCK, OFUNC_OFFSET, XIO_OFFSETOF(para.socket.ip.res.nsaddr), XIO_SIZEOF(para.socket.ip.res.retry), RES_MAXRETRY }; +#endif #endif /* HAVE_RESOLV_H */ #endif /* WITH_RESOLVE */ #endif /* WITH_IP4 || WITH_IP6 */ @@ -139,7 +142,9 @@ int xioinit_ip( return 0; } + #if HAVE_RESOLV_H + int Res_init(void) { int result; Debug("res_init()"); @@ -147,13 +152,9 @@ int Res_init(void) { Debug1("res_init() -> %d", result); return result; } + #endif /* HAVE_RESOLV_H */ -#if HAVE_RESOLV_H -unsigned long res_opts() { - return _res.options; -} -#endif /* HAVE_RESOLV_H */ /* the ultimate(?) socat resolver function node: the address to be resolved; supported forms: @@ -1015,7 +1016,18 @@ int xio_res_init( struct __res_state *save_res) { if (sfd->para.socket.ip.res.opts[0] || - sfd->para.socket.ip.res.opts[1]) { + sfd->para.socket.ip.res.opts[1] || +#if HAVE_RES_RETRANS + sfd->para.socket.ip.res.retrans >= 0 || +#endif +#if HAVE_RES_RETRY + sfd->para.socket.ip.res.retry >= 0 || +#endif +#if HAVE_RES_NSADDR_LIST + sfd->para.socket.ip.res.nsaddr.sin_family != PF_UNSPEC || +#endif + 0 /* for canonical reasons */ + ) { if (!(_res.options & RES_INIT)) { if (Res_init() < 0) { Error("res_init() failed"); @@ -1027,8 +1039,44 @@ int xio_res_init( _res.options &= ~sfd->para.socket.ip.res.opts[1]; Debug2("changed _res.options from 0x%lx to 0x%lx", save_res->options, _res.options); + +#if HAVE_RES_RETRANS + if (sfd->para.socket.ip.res.retrans >= 0) { + _res.retrans = sfd->para.socket.ip.res.retrans; + Debug2("changed _res.retrans from 0x%x to 0x%x", + save_res->retrans, _res.retrans); + } +#endif +#if HAVE_RES_RETRY + if (sfd->para.socket.ip.res.retry >= 0) { + _res.retry = sfd->para.socket.ip.res.retry; + Debug2("changed _res.retry from 0x%x to 0x%x", + save_res->retry, _res.retry); + } +#endif +#if HAVE_RES_NSADDR_LIST + if (sfd->para.socket.ip.res.nsaddr.sin_family == PF_INET) { + _res.nscount = 1; + _res.nsaddr_list[0] = sfd->para.socket.ip.res.nsaddr; + if (_res.nsaddr_list[0].sin_port == htons(0)) + _res.nsaddr_list[0].sin_port = htons(53); + Debug10("changed _res.nsaddr_list[0] from %u.%u.%u.%u:%u to %u.%u.%u.%u:%u", + ((unsigned char *)&save_res->nsaddr_list[0].sin_addr.s_addr)[0], + ((unsigned char *)&save_res->nsaddr_list[0].sin_addr.s_addr)[1], + ((unsigned char *)&save_res->nsaddr_list[0].sin_addr.s_addr)[2], + ((unsigned char *)&save_res->nsaddr_list[0].sin_addr.s_addr)[3], + ntohs(save_res->nsaddr_list[0].sin_port), + ((unsigned char *)&_res.nsaddr_list[0].sin_addr.s_addr)[0], + ((unsigned char *)&_res.nsaddr_list[0].sin_addr.s_addr)[1], + ((unsigned char *)&_res.nsaddr_list[0].sin_addr.s_addr)[2], + ((unsigned char *)&_res.nsaddr_list[0].sin_addr.s_addr)[3], + ntohs(_res.nsaddr_list[0].sin_port)); + } +#endif /* HAVE_RES_NSADDR_LIST */ + return 1; } + return 0; } diff --git a/xio-ip.h b/xio-ip.h index 7c6518b..aa2112d 100644 --- a/xio-ip.h +++ b/xio-ip.h @@ -44,6 +44,7 @@ extern const struct optdesc opt_res_stayopen; extern const struct optdesc opt_res_dnsrch; extern const struct optdesc opt_res_retrans; extern const struct optdesc opt_res_retry; +extern const struct optdesc opt_res_nsaddr; extern int xioinit_ip(int *pf, char ipv); diff --git a/xio.h b/xio.h index 5df40f0..ac85bb7 100644 --- a/xio.h +++ b/xio.h @@ -143,6 +143,10 @@ struct para_ip_res { #if HAVE_RES_RETRY int retry; #endif +#if HAVE_RES_NSADDR_LIST +# undef nsaddr /* resolv.h might define this, breaks debugger */ + struct sockaddr_in nsaddr; +#endif } ; #endif /* WITH_RESOLVE && HAVE_RESOLV_H */ @@ -423,6 +427,7 @@ union integral { #endif /* HAVE_STRUCT_TIMESPEC */ #if WITH_IP4 struct in_addr u_ip4addr; + struct sockaddr_in u_ip4sock; #endif } ; diff --git a/xiohelp.c b/xiohelp.c index 2e0b66b..b05127a 100644 --- a/xiohelp.c +++ b/xiohelp.c @@ -22,7 +22,7 @@ static const char *optiontypenames[] = { "OFF64_T", "INT:INT", "INT:INTP", "INT:BIN", "INT:STRING", "INT:INT:INT", "INT:INT:BIN", "INT:INT:STRING", "INT:INT:GENERIC", - "IP4NAME", + "IP4NAME", "IP4SOCK", #if HAVE_STRUCT_LINGER "STRUCT-LINGER", #endif diff --git a/xioopts.c b/xioopts.c index d16748b..9838d87 100644 --- a/xioopts.c +++ b/xioopts.c @@ -430,6 +430,9 @@ const struct optname optionnames[] = { #ifdef VDISCARD IF_TERMIOS("discard", &opt_vdiscard) #endif +#if HAVE_RES_NSADDR_LIST + IF_IP ("dns", &opt_res_nsaddr) +#endif #if HAVE_RESOLV_H IF_RESOLVE("dnsrch", &opt_res_dnsrch) #endif /* HAVE_RESOLV_H */ @@ -1020,6 +1023,9 @@ const struct optname optionnames[] = { IF_IP ("multicast-ttl", &opt_ip_multicast_ttl) IF_IP ("multicastloop", &opt_ip_multicast_loop) IF_IP ("multicastttl", &opt_ip_multicast_ttl) +#if HAVE_RES_NSADDR_LIST + IF_IP ("nameserver", &opt_res_nsaddr) +#endif #if defined(O_NDELAY) && (!defined(O_NONBLOCK) || O_NDELAY != O_NONBLOCK) IF_ANY ("ndelay", &opt_o_ndelay) #else @@ -1085,6 +1091,9 @@ const struct optname optionnames[] = { IF_OPENSSL("nosni", &opt_openssl_no_sni) #endif IF_INTERFACE("notrailers", &opt_iff_notrailers) +#if HAVE_RES_NSADDR_LIST + IF_IP ("nsaddr", &opt_res_nsaddr) +#endif #ifdef O_NSHARE IF_OPEN ("nshare", &opt_o_nshare) #endif @@ -1426,6 +1435,9 @@ const struct optname optionnames[] = { # if HAVE_RES_RETRY IF_RESOLVE("res-maxretry", &opt_res_retry) # endif +# if HAVE_RES_NSADDR_LIST + IF_IP ("res-nsaddr", &opt_res_nsaddr) +# endif # if WITH_RES_PRIMARY IF_RESOLVE("res-primary", &opt_res_primary) # endif @@ -2630,6 +2642,7 @@ int parseopts_table(const char **a, groups_t groups, struct opt **opts, #if WITH_IP4 case TYPE_IP4NAME: { + /*! On a good day merge this with code in retropt_bind() */ struct sockaddr_in sa; socklen_t salen = sizeof(sa); const char *ends[] = { NULL }; const char *nests[] = { "[","]", NULL }; @@ -2659,6 +2672,40 @@ int parseopts_table(const char **a, groups_t groups, struct opt **opts, opt->value.u_ip4addr = sa.sin_addr; } break; + case TYPE_IP4SOCK: + { + /*! On a good day merge this with code for TYPE_IP4NAME */ + struct sockaddr_in sa; socklen_t salen = sizeof(sa); + const char portsep[] = ":"; + const char *ends[] = { portsep, NULL }; + const char *nests[] = { "[","]", NULL }; + char hostname[512], *hostp = hostname, *portp = NULL; + size_t hostlen = sizeof(hostname)-1; + + tokp = token; + parsres = + nestlex((const char **)&tokp, &hostp, &hostlen, + ends, NULL, NULL, nests, + true, false, false); + if (parsres < 0) { + Error1("option too long: \"%s\"", *a); + return -1; + } else if (parsres > 0) { + Error1("syntax error in \"%s\"", *a); + return -1; + } + *hostp++ = '\0'; + if (!strncmp(tokp, portsep, strlen(portsep))) { + portp = tokp + strlen(portsep); + } + if (xioresolve(hostname, portp, AF_INET, SOCK_DGRAM, IPPROTO_IP, + (union sockaddr_union *)&sa, &salen, 0) + != STAT_OK) { + opt->desc = ODESC_ERROR; continue; + } + opt->value.u_ip4sock = sa; + } + break; #endif /* defined(WITH_IP4) */ #if LATER @@ -4132,6 +4179,15 @@ static int applyopt_offset(struct single *sfd, struct opt *opt) { case TYPE_CONST: *(int *)ptr = opt->desc->minor; break; + case TYPE_IP4NAME: + memset(ptr, 0, sizeof(struct sockaddr_in)); + ((struct sockaddr_in *)ptr)->sin_addr = opt->value.u_ip4addr; + ((struct sockaddr_in *)ptr)->sin_family = PF_INET; + break; + case TYPE_IP4SOCK: + memset(ptr, 0, sizeof(struct sockaddr_in)); + *(struct sockaddr_in *)ptr = opt->value.u_ip4sock; + break; default: Error2("applyopt_offset(opt:%s): type %s not implemented", opt->desc->defname, xiohelp_opttypename(opt->desc->type)); diff --git a/xioopts.h b/xioopts.h index 66cb5dc..20caa23 100644 --- a/xioopts.h +++ b/xioopts.h @@ -68,6 +68,7 @@ enum e_types { TYPE_INT_INT_GENERIC, /* 3 params: first and second are int, 3rd is specified by value (dalan syntax) */ TYPE_IP4NAME, /* IPv4 hostname or address */ + TYPE_IP4SOCK, /* IPv4 hostname or address optionally with port */ #if HAVE_STRUCT_LINGER TYPE_LINGER, /* struct linger */ #endif /* HAVE_STRUCT_LINGER */ @@ -628,6 +629,7 @@ enum e_optcode { OPT_RES_DEFNAMES, /* resolver(3) */ OPT_RES_DNSRCH, /* resolver(3) */ OPT_RES_IGNTC, /* resolver(3) */ + OPT_RES_NSADDR, /* undocumented */ OPT_RES_PRIMARY, /* resolver(3) */ OPT_RES_RECURSE, /* resolver(3) */ OPT_RES_RETRANS, /* undocumented */