This is the mail archive of the cygwin-cvs@cygwin.com mailing list for the Cygwin project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[newlib-cygwin] Cygwin: bindresvport: Try hard to find unused port


https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;h=e9ff2d697871eb78f7a4bc246868c855c052a859

commit e9ff2d697871eb78f7a4bc246868c855c052a859
Author: Corinna Vinschen <corinna@vinschen.de>
Date:   Mon Feb 5 21:05:09 2018 +0100

    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>

Diff:
---
 winsup/cygwin/autoload.cc |  2 ++
 winsup/cygwin/net.cc      | 90 ++++++++++++++++++++++++++++++++++++++++++-----
 2 files changed, 84 insertions(+), 8 deletions(-)

diff --git a/winsup/cygwin/autoload.cc b/winsup/cygwin/autoload.cc
index 349f5e7..199821d 100644
--- a/winsup/cygwin/autoload.cc
+++ b/winsup/cygwin/autoload.cc
@@ -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)
diff --git a/winsup/cygwin/net.cc b/winsup/cygwin/net.cc
index cd43347..3fadb2b 100644
--- a/winsup/cygwin/net.cc
+++ b/winsup/cygwin/net.cc
@@ -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)


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]