]> sourceware.org Git - newlib-cygwin.git/commitdiff
Cygwin: bindresvport: Try hard to find unused port
authorCorinna Vinschen <corinna@vinschen.de>
Mon, 5 Feb 2018 20:05:09 +0000 (21:05 +0100)
committerCorinna Vinschen <corinna@vinschen.de>
Mon, 5 Feb 2018 20:05:09 +0000 (21:05 +0100)
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>
winsup/cygwin/autoload.cc
winsup/cygwin/net.cc

index 349f5e7b356cf3fed8781c4f53c1c63194f90a81..199821d9530ccaf796e263e2ac05baec21777bb9 100644 (file)
@@ -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)
index cd4334738fb1addeeeca64a0716a8ccb0a5fdcc5..3fadb2b154bbc715f0db2b4c3aeee0db665c1ce8 100644 (file)
@@ -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)
This page took 0.037994 seconds and 5 git commands to generate.