[Dnsmasq-discuss] [PATCH] Allows zone transfers for PTR domains in IPv6 and IPv4

Daniel Bromberg daniel at basezen.com
Sun Feb 2 06:20:49 GMT 2020


So, continuing to talk to myself a bit, but there was actually no such workaround yet. Adding an explicit PTR-style auth-zone generated nonsensical output.

The following patch implements PTR zone transfer functionality. It is a substantial revision in that it changes a core structure, struct auth_zone, by adding two new fields to track zone attributes: the 1st ‘zone_flags’ to remember if a zone is a reverse, and if so, pre-compute in binary form the subnet it represents. I also renamed in_zone() to reflect its narrower function (fqdn_in_domain) and introduced a more general function: belongs_to_zone() that handles forward and reverse membership. Using the new zone_flags field, the add_resource_record() is able to choose a PTR or A/AAAA record appropriately. Finally filter_zone() was renamed to filter_forward_zone() to make space for filter_reverse_zone().

Since I am new to the list and the code base, I expect scrutiny and guidance to fit conventions I may not yet know. I’m willing to re-factor as necessary, but I believe the underlying algorithm to be sound.

If there is an automated test suite to armor plate this, please send my way so that alpha testing is not foisted on others when I could do it myself.

I hope to have at least an interesting discussion.

commit e869dcea2905bf595912ba487f66d3abfafd72e4
Author: Daniel Bromberg <daniel at basezen.com>
Date:   Sun Feb 2 00:55:03 2020 -0500

    Proposed patch to handle zone transfers (AXFR queries) of entire PTR zones
    properly for IPv4 and IPv6. It is necessary to declare explicitly an
    authoritative zone in dnsmasq.conf.  This is intentional as the old behavior of
    not supplying the PTR zones may be desirable.

    dnsmasq.conf fragments:
    ----------------------
    auth-zone=warehouse.example.com,10.1.65.0/24
    auth-zone=65.1.10.in-addr.arpa

    auth-zone=foohouse.thefoo.com,2001:0db8:abcd:0012::0/48
    auth-zone=0.0.0.0.2.1.0.0.d.c.b.a.8.b.d.0.1.0.0.2.ip6.arpa

    host-record=bud.warehouse.example.com,10.1.65.153,86400
    host-record=bud.foohouse.thefoo.com,2001:0db8:abcd:0012:0000:0000:0000:0097,86400

    Then the zones will be transferred --

    As:

    dig -t AXFR -q 65.1.10.in-addr.arpa -p 5053 @localhost

    ; <<>> DiG 9.11.3-1ubuntu1.11-Ubuntu <<>> -t AXFR -q 65.1.10.in-addr.arpa -p 5053 @localhost
    ;; global options: +cmd
    65.1.10.in-addr.arpa.   600     IN      SOA     router.warehouse.example.com. hostmaster.router.warehouse.example.com. 1580621378 1200 180 1209600 600
    65.1.10.in-addr.arpa.   600     IN      NS      router.warehouse.example.com.
    153.65.1.10.in-addr.arpa. 600   IN      PTR     bud.warehouse.example.com.
    65.1.10.in-addr.arpa.   600     IN      SOA     router.warehouse.example.com. hostmaster.router.warehouse.example.com. 1580621378 1200 180 1209600 600


    And:

    dig -t AXFR -q 0.0.0.0.2.1.0.0.d.c.b.a.8.b.d.0.1.0.0.2.ip6.arpa -p 5053 @localhost

    ; <<>> DiG 9.11.3-1ubuntu1.11-Ubuntu <<>> -t AXFR -q 0.0.0.0.2.1.0.0.d.c.b.a.8.b.d.0.1.0.0.2.ip6.arpa -p 5053 @localhost
    ;; global options: +cmd
    0.0.0.0.2.1.0.0.d.c.b.a.8.b.d.0.1.0.0.2.ip6.arpa. 600 IN SOA router.warehouse.example.com. hostmaster.router.warehouse.example.com. 1580621857 1200 180 1209600 600
    0.0.0.0.2.1.0.0.d.c.b.a.8.b.d.0.1.0.0.2.ip6.arpa. 600 IN NS router.warehouse.example.com.
    151.0.0.0.0.0.0.0.0.0.2.1.0.0.d.c.b.a.8.b.d.0.1.0.0.2.ip6.arpa. 600 IN PTR bud.foohouse.thefoo.com.
    0.0.0.0.2.1.0.0.d.c.b.a.8.b.d.0.1.0.0.2.ip6.arpa. 600 IN SOA router.warehouse.example.com. hostmaster.router.warehouse.example.com. 1580621857 1200 180 1209600 600

M       src/auth.c
M       src/dnsmasq.h
M       src/forward.c
M       src/option.c
M       src/util.c

diff --git a/src/auth.c b/src/auth.c
index 4daae30..b7e82ca 100644
--- a/src/auth.c
+++ b/src/auth.c
@@ -18,6 +18,9 @@
 
 #ifdef HAVE_AUTH
 
+static int add_resource_record_if_auth(struct dns_header *header, char *limit, int *truncp, int nameoffset, unsigned char **pp, 
+				       struct auth_zone *zone, struct crec *crecp, int local_query, char **cutp);
+
 static struct addrlist *find_addrlist(struct addrlist *list, int flag, union all_addr *addr_u)
 {
   do {
@@ -57,7 +60,11 @@ static struct addrlist *find_exclude(struct auth_zone *zone, int flag, union all
   return find_addrlist(zone->exclude, flag, addr_u);
 }
 
-static int filter_zone(struct auth_zone *zone, int flag, union all_addr *addr_u)
+static int filter_reverse_zone(struct auth_zone *zone, int flag, union all_addr *addr_u) {
+  return find_addrlist(zone->reverse_binary, flag, addr_u) != NULL;
+}
+
+static int filter_forward_zone(struct auth_zone *zone, int flag, union all_addr *addr_u)
 {
   if (find_exclude(zone, flag, addr_u))
     return 0;
@@ -69,7 +76,7 @@ static int filter_zone(struct auth_zone *zone, int flag, union all_addr *addr_u)
   return find_subnet(zone, flag, addr_u) != NULL;
 }
 
-int in_zone(struct auth_zone *zone, char *name, char **cut)
+int fqdn_in_domain(struct auth_zone *zone, char *name, char **cut)
 {
   size_t namelen = strlen(name);
   size_t domainlen = strlen(zone->domain);
@@ -205,7 +212,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
 	  
 	  if (intr)
 	    {
-	      if (local_query || in_zone(zone, intr->name, NULL))
+	      if (local_query || fqdn_in_domain(zone, intr->name, NULL))
 		{	
 		  found = 1;
 		  log_query(flag | F_REVERSE | F_CONFIG, intr->name, &addr, NULL);
@@ -239,7 +246,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
 					  T_PTR, C_IN, "d", name))
 		    anscount++;
 		}
-	      else if (crecp->flags & (F_DHCP | F_HOSTS) && (local_query || in_zone(zone, name, NULL)))
+	      else if (crecp->flags & (F_DHCP | F_HOSTS) && (local_query || fqdn_in_domain(zone, name, NULL)))
 		{
 		  log_query(crecp->flags & ~F_FORWARD, name, &addr, record_source(crecp->uid));
 		  found = 1;
@@ -268,7 +275,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
       else
 	{
 	  for (zone = daemon->auth_zones; zone; zone = zone->next)
-	    if (in_zone(zone, name, &cut))
+	    if (fqdn_in_domain(zone, name, &cut))
 	      break;
 	  
 	  if (!zone)
@@ -387,7 +394,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
 	     if (rc == 2 && flag)
 	       for (addrlist = intr->addr; addrlist; addrlist = addrlist->next)  
 		 if (((addrlist->flags & ADDRLIST_IPV6)  ? T_AAAA : T_A) == qtype &&
-		     (local_query || filter_zone(zone, flag, &addrlist->addr)))
+		     (local_query || filter_forward_zone(zone, flag, &addrlist->addr)))
 		   {
 		     if (addrlist->flags & ADDRLIST_REVONLY)
 		       continue;
@@ -468,7 +475,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
 		  { 
 		    nxdomain = 0;
 		    if ((crecp->flags & flag) && 
-			(local_query || filter_zone(zone, flag, &(crecp->addr))))
+			(local_query || filter_forward_zone(zone, flag, &(crecp->addr))))
 		      {
 			*cut = '.'; /* restore domain part */
 			log_query(crecp->flags, name, &crecp->addr, record_source(crecp->uid));
@@ -491,7 +498,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
 	    do
 	      { 
 		 nxdomain = 0;
-		 if ((crecp->flags & flag) && (local_query || filter_zone(zone, flag, &(crecp->addr))))
+		 if ((crecp->flags & flag) && (local_query || filter_forward_zone(zone, flag, &(crecp->addr))))
 		   {
 		     log_query(crecp->flags, name, &crecp->addr, record_source(crecp->uid));
 		     found = 1;
@@ -655,7 +662,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
       if (axfr)
 	{
 	  for (rec = daemon->mxnames; rec; rec = rec->next)
-	    if (in_zone(zone, rec->name, &cut))
+	    if (fqdn_in_domain(zone, rec->name, &cut))
 	      {
 		if (cut)
 		   *cut = 0;
@@ -681,7 +688,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
 	      }
 	      
 	  for (txt = daemon->rr; txt; txt = txt->next)
-	    if (in_zone(zone, txt->name, &cut))
+	    if (fqdn_in_domain(zone, txt->name, &cut))
 	      {
 		if (cut)
 		  *cut = 0;
@@ -696,7 +703,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
 	      }
 	  
 	  for (txt = daemon->txt; txt; txt = txt->next)
-	    if (txt->class == C_IN && in_zone(zone, txt->name, &cut))
+	    if (txt->class == C_IN && fqdn_in_domain(zone, txt->name, &cut))
 	      {
 		if (cut)
 		  *cut = 0;
@@ -711,7 +718,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
 	      }
 	  
 	  for (na = daemon->naptr; na; na = na->next)
-	    if (in_zone(zone, na->name, &cut))
+	    if (fqdn_in_domain(zone, na->name, &cut))
 	      {
 		if (cut)
 		  *cut = 0;
@@ -727,7 +734,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
 	      }
 	  
 	  for (intr = daemon->int_names; intr; intr = intr->next)
-	    if (in_zone(zone, intr->name, &cut))
+	    if (fqdn_in_domain(zone, intr->name, &cut))
 	      {
 		struct addrlist *addrlist;
 		
@@ -736,14 +743,14 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
 		
 		for (addrlist = intr->addr; addrlist; addrlist = addrlist->next) 
 		  if (!(addrlist->flags & ADDRLIST_IPV6) &&
-		      (local_query || filter_zone(zone, F_IPV4, &addrlist->addr)) && 
+		      (local_query || filter_forward_zone(zone, F_IPV4, &addrlist->addr)) && 
 		      add_resource_record(header, limit, &trunc, -axfroffset, &ansp, 
 					  daemon->auth_ttl, NULL, T_A, C_IN, "4", cut ? intr->name : NULL, &addrlist->addr))
 		    anscount++;
 		
 		for (addrlist = intr->addr; addrlist; addrlist = addrlist->next) 
 		  if ((addrlist->flags & ADDRLIST_IPV6) && 
-		      (local_query || filter_zone(zone, F_IPV6, &addrlist->addr)) &&
+		      (local_query || filter_forward_zone(zone, F_IPV6, &addrlist->addr)) &&
 		      add_resource_record(header, limit, &trunc, -axfroffset, &ansp, 
 					  daemon->auth_ttl, NULL, T_AAAA, C_IN, "6", cut ? intr->name : NULL, &addrlist->addr))
 		    anscount++;
@@ -754,7 +761,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
 	      }
              
 	  for (a = daemon->cnames; a; a = a->next)
-	    if (in_zone(zone, a->alias, &cut))
+	    if (fqdn_in_domain(zone, a->alias, &cut))
 	      {
 		strcpy(name, a->target);
 		if (!strchr(name, '.'))
@@ -778,34 +785,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
 	      if ((crecp->flags & (F_IPV4 | F_IPV6)) &&
 		  !(crecp->flags & (F_NEG | F_NXDOMAIN)) &&
 		  (crecp->flags & F_FORWARD))
-		{
-		  if ((crecp->flags & F_DHCP) && !option_bool(OPT_DHCP_FQDN))
-		    {
-		      char *cache_name = cache_get_name(crecp);
-		      if (!strchr(cache_name, '.') && 
-			  (local_query || filter_zone(zone, (crecp->flags & (F_IPV6 | F_IPV4)), &(crecp->addr))) &&
-			  add_resource_record(header, limit, &trunc, -axfroffset, &ansp, 
-					      daemon->auth_ttl, NULL, (crecp->flags & F_IPV6) ? T_AAAA : T_A, C_IN, 
-					      (crecp->flags & F_IPV4) ? "4" : "6", cache_name, &crecp->addr))
-			anscount++;
-		    }
-		  
-		  if ((crecp->flags & F_HOSTS) || (((crecp->flags & F_DHCP) && option_bool(OPT_DHCP_FQDN))))
-		    {
-		      strcpy(name, cache_get_name(crecp));
-		      if (in_zone(zone, name, &cut) && 
-			  (local_query || filter_zone(zone, (crecp->flags & (F_IPV6 | F_IPV4)), &(crecp->addr))))
-			{
-			  if (cut)
-			    *cut = 0;
-
-			  if (add_resource_record(header, limit, &trunc, -axfroffset, &ansp, 
-						  daemon->auth_ttl, NULL, (crecp->flags & F_IPV6) ? T_AAAA : T_A, C_IN, 
-						  (crecp->flags & F_IPV4) ? "4" : "6", cut ? name : NULL, &crecp->addr))
-			    anscount++;
-			}
-		    }
-		}
+		 anscount += add_resource_record_if_auth(header, limit, &trunc, -axfroffset, &ansp, zone, crecp, local_query, &cut);
 	    }
 	   
 	  /* repeat SOA as last record */
@@ -862,7 +842,83 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
   return ansp - (unsigned char *)header;
 }
   
-#endif  
-  
+
+/* The subnet for which we are authoritative, given a cache record, or NULL if not authoritative */
+static struct addrlist *auth_subnet_for_crec(struct crec *crecp) {
+   struct auth_zone *search_zone;
+   struct addrlist *subnet;
+   
+   for ( search_zone = daemon->auth_zones; search_zone; search_zone = search_zone->next ) {
+      if ( (subnet = find_subnet(search_zone, crecp->flags, &crecp->addr)) ) {
+	 return subnet;
+      }
+   }
+   return NULL;
+}
 
 
+static int belongs_to_zone(struct auth_zone *zone, struct crec *crecp, int local_query, char **cutp) {
+   if ( (crecp->flags & F_DHCP) && !option_bool(OPT_DHCP_FQDN) && strchr(cache_get_name(crecp), '.') ) {
+     return 0;
+   }
+
+   if ( zone->zone_flags & F_REVERSE ) { /* .arpa domain */
+     return filter_reverse_zone(zone, (crecp->flags & (F_IPV6 | F_IPV4)), &(crecp->addr));
+   }
+   else {
+     return
+       (local_query || filter_forward_zone(zone, (crecp->flags & (F_IPV6 | F_IPV4)), &(crecp->addr)))
+       && fqdn_in_domain(zone, cache_get_name(crecp), cutp);
+   }
+}
+
+
+static int add_resource_record_if_auth(struct dns_header *header, char *limit, int *truncp, int nameoffset, unsigned char **pp, 
+				       struct auth_zone *zone, struct crec *crecp, int local_query, char **cutp) {
+   const char *name = cache_get_name(crecp);
+   
+   if ( !belongs_to_zone(zone, crecp, local_query, cutp) ) {
+      return 0;
+   }
+   
+   if ( zone->zone_flags & F_REVERSE ) {
+      unsigned char *addr_bytes;
+      char rr_ptr_name[256];
+      const int end_offset = zone->reverse_binary->prefixlen / 8;
+      int start_offset;
+      
+      if ( zone->zone_flags & F_IPV4 ) {
+	 start_offset = 3;
+	 addr_bytes = (unsigned char *)&crecp->addr.addr4;
+      }
+      else {
+	 start_offset = 15;
+	 addr_bytes = (unsigned char *)&crecp->addr.addr6;
+      }
+      int addr_byte_offset;
+      /* Walk backwards; lowest host bits are at end of array. */
+      for ( rr_ptr_name[0] = '\0', addr_byte_offset = start_offset; addr_byte_offset >= end_offset; addr_byte_offset-- ) {
+	 char segment[5];
+	 sprintf(segment, "%hhu.", addr_bytes[addr_byte_offset]);
+	 strcat(rr_ptr_name, segment);
+      }
+      return add_resource_record(header, limit, truncp, nameoffset, pp,
+				 daemon->auth_ttl, NULL,
+				 T_PTR,
+				 C_IN,
+				 "d",
+				 rr_ptr_name,
+				 name);
+   }
+   else {
+      return add_resource_record(header, limit, truncp, nameoffset, pp,
+				 daemon->auth_ttl, NULL, 
+				 (crecp->flags & F_IPV6) ? T_AAAA : T_A,
+				 C_IN, 
+				 (crecp->flags & F_IPV4) ? "4" : "6", 
+				 cutp ? name : NULL, 
+				 &crecp->addr);
+   }
+}
+
+#endif  
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index 7fb440c..dd705b5 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -389,6 +389,7 @@ struct addrlist {
 
 struct auth_zone {
   char *domain;
+  int zone_flags; // Track reverse IPv6 (ip6.arpa), reverse IPv4 (in-addr.arpa) or forward (non-PTR records)
   struct auth_name_list {
     char *name;
     int flags;
@@ -396,6 +397,7 @@ struct auth_zone {
   } *interface_names;
   struct addrlist *subnet;
   struct addrlist *exclude;
+  struct addrlist *reverse_binary; // If reverse, store in binary form '168.192.in-addr.arpa' -> 192.168.0.0/24
   struct auth_zone *next;
 };
 
@@ -1220,7 +1222,7 @@ int private_net(struct in_addr addr, int ban_localhost);
 size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, 
 		   time_t now, union mysockaddr *peer_addr, int local_query,
 		   int do_bit, int have_pseudoheader);
-int in_zone(struct auth_zone *zone, char *name, char **cut);
+int fqdn_in_domain(struct auth_zone *zone, char *name, char **cut);
 #endif
 
 /* dnssec.c */
@@ -1278,6 +1280,7 @@ int read_write(int fd, unsigned char *packet, int size, int rw);
 
 int wildcard_match(const char* wildcard, const char* match);
 int wildcard_matchn(const char* wildcard, const char* match, int num);
+int zone_flags(char *zone_name);
 
 /* log.c */
 void die(char *message, char *arg1, int exit_code) ATTRIBUTE_NORETURN;
diff --git a/src/forward.c b/src/forward.c
index ed9c8f6..dba89a7 100644
--- a/src/forward.c
+++ b/src/forward.c
@@ -1506,7 +1506,7 @@ void receive_query(struct listener *listen, time_t now)
       /* find queries for zones we're authoritative for, and answer them directly */
       if (!auth_dns && !option_bool(OPT_LOCALISE))
 	for (zone = daemon->auth_zones; zone; zone = zone->next)
-	  if (in_zone(zone, daemon->namebuff, NULL))
+	  if (fqdn_in_domain(zone, daemon->namebuff, NULL))
 	    {
 	      auth_dns = 1;
 	      local_auth = 1;
@@ -1877,7 +1877,7 @@ unsigned char *tcp_request(int confd, time_t now,
 	  /* find queries for zones we're authoritative for, and answer them directly */
 	  if (!auth_dns && !option_bool(OPT_LOCALISE))
 	    for (zone = daemon->auth_zones; zone; zone = zone->next)
-	      if (in_zone(zone, daemon->namebuff, NULL))
+	      if (fqdn_in_domain(zone, daemon->namebuff, NULL))
 		{
 		  auth_dns = 1;
 		  local_auth = 1;
diff --git a/src/option.c b/src/option.c
index f77545f..c32ab23 100644
--- a/src/option.c
+++ b/src/option.c
@@ -1607,6 +1607,67 @@ static void server_list_free(struct server *list)
     }
 }
 
+
+/* The number of segments of a domain name. foo.co.uk => 3; foo.co.uk => 3 */
+static int num_segments(char *name) {
+   char *c = name;
+   int count = 1;
+   while ( *c++ ) {
+      if ( *c == '.' && *(c + 1) != '\0' ) {
+	 count++;
+      }
+   }
+   return count;
+}
+
+
+/* 
+   Construct a subnet in binary form corresponding to a PTR domain.
+   e.g. "2.4.in-addr.arpa" -> { addr: 4.2.0.0; flags: IPv4; prefixlen: 16 }
+   Returns newly allocated memory
+ */
+static struct addrlist *subnet_from_ptr_domain(struct auth_zone *zone) {
+   struct addrlist *reverse_zone_subnet = opt_malloc(sizeof (struct addrlist));
+   
+   static char prototype_ptr_name[256];
+   int total_segments;
+   int zone_segments;
+
+   if ( zone->domain ) {
+     zone_segments = num_segments(zone->domain) - 2;
+   }
+   else {
+     my_syslog(LOG_ERR, _("Invalid zone with NULL domain"));
+     return NULL;
+   }
+   
+   if ( zone->zone_flags & F_IPV4 ) {
+     total_segments = 4;
+   }
+   else if ( zone->zone_flags & F_IPV6 ) {
+     total_segments = 32;
+   }
+   else {
+     my_syslog(LOG_ERR, _("Invalid zone flags, neither IPv4 or IPv6 was set for %s"), zone->domain);
+     return NULL;
+   }
+
+   int seg_ctr;
+   for ( prototype_ptr_name[0] = '\0', seg_ctr = zone_segments; seg_ctr < total_segments; seg_ctr++ ) {
+      strcat(prototype_ptr_name, "0.");
+   }
+   strcat(prototype_ptr_name, zone->domain);
+
+   in_arpa_name_2_addr(prototype_ptr_name, &reverse_zone_subnet->addr);
+   reverse_zone_subnet->flags = (zone->zone_flags & F_IPV6) ? ADDRLIST_IPV6 : 0;
+   /* segments in IPv6 are quartets; in IPv4, octets */
+   reverse_zone_subnet->prefixlen = zone_segments * ((reverse_zone_subnet->flags & ADDRLIST_IPV6) ? 4 : 8);
+   reverse_zone_subnet->next = NULL;
+
+   return reverse_zone_subnet;
+}
+
+
 static int one_opt(int option, char *arg, char *errstr, char *gen_err, int command_line, int servers_only)
 {      
   int i;
@@ -2076,11 +2137,13 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
 	new = opt_malloc(sizeof(struct auth_zone));
 	new->domain = opt_string_alloc(arg);
 	new->subnet = NULL;
+	new->zone_flags = zone_flags(new->domain);
+	new->reverse_binary = (new->zone_flags & F_REVERSE) ? subnet_from_ptr_domain(new) : NULL;
 	new->exclude = NULL;
 	new->interface_names = NULL;
 	new->next = daemon->auth_zones;
 	daemon->auth_zones = new;
-
+	  
 	while ((arg = comma))
 	  {
 	    int prefixlen = 0;
diff --git a/src/util.c b/src/util.c
index 73bf62a..a57f271 100644
--- a/src/util.c
+++ b/src/util.c
@@ -741,3 +741,26 @@ int wildcard_matchn(const char* wildcard, const char* match, int num)
 
   return (!num) || (*wildcard == *match);
 }
+
+
+/*
+  Track if the zone is a reverse (RR=PTR) zone, and if so, whether it's for IPv4 or v6. Forward
+  zones can always have both types.
+*/
+int zone_flags(char *zone_name) {
+   static const char *ip4_ptr_suffix = ".in-addr.arpa";
+   static const char *ip6_ptr_suffix = ".ip6.arpa";
+   
+   const char *check_ip4_suffix = zone_name + strlen(zone_name) - strlen(ip4_ptr_suffix);
+   const char *check_ip6_suffix = zone_name + strlen(zone_name) - strlen(ip6_ptr_suffix);
+   
+   if ( strlen(zone_name) > strlen(ip4_ptr_suffix) && strcmp(check_ip4_suffix, ip4_ptr_suffix) == 0 ) {
+      return F_REVERSE | F_IPV4;
+   }
+   else if ( strlen(zone_name) > strlen(ip6_ptr_suffix) && strcmp(check_ip6_suffix, ip6_ptr_suffix) == 0 ) {
+      return F_REVERSE | F_IPV6;
+   }
+   else {
+      return F_FORWARD | F_IPV4 | F_IPV6;
+   }
+}


> Le 31 janv. 2020 à 3:05 PM, Daniel Bromberg <daniel at basezen.com> a écrit :
> 
> Somehow one always finds essential information after sending out an e-mail to a mailing list.
> 
> The manpage indeed states:
> 
>   > Note that at present, reverse (in-addr.arpa and ip6.arpa) zones are not available in zone transfers, so there is  no  point  arranging  sec-
>   > ondary servers for reverse lookups.
> 
> So I revise my report to a bug in the man page, since the workaround is worth mentioning.
> 
> I’ll work on a patch.
> 
> -Daniel
> 



More information about the Dnsmasq-discuss mailing list