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:
parent
2f61f65601
commit
e9ff2d6978
|
@ -573,6 +573,8 @@ LoadDLLfunc (GetIfEntry, 4, iphlpapi)
|
|||
LoadDLLfunc (GetIpAddrTable, 12, iphlpapi)
|
||||
LoadDLLfunc (GetIpForwardTable, 12, iphlpapi)
|
||||
LoadDLLfunc (GetNetworkParams, 8, iphlpapi)
|
||||
LoadDLLfunc (GetTcpTable, 12, iphlpapi)
|
||||
LoadDLLfunc (GetTcp6Table, 12, iphlpapi)
|
||||
LoadDLLfunc (GetUdpTable, 12, iphlpapi)
|
||||
LoadDLLfunc (if_indextoname, 8, iphlpapi)
|
||||
LoadDLLfunc (if_nametoindex, 4, iphlpapi)
|
||||
|
|
|
@ -2461,6 +2461,66 @@ if_freenameindex (struct if_nameindex *ptr)
|
|||
#define PORT_HIGH (IPPORT_RESERVED - 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
|
||||
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;
|
||||
socklen_t salen;
|
||||
int ret = -1;
|
||||
LONG port, next_port;
|
||||
|
||||
__try
|
||||
{
|
||||
|
@ -2507,21 +2568,34 @@ cygwin_bindresvport_sa (int fd, struct sockaddr *sa)
|
|||
but that may lead to EADDRINUSE scenarios when calling bindresvport
|
||||
on the client side. So we ignore any port value that the caller
|
||||
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++)
|
||||
{
|
||||
while ((myport = InterlockedExchange (
|
||||
while ((port = InterlockedExchange (
|
||||
&cygwin_shared->last_used_bindresvport, -1)) == -1)
|
||||
yield ();
|
||||
if (myport == 0 || --myport < PORT_LOW)
|
||||
myport = PORT_HIGH;
|
||||
InterlockedExchange (&cygwin_shared->last_used_bindresvport, myport);
|
||||
|
||||
next_port = port;
|
||||
if (next_port == 0 || --next_port < PORT_LOW)
|
||||
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)
|
||||
sin6->sin6_port = htons (myport);
|
||||
sin6->sin6_port = htons (port);
|
||||
else
|
||||
sin->sin_port = htons (myport);
|
||||
sin->sin_port = htons (port);
|
||||
if (!(ret = fh->bind (sa, salen)))
|
||||
break;
|
||||
if (get_errno () != EADDRINUSE && get_errno () != EINVAL)
|
||||
|
|
Loading…
Reference in New Issue