From 602a54420ea627396a85bbcbc14b43aa83931e86 Mon Sep 17 00:00:00 2001 From: Gerhard Rieger Date: Tue, 20 Aug 2024 12:02:25 +0200 Subject: [PATCH] Added the optional DEVTESTS feature for developer tests with controlled name resolution to both IPv4 and IPV6 addresses --- CHANGES | 6 ++ config.h.in | 2 +- configure.ac | 10 ++ socat.c | 5 + test.sh | 28 +++++- xio-ip.c | 256 ++++++++++++++++++++++++++++++++++++++++++++++++++- xioopts.c | 51 +++++++++- 7 files changed, 350 insertions(+), 8 deletions(-) diff --git a/CHANGES b/CHANGES index f3423b7..a15e331 100644 --- a/CHANGES +++ b/CHANGES @@ -64,6 +64,12 @@ Building: Thanks to Hongxu Jia for providing an inital patch. Testing: + Added the optional DEVTESTS feature for developer tests with controlled + name resolution to both IPv4 and IPV6 addresses: configure Socat with + --enable-devtests, this provides internal resolution of domain + dest-unreach.net with host names: localhost-4, localhost-6, + localhost-4-6, and localhost-6-4 + test.sh: lots of corrections and improvements test.sh: many hardcoded sleep values were replaced by much shorter diff --git a/config.h.in b/config.h.in index 94cd199..682913e 100644 --- a/config.h.in +++ b/config.h.in @@ -748,10 +748,10 @@ #undef WITH_LIBWRAP #undef HAVE_TCPD_H #undef HAVE_LIBWRAP - #undef WITH_SYCLS #undef WITH_FILAN #undef WITH_RETRY +#undef WITH_DEVTESTS #undef WITH_MSGLEVEL diff --git a/configure.ac b/configure.ac index 3e49a81..303fb18 100644 --- a/configure.ac +++ b/configure.ac @@ -942,6 +942,15 @@ AC_ARG_ENABLE(retry, [ --disable-retry disable retry support], esac], [AC_DEFINE(WITH_RETRY) AC_MSG_RESULT(yes)]) +AC_MSG_CHECKING(whether to include devtests support) +AC_ARG_ENABLE(devtests, [ --enable-devtests enable devtests support], + [case "$enableval" in + yes) AC_DEFINE(WITH_DEVTESTS) AC_MSG_RESULT(yes);; + *) AC_MSG_RESULT(no) ;; + esac], + [AC_MSG_RESULT(no)]) + + AC_MSG_CHECKING(included message level) AC_ARG_ENABLE(msglevel, [ --enable-msglevel=N set max verbosity to debug,info,notice,warn,error,fatal], [case "$enableval" in @@ -965,6 +974,7 @@ AC_ARG_ENABLE(default-ipv, [ --enable-default-ipv=N set default/preferred I esac], [AC_DEFINE(WITH_DEFAULT_IPV, '0') AC_MSG_RESULT("0")]) + #AC_SUBST(V_INCL) dnl Checks for typedefs, structures, and compiler characteristics. diff --git a/socat.c b/socat.c index f5d4393..aac1938 100644 --- a/socat.c +++ b/socat.c @@ -714,6 +714,11 @@ void socat_version(FILE *fd) { #else fputs(" #undef WITH_RETRY\n", fd); #endif +#ifdef WITH_DEVTESTS + fprintf(fd, " #define WITH_DEVTESTS %d\n", WITH_DEVTESTS); +#else + fputs(" #undef WITH_DEVTESTS\n", fd); +#endif #ifdef WITH_MSGLEVEL fprintf(fd, " #define WITH_MSGLEVEL %d /*%s*/\n", WITH_MSGLEVEL, &"debug\0\0\0info\0\0\0\0notice\0\0warn\0\0\0\0error\0\0\0fatal\0\0\0"[WITH_MSGLEVEL<<3]); diff --git a/test.sh b/test.sh index 163afa0..9116b6e 100755 --- a/test.sh +++ b/test.sh @@ -15759,8 +15759,8 @@ tf="$td/test$N.stdout" te="$td/test$N.stderr" tdiff="$td/test$N.diff" da="test$N $(date) $RANDOM" -CMD0="$TRACE $SOCAT $opts -t $T4 TCP4-LISTEN:$PORT,reuseaddr EXEC:'$FILAN -s',nofork" -CMD1="$TRACE $SOCAT $opts - TCP4:localhost:$PORT" +CMD0="$TRACE $SOCAT $opts TCP4-LISTEN:$PORT,reuseaddr EXEC:'$FILAN -s',nofork" +CMD1="$TRACE $SOCAT $opts -t $T4 - TCP4:localhost:$PORT" printf "test $F_n $TEST... " $N eval "$CMD0" >/dev/null 2>"${te}0" & pid0=$! @@ -19442,7 +19442,8 @@ else $CMD0 >/dev/null 2>"${te}0" & pid0=$! waittcp4port $tp - echo "$da" |$CMD1 >"${tf}1" 2>"${te}1" + # NetBSD-9 seems to need massive delay + { echo "$da"; relsleep 100; } |$CMD1 >"${tf}1" 2>"${te}1" rc1=$? kill $pid0 2>/dev/null wait 2>/dev/null @@ -19915,6 +19916,27 @@ esac N=$((N+1)) +# DEVTESTS IPv4/IPv6 resolver tests: just manually: + +# Prepare: +#socat TCP4-LISTEN:12345,reuseaddr,fork PIPE +# These must succeed: +#echo AAAA |socat - TCP4:localhost-4.dest-unreach.net:12345 +#echo AAAA |socat - TCP4:localhost-4-6.dest-unreach.net:12345 +#echo AAAA |socat - TCP4:localhost-6-4.dest-unreach.net:12345 + +# Prepare: +#socat TCP6-LISTEN:12345,reuseaddr,fork PIPE +# These must succeed: +#echo AAAA |socat - TCP6:localhost-6.dest-unreach.net:12345 +#echo AAAA |socat - TCP6:localhost-6-4.dest-unreach.net:12345 +#echo AAAA |socat - TCP6:localhost-4-6.dest-unreach.net:12345 + +# These must fail with No address associated with hostname +#socat - TCP4:localhost-6.dest-unreach.net:12345 +#socat - TCP6:localhost-4.dest-unreach.net:12345 + + # end of common tests ################################################################################## diff --git a/xio-ip.c b/xio-ip.c index 14364f1..6d07f63 100644 --- a/xio-ip.c +++ b/xio-ip.c @@ -161,6 +161,208 @@ int Res_init(void) { #endif /* HAVE_RESOLV_H */ +#if WITH_DEVTESTS + +/* Have a couple of hard coded sockaddr records, to be copied and adapted when + needed */ + +static bool devtests_inited = false; + +static struct sockaddr_in sockaddr_localhost_4 = { +#if HAVE_STRUCT_SOCKADDR_SALEN + sizeof(struct sockaddr_in), +#endif + AF_INET, /*htons*/0, { 0 } +}; + +static struct sockaddr_in6 sockaddr_localhost_6 = { +#if HAVE_STRUCT_SOCKADDR_SALEN + sizeof(struct sockaddr_in6), +#endif + AF_INET6, /*htons*/0, 0, { { { 0 } } }, 0 +}; + +static struct addrinfo addrinfo_localhost_4 = { + 0, AF_INET, 0, 0, + sizeof(struct sockaddr_in), + (struct sockaddr *)&sockaddr_localhost_4, + NULL, + NULL +} ; + +static struct addrinfo addrinfo_localhost_6 = { + 0, AF_INET6, 0, 0, + sizeof(struct sockaddr_in6), + (struct sockaddr *)&sockaddr_localhost_6, + NULL, + NULL +} ; + +static struct addrinfo addrinfo_localhost_4_6[2] = + { + { + 0, AF_INET, 0, 0, + sizeof(sockaddr_localhost_4), + NULL, /* memdup(sockaddr_localhost_4) */ + NULL, + NULL /* &addrinfo_localhost_4_6[1] */ + }, + { + 0, AF_INET6, 0, 0, + sizeof(sockaddr_localhost_6), + NULL, /* memdup(sockaddr_localhost_6) */ + NULL, + NULL + }, + } ; + +static struct addrinfo addrinfo_localhost_6_4[2] = + { + { + 0, AF_INET6, 0, 0, + sizeof(sockaddr_localhost_6), + NULL, /* memdup(sockaddr_localhost_6) */ + NULL, + NULL, /* &addrinfo_localhost_6_4[1] */ + }, + { + 0, AF_INET, 0, 0, + sizeof(sockaddr_localhost_4), + NULL, /* memdup(sockaddr_localhost_4) */ + NULL, + NULL }, + } ; + +/* We keep track of the copied records because they must not be paaed to + freeaddrinfo() */ +#define MAX_HARDCODED_RECORDS 16 +static struct addrinfo *keep_hardcoded_records[MAX_HARDCODED_RECORDS]; +static int count_hardcoded_records; + +/* returns 0 on success, EAI_NODATA when no matching af, or + EAI_NONAME when node did not match the special names */ +static int xioip_getaddrinfo_devtests( + const char *node, + const char *service, + int family, + int socktype, + int protocol, + struct addrinfo **res, + const int ai_flags[2]) +{ + if (!devtests_inited) { + devtests_inited = true; + sockaddr_localhost_4.sin_addr.s_addr = htonl((127<<24)+1); /* 127.0.0.1 */ +#if WITH_IP6 + xioip6_pton("::1", &sockaddr_localhost_6.sin6_addr, 0); +#endif + } + if (node == NULL) { + ; +#if WITH_IP4 + } else if (!strcmp(node, "localhost-4") + || !strcmp(node, "localhost-4-6") && family == AF_INET + || !strcmp(node, "localhost-6-4") && family == AF_INET +#if !WITH_IP6 + || !strcmp(node, "localhost-4-6") + || !strcmp(node, "localhost-6-4") +#endif /* !WITH_IP6 */ + ) { + if (family == AF_INET6) + return EAI_NODATA; + *res = memdup(&addrinfo_localhost_4, sizeof(addrinfo_localhost_4)); + (*res)->ai_socktype = socktype; + (*res)->ai_protocol = protocol; + (*res)->ai_addr = memdup(&sockaddr_localhost_4, sizeof(sockaddr_localhost_4)); + ((struct sockaddr_in *)((*res)->ai_addr))->sin_port = (service?htons(atoi(service)):0); + keep_hardcoded_records[count_hardcoded_records++] = *res; + return 0; +#endif /* WITH_IP4 */ + +#if WITH_IP6 + } else if (!strcmp(node, "localhost-6") + || !strcmp(node, "localhost-4-6") && family == AF_INET6 + || !strcmp(node, "localhost-6-4") && family == AF_INET6 +#if !WITH_IP4 + || !strcmp(node, "localhost-4-6") + || !strcmp(node, "localhost-6-4") +#endif /* !WITH_IP4 */ + ) { + if (family == AF_INET) + return EAI_NODATA; + *res = memdup(&addrinfo_localhost_6, sizeof(addrinfo_localhost_6)); + (*res)->ai_socktype = socktype; + (*res)->ai_protocol = protocol; + (*res)->ai_addr = memdup(&sockaddr_localhost_6, sizeof(sockaddr_localhost_6)); + ((struct sockaddr_in6 *)((*res)->ai_addr))->sin6_port = + (service?htons(atoi(service)):0); + keep_hardcoded_records[count_hardcoded_records++] = *res; + return 0; +#endif /* !WITH_IP6 */ + +#if WITH_IP4 && WITH_IP6 + } else if (!strcmp(node, "localhost-4-6")) { + /* here we come only when both WITH_IP4,WITH_IP6, and family not 4 or 6 */ + *res = memdup(&addrinfo_localhost_4_6, sizeof(addrinfo_localhost_4_6)); + (*res)[0].ai_socktype = socktype; + (*res)[0].ai_protocol = protocol; + (*res)[0].ai_addr = memdup(&sockaddr_localhost_4, sizeof(sockaddr_localhost_4)); + ((struct sockaddr_in *)((*res)[0].ai_addr))->sin_port = + (service?htons(atoi(service)):0); + (*res)[0].ai_next = &(*res)[1]; + (*res)[1].ai_socktype = socktype; + (*res)[1].ai_protocol = protocol; + (*res)[1].ai_addr = memdup(&sockaddr_localhost_6, sizeof(sockaddr_localhost_6)); + ((struct sockaddr_in6 *)((*res)[1].ai_addr))->sin6_port = + (service?htons(atoi(service)):0); + keep_hardcoded_records[count_hardcoded_records++] = *res; + return 0; +#endif /* WITH_IP4 && WITH_IP6 */ + +#if WITH_IP4 && WITH_IP6 + } else if (!strcmp(node, "localhost-6-4")) { + /* here we come only when both WITH_IP4,WITH_IP6, and family not 4,6 */ + *res = memdup(&addrinfo_localhost_6_4, sizeof(addrinfo_localhost_6_4)); + (*res)[0].ai_socktype = socktype; + (*res)[0].ai_protocol = protocol; + (*res)[0].ai_addr = memdup(&sockaddr_localhost_6, sizeof(sockaddr_localhost_6)); + ((struct sockaddr_in6 *)((*res)[0].ai_addr))->sin6_port = + (service?htons(atoi(service)):0); + (*res)[0].ai_next = &(*res)[1]; + (*res)[1].ai_socktype = socktype; + (*res)[1].ai_protocol = protocol; + (*res)[1].ai_addr = memdup(&sockaddr_localhost_4, sizeof(sockaddr_localhost_4)); + ((struct sockaddr_in *)((*res)[1].ai_addr))->sin_port = + service?htons(atoi(service)):0; + keep_hardcoded_records[count_hardcoded_records++] = *res; + return 0; +#endif /* WITH_IP4 && WITH_IP6 */ + + } + if (count_hardcoded_records == MAX_HARDCODED_RECORDS) + --count_hardcoded_records; /* more records will leak memory */ + + return EAI_NONAME; +} + +/* Checks if res is a devtests construct, returns 0 if so, + or 1 otherwise */ +static int xioip_freeaddrinfo_devtests( + struct addrinfo *res) +{ + int i; + for (i=0; i<16; ++i) { + if (res == keep_hardcoded_records[i]) { + free(res); + keep_hardcoded_records[i] = NULL; + return 0; + } + } + return 1; +} +#endif /* WITH_DEVTESTS */ + + /* the ultimate(?) socat resolver function node: the address to be resolved; supported forms: 1.2.3.4 (IPv4 address) @@ -171,7 +373,7 @@ int Res_init(void) { family: PF_INET, PF_INET6, or PF_UNSPEC permitting both socktype: SOCK_STREAM, SOCK_DGRAM, ... protocol: IPPROTO_UDP, IPPROTO_TCP - sau: an uninitialized storage for the resulting socket address + res: a pointer to an uninitialized ptr var for the resulting socket address returns: STAT_OK, STAT_RETRYLATER, STAT_NORETRY, prints message */ int xiogetaddrinfo(const char *node, const char *service, @@ -186,8 +388,11 @@ int xiogetaddrinfo(const char *node, const char *service, #endif int error_num; + Debug8("xiogetaddrinfo(node=\"%s\", service=\"%s\", family=%d, socktype=%d, protoco=%d, ai_flags={0x%04x/0x%04x} }, res=%p", + node?node:"NULL", service?service:"NULL", family, socktype, protocol, + ai_flags?ai_flags[0]:0, ai_flags?ai_flags[1]:0, res); if (service && service[0]=='\0') { - Error("empty port/service"); + Error("xiogetaddrinfo(): empty port and service"); } #if LATER @@ -202,6 +407,48 @@ int xiogetaddrinfo(const char *node, const char *service, #endif /* WITH_VSOCK */ #endif /* LATER */ +#if WITH_DEVTESTS + if (node != NULL && strchr(node, '.') && + (!strcmp(strchr(node, '.'), ".dest-unreach.net") || + !strcmp(strchr(node, '.'), ".dest-unreach.net."))) { + char *hname = strdup(node); + + Info("dest-unreach.net domain handled specially"); + if (hname == NULL) + return EAI_MEMORY; + if (hname[strlen(hname)-1] == '.') + hname[strlen(hname)-1] = '\0'; + *strchr(hname, '.') = '\0'; + error_num = + xioip_getaddrinfo_devtests(hname, service, family, socktype, protocol, + res, ai_flags); + if (error_num == EAI_NONAME) { + Warn("dest-unreach.net domain name does not resolve specially"); + /* Pass through to libc resolver */ + } else if (error_num != 0) { + Error7("getaddrinfo(\"%s\", \"%s\", {0x%02x,%d,%d,%d}, {}): %s", + node?node:"NULL", service?service:"NULL", + hints.ai_flags, hints.ai_family, + hints.ai_socktype, hints.ai_protocol, + (error_num == EAI_SYSTEM)? + strerror(errno):gai_strerror(error_num)); + return error_num; + } else { /* ok */ +#if WITH_MSGLEVEL <= E_DEBUG + struct addrinfo *record; + record = *res; + while (record) { + char buff[256/*!*/]; + sockaddr_info(record->ai_addr, record->ai_addrlen, buff, sizeof(buff)); + Debug5("getaddrinfo() -> flags=0x%02x family=%d socktype=%d protocol=%d addr=%s", record->ai_flags, record->ai_family, record->ai_socktype, record->ai_protocol, buff); + record = record->ai_next; + } +#endif /* WITH_MSGLEVEL <= E_DEBUG */ + return error_num; + } + } +#endif /* WITH_DEVTESTS */ + /* the resolver functions might handle numeric forms of node names by reverse lookup, that's not what we want. So we detect these and handle them specially */ @@ -407,6 +654,11 @@ int xiogetaddrinfo(const char *node, const char *service, } void xiofreeaddrinfo(struct addrinfo *res) { +#if WITH_DEVTESTS + if (!xioip_freeaddrinfo_devtests(res)) { + return; + } +#endif #if HAVE_GETADDRINFO freeaddrinfo(res); #else diff --git a/xioopts.c b/xioopts.c index c87e89d..a751fd3 100644 --- a/xioopts.c +++ b/xioopts.c @@ -3338,7 +3338,7 @@ int retropt_bind(struct opt *opts, return STAT_NORETRY; } break; -#endif /* WITH_IP4 || WITH_IP6 */ +#endif /* WITH_IP4 || WITH_IP6 || WITH_VSOCK */ #if WITH_UNIX case AF_UNIX: @@ -3367,6 +3367,52 @@ int retropt_bind(struct opt *opts, } return STAT_OK; } + +#if 0 +#if _WITH_IP4 || _WITH_IP6 +/* Looks for a bind option and, if found, calls xiogetaddrinfo and provides the + results list in bindlist. + returns STAT_OK if option exists and could be resolved, + STAT_NORETRY if option exists but had error, + or STAT_NOACTION if it does not exist */ +int retropt_bind_gai(struct opt *opts, + int af, + int socktype, + int ipproto, + struct addrinfo **bindlist, + int feats, /* TCP etc: 1..address allowed, + 3..address and port allowed + */ + const int ai_flags[2]) +{ + + if (retropt_string(opts, OPT_BIND, &bindname) < 0) { + return STAT_NOACTION; + } + bindp = bindname; + + switch (af) { + +#if WITH_IP4 || WITH_IP6 + case AF_UNSPEC: +#if WITH_IP4 + case AF_INET: +#endif +#if WITH_IP6 + case AF_INET6: +#endif /*WITH_IP6 */ + break; +#endif /* WITH_IP4 || WITH_IP6 */ + + default: + Error1("bind: unknown address family %d", af); + return STAT_NORETRY; + } + return STAT_OK; +} +#endif /* _WITH_IP4 || _WITH_IP6 */ +#endif /* 0 */ + #endif /* _WITH_SOCKET */ @@ -4196,7 +4242,8 @@ int applyopts_fchown(int fd, struct opt *opts) { return 0; } -/* caller must make sure that option is not yet consumed */ +/* Offset means a position in the sfd record where value is written. + Caller must make sure that option is not yet consumed */ static int applyopt_offset(struct single *sfd, struct opt *opt) { unsigned char *ptr;