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.
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

View file

@ -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

View file

@ -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.

View file

@ -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]);

28
test.sh
View file

@ -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
##################################################################################

256
xio-ip.c
View file

@ -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

View file

@ -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;