Re: [Dnsmasq-discuss] [PATCH] Reimplement rev-server to support arbitrary IPv4 prefix lengths

2020-04-15 Thread Dominik
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::: 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 
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=/[/...]/[]
 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*ip6.arpa")+1 */
+  memset(, 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 << 

[Dnsmasq-discuss] [PATCH] Reimplement rev-server to support arbitrary IPv4 prefix lengths

2020-04-14 Thread Dominik
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::: are sent to 10.0.0.1

(the displayed IPv6 addresses try to be as short as possible)

Best regards,
Dominik

From d038771987084c09df60af9c9c117e3fe14cdaa8 Mon Sep 17 00:00:00 2001
From: Dominik Derigs 
Date: Tue, 14 Apr 2020 19:40:21 +0200
Subject: [PATCH 3/3] Add explanatory log message to rev-server to facilitate
 understanding of the --rev-server option.
---
 src/option.c | 75 +++-
 1 file changed, 68 insertions(+), 7 deletions(-)

diff --git a/src/option.c b/src/option.c
index 809739e..1cb3caf 100644
--- a/src/option.c
+++ b/src/option.c
@@ -914,7 +914,7 @@ static int add_rev_serv(char* domain, char *comma, const int serv_flags, char *e
   return 1;
 }
 
-static int add_rev4(struct in_addr addr, int msize, char *comma, const int serv_flags, char *errstr)
+static int add_rev4(struct in_addr addr, int msize, char *comma, const int serv_flags, int explain, char *errstr)
 {
   int i;
   unsigned char a[3];
@@ -930,6 +930,25 @@ static int add_rev4(struct in_addr addr, int msize, char *comma, const int serv_
   a_min.s_addr = addr.s_addr & bitmask.s_addr;
   a_max.s_addr = a_min.s_addr + ~bitmask.s_addr;
 
+  /* Print derived subnet address range to ease configuration (if requested) */
+  if(explain)
+{
+  char original_address[INET_ADDRSTRLEN], min_address[INET_ADDRSTRLEN], max_address[INET_ADDRSTRLEN];
+  inet_ntop(AF_INET, , original_address, sizeof(original_address));
+  inet_ntop(AF_INET, _min, min_address, sizeof(min_address));
+  if(a_min.s_addr != a_max.s_addr)
+	{
+	  inet_ntop(AF_INET, _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);
+	}
+  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);
+	}
+}
+
   /* Extract IP address octets in host-format for in-addr.arpa string-assembly */
   for(i = 0; i < 3; i++)
 a[i] = (ntohl(a_min.s_addr) >> 8*(3-i)) & 0xFF;
@@ -961,7 +980,7 @@ static int add_rev4(struct in_addr addr, int msize, char *comma, const int serv_
   return 1;
 }
 
-static int add_rev6(struct in6_addr *addr, int msize, char *comma, const int serv_flags, char *errstr)
+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;
@@ -970,7 +989,49 @@ static int add_rev6(struct in6_addr *addr, int msize, char *comma, const int ser
 ret_err("bad prefix");
 
   p = domain = opt_malloc(73); /* strlen("32*ip6.arpa")+1 */
-  
+
+  /* Print derived subnet address range to ease configuration (if requested) */
+  if(explain)
+{
+  struct in6_addr bitmask, a_min, a_max;
+  memset(, 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];