Added the optional DEVTESTS feature for developer tests with controlled name resolution to both IPv4 and IPV6 addresses

This commit is contained in:
Gerhard Rieger 2024-08-20 12:02:25 +02:00
parent a86376cd1e
commit 602a54420e
7 changed files with 350 additions and 8 deletions

View file

@ -64,6 +64,12 @@ Building:
Thanks to Hongxu Jia for providing an inital patch. Thanks to Hongxu Jia for providing an inital patch.
Testing: 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: lots of corrections and improvements
test.sh: many hardcoded sleep values were replaced by much shorter test.sh: many hardcoded sleep values were replaced by much shorter

View file

@ -748,10 +748,10 @@
#undef WITH_LIBWRAP #undef WITH_LIBWRAP
#undef HAVE_TCPD_H #undef HAVE_TCPD_H
#undef HAVE_LIBWRAP #undef HAVE_LIBWRAP
#undef WITH_SYCLS #undef WITH_SYCLS
#undef WITH_FILAN #undef WITH_FILAN
#undef WITH_RETRY #undef WITH_RETRY
#undef WITH_DEVTESTS
#undef WITH_MSGLEVEL #undef WITH_MSGLEVEL

View file

@ -942,6 +942,15 @@ AC_ARG_ENABLE(retry, [ --disable-retry disable retry support],
esac], esac],
[AC_DEFINE(WITH_RETRY) AC_MSG_RESULT(yes)]) [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_MSG_CHECKING(included message level)
AC_ARG_ENABLE(msglevel, [ --enable-msglevel=N set max verbosity to debug,info,notice,warn,error,fatal], AC_ARG_ENABLE(msglevel, [ --enable-msglevel=N set max verbosity to debug,info,notice,warn,error,fatal],
[case "$enableval" in [case "$enableval" in
@ -965,6 +974,7 @@ AC_ARG_ENABLE(default-ipv, [ --enable-default-ipv=N set default/preferred I
esac], esac],
[AC_DEFINE(WITH_DEFAULT_IPV, '0') AC_MSG_RESULT("0")]) [AC_DEFINE(WITH_DEFAULT_IPV, '0') AC_MSG_RESULT("0")])
#AC_SUBST(V_INCL) #AC_SUBST(V_INCL)
dnl Checks for typedefs, structures, and compiler characteristics. dnl Checks for typedefs, structures, and compiler characteristics.

View file

@ -714,6 +714,11 @@ void socat_version(FILE *fd) {
#else #else
fputs(" #undef WITH_RETRY\n", fd); fputs(" #undef WITH_RETRY\n", fd);
#endif #endif
#ifdef WITH_DEVTESTS
fprintf(fd, " #define WITH_DEVTESTS %d\n", WITH_DEVTESTS);
#else
fputs(" #undef WITH_DEVTESTS\n", fd);
#endif
#ifdef WITH_MSGLEVEL #ifdef WITH_MSGLEVEL
fprintf(fd, " #define WITH_MSGLEVEL %d /*%s*/\n", 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]); &"debug\0\0\0info\0\0\0\0notice\0\0warn\0\0\0\0error\0\0\0fatal\0\0\0"[WITH_MSGLEVEL<<3]);

28
test.sh
View file

@ -15759,8 +15759,8 @@ tf="$td/test$N.stdout"
te="$td/test$N.stderr" te="$td/test$N.stderr"
tdiff="$td/test$N.diff" tdiff="$td/test$N.diff"
da="test$N $(date) $RANDOM" da="test$N $(date) $RANDOM"
CMD0="$TRACE $SOCAT $opts -t $T4 TCP4-LISTEN:$PORT,reuseaddr EXEC:'$FILAN -s',nofork" CMD0="$TRACE $SOCAT $opts TCP4-LISTEN:$PORT,reuseaddr EXEC:'$FILAN -s',nofork"
CMD1="$TRACE $SOCAT $opts - TCP4:localhost:$PORT" CMD1="$TRACE $SOCAT $opts -t $T4 - TCP4:localhost:$PORT"
printf "test $F_n $TEST... " $N printf "test $F_n $TEST... " $N
eval "$CMD0" >/dev/null 2>"${te}0" & eval "$CMD0" >/dev/null 2>"${te}0" &
pid0=$! pid0=$!
@ -19442,7 +19442,8 @@ else
$CMD0 >/dev/null 2>"${te}0" & $CMD0 >/dev/null 2>"${te}0" &
pid0=$! pid0=$!
waittcp4port $tp 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=$? rc1=$?
kill $pid0 2>/dev/null kill $pid0 2>/dev/null
wait 2>/dev/null wait 2>/dev/null
@ -19915,6 +19916,27 @@ esac
N=$((N+1)) 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 # end of common tests
################################################################################## ##################################################################################

256
xio-ip.c
View file

@ -161,6 +161,208 @@ int Res_init(void) {
#endif /* HAVE_RESOLV_H */ #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 /* the ultimate(?) socat resolver function
node: the address to be resolved; supported forms: node: the address to be resolved; supported forms:
1.2.3.4 (IPv4 address) 1.2.3.4 (IPv4 address)
@ -171,7 +373,7 @@ int Res_init(void) {
family: PF_INET, PF_INET6, or PF_UNSPEC permitting both family: PF_INET, PF_INET6, or PF_UNSPEC permitting both
socktype: SOCK_STREAM, SOCK_DGRAM, ... socktype: SOCK_STREAM, SOCK_DGRAM, ...
protocol: IPPROTO_UDP, IPPROTO_TCP 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 returns: STAT_OK, STAT_RETRYLATER, STAT_NORETRY, prints message
*/ */
int xiogetaddrinfo(const char *node, const char *service, int xiogetaddrinfo(const char *node, const char *service,
@ -186,8 +388,11 @@ int xiogetaddrinfo(const char *node, const char *service,
#endif #endif
int error_num; 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') { if (service && service[0]=='\0') {
Error("empty port/service"); Error("xiogetaddrinfo(): empty port and service");
} }
#if LATER #if LATER
@ -202,6 +407,48 @@ int xiogetaddrinfo(const char *node, const char *service,
#endif /* WITH_VSOCK */ #endif /* WITH_VSOCK */
#endif /* LATER */ #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 /* the resolver functions might handle numeric forms of node names by
reverse lookup, that's not what we want. reverse lookup, that's not what we want.
So we detect these and handle them specially */ 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) { void xiofreeaddrinfo(struct addrinfo *res) {
#if WITH_DEVTESTS
if (!xioip_freeaddrinfo_devtests(res)) {
return;
}
#endif
#if HAVE_GETADDRINFO #if HAVE_GETADDRINFO
freeaddrinfo(res); freeaddrinfo(res);
#else #else

View file

@ -3338,7 +3338,7 @@ int retropt_bind(struct opt *opts,
return STAT_NORETRY; return STAT_NORETRY;
} }
break; break;
#endif /* WITH_IP4 || WITH_IP6 */ #endif /* WITH_IP4 || WITH_IP6 || WITH_VSOCK */
#if WITH_UNIX #if WITH_UNIX
case AF_UNIX: case AF_UNIX:
@ -3367,6 +3367,52 @@ int retropt_bind(struct opt *opts,
} }
return STAT_OK; 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 */ #endif /* _WITH_SOCKET */
@ -4196,7 +4242,8 @@ int applyopts_fchown(int fd, struct opt *opts) {
return 0; 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) { static int applyopt_offset(struct single *sfd, struct opt *opt) {
unsigned char *ptr; unsigned char *ptr;