Cygwin: bindresvport: Try hard to find unused port

Workaround the problem that bind doesn't fail with EADDRINUSE
if a socket with the same local address is still in TIME_WAIT.

Use IP Helper functions to check if such a socket exist and don't
even try this port, if so.

Signed-off-by: Corinna Vinschen <corinna@vinschen.de>
This commit is contained in:
Corinna Vinschen 2018-02-05 21:05:09 +01:00
parent 2f61f65601
commit e9ff2d6978
2 changed files with 84 additions and 8 deletions

View File

@ -573,6 +573,8 @@ LoadDLLfunc (GetIfEntry, 4, iphlpapi)
LoadDLLfunc (GetIpAddrTable, 12, iphlpapi) LoadDLLfunc (GetIpAddrTable, 12, iphlpapi)
LoadDLLfunc (GetIpForwardTable, 12, iphlpapi) LoadDLLfunc (GetIpForwardTable, 12, iphlpapi)
LoadDLLfunc (GetNetworkParams, 8, iphlpapi) LoadDLLfunc (GetNetworkParams, 8, iphlpapi)
LoadDLLfunc (GetTcpTable, 12, iphlpapi)
LoadDLLfunc (GetTcp6Table, 12, iphlpapi)
LoadDLLfunc (GetUdpTable, 12, iphlpapi) LoadDLLfunc (GetUdpTable, 12, iphlpapi)
LoadDLLfunc (if_indextoname, 8, iphlpapi) LoadDLLfunc (if_indextoname, 8, iphlpapi)
LoadDLLfunc (if_nametoindex, 4, iphlpapi) LoadDLLfunc (if_nametoindex, 4, iphlpapi)

View File

@ -2461,6 +2461,66 @@ if_freenameindex (struct if_nameindex *ptr)
#define PORT_HIGH (IPPORT_RESERVED - 1) #define PORT_HIGH (IPPORT_RESERVED - 1)
#define NUM_PORTS (PORT_HIGH - PORT_LOW + 1) #define NUM_PORTS (PORT_HIGH - PORT_LOW + 1)
/* port is in host byte order */
static in_port_t
next_free_port (sa_family_t family, in_port_t in_port)
{
DWORD ret;
ULONG size = 0;
char *tab = NULL;
PMIB_TCPTABLE tab4 = NULL;
PMIB_TCP6TABLE tab6 = NULL;
/* Start testing with incoming port number. */
in_port_t tst_port = in_port;
in_port_t res_port = 0;
in_port_t tab_port;
do
{
if (family == AF_INET)
ret = GetTcpTable ((PMIB_TCPTABLE) tab, &size, TRUE);
else
ret = GetTcp6Table ((PMIB_TCP6TABLE) tab, &size, TRUE);
if (ret == ERROR_INSUFFICIENT_BUFFER)
tab = (char *) realloc (tab, size);
}
while (ret == ERROR_INSUFFICIENT_BUFFER);
tab4 = (PMIB_TCPTABLE) tab;
tab6 = (PMIB_TCP6TABLE) tab;
/* dwNumEntries has offset 0 in both structs. */
for (int idx = tab4->dwNumEntries - 1; idx >= 0; --idx)
{
if (family == AF_INET)
tab_port = ntohs (tab4->table[idx].dwLocalPort);
else
tab_port = ntohs (tab6->table[idx].dwLocalPort);
/* Skip table entries with too high port number. */
if (tab_port > tst_port)
continue;
/* Is the current port number free? */
if (tab_port < tst_port)
{
res_port = tst_port;
break;
}
/* Decrement port and handle underflow of the reserved area. */
if (--tst_port < PORT_LOW)
{
tst_port = PORT_HIGH;
idx = tab4->dwNumEntries;
}
/* Check if we're round to the incoming port. */
if (tst_port == in_port)
break;
}
free (tab);
return res_port;
}
extern "C" int extern "C" int
cygwin_bindresvport_sa (int fd, struct sockaddr *sa) cygwin_bindresvport_sa (int fd, struct sockaddr *sa)
{ {
@ -2469,6 +2529,7 @@ cygwin_bindresvport_sa (int fd, struct sockaddr *sa)
struct sockaddr_in6 *sin6 = NULL; struct sockaddr_in6 *sin6 = NULL;
socklen_t salen; socklen_t salen;
int ret = -1; int ret = -1;
LONG port, next_port;
__try __try
{ {
@ -2507,21 +2568,34 @@ cygwin_bindresvport_sa (int fd, struct sockaddr *sa)
but that may lead to EADDRINUSE scenarios when calling bindresvport but that may lead to EADDRINUSE scenarios when calling bindresvport
on the client side. So we ignore any port value that the caller on the client side. So we ignore any port value that the caller
supplies, just like glibc. */ supplies, just like glibc. */
LONG myport;
/* Note that repeating this loop NUM_PORTS times is arbitrary. The
job is mainly done by next_free_port() but it doesn't cover bound
sockets. And calling and checking GetTcpTable and subsequently
calling bind is inevitably racy. We have to continue if bind fails
with EADDRINUSE. */
for (int i = 0; i < NUM_PORTS; i++) for (int i = 0; i < NUM_PORTS; i++)
{ {
while ((myport = InterlockedExchange ( while ((port = InterlockedExchange (
&cygwin_shared->last_used_bindresvport, -1)) == -1) &cygwin_shared->last_used_bindresvport, -1)) == -1)
yield (); yield ();
if (myport == 0 || --myport < PORT_LOW) next_port = port;
myport = PORT_HIGH; if (next_port == 0 || --next_port < PORT_LOW)
InterlockedExchange (&cygwin_shared->last_used_bindresvport, myport); next_port = PORT_HIGH;
/* Returns 0 if no reserved port is free. */
next_port = next_free_port (sa->sa_family, next_port);
if (next_port)
port = next_port;
InterlockedExchange (&cygwin_shared->last_used_bindresvport, port);
if (next_port == 0)
{
set_errno (EADDRINUSE);
break;
}
if (sa->sa_family == AF_INET6) if (sa->sa_family == AF_INET6)
sin6->sin6_port = htons (myport); sin6->sin6_port = htons (port);
else else
sin->sin_port = htons (myport); sin->sin_port = htons (port);
if (!(ret = fh->bind (sa, salen))) if (!(ret = fh->bind (sa, salen)))
break; break;
if (get_errno () != EADDRINUSE && get_errno () != EINVAL) if (get_errno () != EADDRINUSE && get_errno () != EINVAL)