From e5c319186d079eeef55a7ee62fac2a993e932938 Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Wed, 22 Oct 1997 11:02:00 +0000 Subject: Implemented asynchronous DNS lookups in nmbd. 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 | 23 ++-- source3/libsmb/nmblib.c | 9 +- source3/namedbname.c | 49 +-------- source3/namepacket.c | 55 ++++++---- source3/nameservreply.c | 32 +++--- source3/nmbd/asyncdns.c | 257 +++++++++++++++++++++++++++++++++++++++++++++ source3/nmbd/nmbd.c | 13 +-- source3/utils/nmblookup.c | 9 -- 8 files changed, 340 insertions(+), 107 deletions(-) create mode 100644 source3/nmbd/asyncdns.c diff --git a/source3/include/nameserv.h b/source3/include/nameserv.h index ecd19b9563..5c8ec1e4eb 100644 --- a/source3/include/nameserv.h +++ b/source3/include/nameserv.h @@ -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 */ diff --git a/source3/libsmb/nmblib.c b/source3/libsmb/nmblib.c index e8f281bc25..121008685b 100644 --- a/source3/libsmb/nmblib.c +++ b/source3/libsmb/nmblib.c @@ -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) diff --git a/source3/namedbname.c b/source3/namedbname.c index 51571d786a..f126b4651c 100644 --- a/source3/namedbname.c +++ b/source3/namedbname.c @@ -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); -} diff --git a/source3/namepacket.c b/source3/namepacket.c index 3a23806a9c..c510c21169 100644 --- a/source3/namepacket.c +++ b/source3/namepacket.c @@ -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)) diff --git a/source3/nameservreply.c b/source3/nameservreply.c index c901059f9b..6585a02261 100644 --- a/source3/nameservreply.c +++ b/source3/nameservreply.c @@ -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 index 0000000000..548781edea --- /dev/null +++ b/source3/nmbd/asyncdns.c @@ -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 diff --git a/source3/nmbd/nmbd.c b/source3/nmbd/nmbd.c index 047284832f..5feeb07c90 100644 --- a/source3/nmbd/nmbd.c +++ b/source3/nmbd/nmbd.c @@ -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(); diff --git a/source3/utils/nmblookup.c b/source3/utils/nmblookup.c index 63ca156449..d26d199695 100644 --- a/source3/utils/nmblookup.c +++ b/source3/utils/nmblookup.c @@ -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, -- cgit