Implemented asynchronous DNS lookups in nmbd.
authorAndrew Tridgell <tridge@samba.org>
Wed, 22 Oct 1997 11:02:00 +0000 (11:02 +0000)
committerAndrew Tridgell <tridge@samba.org>
Wed, 22 Oct 1997 11:02:00 +0000 (11:02 +0000)
I realised this afternoon just how easy it is to add this, so I
thought I'd implement it while the idea was fresh.

nmbd forks at startup and uses a pipe to talk to its child. The child
does the DNS lookups and the file descriptor of the child is added to
the main select loop.

While I was doing this I discovered a bug in nmbd that explains why
the dns proxy option has been so expensive. The DNS cache entries in
the WINS list were never being checked, which means we always did a
DNS lookup even if we have done it before and it is in cache. I'm sure
this used to work (I tested the DNS cache when I added it) so someone
broke it :-(

Anyway, the async DNS gets rid of the problem completely. I'll commit
just the fix to the DNS cache bug to the 1.9.17 tree.

You can disable async DNS by adding -DSYNC_DNS to the compile flags.
(This used to be commit 178e27de0791c1ff3268cb456ed5c5efc9ac2a01)

source3/include/nameserv.h
source3/libsmb/nmblib.c
source3/namedbname.c
source3/namepacket.c
source3/nameservreply.c
source3/nmbd/asyncdns.c [new file with mode: 0644]
source3/nmbd/nmbd.c
source3/utils/nmblookup.c

index ecd19b9563475777cdfed493af4cb327c847c536..5c8ec1e4ebdc3114c916312921f04fbd8c2d40dc 100644 (file)
@@ -363,17 +363,18 @@ struct dgram_packet {
  list of nmb packets */
 struct packet_struct
 {
-  struct packet_struct *next;
-  struct packet_struct *prev;
-  struct in_addr ip;
-  int port;
-  int fd;
-  time_t timestamp;
-  enum packet_type packet_type;
-  union {
-    struct nmb_packet nmb;
-    struct dgram_packet dgram;
-  } packet;
+       struct packet_struct *next;
+       struct packet_struct *prev;
+       BOOL locked;
+       struct in_addr ip;
+       int port;
+       int fd;
+       time_t timestamp;
+       enum packet_type packet_type;
+       union {
+               struct nmb_packet nmb;
+               struct dgram_packet dgram;
+       } packet;
 };
 
 /* NETLOGON opcodes */
index e8f281bc257de1801890f2cf0a6c4ddadfd9adc7..121008685b9a0cb25a4c81432e1c4f3b8f60dc9c 100644 (file)
@@ -482,9 +482,11 @@ void free_nmb_packet(struct nmb_packet *nmb)
   ******************************************************************/
 void free_packet(struct packet_struct *packet)
 {  
-  if (packet->packet_type == NMB_PACKET)
-    free_nmb_packet(&packet->packet.nmb);
-  free(packet);
+       if (packet->locked) 
+               return;
+       if (packet->packet_type == NMB_PACKET)
+               free_nmb_packet(&packet->packet.nmb);
+       free(packet);
 }
 
 /*******************************************************************
@@ -511,6 +513,7 @@ struct packet_struct *read_packet(int fd,enum packet_type packet_type)
   packet->ip = lastip;
   packet->port = lastport;
   packet->fd = fd;
+  packet->locked = False;
   packet->timestamp = time(NULL);
   packet->packet_type = packet_type;
   switch (packet_type) 
index 51571d786a86616406642f328d394665a891e8d2..f126b4651c90dcb1094aad5b6e640fbd8d843039 100644 (file)
@@ -166,8 +166,8 @@ struct name_record *find_name(struct name_record *n,
       {
         continue;
       }
-      DEBUG(9,("find_name: found name %s(%02x)\n", 
-                name->name, name->name_type));
+      DEBUG(9,("find_name: found name %s(%02x) source=%d\n", 
+                name->name, name->name_type, ret->source));
       return ret;
     }
   }
@@ -185,8 +185,8 @@ struct name_record *find_name(struct name_record *n,
   FIND_WINS - look for names in the WINS record
   **************************************************************************/
 struct name_record *find_name_search(struct subnet_record **d,
-                       struct nmb_name *name,
-                       int search, struct in_addr ip)
+                                    struct nmb_name *name,
+                                    int search, struct in_addr ip)
 {
   if (d == NULL) return NULL; /* bad error! */
        
@@ -558,44 +558,3 @@ void expire_names(time_t t)
 }
 
 
-/***************************************************************************
-  assume a WINS name is a dns name, and do a gethostbyname() on it.
-  ****************************************************************************/
-struct name_record *dns_name_search(struct nmb_name *question, int Time)
-{
-  int name_type = question->name_type;
-  char *qname = question->name;
-  BOOL dns_type = (name_type == 0x20 || name_type == 0);
-  struct in_addr dns_ip;
-
-  if (wins_subnet == NULL) 
-    return NULL;
-
-  DEBUG(3,("Search for %s - ", namestr(question)));
-
-  /* only do DNS lookups if the query is for type 0x20 or type 0x0 */
-  if (!dns_type)
-  {
-    DEBUG(3,("types 0x20 0x0 only: name not found\n"));
-    return NULL;
-  }
-
-  /* look it up with DNS */      
-  dns_ip.s_addr = interpret_addr(qname);
-
-  if (!dns_ip.s_addr)
-  {
-    /* no luck with DNS. We could possibly recurse here XXXX */
-    DEBUG(3,("not found. no recursion.\n"));
-    /* add the fail to WINS cache of names. give it 1 hour in the cache */
-    add_netbios_entry(wins_subnet,qname,name_type,NB_ACTIVE,60*60,DNSFAIL,dns_ip,
-                  True, True);
-    return NULL;
-  }
-
-  DEBUG(3,("found with DNS: %s\n", inet_ntoa(dns_ip)));
-
-  /* add it to our WINS cache of names. give it 2 hours in the cache */
-  return add_netbios_entry(wins_subnet,qname,name_type,NB_ACTIVE,2*60*60,DNS,dns_ip,
-                         True,True);
-}
index 3a23806a9c4cb808f5edcf2656e13e03a7489c81..c510c2116992663b4f5c63c394a9b36ef8b10b97 100644 (file)
@@ -171,6 +171,7 @@ void initiate_netbios_packet(uint16 *id,
   p.fd = fd;
   p.timestamp = time(NULL);
   p.packet_type = NMB_PACKET;
+  p.locked = False;
   
   debug_nmb_packet(&p);
   
@@ -482,32 +483,31 @@ static void process_nmb(struct packet_struct *p)
   ******************************************************************/
 void run_packet_queue()
 {
-  struct packet_struct *p;
-
-  while ((p=packet_queue))
-    {
-      switch (p->packet_type)
-       {
-       case NMB_PACKET:
-         process_nmb(p);
-         break;
-         
-       case DGRAM_PACKET:
-         process_dgram(p);
-         break;
+       struct packet_struct *p, *nextp;
+
+       while ((p=packet_queue)) {
+               packet_queue = p->next;
+               if (packet_queue) packet_queue->prev = NULL;
+               p->next = p->prev = NULL;
+
+               switch (p->packet_type) {
+               case NMB_PACKET:
+                       process_nmb(p);
+                       break;
+                       
+               case DGRAM_PACKET:
+                       process_dgram(p);
+                       break;
+               }
+               free_packet(p);
        }
-      
-      packet_queue = packet_queue->next;
-      if (packet_queue) packet_queue->prev = NULL;
-      free_packet(p);
-    }
 }
 
+
 /****************************************************************************
   Create an fd_set containing all the sockets in the subnet structures,
   plus the broadcast sockets.
   ***************************************************************************/
-
 static BOOL create_listen_fdset(fd_set **ppset, int **psock_array, int *listen_number)
 {
   int *sock_array = NULL;
@@ -582,6 +582,9 @@ BOOL listen_for_packets(BOOL run_election)
   fd_set fds;
   int selrtn;
   struct timeval timeout;
+#ifndef SYNC_DNS
+  int dns_fd;
+#endif
 
   if(listen_set == NULL)
   {
@@ -594,6 +597,14 @@ BOOL listen_for_packets(BOOL run_election)
 
   memcpy((char *)&fds, (char *)listen_set, sizeof(fd_set));
 
+#ifndef SYNC_DNS
+  dns_fd = asyncdns_fd();
+  if (dns_fd != -1) {
+         FD_SET(dns_fd, &fds);
+  }
+#endif
+
+
   /* during elections and when expecting a netbios response packet we
   need to send election packets at tighter intervals 
 
@@ -612,6 +623,12 @@ BOOL listen_for_packets(BOOL run_election)
   {
     int i;
 
+#ifndef SYNC_DNS
+    if (dns_fd != -1 && FD_ISSET(dns_fd,&fds)) {
+           run_dns_queue();
+    }
+#endif
+
     for(i = 0; i < listen_number; i++)
     {
       if(i < (listen_number/2))
index c901059f9b64664d6e38b5991ffeb508be2fa099..6585a022611a59357b6cdc5fc0de35c71bb38139 100644 (file)
@@ -569,23 +569,31 @@ void reply_name_query(struct packet_struct *p)
     /* look up the name in the cache */
     n = find_name_search(&d, question, FIND_LOCAL, p->ip);
 
+    /* check for a previous DNS lookup */
+    if (!n && (n = find_name_search(&d, question, FIND_WINS, p->ip))) {
+           if (n->source != DNS && n->source != DNSFAIL) {
+                   n = NULL;
+           } else {
+                   DEBUG(5,("Found DNS cache entry %s\n", namestr(&n->name)));
+           }
+    }
+
     /* it is a name that already failed DNS lookup or it's expired */
     if (n && (n->source == DNSFAIL ||
-              (n->death_time && n->death_time < p->timestamp)))
-    {
-      success = False;
+              (n->death_time && n->death_time < p->timestamp))) {
+           DEBUG(5,("expired name %s\n", namestr(&n->name)));
+           success = False;
     }
+
    
     /* do we want to do dns lookups? */
-    /* XXXX this DELAYS nmbd while it does a search.  lp_dns_proxy()
-       can be switched off, to ensure that the blocking doesn't occur.
-       a better solution would be to fork, but this will require a
-       mechanism to carry on processing after the query is resolved
-       (similar to the netbios queue).
-     */
-    if (success && !n && (lp_dns_proxy() || !bcast))
-    {
-      n = dns_name_search(question, p->timestamp);
+    if (success && !n && (lp_dns_proxy() || !bcast)) {
+           BOOL dns_type = (name_type == 0x20 || name_type == 0);
+           if (dns_type && wins_subnet) {
+                   /* add it to the dns name query queue */
+                   if (queue_dns_query(p, question, &n))
+                           return;
+           }
     }
   }
 
diff --git a/source3/nmbd/asyncdns.c b/source3/nmbd/asyncdns.c
new file mode 100644 (file)
index 0000000..548781e
--- /dev/null
@@ -0,0 +1,257 @@
+/*
+   Unix SMB/Netbios implementation.
+   Version 1.9.
+   a async DNS handler
+   Copyright (C) Andrew Tridgell 1994-1997
+   
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+   
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+   
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+   
+   Revision History:
+
+   14 jan 96: lkcl@pires.co.uk
+   added multiple workgroup domain master support
+
+*/
+
+#include "includes.h"
+
+extern int DEBUGLEVEL;
+
+
+/***************************************************************************
+  add a DNS result to the name cache
+  ****************************************************************************/
+static struct name_record *add_dns_result(struct nmb_name *question, struct in_addr addr)
+{
+       int name_type = question->name_type;
+       char *qname = question->name;
+
+       if (!addr.s_addr) {
+               /* add the fail to WINS cache of names. give it 1 hour in the cache */
+               DEBUG(3,("Negative DNS answer for %s\n", qname));
+               add_netbios_entry(wins_subnet,qname,name_type,NB_ACTIVE,60*60,DNSFAIL,addr,
+                                 True, True);
+               return NULL;
+       }
+
+       /* add it to our WINS cache of names. give it 2 hours in the cache */
+       DEBUG(3,("DNS gave answer for %s of %s\n", qname, inet_ntoa(addr)));
+
+       return add_netbios_entry(wins_subnet,qname,name_type,NB_ACTIVE,2*60*60,DNS,addr,
+                                True,True);
+}
+
+
+
+#ifndef SYNC_DNS
+
+static int fd_in = -1, fd_out = -1;
+static int child_pid = -1;
+static int in_dns;
+
+/* this is the structure that is passed between the parent and child */
+struct query_record {
+       struct nmb_name name;
+       struct in_addr result;
+};
+
+/* a queue of pending requests waiting for DNS responses */
+static struct packet_struct *dns_queue;
+
+
+
+/***************************************************************************
+  return the fd used to gather async dns replies. This is added to the select
+  loop
+  ****************************************************************************/
+int asyncdns_fd(void)
+{
+       return fd_in;
+}
+
+/***************************************************************************
+  handle DNS queries arriving from the parent
+  ****************************************************************************/
+static void asyncdns_process(void)
+{
+       struct query_record r;
+       fstring qname;
+
+       DEBUGLEVEL = 0;
+
+       while (1) {
+               if (read_data(fd_in, (char *)&r, sizeof(r)) != sizeof(r)) 
+                       break;
+
+               fstrcpy(qname, r.name.name);
+
+               r.result.s_addr = interpret_addr(qname);
+
+               if (write_data(fd_out, (char *)&r, sizeof(r)) != sizeof(r))
+                       break;
+       }
+
+       exit(0);
+}
+
+
+/***************************************************************************
+  create a child process to handle DNS lookups
+  ****************************************************************************/
+void start_async_dns(void)
+{
+       int fd1[2], fd2[2];
+
+       signal(SIGCLD, SIG_IGN);
+
+       if (pipe(fd1) || pipe(fd2)) {
+               return;
+       }
+
+       child_pid = fork();
+
+       if (child_pid) {
+               fd_in = fd1[0];
+               fd_out = fd2[1];
+               close(fd1[1]);
+               close(fd2[0]);
+               DEBUG(3,("async DNS initialised\n"));
+               return;
+       }
+
+       fd_in = fd2[0];
+       fd_out = fd1[1];
+
+       asyncdns_process();
+}
+
+
+/***************************************************************************
+check if a particular name is already being queried
+  ****************************************************************************/
+static BOOL query_in_queue(struct query_record *r)
+{
+       struct packet_struct *p;
+       for (p = dns_queue; p; p = p->next) {
+               struct nmb_packet *nmb = &p->packet.nmb;
+               struct nmb_name *question = &nmb->question.question_name;
+
+               if (name_equal(question, &r->name)) 
+                       return True;
+       }
+       return False;
+}
+
+
+/***************************************************************************
+  check the DNS queue
+  ****************************************************************************/
+void run_dns_queue(void)
+{
+       struct query_record r;
+       struct packet_struct *p, *p2;
+
+       if (fd_in == -1)
+               return;
+
+       if (read_data(fd_in, (char *)&r, sizeof(r)) != sizeof(r)) {
+               DEBUG(0,("Incomplete DNS answer from child!\n"));
+               fd_in = -1;
+               return;
+       }
+
+       add_dns_result(&r.name, r.result);
+
+       /* loop over the whole dns queue looking for entries that
+          match the result we just got */
+       for (p = dns_queue; p;) {
+               struct nmb_packet *nmb = &p->packet.nmb;
+               struct nmb_name *question = &nmb->question.question_name;
+
+               if (name_equal(question, &r.name)) {
+                       DEBUG(3,("DNS calling reply_name_query\n"));
+                       in_dns = 1;
+                       reply_name_query(p);
+                       in_dns = 0;
+                       p->locked = False;
+
+                       if (p->prev)
+                               p->prev->next = p->next;
+                       else
+                               dns_queue = p->next;
+                       if (p->next)
+                               p->next->prev = p->prev;
+                       p2 = p->next;
+                       free_packet(p);
+                       p = p2;
+               } else {
+                       p = p->next;
+               }
+       }
+
+}
+
+/***************************************************************************
+queue a DNS query
+  ****************************************************************************/
+BOOL queue_dns_query(struct packet_struct *p,struct nmb_name *question,
+                    struct name_record **n)
+{
+       struct query_record r;
+       
+       if (in_dns || fd_in == -1)
+               return False;
+
+       r.name = *question;
+
+       if (!query_in_queue(&r) && 
+           !write_data(fd_out, (char *)&r, sizeof(r))) {
+               DEBUG(3,("failed to send DNS query to child!\n"));
+               return False;
+       }
+
+       p->locked = True;
+       p->next = dns_queue;
+       p->prev = NULL;
+       if (p->next)
+               p->next->prev = p;
+       dns_queue = p;
+
+
+       DEBUG(3,("added DNS query for %s\n", namestr(question)));
+       return True;
+}
+
+#else
+
+
+/***************************************************************************
+  we use this then we can't do async DNS lookups
+  ****************************************************************************/
+BOOL queue_dns_query(struct packet_struct *p,struct nmb_name *question,
+                    struct name_record **n)
+{
+       int name_type = question->name_type;
+       char *qname = question->name;
+       struct in_addr dns_ip;
+
+       DEBUG(3,("DNS search for %s - ", namestr(question)));
+
+       dns_ip.s_addr = interpret_addr(qname);
+
+       *n = add_dns_result(question, dns_ip);
+       return False;
+}
+#endif
index 047284832f01f024f975b70311c9524d5f930a83..5feeb07c90daa9d124350229896e6496b1c4eac9 100644 (file)
@@ -332,14 +332,6 @@ static void process(void)
 ****************************************************************************/
 static BOOL open_sockets(BOOL isdaemon, int port)
 {
-  struct hostent *hp;
-  /* get host info */
-  if ((hp = Get_Hostbyname(myhostname)) == 0) {
-    DEBUG(0,( "Get_Hostbyname: Unknown host. %s\n",myhostname));
-    return False;
-  }   
-
   /* The sockets opened here will be used to receive broadcast
      packets *only*. Interface specific sockets are opened in
      make_subnet() in namedbsubnet.c. Thus we bind to the
@@ -598,6 +590,10 @@ static void usage(char *pname)
     become_daemon();
   }
 
+#ifndef SYNC_DNS
+  start_async_dns();
+#endif
+
   if (*pidFile)
     {
       int     fd;
@@ -653,6 +649,7 @@ static void usage(char *pname)
 
   /* We can only take sigterm signals in the select. */
   BlockSignals(True,SIGTERM);
+
   process();
   close_sockets();
 
index 63ca156449f062a28283f400d15ad7480aa3f676..d26d19969578bd8a0238fdb7ef837d366287540a 100644 (file)
@@ -42,15 +42,6 @@ int RootPort = 0;
   **************************************************************************/
 static BOOL open_sockets(void)
 {
-  struct hostent *hp;
-  /* get host info */
-  if ((hp = Get_Hostbyname(myhostname)) == 0) 
-    {
-      DEBUG(0,( "Get_Hostbyname: Unknown host. %s\n",myhostname));
-      return False;
-    }   
-
   ServerFD = open_socket_in( SOCK_DGRAM,
                              (RootPort ? 137 :0),
                              3,