mirror of
https://repo.or.cz/socat.git
synced 2024-12-22 15:32:35 +00:00
Added the optional DEVTESTS feature for developer tests with controlled name resolution to both IPv4 and IPV6 addresses
This commit is contained in:
parent
a86376cd1e
commit
602a54420e
7 changed files with 350 additions and 8 deletions
6
CHANGES
6
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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
10
configure.ac
10
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.
|
||||
|
|
5
socat.c
5
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]);
|
||||
|
|
28
test.sh
28
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
|
||||
|
||||
##################################################################################
|
||||
|
|
256
xio-ip.c
256
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
|
||||
|
|
51
xioopts.c
51
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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue