Subject: Quick hack to implement gethostbyname_r() through gethostbyname()+mutex lock
From: "Enzo Michelangeli" <[email-masked]>
To: <[email-masked]>
Date: Thu, 15 Apr 2004 20:49:12 +0800

Another self-followup :-)

----- Original Message ----- 
From: "Enzo Michelangeli" <[email-masked]>
To: <[email-masked]>
Cc: ""Brian Ford"" <[email-masked]>
Sent: Thursday, April 15, 2004 12:03 PM
Subject: Re: 1.5.9-1: socket() appears NOT to be thread-safe

> P.S. By the way, Corinna: couldn't I just put my gethostbyname_r() in
> the public domain, rather than going through the bureaucratic chore of
> the copyright assignment? Also because I feel that implementing it
> through mutex-protection of gethostbyname(), as I did, is just a quick
> hack, as it unnecessarily blocks other threads that could access the
> name server in parallel (with separate network I/O and properly
> re-entrant code). It may help other implementors to solve an urgent
> problem, but I don't think it should be released as part of the Cygwin
> code.

Well, OK, here is the code, hereby placed in the public domain. Everybody
can do with it whatever s/he likes; attribution will be appreciated. Of
course, no guarantees etc.

On an unrelated matter: I noticed that when a Cygwin application listens
on a TCP port, it also listens on a random TCP port on 127.0.0.1 . What is
the reason for that? I'm new to cygwin programming, and initially I was
suspecting a bug in my application, but then I saw that netcat was doing
the same thing... According to my Kerio firewall, the program opens a
connection to that port when it receives a signal (either from a Ctrl-C or
from a "kill" issued on another console.).

Enzo

====================================================================

All the code in this message is hereby put in the public domain
(http://www.gnu.org/philosophy/categories.html#PublicDomainSoftware ).

Results of test program (for the code see further below):
-------------------------------------------
em@emnb ~/unsafesocket
$ cc -o gh ghmain.c gethostbyname_r.c

em@emnb ~/unsafesocket
$ ./gh n
NOT protecting socket() call with mutex:
Thread 0:
host information for mailin-00.mx.aol.com. not found - h_errno: 1
Thread 1:
mailin-01.mx.aol.com
        205.188.156.185
        205.188.159.57
        64.12.137.89
        64.12.138.57
 Aliases:
Thread 2:
mailin-02.mx.aol.com
        205.188.156.249
        205.188.159.217
        64.12.137.121
        64.12.138.89
 Aliases:
Thread 3:
www.yahoo.akadns.net
        66.94.230.43
        66.94.230.34
        66.94.230.32
        66.94.230.36
        66.94.230.41
        66.94.230.42
        66.94.230.33
        66.94.230.38
 Aliases:
        www.yahoo.com

em@emnb ~/unsafesocket
$
(the results are the same with "./gh y", which protects the call to
gethostbyname_r through an additional mutex lock in the main(). That shows
that gethostbyname_r() is already thread-safe by itself).
-------------------------------------------

--------- begin gethostbyname_r.h ---------
/* including netdb.h suppresses warnings from the use of hostent */
#include <netdb.h>
#ifndef __LINUX__
#define LOCAL_GETHOSTBYNAME_R
int gethostbyname_r (const char *name,
     struct hostent *ret,
     char *buf,
     size_t buflen,
     struct hostent **result,
     int *h_errnop);
#endif /* gethostbyname_r */
---------  end  gethostbyname_r.h ---------

--------- begin gethostbyname_r.c ---------
#include <netdb.h>
#include <sys/socket.h>
#include "gethostbyname_r.h"
#include <string.h>
#include <pthread.h>

#ifdef LOCAL_GETHOSTBYNAME_R
/* duh? ERANGE value copied from web... */
#define ERANGE 34
int gethostbyname_r (const char *name,
     struct hostent *ret,
     char *buf,
     size_t buflen,
     struct hostent **result,
     int *h_errnop) {

 int hsave;
 struct hostent *ph;
 static pthread_mutex_t __mutex = PTHREAD_MUTEX_INITIALIZER;
 pthread_mutex_lock(&__mutex); /* begin critical area */
    hsave = h_errno;
    ph = gethostbyname(name);
    *h_errnop = h_errno; /* copy h_errno to *h_herrnop */
    if (ph == NULL) {
  *result = NULL;
 } else {
  char **p, **q;
  char *pbuf;
  int nbytes=0;
  int naddr=0, naliases=0;
  /* determine if we have enough space in buf */

  /* count how many addresses */
  for (p = ph->h_addr_list; *p != 0; p++) {
   nbytes += ph->h_length; /* addresses */
   nbytes += sizeof(*p); /* pointers */
   naddr++;
  }
  nbytes += sizeof(*p); /* one more for the terminating NULL */

  /* count how many aliases, and total length of strings */

  for (p = ph->h_aliases; *p != 0; p++) {
   nbytes += (strlen(*p)+1); /* aliases */
   nbytes += sizeof(*p);  /* pointers */
   naliases++;
  }
  nbytes += sizeof(*p); /* one more for the terminating NULL */

  /* here nbytes is the number of bytes required in buffer */
  /* as a terminator must be there, the minimum value is ph->h_length */
  if(nbytes > buflen) {
   *result = NULL;
   pthread_mutex_unlock(&__mutex); /* end critical area */
   return ERANGE; /* not enough space in buf!! */
  }

  /* There is enough space. Now we need to do a deep copy! */
  /* Allocation in buffer:
   from [0] to [(naddr-1) * sizeof(*p)]:
       pointers to addresses
   at [naddr * sizeof(*p)]:
       NULL
   from [(naddr+1) * sizeof(*p)] to [(naddr+naliases) * sizeof(*p)] :
       pointers to aliases
   at [(naddr+naliases+1) * sizeof(*p)]:
       NULL
   then naddr addresses (fixed length), and naliases aliases (asciiz).
  */

  *ret = *ph;   /* copy whole structure (not its address!) */

  /* copy addresses */
  q = (char **)buf; /* pointer to pointers area (type: char **) */
  ret->h_addr_list = q; /* update pointer to address list */
  pbuf = buf + ((naddr+naliases+2)*sizeof(*p)); /* skip that area */
  for (p = ph->h_addr_list; *p != 0; p++) {
   memcpy(pbuf, *p, ph->h_length); /* copy address bytes */
   *q++ = pbuf; /* the pointer is the one inside buf... */
   pbuf += ph->h_length; /* advance pbuf */
  }
  *q++ = NULL; /* address list terminator */

  /* copy aliases */

  ret->h_aliases = q; /* update pointer to aliases list */
  for (p = ph->h_aliases; *p != 0; p++) {
   strcpy(pbuf, *p); /* copy alias strings */
   *q++ = pbuf; /* the pointer is the one inside buf... */
   pbuf += strlen(*p); /* advance pbuf */
   *pbuf++ = 0; /* string terminator */
  }
  *q++ = NULL; /* terminator */

  strcpy(pbuf, ph->h_name); /* copy alias strings */
  ret->h_name = pbuf;
  pbuf += strlen(ph->h_name); /* advance pbuf */
  *pbuf++ = 0; /* string terminator */

  *result = ret;  /* and let *result point to structure */

 }
 h_errno = hsave;  /* restore h_errno */

 pthread_mutex_unlock(&__mutex); /* end critical area */

 return (*result == NULL);

}

#endif /* LOCAL_GETHOSTBYNAME_R */
---------  end  gethostbyname_r.c ---------

--------- begin ghmain.c ------------------
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include "gethostbyname_r.h"

#define MAXTHREADS 4

static int sync_with_mutex = 0;

struct thrdata {
 int thrs;
 pthread_t t;
 char *name;
 int gh_status;
 int gh_h_errno;
 struct hostent *gh_ph;
} th[MAXTHREADS];

static void *thread_code(void *p);

main(int argc, char *argv[]) {
 int i;
 char s[80];

 if(argc == 1) {
  printf("usage: %s [n|y]\n", argv[0]);
  exit(1);
 }

 if(argv[1][0] == 'y' || argv[1][0] == 'Y') {
  printf("Protecting socket() call with mutex:\n");
  sync_with_mutex = 1;
 } else {
  printf("NOT protecting socket() call with mutex:\n");
  sync_with_mutex = 0;
 }

 for(i=0; i<MAXTHREADS; i++)
 {
  if(i < MAXTHREADS-1) {
   sprintf(s, "mailin-%02d.mx.aol.com.", i);
   th[i].name = strdup(s);  // freed at exit...
  } else // illustrates CNAME
   th[i].name = strdup("www.yahoo.com");  // freed at exit...

  pthread_create(&th[i].t, NULL, thread_code, (void *)&(th[i]));
 }

 for(i=0; i<MAXTHREADS; i++) {
  pthread_join(th[i].t, NULL);
 }

 for(i=0; i<MAXTHREADS; i++) {
  struct hostent *hp;
  char **p;

  hp = th[i].gh_ph;
  printf("Thread %d: \n", i);
  if (hp == NULL) {
   printf("host information for %s not found - h_errno: %d\n",
     th[i].name, th[i].gh_h_errno);
   continue;
  }
  printf("%s\n", hp->h_name);
  for (p = hp->h_addr_list; *p != 0; p++) {
   struct in_addr in;

   memcpy(&in.s_addr, *p, sizeof (in.s_addr));
   printf("\t%s\n", inet_ntoa(in));
  }

  printf(" Aliases:\n");
  for (p = hp->h_aliases; *p != 0; p++) {
   (void) printf("\t%s", *p);
   putchar('\n');
  }
 }

 exit(0);
}

static void *thread_code(void *p) {
 struct thrdata *pt = (struct thrdata *)p;
 struct hostent *phe = malloc(sizeof(struct hostent)); // freed at exit...
 char *buf = malloc(4096); // freed at exit...
 struct hostent *result;
 int local_h_errno;
 int status;

 static pthread_mutex_t __mutex = PTHREAD_MUTEX_INITIALIZER;

 if(sync_with_mutex) {
  pthread_mutex_lock(&__mutex); /* begin critical area */
 }

 status = gethostbyname_r(pt->name, phe, buf,
       4096, &result, &local_h_errno);

 if(sync_with_mutex) {
  pthread_mutex_unlock(&__mutex); /* end critical area */
 }

 /* report results to main thread */
 pt->gh_status = status;
 pt->gh_h_errno = local_h_errno;
 pt->gh_ph = result;

 return(NULL);
}
---------  end  ghmain.c ------------------

--
Unsubscribe info:      http://cygwin.com/ml/#unsubscribe-simple
Problem reports:       http://cygwin.com/problems.html
Documentation:         http://cygwin.com/docs.html
FAQ:                   http://cygwin.com/faq/