This is the mail archive of the cygwin-developers@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]

Re: TCP connections can occasionally fail because of a winsock bug

[Get raw message]
>  From: robert bowman <bowman@montana.com>
>  Date: Thu, 15 Nov 2001 20:00:18 -0700
>  
>  For what it's worth, we recently encountered this problem in the ONC RPC 
>  library. The original Sun code, and any revision I've been able to find, 
>  binds a local port even on the TCP protocol.

Ah, but my test case fails even if you remove the "bind" from my test
program.

Also, there was another problem in my test program -- the reason why I
was getting ENOBUFS was because I wasn't calling closesocket() on
sockets which failed to connect() properly.  This also explains why
the program would not reassign a port once it failed once -- the
program had a socket open using that port, so winsock knew not to
reassign it!

I've modified the program to remove the bind() call and all other
extraneous code, as well as to call closesocket() when it needs to,
and now it will run forever, failing sporadically as it goes.  I've
appended the newest version of the test program below.

>  Interestingly enough, on Linux, the bind also fails unless the process has 
>  root priveleges.

This probably means you were trying to bind to a port number lower
than 1025.  Reserved ports can only be bound to by the superuser.

>  I believe the root of the problem is that both the remote host address and 
>  local port are used to determine if the connection is unique.

This shouldn't matter, because the socket implementation should be
smart enough to assign a local port number which is completely unused,
such that there's no chance that it'll conflict with an existing
connection.

Did you ever contact Microsoft about the problems you were having?
Did you get anything useful out of them?

Thanks,

  jik

/*

  This program illustrates a bug in the Windows winsock layer.  This
  bug manifests itself both on Windows NT 4.0 and Windows 2000.  The
  bug is that the winsock layer is apparently willing to assign a
  local port number to a socket which is actually already in use, such
  that when the program later to connect() the socket to a remote
  port, the connection fails with WSAEADDRINUSE.

  When you run the program, it will keep creating and closing socket
  connections over and over again.  It will report errors when a
  connection fails because of an EADDRINUSE error.
  
*/

/*

  With Visual Studio, compile this program as "cl doecho.c
  ws2_32.lib".

  With Cygwin, compile this program with "gcc -mno-cygwin -mwindows
  doecho.c -lws2_32" if you want a native Windows version or "gcc
  doecho.c -lws32_32" for a Cygwin version.  Both the native Windows
  and Cygwin versions illustrate the bug.

  With Linux, compile the program with "gcc doecho.c".  You can then
  run it essentially forever and it will never print any errors, thus
  illustrating that there is a bug in winsock that isn't in Linux.

*/

/*

  Run the program with two arguments -- an IP address in dotted quad
  notation, and a port number.

  Separately from this program, you need to run a listener on the
  specified address and port.  All the listener should do is accept
  and close connections.  Something like the following Perl script
  should do.  Note that the listener can *not* be running on Windows
  -- that seems to interfere with the manifestation of the bug.  The
  listener should be running on a Unix machine of some sort (I used
  Linux).
   
  use Socket;

  ($port = shift) || die;

  socket(ACCEPTOR, AF_INET, SOCK_STREAM, 0) || die;

  $iaddr = INADDR_ANY;
  $sockaddr = sockaddr_in($port, $iaddr);

  setsockopt(ACCEPTOR, SOL_SOCKET, SO_REUSEADDR, pack("l", 1));

  bind(ACCEPTOR, $sockaddr) || die;
  listen(ACCEPTOR, SOMAXCONN) || die;

  while (accept(CLIENT, ACCEPTOR)) {
      close(CLIENT);
  }

*/

#ifndef WIN32
#ifdef _MSC_VER
#define WIN32
#endif
#endif

#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#ifdef WIN32
#include <winsock2.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#endif

#ifndef WIN32
#define wsaperror perror
#else
void wsaperror(char *);
#endif

main(int argc, char *argv[])
{
#ifdef WIN32
  WSADATA wsaData;
#endif
  int succeeded = 0;
  struct sockaddr_in addr = {0};
  int ret;
  char *endptr = NULL;

  if (argc != 3) {
    fprintf(stderr, "Must specify IP address and port arguments\n");
    exit(1);
  }

  addr.sin_family = AF_INET;

  if ((addr.sin_addr.s_addr = inet_addr(argv[1])) == INADDR_NONE) {
    fprintf(stderr, "First argument must be IP address\n");
    exit(1);
  }

  addr.sin_port = strtol(argv[2], &endptr, 10);
  if (! (*argv[2] && endptr && ! *endptr)) {
    fprintf(stderr, "Second argument must be port number\n");
    exit(1);
  }
  addr.sin_port = htons(addr.sin_port);

#ifdef WIN32
  if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
    wsaperror("WSAStartup");
    exit(1);
  }
#endif

  while (1) {
    int s; 
    struct sockaddr_in local_addr = {0};
#ifdef WIN32
    int name_len = sizeof(local_addr);
#else
    socklen_t name_len = sizeof(local_addr);
#endif

    if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
      wsaperror("socket");
      break;
    }

    ret = connect(s, (struct sockaddr *) &addr, sizeof(addr));

    if (getsockname(s, (struct sockaddr *) &local_addr, &name_len) < 0)
      wsaperror("getsockname");
    else if (ret < 0)
      fprintf(stderr, "Local port failed: %d\n", ntohs(local_addr.sin_port));

    if (ret < 0) {
      wsaperror("connect");
#ifdef WIN32
      closesocket(s);
#else
      close(s);
#endif
      continue;
    }

    if (send(s, "foo\n", 4, 0) < 0) {
      wsaperror("send");
      break;
    }

#ifdef WIN32
    closesocket(s);
#else
    close(s);
#endif

    fprintf(stderr, "%d\n", ntohs(local_addr.sin_port));
    fflush(stdout);
  }

#ifdef WIN32
  WSACleanup();
#endif
  exit(1);
}

#ifdef WIN32
void wsaperror(char *str) 
{
  int err = WSAGetLastError();
  char *errstr;

  switch (err) {
  case WSAEADDRINUSE:
    errstr = "WSAEADDRINUSE";
    break;
  case WSAENOBUFS:
    errstr = "WSAENOBUFS";
    break;
  default:
    errstr = "Unknown error";
    break;
  }
  
  fprintf(stderr, "%s: Error %d (%s)\n", str, err, errstr);
}
#endif


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