[Pdns-users] LUA createForward() records and improvement suggestions

Michael Rommel rommel at layer-7.net
Mon Jun 22 20:11:30 UTC 2020

Dear all,

a while ago (2020-03-01) I asked about setting up domains with LUA createForward() 

I suceeded in setting it up and found some peculiarities, which I would like to
discuss here (in parallel I consider to submit PRs for some issues in Github and 
would appreciate guidance, whether it makes sense to open them).

There are four (4) questions in this mail and sorry for the length, but I wanted 
to make it explicit with all possible information provided from the get-go.

The setup for the proof-of-concept is a MASTER/SLAVE setup with sqlite3 as
backend. The used version is 4.3.0-1pdns.bionic from
http://repo.powerdns.com/ubuntu bionic-auth-43. 

The demo setup has essentially these domains and records (taken from the master):

sqlite> select * from records;
1|1|example.com|SOA|ns1.example.com ra-dns-admin.example.com 3 10380 3600 604800 3600|86400|||0||1
7|1|*.11111111.1001.example.com|LUA|A "createForward()"|60|||0||1
8|1|*.22222222-2002.example.com|LUA|A "createForward()"|60|||0||1
9|2|33333333-3003.example.com|SOA|ns1.example.com ra-dns-admin.example.com 2 10380 3600 604800 3600|86400|||0||1
10|2|*.33333333-3003.example.com|LUA|A "createForward()"|60|||0||1

sqlite> select * from domains;

Other tables available on request, I'll try to be as brief as possible.

The intended use is a DNS resolver for approx. 200.000 devices (more
later), each device shall have one of those wildcard createForward()
records and an accompanying _acme-challenge TXT record to obtain a Let's
Encrypt certificate for that record.

Q 1: Structure of the domain/subdomains / current implementation limitations

Currently the implementation of the LUA createForward() is in a way that
accepts the wildcard only as being directly underneath the domain in
question. In the example setup above, the 4.3 version:

- will not resolve the record ip10203040.22222222-2002.example.com
- will resolve the record ip10203040.33333333-3003.example.com

because only the latter one is directly beneath the domain. In my use case
that would mean to create 200.0000 additional entries in the domain table
(the NS records for a proper DNS delegation can be omitted here, because
all live on the same server). Each domain would only have two entries.

Even with a less aggressive SOA refresh time, that would mean, that pdns
would check all of those 200K domains within one hour. Since they mostly
stay the same, there is no AXFR involved, but the checking imposes a load
on the database and logging (tuneable of course). With PGSQL later this
will certainly bearable, but I think a multi-level structure might be
better suited. Hence the first patch:

I suggest changing the line 616 in lua-record.cc to

    if(parts.size()<4) {

This would retain the behaviour of accepting questions like:

but would enable additionally questions like:


letting me subdivide the domain without the need for separate subdomains
just for the resolution purpose.

It would be breaking for setups where the top level domain also has a
wildcard record and it is not wished that subdomains are resolved:

*.example.com|LUA|A "createForward()"

And ip10203040.test.example.com shall NOT be resolved. With the patch, it

Shall I submit a PR with this or do you have better ideas for an

Q 2: Does it make sense to subdivide the domain

The patch above allows me to structure the domain like the example
1001.11111111.example.com or vice versa. This would result in

~ 850 records like 11111111.example.com, each with 
1 - 10.000 records underneath it like 1001.11111111.example.com each with:
   *.1001.11111111.example.com LUA "createForward()" and 
  _acme-challenge.1001.11111111.example.com TXT "token from LE"
The 850 records would be full domains with their entry in the domains
table, but the 10.000 entries below would not be separate domains.

This means that once a new device needs a certificate, two records would be
created and in the worst case a domain with 20.000 entries would be needed
to AXFR by the SLAVE (or via native replication later).

But the refresh would only check the SOA for 850 records between pdns and
its backend db.

Would you consider a different solution / structure or does that make sense
to you?

Q 3: SERVFAIL with special questions

Currently there is a strange behaviour with createForward(). I would
consider this a bug, but am open to corrections.

The implementation skips the first two octets, then parses the remainder
with sscanf. This leads to a problem, when someone asks a question like


which leads to a SERVFAIL, because the string returned from the function is 
2.4294967295.104.4294967293 = 2 . -1 . 0x68 . -3
which then cannot be put into the answer packet.

; <<>> DiG 9.11.3-1ubuntu1.12-Ubuntu <<>> +norecurse @ 192-168-3-4.33333333-3003.example.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 10082
;; flags: qr aa; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

; EDNS: version: 0, flags:; udp: 1232
;192-168-3-4.33333333-3003.example.com. IN A

;; Query time: 1 msec
;; WHEN: Mon Jun 22 19:46:50 UTC 2020
;; MSG SIZE  rcvd: 66

root:/home/rommel/configuration# tail -100 /var/log/syslog |grep pdns
Jun 22 19:46:50 CertifVM01 pdns_server[1276]: Remote wants '192-168-3-4.33333333-3003.example.com|A', do = 0, bufsize = 1232 (4096): packetcache MISS
Jun 22 19:46:50 CertifVM01 pdns_server[1276]: Lua record (192-168-3-4.33333333-3003.example.com|A) reported: Parsing record content (try 'pdnsutil check-zone'): unable to parse IP address
Jun 22 19:46:50 CertifVM01 pdns_server[1276]: Exception building answer packet for 192-168-3-4.33333333-3003.example.com/A (Parsing record content (try 'pdnsutil check-zone'): unable to parse IP address) sending out servfail

My patch suggestion would be to add in the check for values above 255, like:
  if(sscanf(parts[0].c_str()+2, "%02x%02x%02x%02x", &x1, &x2, &x3, &x4)==4) {
    if(x1<=0xff && x2<=0xff && x3<=0xff && x4<=0xff)
      return std::to_string(x1)+"."+std::to_string(x2)+"."+std::to_string(x3)+"."+std::to_string(x4);

Would you agree that this might be better to fall through to returning "" 
rather than SERVFAIL?

Q 4: Adding the (forgotten) ability to parse the dash delimited decimal questions

In addition to the hexadecimal notation, I would really like to see the
proper resolving of entries like 192-168-3-4.33333333-3003.example.com.

These additional lines below the hex portion would allow this:

        if(sscanf(parts[0].c_str(), "%u-%u-%u-%u", &x1, &x2, &x3, &x4)==4) {
          if(x1<=0xff && x2<=0xff && x3<=0xff && x4<=0xff)
            return std::to_string(x1)+"."+std::to_string(x2)+"."+std::to_string(x3)+"."+std::to_string(x4);

Anyone interested in this PR?

Thanks everybody, who has read through this monster to this point. Any
suggestions or corrections or improvements.


Michael Rommel, Erlangen, Germany

More information about the Pdns-users mailing list