Combined Bind() calls in xiobind(); fixed UDP-SENDTO lowport bug

This commit is contained in:
Gerhard Rieger 2022-07-27 09:17:04 +02:00
parent 56a56e127c
commit d8ee49007e
8 changed files with 194 additions and 180 deletions

View file

@ -27,6 +27,10 @@ Corrections:
The rawer option failed because it tried to clear CREAD.
Test: RAWER
UDP-SEND and UPD-SENDTO with option lowport always bound to port 1
instead of a free port in range 640..1023
Test: UDP_LOWPORT
Porting:
OpenSSL, at least 1.1 on Ubuntu, crashed with SIGSEGV under certain
conditions: client connection to server with certificate with empty

41
test.sh
View file

@ -15496,6 +15496,47 @@ esac
PORT=$((PORT+1))
N=$((N+1))
# Up to 1.7.4.3 there was a bug with the lowport option:
# Active addresses UDP-SEND, UDP-SENDTO always bound to port 1 instead of
# 640..1023
NAME=UDP_LOWPORT
case "$TESTS" in
*%$N%*|*%functions%*|*%bugs%*|*%socket%*|*%$NAME%*)
TEST="$NAME: UDP4-SEND with lowport"
# Run Socat with UDP4-SEND:...,lowport and full logging and check the
# parameters of bind() call. It port is in the range 640..1023 the test
# succeeded.
if ! eval $NUMCOND; then :; else
tf="$td/test$N.stdout"
te="$td/test$N.stderr"
tdiff="$td/test$N.diff"
da="test$N $(date) $RANDOM"
CMD="$TRACE $SOCAT $opts -d -d -d -d /dev/null UDP4-SENDTO:$LOCALHOST:$PORT,lowport"
printf "test $F_n $TEST... " $N
$CMD >/dev/null 2>"${te}"
rc1=$?
LOWPORT=$(grep 'D bind(.*:' $te |sed 's/.*:\([0-9][0-9]*\),.*/\1/')
#echo "LOWPORT=\"$LOWPORT\"" >&2
#type socat >&2
if [[ $LOWPORT =~ [0-9][0-9]* ]] && [ "$LOWPORT" -ge 640 -a "$LOWPORT" -le 1023 ]; then
$PRINTF "$OK\n"
if [ "$VERBOSE" ]; then
echo "$CMD" >&2
fi
numOK=$((numOK+1))
else
$PRINTF "$FAILED\n"
echo "$CMD" >&2
cat "${te}" >&2
numFAIL=$((numFAIL+1))
listFAIL="$listFAIL $N"
fi
fi # NUMCOND
;;
esac
PORT=$((PORT+1))
N=$((N+1))
# end of common tests
##################################################################################

View file

@ -69,7 +69,7 @@ int _xioopen_interface(const char *ifname,
return
_xioopen_dgram_sendto(needbind?&us:NULL, uslen,
opts, xioflags, xfd, groups, pf, socktype, 0);
opts, xioflags, xfd, groups, pf, socktype, 0, 0);
}
static

View file

@ -143,7 +143,7 @@ int _xioopen_rawip_sendto(const char *hostname, const char *protname,
}
return
_xioopen_dgram_sendto(needbind?&us:NULL, uslen,
opts, xioflags, xfd, groups, *pf, socktype, ipproto);
opts, xioflags, xfd, groups, *pf, socktype, ipproto, 0);
}

View file

@ -63,6 +63,14 @@ xiolog_ancillary_socket(struct cmsghdr *cmsg, int *num,
char *nambuff, int namlen,
char *envbuff, int envlen,
char *valbuff, int vallen);
static int xiobind(
struct single *xfd,
union sockaddr_union *us,
size_t uslen,
struct opt *opts,
int pf,
bool alt,
int level);
#if WITH_GENERICSOCKET
@ -457,7 +465,7 @@ int _xioopen_socket_sendto(const char *pfname, const char *type,
return
_xioopen_dgram_sendto(needbind?&us:NULL, uslen,
opts, xioflags, xfd, groups, pf, socktype, proto);
opts, xioflags, xfd, groups, pf, socktype, proto, 0);
}
@ -775,111 +783,9 @@ int _xioopen_connect(struct single *xfd, union sockaddr_union *us, size_t uslen,
applyopts_cloexec(xfd->fd, opts);
#if WITH_UNIX
if (pf == PF_UNIX && us != NULL) {
applyopts_named(us->un.sun_path, opts, PH_PREOPEN);
if (xiobind(xfd, us, uslen, opts, pf, alt, level) < 0) {
return -1;
}
#endif
applyopts(xfd->fd, opts, PH_PREBIND);
applyopts(xfd->fd, opts, PH_BIND);
#if WITH_TCP || WITH_UDP
if (alt) {
union sockaddr_union sin, *sinp;
unsigned short *port, i, N;
div_t dv;
/* prepare sockaddr for bind probing */
if (us) {
sinp = us;
} else {
if (them->sa_family == AF_INET) {
socket_in_init(&sin.ip4);
#if WITH_IP6
} else {
socket_in6_init(&sin.ip6);
#endif
}
sinp = &sin;
}
if (them->sa_family == AF_INET) {
port = &sin.ip4.sin_port;
#if WITH_IP6
} else if (them->sa_family == AF_INET6) {
port = &sin.ip6.sin6_port;
#endif
} else {
port = 0; /* just to make compiler happy */
}
/* combine random+step variant to quickly find a free port when only
few are in use, and certainly find a free port in defined time even
if there are almost all in use */
/* dirt 1: having tcp/udp code in socket function */
/* dirt 2: using a time related system call for init of random */
{
/* generate a random port, with millisecond random init */
#if 0
struct timeb tb;
ftime(&tb);
srandom(tb.time*1000+tb.millitm);
#else
struct timeval tv;
struct timezone tz;
tz.tz_minuteswest = 0;
tz.tz_dsttime = 0;
if ((result = Gettimeofday(&tv, &tz)) < 0) {
Warn2("gettimeofday(%p, {0,0}): %s", &tv, strerror(errno));
}
srandom(tv.tv_sec*1000000+tv.tv_usec);
#endif
}
dv = div(random(), IPPORT_RESERVED-XIO_IPPORT_LOWER);
i = N = XIO_IPPORT_LOWER + dv.rem;
do { /* loop over lowport bind() attempts */
*port = htons(i);
if (Bind(xfd->fd, &sinp->soa, sizeof(*sinp)) < 0) {
Msg4(errno==EADDRINUSE?E_INFO:level,
"bind(%d, {%s}, "F_Zd"): %s", xfd->fd,
sockaddr_info(&sinp->soa, sizeof(*sinp), infobuff, sizeof(infobuff)),
sizeof(*sinp), strerror(errno));
if (errno != EADDRINUSE) {
Close(xfd->fd);
return STAT_RETRYLATER;
}
} else {
break; /* could bind to port, good, continue past loop */
}
--i; if (i < XIO_IPPORT_LOWER) i = IPPORT_RESERVED-1;
if (i == N) {
Msg(level, "no low port available");
/*errno = EADDRINUSE; still assigned */
Close(xfd->fd);
return STAT_RETRYLATER;
}
} while (i != N);
} else
#endif /* WITH_TCP || WITH_UDP */
if (us) {
#if WITH_UNIX
if (pf == PF_UNIX && us != NULL) {
applyopts_named(us->un.sun_path, opts, PH_PREOPEN);
}
#endif
if (Bind(xfd->fd, &us->soa, uslen) < 0) {
Msg4(level, "bind(%d, {%s}, "F_Zd"): %s",
xfd->fd, sockaddr_info(&us->soa, uslen, infobuff, sizeof(infobuff)),
uslen, strerror(errno));
Close(xfd->fd);
return STAT_RETRYLATER;
}
}
#if WITH_UNIX
if (pf == PF_UNIX && us != NULL) {
applyopts_named(us->un.sun_path, opts, PH_PASTOPEN);
}
#endif
applyopts(xfd->fd, opts, PH_PASTBIND);
applyopts(xfd->fd, opts, PH_CONNECT);
@ -1116,7 +1022,7 @@ int _xioopen_dgram_sendto(/* them is already in xfd->peersa */
union sockaddr_union *us, socklen_t uslen,
struct opt *opts,
int xioflags, xiosingle_t *xfd, unsigned groups,
int pf, int socktype, int ipproto) {
int pf, int socktype, int ipproto, bool alt) {
int level = E_ERROR;
union sockaddr_union la; socklen_t lalen = sizeof(la);
char infobuff[256];
@ -1138,30 +1044,9 @@ int _xioopen_dgram_sendto(/* them is already in xfd->peersa */
applyopts_cloexec(xfd->fd, opts);
#if WITH_UNIX
if (pf == PF_UNIX && us != NULL) {
applyopts_named(us->un.sun_path, opts, PH_PREOPEN);
if (xiobind(xfd, us, uslen, opts, pf, alt, level) < 0) {
return -1;
}
#endif
applyopts(xfd->fd, opts, PH_PREBIND);
applyopts(xfd->fd, opts, PH_BIND);
if (us) {
if (Bind(xfd->fd, &us->soa, uslen) < 0) {
Msg4(level, "bind(%d, {%s}, "F_socklen"): %s",
xfd->fd, sockaddr_info(&us->soa, uslen, infobuff, sizeof(infobuff)),
uslen, strerror(errno));
Close(xfd->fd);
return STAT_RETRYLATER;
}
}
#if WITH_UNIX
if (pf == PF_UNIX && us != NULL) {
applyopts_named(us->un.sun_path, opts, PH_PASTOPEN);
}
#endif
applyopts(xfd->fd, opts, PH_PASTBIND);
/*applyopts(xfd->fd, opts, PH_CONNECT);*/
@ -1333,25 +1218,16 @@ int _xioopen_dgram_recvfrom(struct single *xfd, int xioflags,
applyopts_cloexec(xfd->fd, opts);
applyopts(xfd->fd, opts, PH_PREBIND);
applyopts(xfd->fd, opts, PH_BIND);
if ((us != NULL) && Bind(xfd->fd, us, uslen) < 0) {
Msg4(level, "bind(%d, {%s}, "F_socklen"): %s", xfd->fd,
sockaddr_info(us, uslen, infobuff, sizeof(infobuff)), uslen,
strerror(errno));
Close(xfd->fd);
return STAT_RETRYLATER;
if (xiobind(xfd, (union sockaddr_union *)us, uslen,
opts, pf, 0, level) < 0) {
return -1;
}
applyopts(xfd->fd, opts, PH_PASTBIND);
#if WITH_UNIX
if (pf == AF_UNIX && us != NULL) {
applyopts_named(((struct sockaddr_un *)us)->sun_path, opts, PH_FD);
}
#endif
applyopts(xfd->fd, opts, PH_PASTBIND);
#if WITH_UNIX
if (pf == AF_UNIX && us != NULL) {
/*applyopts_early(((struct sockaddr_un *)us)->sun_path, opts);*/
applyopts_named(((struct sockaddr_un *)us)->sun_path, opts, PH_EARLY);
applyopts_named(((struct sockaddr_un *)us)->sun_path, opts, PH_PREOPEN);
@ -1592,7 +1468,6 @@ int _xioopen_dgram_recv(struct single *xfd, int xioflags,
struct opt *opts, int pf, int socktype, int proto,
int level) {
char *rangename;
char infobuff[256];
if (applyopts_single(xfd, opts, PH_INIT) < 0) return STAT_NORETRY;
@ -1605,26 +1480,13 @@ int _xioopen_dgram_recv(struct single *xfd, int xioflags,
applyopts_cloexec(xfd->fd, opts);
applyopts(xfd->fd, opts, PH_PREBIND);
applyopts(xfd->fd, opts, PH_BIND);
if ((us != NULL) && Bind(xfd->fd, us, uslen) < 0) {
Msg4(level, "bind(%d, {%s}, "F_socklen"): %s", xfd->fd,
sockaddr_info(us, uslen, infobuff, sizeof(infobuff)), uslen,
strerror(errno));
Close(xfd->fd);
return STAT_RETRYLATER;
if (xiobind(xfd, (union sockaddr_union *)us, uslen, opts, pf, 0, level) < 0) {
return -1;
}
#if WITH_UNIX
if (pf == AF_UNIX && us != NULL) {
applyopts_named(((struct sockaddr_un *)us)->sun_path, opts, PH_FD);
}
#endif
applyopts_single(xfd, opts, PH_PASTBIND);
applyopts(xfd->fd, opts, PH_PASTBIND);
#if WITH_UNIX
if (pf == AF_UNIX && us != NULL) {
/*applyopts_early(((struct sockaddr_un *)us)->sun_path, opts);*/
applyopts_named(((struct sockaddr_un *)us)->sun_path, opts, PH_EARLY);
applyopts_named(((struct sockaddr_un *)us)->sun_path, opts, PH_PREOPEN);
@ -2262,3 +2124,124 @@ xiosocketpair(struct opt *opts, int pf, int socktype, int proto, int sv[2]) {
}
return result;
}
int xiobind(
struct single *xfd,
union sockaddr_union *us,
size_t uslen,
struct opt *opts,
int pf,
bool alt,
int level)
{
char infobuff[256];
int result;
#if WITH_UNIX
if (pf == PF_UNIX && us != NULL) {
applyopts_named(us->un.sun_path, opts, PH_PREOPEN);
}
#endif
applyopts(xfd->fd, opts, PH_PREBIND);
applyopts(xfd->fd, opts, PH_BIND);
#if WITH_TCP || WITH_UDP
if (alt) {
union sockaddr_union sin, *sinp;
unsigned short *port, i, N;
div_t dv;
/* prepare sockaddr for bind probing */
if (us) {
sinp = us;
} else {
if (pf == AF_INET) {
socket_in_init(&sin.ip4);
#if WITH_IP6
} else {
socket_in6_init(&sin.ip6);
#endif
}
sinp = &sin;
}
if (pf == AF_INET) {
port = &sin.ip4.sin_port;
#if WITH_IP6
} else if (pf == AF_INET6) {
port = &sin.ip6.sin6_port;
#endif
} else {
port = 0; /* just to make compiler happy */
}
/* combine random+step variant to quickly find a free port when only
few are in use, and certainly find a free port in defined time even
if there are almost all in use */
/* dirt 1: having tcp/udp code in socket function */
/* dirt 2: using a time related system call for init of random */
{
/* generate a random port, with millisecond random init */
#if 0
struct timeb tb;
ftime(&tb);
srandom(tb.time*1000+tb.millitm);
#else
struct timeval tv;
struct timezone tz;
tz.tz_minuteswest = 0;
tz.tz_dsttime = 0;
if ((result = Gettimeofday(&tv, &tz)) < 0) {
Warn2("gettimeofday(%p, {0,0}): %s", &tv, strerror(errno));
}
srandom(tv.tv_sec*1000000+tv.tv_usec);
#endif
}
/* Note: IPPORT_RESERVED is from includes, 1024 */
dv = div(random(), IPPORT_RESERVED-XIO_IPPORT_LOWER);
i = N = XIO_IPPORT_LOWER + dv.rem;
do { /* loop over lowport bind() attempts */
*port = htons(i);
if (Bind(xfd->fd, &sinp->soa, sizeof(*sinp)) < 0) {
Msg4(errno==EADDRINUSE?E_INFO:level,
"bind(%d, {%s}, "F_Zd"): %s", xfd->fd,
sockaddr_info(&sinp->soa, sizeof(*sinp), infobuff, sizeof(infobuff)),
sizeof(*sinp), strerror(errno));
if (errno != EADDRINUSE) {
Close(xfd->fd);
return STAT_RETRYLATER;
}
} else {
break; /* could bind to port, good, continue past loop */
}
--i; if (i < XIO_IPPORT_LOWER) i = IPPORT_RESERVED-1;
if (i == N) {
Msg(level, "no low port available");
/*errno = EADDRINUSE; still assigned */
Close(xfd->fd);
return STAT_RETRYLATER;
}
} while (i != N);
} else
#endif /* WITH_TCP || WITH_UDP */
if (us) {
#if WITH_UNIX
if (pf == PF_UNIX && us != NULL) {
applyopts_named(us->un.sun_path, opts, PH_PREOPEN);
}
#endif
if (Bind(xfd->fd, &us->soa, uslen) < 0) {
Msg4(level, "bind(%d, {%s}, "F_Zd"): %s",
xfd->fd, sockaddr_info(&us->soa, uslen, infobuff, sizeof(infobuff)),
uslen, strerror(errno));
Close(xfd->fd);
return STAT_RETRYLATER;
}
}
#if WITH_UNIX
if (pf == PF_UNIX && us != NULL) {
applyopts_named(us->un.sun_path, opts, PH_PASTOPEN);
}
#endif
applyopts(xfd->fd, opts, PH_PASTBIND);
return 0;
}

View file

@ -102,7 +102,7 @@ int _xioopen_dgram_sendto(/* them is already in xfd->peersa */
union sockaddr_union *us, socklen_t uslen,
struct opt *opts,
int xioflags, xiosingle_t *xfd, unsigned groups,
int pf, int socktype, int ipproto);
int pf, int socktype, int ipproto, bool alt);
extern
int _xioopen_dgram_recvfrom(struct single *xfd, int xioflags,
struct sockaddr *us, socklen_t uslen,

View file

@ -410,26 +410,12 @@ int _xioopen_udp_sendto(const char *hostname, const char *servname,
}
retropt_bool(opts, OPT_LOWPORT, &xfd->para.socket.ip.lowport);
if (xfd->para.socket.ip.lowport) {
switch (pf) {
#if WITH_IP4
case PF_INET:
/*!!! this is buggy */
us.ip4.sin_port = htons(xfd->para.socket.ip.lowport); break;
#endif
#if WITH_IP6
case PF_INET6:
/*!!! this is buggy */
us.ip6.sin6_port = htons(xfd->para.socket.ip.lowport); break;
#endif
}
needbind = true;
}
xfd->dtype = XIODATA_RECVFROM;
return _xioopen_dgram_sendto(needbind?&us:NULL, uslen,
opts, xioflags, xfd, groups,
pf, socktype, ipproto);
pf, socktype, ipproto,
xfd->para.socket.ip.lowport);
}

View file

@ -411,7 +411,7 @@ static int xioopen_unix_sendto(int argc, const char *argv[], struct opt *opts, i
return
_xioopen_dgram_sendto(needbind?&us:NULL, uslen,
opts, xioflags, xfd, groups,
pf, socktype, protocol);
pf, socktype, protocol, 0);
}
@ -690,7 +690,7 @@ _xioopen_unix_client(xiosingle_t *xfd, int xioflags, unsigned groups,
if ((result =
_xioopen_dgram_sendto(needbind?&us:NULL, uslen,
opts, xioflags, xfd, groups,
pf, SOCK_DGRAM, protocol))
pf, SOCK_DGRAM, protocol, 0))
== 0) {
xfd->dtype = XIODATA_RECVFROM;
break;