Add --dns-loop-detect feature.
diff --git a/CHANGELOG b/CHANGELOG
index 5b7dfb8..62f2e4a 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -20,6 +20,13 @@
 	    longer prefix length.) Thanks to Lung-Pin Chang for the 
 	    patch.
 	    
+	    Add a mode which detects and removes DNS forwarding loops, ie 
+	    a query sent to an upstream server returns as a new query to 
+	    dnsmasq, and would therefore be forwarded again, resulting in 
+	    a query which loops many times before being dropped. Upstream
+	    servers which loop back are disabled and this event is logged.
+	    Thanks to Smoothwall for their sponsorship of this feature.
+
 
 version 2.71
             Subtle change to error handling to help DNSSEC validation 
diff --git a/Makefile b/Makefile
index 17eeb27..58a7975 100644
--- a/Makefile
+++ b/Makefile
@@ -69,7 +69,7 @@
        dnsmasq.o dhcp.o lease.o rfc2131.o netlink.o dbus.o bpf.o \
        helper.o tftp.o log.o conntrack.o dhcp6.o rfc3315.o \
        dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o \
-       domain.o dnssec.o blockdata.o tables.o
+       domain.o dnssec.o blockdata.o tables.o loop.o
 
 hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \
        dns-protocol.h radv-protocol.h ip6addr.h
diff --git a/bld/Android.mk b/bld/Android.mk
index 5255ec9..d855094 100644
--- a/bld/Android.mk
+++ b/bld/Android.mk
@@ -9,7 +9,8 @@
 		    rfc2131.c tftp.c util.c conntrack.c \
 		    dhcp6.c rfc3315.c dhcp-common.c outpacket.c \
 		    radv.c slaac.c auth.c ipset.c domain.c \
-	            dnssec.c dnssec-openssl.c blockdata.c tables.c
+	            dnssec.c dnssec-openssl.c blockdata.c tables.c \
+		    loop.c
 
 LOCAL_MODULE := dnsmasq
 
diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
index 0530a19..7b4cc98 100644
--- a/man/dnsmasq.8
+++ b/man/dnsmasq.8
@@ -334,6 +334,16 @@
 dnsmasq to send all queries to all available servers. The reply from
 the server which answers first will be returned to the original requester.
 .TP
+.B --dns-loop-detect
+Enable code to detect DNS forwarding loops; ie the situation where a query sent to one 
+of the upstream server eventually returns as a new query to the dnsmasq instance. The
+process works by generating TXT queries of the form <hex>.test and sending them to
+each upstream server. The hex is a UID which encodes the instance of dnsmasq sending the query
+and the upstream server to which it was sent. If the query returns to the server which sent it, then
+the upstream server through which it was sent is disabled and this event is logged. Each time the
+set of upstream servers changes, the test is re-run on all of them, including ones which
+were previously disabled.
+.TP
 .B --stop-dns-rebind
 Reject (and log) addresses from upstream nameservers which are in the
 private IP ranges. This blocks an attack where a browser behind a
diff --git a/src/config.h b/src/config.h
index 87f8f8a..145820a 100644
--- a/src/config.h
+++ b/src/config.h
@@ -47,6 +47,8 @@
 #define SOA_REFRESH 1200 /* SOA refresh default */
 #define SOA_RETRY 180 /* SOA retry default */
 #define SOA_EXPIRY 1209600 /* SOA expiry default */
+#define LOOP_TEST_DOMAIN "test" /* domain for loop testing, "test" is reserved by RFC 2606 and won't therefore clash */
+#define LOOP_TEST_TYPE T_TXT
  
 /* compile-time options: uncomment below to enable or do eg.
    make COPTS=-DHAVE_BROKEN_RTC
@@ -108,6 +110,10 @@
 HAVE_DNSSEC
    include DNSSEC validator.
 
+HAVE_LOOP
+   include functionality to probe for and remove DNS forwarding loops.
+
+
 NO_IPV6
 NO_TFTP
 NO_DHCP
@@ -148,6 +154,7 @@
 #define HAVE_SCRIPT
 #define HAVE_AUTH
 #define HAVE_IPSET 
+#define HAVE_LOOP
 
 /* Build options which require external libraries.
    
@@ -342,6 +349,10 @@
 #undef HAVE_IPSET
 #endif
 
+#ifdef NO_LOOP
+#undef HAVE_LOOP
+#endif
+
 /* Define a string indicating which options are in use.
    DNSMASQP_COMPILE_OPTS is only defined in dnsmasq.c */
 
@@ -411,7 +422,11 @@
 #ifndef HAVE_DNSSEC
 "no-"
 #endif
-"DNSSEC";
+"DNSSEC "
+#ifndef HAVE_LOOP
+"no-"
+#endif
+"loop-detect";
 
 
 #endif
diff --git a/src/dnsmasq.c b/src/dnsmasq.c
index 8b375de..f4a89fc 100644
--- a/src/dnsmasq.c
+++ b/src/dnsmasq.c
@@ -80,7 +80,9 @@
   sigaction(SIGPIPE, &sigact, NULL);
 
   umask(022); /* known umask, create leases and pid files as 0644 */
-
+ 
+  rand_init(); /* Must precede read_opts() */
+  
   read_opts(argc, argv, compile_opts);
  
   if (daemon->edns_pktsz < PACKETSZ)
@@ -186,7 +188,10 @@
     die(_("authoritative DNS not available: set HAVE_AUTH in src/config.h"), NULL, EC_BADCONF);
 #endif
 
-  rand_init();
+#ifndef HAVE_LOOP
+  if (option_bool(OPT_LOOP_DETECT))
+    die(_("Loop detection not available: set HAVE_LOOP in src/config.h"), NULL, EC_BADCONF);
+#endif
   
   now = dnsmasq_time();
 
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index e70d10a..a1ac1d1 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -237,7 +237,8 @@
 #define OPT_DNSSEC_DEBUG   47
 #define OPT_DNSSEC_NO_SIGN 48 
 #define OPT_LOCAL_SERVICE  49
-#define OPT_LAST           50
+#define OPT_LOOP_DETECT    50
+#define OPT_LAST           51
 
 /* extra flags for my_syslog, we use a couple of facilities since they are known 
    not to occupy the same bits as priorities, no matter how syslog.h is set up. */
@@ -478,6 +479,7 @@
 #define SERV_USE_RESOLV     1024  /* forward this domain in the normal way */
 #define SERV_NO_REBIND      2048  /* inhibit dns-rebind protection */
 #define SERV_FROM_FILE      4096  /* read from --servers-file */
+#define SERV_LOOP           8192  /* server causes forwarding loop */
 
 struct serverfd {
   int fd;
@@ -498,6 +500,9 @@
   char *domain; /* set if this server only handles a domain. */ 
   int flags, tcpfd;
   unsigned int queries, failed_queries;
+#ifdef HAVE_LOOP
+  u32 uid;
+#endif
   struct server *next; 
 };
 
@@ -1123,6 +1128,7 @@
 /* util.c */
 void rand_init(void);
 unsigned short rand16(void);
+u32 rand32(void);
 u64 rand64(void);
 int legal_hostname(char *c);
 char *canonicalise(char *s, int *nomem);
@@ -1188,6 +1194,8 @@
 	       union mysockaddr *to, struct all_addr *source,
 	       unsigned int iface);
 void resend_query();
+struct randfd *allocate_rfd(int family);
+void free_rfd(struct randfd *rfd);
 
 /* network.c */
 int indextoname(int fd, int index, char *name);
@@ -1453,3 +1461,10 @@
 time_t periodic_slaac(time_t now, struct dhcp_lease *leases);
 void slaac_ping_reply(struct in6_addr *sender, unsigned char *packet, char *interface, struct dhcp_lease *leases);
 #endif
+
+/* loop.c */
+#ifdef HAVE_LOOP
+void loop_send_probes();
+int detect_loop(char *query, int type);
+#endif
+
diff --git a/src/forward.c b/src/forward.c
index 1a657bb..3afd1b1 100644
--- a/src/forward.c
+++ b/src/forward.c
@@ -22,7 +22,6 @@
 					  void *hash);
 static unsigned short get_id(void);
 static void free_frec(struct frec *f);
-static struct randfd *allocate_rfd(int family);
 
 #ifdef HAVE_DNSSEC
 static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n, 
@@ -427,7 +426,7 @@
 	  
 	  if (type == (start->flags & SERV_TYPE) &&
 	      (type != SERV_HAS_DOMAIN || hostname_isequal(domain, start->domain)) &&
-	      !(start->flags & SERV_LITERAL_ADDRESS))
+	      !(start->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP)))
 	    {
 	      int fd;
 
@@ -1271,6 +1270,12 @@
 	      break;
 	    }
 #endif
+      
+#ifdef HAVE_LOOP
+      /* Check for forwarding loop */
+      if (detect_loop(daemon->namebuff, type))
+	return;
+#endif
     }
   
 #ifdef HAVE_AUTH
@@ -1782,7 +1787,8 @@
 		      
 		      /* server for wrong domain */
 		      if (type != (last_server->flags & SERV_TYPE) ||
-			  (type == SERV_HAS_DOMAIN && !hostname_isequal(domain, last_server->domain)))
+			  (type == SERV_HAS_DOMAIN && !hostname_isequal(domain, last_server->domain)) ||
+			  (last_server->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP)))
 			continue;
 		      
 		      if (last_server->tcpfd == -1)
@@ -1958,7 +1964,7 @@
   return f;
 }
 
-static struct randfd *allocate_rfd(int family)
+struct randfd *allocate_rfd(int family)
 {
   static int finger = 0;
   int i;
@@ -1993,19 +1999,22 @@
 
   return NULL; /* doom */
 }
+
+void free_rfd(struct randfd *rfd)
+{
+  if (rfd && --(rfd->refcount) == 0)
+    close(rfd->fd);
+}
+
 static void free_frec(struct frec *f)
 {
-  if (f->rfd4 && --(f->rfd4->refcount) == 0)
-    close(f->rfd4->fd);
-    
+  free_rfd(f->rfd4);
   f->rfd4 = NULL;
   f->sentto = NULL;
   f->flags = 0;
   
 #ifdef HAVE_IPV6
-  if (f->rfd6 && --(f->rfd6->refcount) == 0)
-    close(f->rfd6->fd);
-    
+  free_rfd(f->rfd6);
   f->rfd6 = NULL;
 #endif
 
diff --git a/src/loop.c b/src/loop.c
new file mode 100644
index 0000000..bb377ad
--- /dev/null
+++ b/src/loop.c
@@ -0,0 +1,116 @@
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
+
+   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; version 2 dated June, 1991, or
+   (at your option) version 3 dated 29 June, 2007.
+ 
+   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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "dnsmasq.h"
+
+#ifdef HAVE_LOOP
+static ssize_t loop_make_probe(u32 uid);
+
+void loop_send_probes()
+{
+   struct server *serv;
+   
+   if (!option_bool(OPT_LOOP_DETECT))
+     return;
+
+   /* Loop through all upstream servers not for particular domains, and send a query to that server which is
+      identifiable, via the uid. If we see that query back again, then the server is looping, and we should not use it. */
+   for (serv = daemon->servers; serv; serv = serv->next)
+     if (!(serv->flags & 
+	   (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND | SERV_HAS_DOMAIN | SERV_FOR_NODOTS | SERV_LOOP)))
+       {
+	 ssize_t len = loop_make_probe(serv->uid);
+	 int fd;
+	 struct randfd *rfd = NULL;
+	 
+	 if (serv->sfd)
+	   fd = serv->sfd->fd;
+	 else 
+	   {
+	     if (!(rfd = allocate_rfd(serv->addr.sa.sa_family)))
+	       continue;
+	     fd = rfd->fd;
+	   }
+
+	 while (sendto(fd, daemon->packet, len, 0, &serv->addr.sa, sa_len(&serv->addr)) == -1 && retry_send());
+
+	 free_rfd(rfd);
+       }
+}
+  
+static ssize_t loop_make_probe(u32 uid)
+{
+  struct dns_header *header = (struct dns_header *)daemon->packet;
+  unsigned char *p = (unsigned char *)(header+1);
+
+  /* packet buffer overwritten */
+  daemon->srv_save = NULL;
+  
+  header->id = rand16();
+  header->ancount = header->nscount = header->arcount = htons(0);
+  header->qdcount = htons(1);
+  header->hb3 = HB3_RD;
+  header->hb4 = 0;
+  SET_OPCODE(header, QUERY);
+
+  *p++ = 8;
+  sprintf((char *)p, "%.8x", uid);
+  p += 8;
+  *p++ = strlen(LOOP_TEST_DOMAIN);
+  strcpy((char *)p, LOOP_TEST_DOMAIN); /* Add terminating zero */
+  p += strlen(LOOP_TEST_DOMAIN) + 1;
+
+  PUTSHORT(LOOP_TEST_TYPE, p);
+  PUTSHORT(C_IN, p);
+
+  return p - (unsigned char *)header;
+}
+  
+
+int detect_loop(char *query, int type)
+{
+  int i;
+  u32 uid;
+  struct server *serv;
+  
+  if (!option_bool(OPT_LOOP_DETECT))
+    return 0;
+
+  if (type != LOOP_TEST_TYPE ||
+      strlen(LOOP_TEST_DOMAIN) + 9 != strlen(query) ||
+      strstr(query, LOOP_TEST_DOMAIN) != query + 9)
+    return 0;
+
+  for (i = 0; i < 8; i++)
+    if (!isxdigit(query[i]))
+      return 0;
+
+  uid = strtol(query, NULL, 16);
+
+  for (serv = daemon->servers; serv; serv = serv->next)
+     if (!(serv->flags & 
+	   (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND | SERV_HAS_DOMAIN | SERV_FOR_NODOTS | SERV_LOOP)) &&
+	 uid == serv->uid)
+       {
+	 serv->flags |= SERV_LOOP;
+	 check_servers(); /* log new state */
+	 return 1;
+       }
+
+  return 0;
+}
+
+#endif
diff --git a/src/network.c b/src/network.c
index b188f50..9c46b8a 100644
--- a/src/network.c
+++ b/src/network.c
@@ -1298,7 +1298,13 @@
   /* mark everything with argument flag */
   for (serv = daemon->servers; serv; serv = serv->next)
     if (serv->flags & flag)
-      serv->flags |= SERV_MARK;
+      {
+	serv->flags |= SERV_MARK;
+#ifdef HAVE_LOOP
+	/* Give looped servers another chance */
+	serv->flags &= ~SERV_LOOP;
+#endif
+      }
 }
 
 void cleanup_servers(void)
@@ -1320,6 +1326,11 @@
       else 
        up = &serv->next;
     }
+
+#ifdef HAVE_LOOP
+  /* Now we have a new set of servers, test for loops. */
+  loop_send_probes();
+#endif
 }
 
 void add_update_server(int flags,
@@ -1385,7 +1396,10 @@
       serv->domain = domain_str;
       serv->next = next;
       serv->queries = serv->failed_queries = 0;
-      
+#ifdef HAVE_LOOP
+      serv->uid = rand32();
+#endif      
+
       if (domain)
 	serv->flags |= SERV_HAS_DOMAIN;
       
@@ -1464,6 +1478,10 @@
 	      else if (!(serv->flags & SERV_LITERAL_ADDRESS))
 		my_syslog(LOG_INFO, _("using nameserver %s#%d for %s %s"), daemon->namebuff, port, s1, s2);
 	    }
+#ifdef HAVE_LOOP
+	  else if (serv->flags & SERV_LOOP)
+	    my_syslog(LOG_INFO, _("NOT using nameserver %s#%d - query loop detected"), daemon->namebuff, port); 
+#endif
 	  else if (serv->interface[0] != 0)
 	    my_syslog(LOG_INFO, _("using nameserver %s#%d(via %s)"), daemon->namebuff, port, serv->interface); 
 	  else
diff --git a/src/option.c b/src/option.c
index a0a6e46..07e7d36 100644
--- a/src/option.c
+++ b/src/option.c
@@ -146,6 +146,7 @@
 #define LOPT_DNSSEC_CHECK  334
 #define LOPT_LOCAL_SERVICE 335
 #define LOPT_DNSSEC_TIME   336
+#define LOPT_LOOP_DETECT   337
 
 #ifdef HAVE_GETOPT_LONG
 static const struct option opts[] =  
@@ -297,6 +298,7 @@
     { "quiet-dhcp", 0, 0, LOPT_QUIET_DHCP },
     { "quiet-dhcp6", 0, 0, LOPT_QUIET_DHCP6 },
     { "quiet-ra", 0, 0, LOPT_QUIET_RA },
+    { "dns-loop-detect", 0, 0, LOPT_LOOP_DETECT },
     { NULL, 0, 0, 0 }
   };
 
@@ -454,6 +456,7 @@
   { LOPT_QUIET_DHCP6, OPT_QUIET_DHCP6, NULL, gettext_noop("Do not log routine DHCPv6."), NULL },
   { LOPT_QUIET_RA, OPT_QUIET_RA, NULL, gettext_noop("Do not log RA."), NULL },
   { LOPT_LOCAL_SERVICE, OPT_LOCAL_SERVICE, NULL, gettext_noop("Accept queries only from directly-connected networks"), NULL },
+  { LOPT_LOOP_DETECT, OPT_LOOP_DETECT, NULL, gettext_noop("Detect and remove DNS forwarding loops"), NULL },
   { 0, 0, NULL, NULL, NULL }
 }; 
 
@@ -2171,6 +2174,9 @@
 	  {
 	    newlist = opt_malloc(sizeof(struct server));
 	    memset(newlist, 0, sizeof(struct server));
+#ifdef HAVE_LOOP
+	    newlist->uid = rand32();
+#endif
 	  }
 	
 	if (servers_only && option == 'S')
@@ -4269,6 +4275,7 @@
   daemon->soa_refresh = SOA_REFRESH;
   daemon->soa_retry = SOA_RETRY;
   daemon->soa_expiry = SOA_EXPIRY;
+
   add_txt("version.bind", "dnsmasq-" VERSION, 0 );
   add_txt("authors.bind", "Simon Kelley", 0);
   add_txt("copyright.bind", COPYRIGHT, 0);
diff --git a/src/util.c b/src/util.c
index 660347f..df751c7 100644
--- a/src/util.c
+++ b/src/util.c
@@ -81,6 +81,18 @@
   return (unsigned short) out[--outleft];
 }
 
+u32 rand32(void)
+{
+ if (!outleft) 
+    {
+      if (!++in[0]) if (!++in[1]) if (!++in[2]) ++in[3];
+      surf();
+      outleft = 8;
+    }
+  
+  return out[--outleft]; 
+}
+
 u64 rand64(void)
 {
   static int outleft = 0;