[Dnsmasq-discuss] [PATCH 4/4] process client request based on domain name

weichen302 at icloud.com weichen302 at icloud.com
Sun Feb 8 10:11:22 GMT 2015


Process client's UDP & TCP request based on domain name. If the domain name
matches a ipset rule, the result ip will be added to ipset.


---
 src/forward.c |  779 +++++++++++++++++++++++++++++----------------------------
 1 file changed, 401 insertions(+), 378 deletions(-)

diff --git a/src/forward.c b/src/forward.c
index 438e9fa..a846a7b 100644
--- a/src/forward.c
+++ b/src/forward.c
@@ -30,7 +30,9 @@ static int do_check_sign(struct frec *forward, int status, time_t now, char *nam
 static int send_check_sign(struct frec *forward, time_t now, struct dns_header *header, size_t plen, 
 			   char *name, char *keyname);
 #endif
-
+static int tcp_conn_serv(struct server *serv, time_t now,
+                         unsigned char *packet, size_t payload_size,
+                         union mysockaddr *peer_addr);
 
 /* Send a UDP packet with its source address set as "source" 
    unless nowild is true, when we just send it with the kernel default */
@@ -118,126 +120,115 @@ int send_from(int fd, int nowild, char *packet, size_t len,
   
   return 1;
 }
-          
-static unsigned int search_servers(time_t now, struct all_addr **addrpp, 
-				     unsigned int qtype, char *qdomain, int *type, char **domain, int *norebind)
-			      
+
+/* search domain pattern for --address, --local, --server, --rebind-domain-ok
+
+   if matches --address, the address is stored in addrpp
+
+   if matches --server, a pointer to one of servers in daemon->servers is
+   stored in fwdserv, unless --server=/example.org/#, in which case fwdserv
+   will be NULL, means use normal server
+
+   if matches --rebind-domain-ok, the pass in norebind will be set to 1
+
+   we find largest match, e.g. given pattern debian.org and cn.debian.org,
+   ftp.cn.debian.org  will match cn.debian.org
+ */
+static unsigned int
+search_servers (time_t now, struct all_addr **addrpp,
+                unsigned int qtype, char *qdomain, int *type,
+                char **domain, int *norebind, struct server **fwdserv)
 {
-  /* If the query ends in the domain in one of our servers, set
-     domain to point to that name. We find the largest match to allow both
-     domain.org and sub.domain.org to exist. */
-  
-  unsigned int namelen = strlen(qdomain);
-  unsigned int matchlen = 0;
-  struct server *serv;
+  unsigned int namelen = strlen (qdomain);
   unsigned int flags = 0;
-  
-  for (serv = daemon->servers; serv; serv=serv->next)
-    /* domain matches take priority over NODOTS matches */
-    if ((serv->flags & SERV_FOR_NODOTS) && *type != SERV_HAS_DOMAIN && !strchr(qdomain, '.') && namelen != 0)
-      {
-	unsigned int sflag = serv->addr.sa.sa_family == AF_INET ? F_IPV4 : F_IPV6; 
-	*type = SERV_FOR_NODOTS;
-	if (serv->flags & SERV_NO_ADDR)
-	  flags = F_NXDOMAIN;
-	else if (serv->flags & SERV_LITERAL_ADDRESS) 
-	  { 
-	    if (sflag & qtype)
-	      {
-		flags = sflag;
-		if (serv->addr.sa.sa_family == AF_INET) 
-		  *addrpp = (struct all_addr *)&serv->addr.in.sin_addr;
-#ifdef HAVE_IPV6
-		else
-		  *addrpp = (struct all_addr *)&serv->addr.in6.sin6_addr;
-#endif 
-	      }
-	    else if (!flags || (flags & F_NXDOMAIN))
-	      flags = F_NOERR;
-	  } 
-      }
-    else if (serv->flags & SERV_HAS_DOMAIN)
-      {
-	unsigned int domainlen = strlen(serv->domain);
-	char *matchstart = qdomain + namelen - domainlen;
-	if (namelen >= domainlen &&
-	    hostname_isequal(matchstart, serv->domain) &&
-	    (domainlen == 0 || namelen == domainlen || *(matchstart-1) == '.' ))
-	  {
-	    if (serv->flags & SERV_NO_REBIND)	
-	      *norebind = 1;
-	    else
-	      {
-		unsigned int sflag = serv->addr.sa.sa_family == AF_INET ? F_IPV4 : F_IPV6;
-		/* implement priority rules for --address and --server for same domain.
-		   --address wins if the address is for the correct AF
-		   --server wins otherwise. */
-		if (domainlen != 0 && domainlen == matchlen)
-		  {
-		    if ((serv->flags & SERV_LITERAL_ADDRESS))
-		      {
-			if (!(sflag & qtype) && flags == 0)
-			  continue;
-		      }
-		    else
-		      {
-			if (flags & (F_IPV4 | F_IPV6))
-			  continue;
-		      }
-		  }
-		
-		if (domainlen >= matchlen)
-		  {
-		    *type = serv->flags & (SERV_HAS_DOMAIN | SERV_USE_RESOLV | SERV_NO_REBIND);
-		    *domain = serv->domain;
-		    matchlen = domainlen;
-		    if (serv->flags & SERV_NO_ADDR)
-		      flags = F_NXDOMAIN;
-		    else if (serv->flags & SERV_LITERAL_ADDRESS)
-		      {
-			if (sflag & qtype)
-			  {
-			    flags = sflag;
-			    if (serv->addr.sa.sa_family == AF_INET) 
-			      *addrpp = (struct all_addr *)&serv->addr.in.sin_addr;
+  unsigned int sflag;
+  struct dict_node *np;
+  struct special_domain *obj;
+
+  *type = 0;
+  /* label of root node is "#", means --address=/#/1.2.3.4 */
+  if (daemon->dh_special_domains && daemon->dh_special_domains->label &&
+                                    *daemon->dh_special_domains->label == '#')
+    np = daemon->dh_special_domains;
+  else
+    np = match_domain (daemon->dh_special_domains, qdomain);
+
+  if (np != NULL)
+    {
+      obj = (struct special_domain *) np->obj;
+
+      *type |= SERV_HAS_DOMAIN;
+
+      if (obj->domain_flags & SERV_NO_REBIND)
+        *norebind = 1;
+
+      // no server, domain is local only
+      if (obj->domain_flags & SERV_NO_ADDR)
+        {
+          flags = F_NXDOMAIN;
+
+        }
+      else if (obj->domain_flags & SERV_LITERAL_ADDRESS)
+        {
+          // --address and AF matches
+          sflag = obj->addr.sa.sa_family == AF_INET ? F_IPV4 : F_IPV6;
+          if (sflag & qtype)
+            {
+              flags = sflag;
+              if (obj->addr.sa.sa_family == AF_INET)
+                *addrpp = (struct all_addr *) &obj->addr.in.sin_addr;
 #ifdef HAVE_IPV6
-			    else
-			      *addrpp = (struct all_addr *)&serv->addr.in6.sin6_addr;
+              else
+                *addrpp = (struct all_addr *) &obj->addr.in6.sin6_addr;
 #endif
-			  }
-			else if (!flags || (flags & F_NXDOMAIN))
-			  flags = F_NOERR;
-		      }
-		    else
-		      flags = 0;
-		  } 
-	      }
-	  }
-      }
-  
-  if (flags == 0 && !(qtype & F_QUERY) && 
-      option_bool(OPT_NODOTS_LOCAL) && !strchr(qdomain, '.') && namelen != 0)
+            }
+
+        }
+      else if (obj->domain_flags & SERV_USE_RESOLV)
+        {
+          // --server=8.8.8.8
+          *type = 0;                // use normal server
+          *fwdserv = NULL;
+
+        }
+      else
+        {
+          *fwdserv = obj->server;
+          flags = 0;
+        }
+    }
+  else
+    {
+      *type = 0;    /* use normal servers for this domain */
+      *fwdserv = NULL;
+    }
+
+  if (flags == 0 && !(qtype & F_QUERY) &&
+      option_bool (OPT_NODOTS_LOCAL) && !strchr (qdomain, '.')
+      && namelen != 0)
     /* don't forward A or AAAA queries for simple names, except the empty name */
     flags = F_NOERR;
-  
-  if (flags == F_NXDOMAIN && check_for_local_domain(qdomain, now))
+
+  if (flags == F_NXDOMAIN && check_for_local_domain (qdomain, now))
     flags = F_NOERR;
 
   if (flags)
     {
       int logflags = 0;
-      
+
       if (flags == F_NXDOMAIN || flags == F_NOERR)
-	logflags = F_NEG | qtype;
-  
-      log_query(logflags | flags | F_CONFIG | F_FORWARD, qdomain, *addrpp, NULL);
+        logflags = F_NEG | qtype;
+
+      log_query (logflags | flags | F_CONFIG | F_FORWARD, qdomain, *addrpp,
+                 NULL);
     }
   else if ((*type) & SERV_USE_RESOLV)
     {
-      *type = 0; /* use normal servers for this domain */
+      *type = 0;    /* use normal servers for this domain */
       *domain = NULL;
     }
-  return  flags;
+
+  return flags;
 }
 
 static int forward_query(int udpfd, union mysockaddr *udpaddr,
@@ -250,6 +241,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
   struct all_addr *addrp = NULL;
   unsigned int flags = 0;
   struct server *start = NULL;
+  struct server *fwdserv = NULL;
 #ifdef HAVE_DNSSEC
   void *hash = hash_questions(header, plen, daemon->namebuff);
 #else
@@ -321,7 +313,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
   else 
     {
       if (gotname)
-	flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind);
+	flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind, &fwdserv);
       
       if (!flags && !(forward = get_new_frec(now, NULL, 0)))
 	/* table full - server failure. */
@@ -386,7 +378,6 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
   
   if (!flags && forward)
     {
-      struct server *firstsentto = start;
       int forwarded = 0;
       
       /* If a query is retried, use the log_id for the retry when logging the answer. */
@@ -422,95 +413,89 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
 	}
 #endif
 
-      while (1)
-	{ 
-	  /* only send to servers dealing with our domain.
-	     domain may be NULL, in which case server->domain 
-	     must be NULL also. */
-	  
-	  if (type == (start->flags & SERV_TYPE) &&
-	      (type != SERV_HAS_DOMAIN || hostname_isequal(domain, start->domain)) &&
-	      !(start->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP)))
-	    {
-	      int fd;
+      // we have the server for our domain by lookup daemon->dh_special_domains
+      int fd;
 
-	      /* find server socket to use, may need to get random one. */
-	      if (start->sfd)
-		fd = start->sfd->fd;
-	      else 
-		{
+      /* didn't find a server matches query domain */
+      if (fwdserv == NULL)
+        {
+          for (fwdserv = daemon->servers;
+               fwdserv != NULL; fwdserv = fwdserv->next)
+            {
+              //TODO figure out how to skip unresponsive server
+              if (!(fwdserv->flags & (SERV_HAS_DOMAIN | SERV_LOOP)))
+                {
+                  break;
+                }
+            }
+        }
+
+      if (fwdserv->sfd)
+        fd = fwdserv->sfd->fd;
+      else
+        {
 #ifdef HAVE_IPV6
-		  if (start->addr.sa.sa_family == AF_INET6)
-		    {
-		      if (!forward->rfd6 &&
-			  !(forward->rfd6 = allocate_rfd(AF_INET6)))
-			break;
-		      daemon->rfd_save = forward->rfd6;
-		      fd = forward->rfd6->fd;
-		    }
-		  else
+          if (fwdserv->addr.sa.sa_family == AF_INET6)
+            {
+              if (forward->rfd6 == NULL)
+                forward->rfd6 = allocate_rfd (AF_INET6);
+              daemon->rfd_save = forward->rfd6;
+              fd = forward->rfd6->fd;
+            }
+          else
 #endif
-		    {
-		      if (!forward->rfd4 &&
-			  !(forward->rfd4 = allocate_rfd(AF_INET)))
-			break;
-		      daemon->rfd_save = forward->rfd4;
-		      fd = forward->rfd4->fd;
-		    }
+            {
+              if (forward->rfd4 == NULL)
+                forward->rfd4 = allocate_rfd (AF_INET);
+              daemon->rfd_save = forward->rfd4;
+              fd = forward->rfd4->fd;
+            }
 
 #ifdef HAVE_CONNTRACK
-		  /* Copy connection mark of incoming query to outgoing connection. */
-		  if (option_bool(OPT_CONNTRACK))
-		    {
-		      unsigned int mark;
-		      if (get_incoming_mark(&forward->source, &forward->dest, 0, &mark))
-			setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int));
-		    }
+          /* Copy connection mark of incoming query to outgoing connection. */
+          if (option_bool (OPT_CONNTRACK))
+            {
+              unsigned int mark;
+              if (get_incoming_mark (&forward->source, &forward->dest, 0, &mark))
+                setsockopt (fd, SOL_SOCKET, SO_MARK, &mark,
+                            sizeof (unsigned int));
+            }
 #endif
-		}
-	      
-	      if (sendto(fd, (char *)header, plen, 0,
-			 &start->addr.sa,
-			 sa_len(&start->addr)) == -1)
-		{
-		  if (retry_send())
-		    continue;
-		}
-	      else
-		{
-		  /* Keep info in case we want to re-send this packet */
-		  daemon->srv_save = start;
-		  daemon->packet_len = plen;
-		  
-		  if (!gotname)
-		    strcpy(daemon->namebuff, "query");
-		  if (start->addr.sa.sa_family == AF_INET)
-		    log_query(F_SERVER | F_IPV4 | F_FORWARD, daemon->namebuff, 
-			      (struct all_addr *)&start->addr.in.sin_addr, NULL); 
+        }
+
+      //TODO how to retry here?
+      if (sendto (fd, (char *) header, plen, 0,
+                  &fwdserv->addr.sa, sa_len (&fwdserv->addr)) == -1)
+        {
+          retry_send ();
+        }
+
+      else
+        {
+          /* Keep info in case we want to re-send this packet */
+          daemon->srv_save = fwdserv;
+          daemon->packet_len = plen;
+
+          if (!gotname)
+            strcpy (daemon->namebuff, "query");
+          if (fwdserv->addr.sa.sa_family == AF_INET)
+            log_query (F_SERVER | F_IPV4 | F_FORWARD, daemon->namebuff,
+                       (struct all_addr *) &fwdserv->addr.in.sin_addr, NULL);
 #ifdef HAVE_IPV6
-		  else
-		    log_query(F_SERVER | F_IPV6 | F_FORWARD, daemon->namebuff, 
-			      (struct all_addr *)&start->addr.in6.sin6_addr, NULL);
-#endif 
-		  start->queries++;
-		  forwarded = 1;
-		  forward->sentto = start;
-		  if (!forward->forwardall) 
-		    break;
-		  forward->forwardall++;
-		}
-	    } 
-	  
-	  if (!(start = start->next))
- 	    start = daemon->servers;
-	  
-	  if (start == firstsentto)
-	    break;
-	}
-      
+          else
+            log_query (F_SERVER | F_IPV6 | F_FORWARD, daemon->namebuff,
+                       (struct all_addr *) &fwdserv->addr.in6.sin6_addr, NULL);
+#endif
+          fwdserv->queries++;
+          forwarded = 1;
+          forward->sentto = fwdserv;
+          if (forward->forwardall)
+            forward->forwardall++;
+        }
+
       if (forwarded)
-	return 1;
-      
+        return 1;
+
       /* could not send on, prepare to return */ 
       header->id = htons(forward->orig_id);
       free_frec(forward); /* cancel */
@@ -533,29 +518,21 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
   char **sets = 0;
   int munged = 0, is_sign;
   size_t plen; 
+  struct dict_node *np;
+  struct ipsets_names *obj;
 
   (void)ad_reqd;
   (void) do_bit;
 
 #ifdef HAVE_IPSET
-  if (daemon->ipsets && extract_request(header, n, daemon->namebuff, NULL))
+  if (daemon->dh_ipsets && extract_request(header, n, daemon->namebuff, NULL))
     {
-      /* Similar algorithm to search_servers. */
-      struct ipsets *ipset_pos;
-      unsigned int namelen = strlen(daemon->namebuff);
-      unsigned int matchlen = 0;
-      for (ipset_pos = daemon->ipsets; ipset_pos; ipset_pos = ipset_pos->next) 
-	{
-	  unsigned int domainlen = strlen(ipset_pos->domain);
-	  char *matchstart = daemon->namebuff + namelen - domainlen;
-	  if (namelen >= domainlen && hostname_isequal(matchstart, ipset_pos->domain) &&
-	      (domainlen == 0 || namelen == domainlen || *(matchstart - 1) == '.' ) &&
-	      domainlen >= matchlen) 
-	    {
-	      matchlen = domainlen;
-	      sets = ipset_pos->sets;
-	    }
-	}
+      np = match_domain(daemon->dh_ipsets, daemon->namebuff);
+      if (np != NULL)
+        {
+          obj = (struct ipsets_names *) np->obj;
+          sets = obj->sets;
+        }
     }
 #endif
   
@@ -1659,6 +1636,167 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
 }
 #endif
 
+/* try establish tcp connection to upstream server and forward query
+ *
+ * return -1 on tcp connect/read/write error
+ *         0 on upstream response has 0 length DNS message, or DNSSEC error
+ *         > 0 DNS message length received from upstream server */
+static int tcp_conn_serv(struct server *serv, time_t now,
+                         unsigned char *packet, size_t payload_size,
+                         union mysockaddr *peer_addr)
+{
+  unsigned char *payload = packet + 2;        /* skip msg length field */
+  struct dns_header *header = (struct dns_header *) payload;
+  unsigned int gotname;
+  unsigned short qtype;
+  int checking_disabled = header->hb4 & HB4_CD;
+  unsigned int m = 0;
+  u16 msg_len;
+
+#ifdef HAVE_DNSSEC
+  unsigned char *newhash, hash[HASH_SIZE];
+  if ((newhash = hash_questions (header, (unsigned int) payload_size,
+                                 daemon->namebuff)))
+    memcpy (hash, newhash, HASH_SIZE);
+  else
+    memset (hash, 0, HASH_SIZE);
+#else
+  unsigned int crc =
+    questions_crc (header, (unsigned int) payload_size, daemon->namebuff);
+#endif
+
+  if (serv->tcpfd == -1)
+    {
+      if ((serv->tcpfd =
+           socket (serv->addr.sa.sa_family, SOCK_STREAM, 0)) == -1)
+        return -1;
+
+#ifdef HAVE_CONNTRACK
+      /* Copy connection mark of incoming query to outgoing connection. */
+      if (option_bool (OPT_CONNTRACK))
+        {
+          unsigned int mark;
+          struct all_addr local;
+#ifdef HAVE_IPV6
+          if (local_addr->sa.sa_family == AF_INET6)
+            local.addr.addr6 = local_addr->in6.sin6_addr;
+          else
+#endif
+            local.addr.addr4 = local_addr->in.sin_addr;
+
+          if (get_incoming_mark (&peer_addr, &local, 1, &mark))
+            setsockopt (serv->tcpfd, SOL_SOCKET, SO_MARK, &mark,
+                        sizeof (unsigned int));
+        }
+#endif
+
+      if (!local_bind (serv->tcpfd, &serv->source_addr, serv->interface, 1) ||
+          connect (serv->tcpfd, &serv->addr.sa, sa_len (&serv->addr)) == -1)
+        {
+          close (serv->tcpfd);
+          serv->tcpfd = -1;
+          return -1;
+        }
+
+#ifdef HAVE_DNSSEC
+      if (option_bool (OPT_DNSSEC_VALID))
+        {
+          size_t new_size =
+            add_do_bit (header, payload_size, ((char *) header) + 65536);
+
+          /* For debugging, set Checking Disabled, otherwise, have the upstream
+            check too, this allows it to select auth servers when one is
+            returning bad data. */
+          if (option_bool (OPT_DNSSEC_DEBUG))
+            header->hb4 |= HB4_CD;
+
+          if (payload_size != new_size)
+            added_pheader = 1;
+
+          payload_size = new_size;
+        }
+#endif
+    }
+
+  /* get query name again for logging - may have been overwritten */
+  if (!(gotname = extract_request (header, (unsigned int) payload_size,
+                                   daemon->namebuff, &qtype)))
+    strcpy (daemon->namebuff, "query");
+
+  u16 n_size = htons (payload_size);
+  memcpy (packet, &n_size, sizeof (u16));
+
+  if (!read_write (serv->tcpfd, packet, payload_size + sizeof (u16), 0) ||
+      !read_write (serv->tcpfd, (unsigned char *) &msg_len, 2, 1) ||
+      !(m = (unsigned int) ntohs (msg_len)) ||
+      !read_write (serv->tcpfd, payload, (int) m, 1))
+    {
+      close (serv->tcpfd);
+      serv->tcpfd = -1;
+      return -1;
+    }
+
+  if (serv->addr.sa.sa_family == AF_INET)
+    log_query (F_SERVER | F_IPV4 | F_FORWARD, daemon->namebuff,
+               (struct all_addr *) &serv->addr.in.sin_addr, NULL);
+#ifdef HAVE_IPV6
+  else
+    log_query (F_SERVER | F_IPV6 | F_FORWARD, daemon->namebuff,
+               (struct all_addr *) &serv->addr.in6.sin6_addr, NULL);
+#endif
+
+#ifdef HAVE_DNSSEC
+  if (option_bool (OPT_DNSSEC_VALID) && !checking_disabled)
+    {
+      /* Limit to number of DNSSEC questions, to catch loops and avoid filling
+       * cache. */
+      int keycount = DNSSEC_WORK;
+      int status =
+        tcp_key_recurse (now, STAT_TRUNCATED, header, m, 0, daemon->namebuff,
+                         daemon->keyname, serv, &keycount);
+      char *result;
+
+      if (keycount == 0)
+        result = "ABANDONED";
+      else
+        result =
+          (status ==
+           STAT_SECURE ? "SECURE" : (status ==
+                                     STAT_INSECURE ? "INSECURE" : "BOGUS"));
+
+      log_query (F_KEYTAG | F_SECSTAT, "result", NULL, result);
+
+      if (status == STAT_BOGUS)
+        no_cache_dnssec = 1;
+
+      if (status == STAT_SECURE)
+        cache_secure = 1;
+    }
+#endif
+
+  /* restore CD bit to the value in the query */
+  if (checking_disabled)
+    header->hb4 |= HB4_CD;
+  else
+    header->hb4 &= ~HB4_CD;
+
+  /* There's no point in updating the cache, since this process will exit and
+     lose the information after a few queries. We make this call for the alias and
+     bogus-nxdomain side-effects. */
+  /* If the crc of the question section doesn't match the crc we sent, then
+     someone might be attempting to insert bogus values into the cache by
+     sending replies containing questions and bogus answers. */
+#ifdef HAVE_DNSSEC
+  newhash = hash_questions (header, m, daemon->namebuff);
+  if (!newhash || memcmp (hash, newhash, HASH_SIZE) != 0)
+    m = 0;
+#else
+  if (crc != questions_crc (header, m, daemon->namebuff))
+    m = 0;
+#endif
+
+  return (int) m;
+}
 
 /* The daemon forks before calling this: it should deal with one connection,
    blocking as neccessary, and then return. Note, need to be a bit careful
@@ -1684,11 +1822,12 @@ unsigned char *tcp_request(int confd, time_t now,
   /* largest field in header is 16-bits, so this is still sufficiently aligned */
   struct dns_header *header = (struct dns_header *)payload;
   u16 *length = (u16 *)packet;
-  struct server *last_server;
+  struct server *last_server, *fwdserv, *serv;
   struct in_addr dst_addr_4;
   union mysockaddr peer_addr;
   socklen_t peer_len = sizeof(union mysockaddr);
   int query_count = 0;
+  int ret;
 
   if (getpeername(confd, (struct sockaddr *)&peer_addr, &peer_len) == -1)
     return packet;
@@ -1817,182 +1956,66 @@ unsigned char *tcp_request(int confd, time_t now,
 		}
 
 	      if (gotname)
-		flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind);
-	      
-	      if (type != 0  || option_bool(OPT_ORDER) || !daemon->last_server)
-		last_server = daemon->servers;
-	      else
-		last_server = daemon->last_server;
+		flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind, &fwdserv);
 	      
-	      if (!flags && last_server)
-		{
-		  struct server *firstsendto = NULL;
-#ifdef HAVE_DNSSEC
-		  unsigned char *newhash, hash[HASH_SIZE];
-		  if ((newhash = hash_questions(header, (unsigned int)size, daemon->namebuff)))
-		    memcpy(hash, newhash, HASH_SIZE);
-		  else
-		    memset(hash, 0, HASH_SIZE);
-#else
-		  unsigned int crc = questions_crc(header, (unsigned int)size, daemon->namebuff);
-#endif		  
-		  /* Loop round available servers until we succeed in connecting to one.
-		     Note that this code subtley ensures that consecutive queries on this connection
-		     which can go to the same server, do so. */
-		  while (1) 
-		    {
-		      if (!firstsendto)
-			firstsendto = last_server;
-		      else
-			{
-			  if (!(last_server = last_server->next))
-			    last_server = daemon->servers;
-			  
-			  if (last_server == firstsendto)
-			    break;
-			}
-		      
-		      /* server for wrong domain */
-		      if (type != (last_server->flags & SERV_TYPE) ||
-			  (type == SERV_HAS_DOMAIN && !hostname_isequal(domain, last_server->domain)) ||
-			  (last_server->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP)))
-			continue;
-		      
-		      if (last_server->tcpfd == -1)
-			{
-			  if ((last_server->tcpfd = socket(last_server->addr.sa.sa_family, SOCK_STREAM, 0)) == -1)
-			    continue;
-			  
-#ifdef HAVE_CONNTRACK
-			  /* Copy connection mark of incoming query to outgoing connection. */
-			  if (option_bool(OPT_CONNTRACK))
-			    {
-			      unsigned int mark;
-			      struct all_addr local;
-#ifdef HAVE_IPV6		      
-			      if (local_addr->sa.sa_family == AF_INET6)
-				local.addr.addr6 = local_addr->in6.sin6_addr;
-			      else
-#endif
-				local.addr.addr4 = local_addr->in.sin_addr;
-			      
-			      if (get_incoming_mark(&peer_addr, &local, 1, &mark))
-				setsockopt(last_server->tcpfd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int));
-			    }
-#endif	
-		      
-			  if ((!local_bind(last_server->tcpfd,  &last_server->source_addr, last_server->interface, 1) ||
-			       connect(last_server->tcpfd, &last_server->addr.sa, sa_len(&last_server->addr)) == -1))
-			    {
-			      close(last_server->tcpfd);
-			      last_server->tcpfd = -1;
-			      continue;
-			    }
-			  
-#ifdef HAVE_DNSSEC
-			  if (option_bool(OPT_DNSSEC_VALID))
-			    {
-			      size_t new_size = add_do_bit(header, size, ((char *) header) + 65536);
-			      
-			      /* For debugging, set Checking Disabled, otherwise, have the upstream check too,
-				 this allows it to select auth servers when one is returning bad data. */
-			      if (option_bool(OPT_DNSSEC_DEBUG))
-				header->hb4 |= HB4_CD;
-			      
-			      if (size != new_size)
-				added_pheader = 1;
-			      
-			      size = new_size;
-			    }
-#endif
-			}
-		      
-		      *length = htons(size);
-
-		      /* get query name again for logging - may have been overwritten */
-		      if (!(gotname = extract_request(header, (unsigned int)size, daemon->namebuff, &qtype)))
-			strcpy(daemon->namebuff, "query");
-		      
-		      if (!read_write(last_server->tcpfd, packet, size + sizeof(u16), 0) ||
-			  !read_write(last_server->tcpfd, &c1, 1, 1) ||
-			  !read_write(last_server->tcpfd, &c2, 1, 1) ||
-			  !read_write(last_server->tcpfd, payload, (c1 << 8) | c2, 1))
-			{
-			  close(last_server->tcpfd);
-			  last_server->tcpfd = -1;
-			  continue;
-			} 
-		      
-		      m = (c1 << 8) | c2;
-		      
-		      if (last_server->addr.sa.sa_family == AF_INET)
-			log_query(F_SERVER | F_IPV4 | F_FORWARD, daemon->namebuff, 
-				  (struct all_addr *)&last_server->addr.in.sin_addr, NULL); 
-#ifdef HAVE_IPV6
-		      else
-			log_query(F_SERVER | F_IPV6 | F_FORWARD, daemon->namebuff, 
-				  (struct all_addr *)&last_server->addr.in6.sin6_addr, NULL);
-#endif 
-
-#ifdef HAVE_DNSSEC
-		      if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled)
-			{
-			  int keycount = DNSSEC_WORK; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */
-			  int status = tcp_key_recurse(now, STAT_TRUNCATED, header, m, 0, daemon->namebuff, daemon->keyname, last_server, &keycount);
-			  char *result;
+              ret = 0;
+              serv = last_server = NULL;
+              /* --address=/xxx/1.2.3.4 */
+              if (addrp != NULL)
+                {
+                  m = 0;
+                }
+              else if (fwdserv != NULL)
+                {
+                  /* --server=/example.org/1.2.3.4 */
+                  serv = fwdserv;
+                  ret = tcp_conn_serv (serv, now, packet, size, &peer_addr);
+                }
+              else
+                {
+                  /* use normal server */
+                  struct server *firstsendto = NULL;
+                  if (option_bool (OPT_ORDER) || !daemon->last_server)
+                    last_server = daemon->servers;
+                  else
+                    last_server = daemon->last_server;
+
+                  while (1)
+                    {
+                      if (!firstsendto)
+                        firstsendto = last_server;
+                      else
+                        {
+                          if (!(last_server = last_server->next))
+                            last_server = daemon->servers;
+
+                          if (last_server == firstsendto)
+                            break;
+                        }
+
+                      if (type != (last_server->flags & SERV_TYPE) ||
+                          last_server->flags & SERV_HAS_DOMAIN ||
+                          last_server->flags & SERV_LOOP)
+                        continue;
+
+                      serv = last_server;
+                      ret = tcp_conn_serv (serv, now, packet, size, &peer_addr);
+                      /* something wrong with tcp connect/read/write */
+                      if (ret <= 0)
+                        continue;
+
+                      break;
+                    }
+                }
+
+              if (ret > 0) 
+                m = process_reply (header, now, serv, ret,
+                        option_bool (OPT_NO_REBIND) && !norebind, 
+                        no_cache_dnssec, cache_secure, ad_question,
+                        do_bit, added_pheader, check_subnet, &peer_addr);
 
-			  if (keycount == 0)
-			    result = "ABANDONED";
-			  else
-			    result = (status == STAT_SECURE ? "SECURE" : (status == STAT_INSECURE ? "INSECURE" : "BOGUS"));
-			  
-			  log_query(F_KEYTAG | F_SECSTAT, "result", NULL, result);
-			  
-			  if (status == STAT_BOGUS)
-			    no_cache_dnssec = 1;
-			  
-			  if (status == STAT_SECURE)
-			    cache_secure = 1;
-			}
-#endif
-
-		      /* restore CD bit to the value in the query */
-		      if (checking_disabled)
-			header->hb4 |= HB4_CD;
-		      else
-			header->hb4 &= ~HB4_CD;
-		      
-		      /* There's no point in updating the cache, since this process will exit and
-			 lose the information after a few queries. We make this call for the alias and 
-			 bogus-nxdomain side-effects. */
-		      /* If the crc of the question section doesn't match the crc we sent, then
-			 someone might be attempting to insert bogus values into the cache by 
-			 sending replies containing questions and bogus answers. */
-#ifdef HAVE_DNSSEC
-		      newhash = hash_questions(header, (unsigned int)m, daemon->namebuff);
-		      if (!newhash || memcmp(hash, newhash, HASH_SIZE) != 0)
-			{ 
-			  m = 0;
-			  break;
-			}
-#else			  
-		      if (crc != questions_crc(header, (unsigned int)m, daemon->namebuff))
-			{
-			  m = 0;
-			  break;
-			}
-#endif
-
-		      m = process_reply(header, now, last_server, (unsigned int)m, 
-					option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec,
-					cache_secure, ad_question, do_bit, added_pheader, check_subnet, &peer_addr); 
-		      
-		      break;
-		    }
-		}
-	
 	      /* In case of local answer or no connections made. */
-	      if (m == 0)
+              if (m == 0)
 		m = setup_reply(header, (unsigned int)size, addrp, flags, daemon->local_ttl);
 	    }
 	}
-- 
1.7.10.4




More information about the Dnsmasq-discuss mailing list