Hey (again) Simon and the other list members,

I received some private user's requests (mostly Pi-hole users,
apparently) which do seem to ultimately need arbitrary prefix-lengths
also for IPv6 addresses. As the majority of code now already existed for
IPv4, I changed my mind and added arbitrary prefix-lengths now as well
for IPv6 (patch 4). I was going back and forth about the choice of a
more simple or a more compact algorithm. As this is code is absolutely
not performance-critical, I chose the former as this eases accessibility
of the code and, thereby, possible maintenance in the future.

The result is a new set of three patches meant to come on top of my
patches from yesterday (this also makes patch 3 mandatory). I also found
it useful to make the prefix-length an optional field while I was at it
(patch 5).

> ./dnsmasq --rev-server=fe80::3aea:a711:fea1:2101,10.0.0.1
> dnsmasq[9870]: rev-server fe80::3aea:a711:fea1:2101[/128]:
> address-to-name queries for fe80::3aea:a711:fea1:2101 are sent to 10.0.0.1
The last patch improves the output of the explanations provided for
rev-server in case no server is specified (patch 6).

Best regards,
Dominik

On 14.04.20 20:26, Dominik wrote:
> Hey Simon,
>
> I recently noticed that the "--rev-server" option is limited to 8, 16,
> 24, and 32 bit prefixes for IPv4 addresses. Since I needed support for
> 22 bit subnets in the network I'm currently setting up, I decided to
> extend dnsmasq to support arbitrary IPv4 prefix-lengths. This mail has
> three patches attached that contain my changes. I hope this is helpful
> and can be merged into upstream dnsmasq.
>
> Patch 1 implements support for arbitrary IPv4 prefixes by largely
> rewriting option.c:add_rev4() and option.c:add_rev6()
>
> Patch 2 adds error checking for IPv6 prefix-lengths as the current
> algorithm only makes sense in 4 bit steps. As 4 bit steps in 128 bit
> wide addresses seems sufficient to me, I didn't change the nice and
> short algorithm used here.
>
> Patch 3 adds logging for the inner details of what rev-server is doing.
> It is an entirely optional patch. However, I would still recommend
> adding it as it facilitates understanding of what the rev-server option
> actually does (and if it does what the user is thinking it should be doing).
>
> Exemplary log outputs:
>
>> ./dnsmasq --rev-server=10.0.3.10/22,10.0.0.1
>> dnsmasq[29637]: rev-server 10.0.3.10/22: address-to-name queries for 
>> 10.0.0.0 to 10.0.3.255 are sent to 10.0.0.1
>
>> ./dnsmasq --rev-server=10.0.3.10/17,10.0.0.1#5353
>> dnsmasq[29662]: rev-server 10.0.3.10/17: address-to-name queries for 
>> 10.0.0.0 to 10.0.127.255 are sent to 10.0.0.1#5353
>
>> ./dnsmasq --rev-server=fe80::3aea:a711:fea1:2101/64,10.0.0.1
>> dnsmasq[29667]: rev-server fe80::3aea:a711:fea1:2101/64: address-to-name 
>> queries for fe80::3a00:0:0:0 to fe80::3aff:ffff:ffff:ffff are sent to 
>> 10.0.0.1
>
>
> (the displayed IPv6 addresses try to be as short as possible)
>
> Best regards,
> Dominik
>
From f02939cdbbf33f2591e6667b5766596279a45395 Mon Sep 17 00:00:00 2001
From: Dominik Derigs <dl...@dl6er.de>
Date: Wed, 15 Apr 2020 20:01:54 +0200
Subject: [PATCH 4/6] Add support for arbitrary IPv6 prefix-lengths in
 --rev-server.
---
 man/dnsmasq.8 |  2 +-
 src/option.c  | 99 +++++++++++++++++++++++++++++++--------------------
 2 files changed, 62 insertions(+), 39 deletions(-)

diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
index c836ae5..4a64df0 100644
--- a/man/dnsmasq.8
+++ b/man/dnsmasq.8
@@ -500,7 +500,7 @@ while
 .B --rev-server=192.168.3.10/22,192.168.0.1
 is equivalent to 
 .B --server=/0.168.192.in-addr.arpa/192.168.0.1 --server=/1.168.192.in-addr.arpa/192.168.0.1 --server=/2.168.192.in-addr.arpa/192.168.0.1 --server=/3.168.192.in-addr.arpa/192.168.0.1 
-While any valid prefix-length may be used with IPv4 addresses (/1 - /32), dnsmasq only accepts IPv6 prefix-lengths in steps of 4 (/4, /8, /12, ..., /128).
+Allowed prefix lengths are 1-32 (IPv4) and 1-128 (IPv6).
 .TP
 .B \-A, --address=/<domain>[/<domain>...]/[<ipaddr>]
 Specify an IP address to return for any host in the given domains.
diff --git a/src/option.c b/src/option.c
index 1cb3caf..9105b4c 100644
--- a/src/option.c
+++ b/src/option.c
@@ -983,65 +983,88 @@ static int add_rev4(struct in_addr addr, int msize, char *comma, const int serv_
 static int add_rev6(struct in6_addr *addr, int msize, char *comma, const int serv_flags, int explain, char *errstr)
 {
   int i;
-  char *p, *domain;
+  int idx = 31;
+  char *p, *top_domain;
+  unsigned char a_min8[32], a_max8[32];
+  struct in6_addr bitmask, a_min, a_max;
 
-  if(msize < 1 || msize > 128 || msize%4)
+  if(msize < 1 || msize > 128)
     ret_err("bad prefix");
 
-  p = domain = opt_malloc(73); /* strlen("32*<n.>ip6.arpa")+1 */
+  memset(&bitmask, 0, sizeof(bitmask));
+  for(i = 0; i < (int)sizeof(bitmask.s6_addr); i++)
+    {
+      /* Construct binary mask from specified prefix-length */
+      if(msize >= 8*(i+1))
+	bitmask.s6_addr[i] = 0xFF;
+      else if(msize > 8*i)
+	bitmask.s6_addr[i] = 0xFF << (8-msize%8);
+
+      /* Apply bitmask to address to compute subnet address range, generate
+         auxiliary 8bit address array for easier handling of IPv6 addresses,
+         and memorize where addresses differ */
+      a_min.s6_addr[i] = (*addr).s6_addr[i] & bitmask.s6_addr[i];
+      a_max.s6_addr[i] = a_min.s6_addr[i] + ~bitmask.s6_addr[i];
+      a_min8[2*i+1] =  a_min.s6_addr[i] & 0x0F;
+      a_min8[2*i  ] = (a_min.s6_addr[i] & 0xF0) >> 4;
+      a_max8[2*i+1] =  a_max.s6_addr[i] & 0x0F;
+      a_max8[2*i  ] = (a_max.s6_addr[i] & 0xF0) >> 4;
+
+      /* Memorize where addresses differ (if they differ) */
+      if(idx == 31 && a_min8[2*i] != a_max8[2*i])
+	idx = 2*i;
+      else if(idx == 31 && a_min8[2*i+1] != a_max8[2*i+1])
+	idx = 2*i + 1;
+    }
 
-  /* Print derived subnet address range to ease configuration (if requested) */
   if(explain)
     {
-      struct in6_addr bitmask, a_min, a_max;
-      memset(&bitmask, 0, sizeof(bitmask));
-      for(i = 0; i < 16; i++)
-	{
-	  if(msize >= 8*i)
-	    bitmask.s6_addr[i] = 0xFF;
-	  else if(msize > 8*(i) && msize < 8*(i+1))
-	    bitmask.s6_addr[i] = ~(((uint16_t)1 << (8 - (msize%8))) - 1) & 0xFF;
-	  else
-	    break;
-	}
-
-      /* Apply bitmask to address to compute subnet address range */
-      int addresses_differ = 0;
-      for(i = 0; i < 16; i++)
-	{
-	  a_min.s6_addr[i] = (*addr).s6_addr[i] & bitmask.s6_addr[i];
-	  a_max.s6_addr[i] = a_min.s6_addr[i] + ~bitmask.s6_addr[i];
-	  if(a_min.s6_addr[i] != a_max.s6_addr[i])
-	    addresses_differ = 1;
-	}
-
       /* Print derived subnet address range */
       char original_address[INET6_ADDRSTRLEN], min_address[INET6_ADDRSTRLEN], max_address[INET6_ADDRSTRLEN];
       inet_ntop(AF_INET6, addr, original_address, sizeof(original_address));
       inet_ntop(AF_INET6, &a_min, min_address, sizeof(min_address));
-      if(addresses_differ)
+      if(msize != 128)
 	{
 	  inet_ntop(AF_INET6, &a_max, max_address, sizeof(max_address));
-	  my_syslog(LOG_INFO, "rev-server %s/%i: address-to-name queries for %s to %s are sent to %s",
+	  my_syslog(LOG_INFO, "rev-server %s/%i: address-to-name queries for %s to %s are sent to %s",
 		original_address, msize, min_address, max_address, comma);
 	}
       else
 	{
-	  my_syslog(LOG_INFO, "rev-server %s/%i: address-to-name queries for %s are sent to %s",
+	  my_syslog(LOG_INFO, "rev-server %s/%i: address-to-name queries for %s are sent to %s",
 		original_address, msize, min_address, comma);
 	}
     }
 
-  for (i = msize-1; i >= 0; i -= 4)
-    { 
-      int dig = ((unsigned char *)addr)[i>>3];
-      p += sprintf(p, "%.1x.", (i>>2) & 1 ? dig & 15 : dig >> 4);
-    }
-  p += sprintf(p, "ip6.arpa");
+  /* Construct top-level domain "....ip6.arpa" */
+  p = top_domain = opt_malloc(73);
+  for (i = idx-1; i >= 0; i--)
+    p += sprintf(p, "%.1x.", a_min8[i]);
+  sprintf(p, "ip6.arpa");
 
-  /* Append server information, return 0 in case of an error */
-  if(!add_rev_serv(domain, comma, serv_flags, errstr))
-    return 0;
+  /* Extract min and max address range for string-assembly loop */
+  int min_cnt = a_min8[idx];
+  int max_cnt = a_max8[idx];
+
+  /* If 0.ip6.arpa to f.ip6.arpa would be added, we prevent this from happening */
+  if(min_cnt == 0x0 && max_cnt == 0xF)
+      max_cnt = min_cnt;
+
+  /* Walk last hex nibble */
+  for(i = min_cnt; i <= max_cnt; i++)
+    {
+      char *domain = opt_malloc(73); /* strlen("32*<n.>ip6.arpa")+1 */
+
+      /* Build specific ip6.arpa address */
+      if(min_cnt == 0 && max_cnt == 0)
+	sprintf(domain, "%s", top_domain);
+      else
+	sprintf(domain, "%.1x.%s", i, top_domain);
+
+      /* Append server information, return 0 in case of an error */
+      if(!add_rev_serv(domain, comma, serv_flags, errstr))
+	return 0;
+    }
 
   /* No error */
   return 1;
-- 
2.17.1
From da7f77a9080fa6338918bb2ce7e32f47997551af Mon Sep 17 00:00:00 2001
From: Dominik Derigs <dl...@dl6er.de>
Date: Wed, 15 Apr 2020 20:15:45 +0200
Subject: [PATCH 5/6] Make prefix-length optional in --rev-server.
---
 man/dnsmasq.8 |  4 ++--
 src/option.c  | 10 +++++++---
 2 files changed, 9 insertions(+), 5 deletions(-)

diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
index 4a64df0..8d25570 100644
--- a/man/dnsmasq.8
+++ b/man/dnsmasq.8
@@ -489,7 +489,7 @@ source address specified but the port may be specified directly as
 part of the source address. Forcing queries to an interface is not
 implemented on all platforms supported by dnsmasq.
 .TP
-.B --rev-server=<ip-address>/<prefix-len>[,<ipaddr>][#<port>][@<source-ip>|<interface>[#<port>]]
+.B --rev-server=<ip-address>[/<prefix-len>][,<ipaddr>][#<port>][@<source-ip>|<interface>[#<port>]]
 This is functionally the same as 
 .B --server, 
 but provides some syntactic sugar to make specifying address-to-name queries easier. For example
@@ -500,7 +500,7 @@ while
 .B --rev-server=192.168.3.10/22,192.168.0.1
 is equivalent to 
 .B --server=/0.168.192.in-addr.arpa/192.168.0.1 --server=/1.168.192.in-addr.arpa/192.168.0.1 --server=/2.168.192.in-addr.arpa/192.168.0.1 --server=/3.168.192.in-addr.arpa/192.168.0.1 
-Allowed prefix lengths are 1-32 (IPv4) and 1-128 (IPv6).
+Allowed prefix lengths are 1-32 (IPv4) and 1-128 (IPv6). If the prefix length is omitted, dnsmasq substitutes either 32 (IPv4) or 128 (IPv6).
 .TP
 .B \-A, --address=/<domain>[/<domain>...]/[<ipaddr>]
 Specify an IP address to return for any host in the given domains.
diff --git a/src/option.c b/src/option.c
index 9105b4c..b062ba8 100644
--- a/src/option.c
+++ b/src/option.c
@@ -944,7 +944,7 @@ static int add_rev4(struct in_addr addr, int msize, char *comma, const int serv_
 	}
       else
 	{
-	  my_syslog(LOG_INFO, "rev-server %s/%i: address-to-name queries for %s are sent to %s",
+	  my_syslog(LOG_INFO, "rev-server %s[/%i]: address-to-name queries for %s are sent to %s",
 		    original_address, msize, min_address, comma);
 	}
     }
@@ -1031,7 +1031,7 @@ static int add_rev6(struct in6_addr *addr, int msize, char *comma, const int ser
 	}
       else
 	{
-	  my_syslog(LOG_INFO, "rev-server %s/%i: address-to-name queries for %s are sent to %s",
+	  my_syslog(LOG_INFO, "rev-server %s[/%i]: address-to-name queries for %s are sent to %s",
 		original_address, msize, min_address, comma);
 	}
     }
@@ -2772,20 +2772,24 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
 	  ret_err(gen_err);
 	
 	comma=split(arg);

 	if (!(string = split_chr(arg, '/')) || !atoi_check(string, &size))
-	  ret_err(gen_err);
+	  size = -1;
 	
 	if (servers_only)
 	  serv_flags = SERV_FROM_FILE;
 
 	if (inet_pton(AF_INET, arg, &addr4))
 	  {
+	    if (size == -1)
+	      size = 32; /* Assume 32 bit prefix-length for an exact IPv4 address */
 	    if (!add_rev4(addr4, size, comma, serv_flags, 1, errstr))
 	      return 0;
 	  }
 	else if (inet_pton(AF_INET6, arg, &addr6))
 	  {
+	    if (size == -1)
+	      size = 128; /* Assume 128 bit prefix-length for an exact IPv6 address */
 	    if(!add_rev6(&addr6, size, comma, serv_flags, 1, errstr))
 	      return 0;
 	  }
-- 
2.17.1
From 05248e8962e01e07bd4bbf574a66eeacfc744f02 Mon Sep 17 00:00:00 2001
From: Dominik Derigs <dl...@dl6er.de>
Date: Wed, 15 Apr 2020 20:20:05 +0200
Subject: [PATCH 6/6] Correctly handle optinal forwarding target.
---
 src/option.c | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/src/option.c b/src/option.c
index b062ba8..6e157dc 100644
--- a/src/option.c
+++ b/src/option.c
@@ -936,16 +936,17 @@ static int add_rev4(struct in_addr addr, int msize, char *comma, const int serv_
       char original_address[INET_ADDRSTRLEN], min_address[INET_ADDRSTRLEN], max_address[INET_ADDRSTRLEN];
       inet_ntop(AF_INET, &addr, original_address, sizeof(original_address));
       inet_ntop(AF_INET, &a_min, min_address, sizeof(min_address));
+      const char *target = (comma != NULL && strlen(comma) > 0) ? comma : "local server";
       if(a_min.s_addr != a_max.s_addr)
 	{
 	  inet_ntop(AF_INET, &a_max, max_address, sizeof(max_address));
 	  my_syslog(LOG_INFO, "rev-server %s/%i: address-to-name queries for %s to %s are sent to %s",
-		    original_address, msize, min_address, max_address, comma);
+		    original_address, msize, min_address, max_address, target);
 	}
       else
 	{
 	  my_syslog(LOG_INFO, "rev-server %s[/%i]: address-to-name queries for %s are sent to %s",
-		    original_address, msize, min_address, comma);
+		    original_address, msize, min_address, target);
 	}
     }
 
@@ -1023,16 +1024,17 @@ static int add_rev6(struct in6_addr *addr, int msize, char *comma, const int ser
       char original_address[INET6_ADDRSTRLEN], min_address[INET6_ADDRSTRLEN], max_address[INET6_ADDRSTRLEN];
       inet_ntop(AF_INET6, addr, original_address, sizeof(original_address));
       inet_ntop(AF_INET6, &a_min, min_address, sizeof(min_address));
+      const char *target = (comma != NULL && strlen(comma) > 0) ? comma : "local server";
       if(msize != 128)
 	{
 	  inet_ntop(AF_INET6, &a_max, max_address, sizeof(max_address));
 	  my_syslog(LOG_INFO, "rev-server %s/%i: address-to-name queries for %s to %s are sent to %s",
-		original_address, msize, min_address, max_address, comma);
+		original_address, msize, min_address, max_address, target);
 	}
       else
 	{
 	  my_syslog(LOG_INFO, "rev-server %s[/%i]: address-to-name queries for %s are sent to %s",
-		original_address, msize, min_address, comma);
+		original_address, msize, min_address, target);
 	}
     }
 
-- 
2.17.1
_______________________________________________
Dnsmasq-discuss mailing list
Dnsmasq-discuss@lists.thekelleys.org.uk
http://lists.thekelleys.org.uk/mailman/listinfo/dnsmasq-discuss

Reply via email to