]> granicus.if.org Git - pdns/commitdiff
Improve Lua scripting and DNS64 docs
authorPieter Lexis <pieter.lexis@powerdns.com>
Mon, 13 Jun 2016 15:24:34 +0000 (17:24 +0200)
committerPieter Lexis <pieter.lexis@powerdns.com>
Tue, 14 Jun 2016 06:59:47 +0000 (08:59 +0200)
Closes #3756
Closes #3952
Closes #3328

docs/markdown/recursor/dns64.md
docs/markdown/recursor/scripting.md
pdns/dns64.lua

index a231493053d804b48992f761b048afcf2231403f..140c172acaab99894682dbbc80489c7298e84dec 100644 (file)
@@ -1,42 +1,34 @@
 # DNS64 support in the PowerDNS Recursor
-DNS64 is a technology to allow IPv6-only clients to receive special IPv6 addresses that are proxied to IPv4 addresses. This proxy service is then called NAT64.
+DNS64, described in [RFC 6147](https://tools.ietf.org/html/rfc6147) is a technology
+to allow IPv6-only clients to receive special IPv6 addresses that are proxied to
+IPv4 addresses. This proxy service is then called NAT64.
 
-So, as an example, let's say an IPv6 only client would want to connect to www.example.com, it would request the AAAA records for that name. However, if example.com does not actually have an IPv6 address, what we do is 'fake up' an IPv6 address. We do this by retrieving the A records for www.example.com, and translating them to AAAA records.
+So, as an example, let's say an IPv6 only client would want to connect to
+`www.example.com`, it would request the AAAA records for that name. However, if
+`example.com` does not actually have an IPv6 address, what we do is 'fake up' an
+IPv6 address. We do this by retrieving the A records for `www.example.com`, and
+translating them to AAAA records. Elsewhere, a NAT64 device listens on these IPv6
+addresses, and extracts the IPv4 address from each packet, and proxies it on.
 
-Elsewhere, a NAT64 device listens on these IPv6 addresses, and extracts the IPv4 address from each packet, and proxies it on.
+For maximum flexibility, DNS64 support is included in the [Lua scripting engine](scripting.md).
+This allows for example to hand out custom IPv6 gateway ranges depending on the
+location of the requestor, enabling the use of NAT64 services close to the user.
 
-DNS64 is described in RFC 6147, and is supported by the PowerDNS Recursor since version 3.4.
 
-For maximum flexibility, DNS64 support is included in the Lua scripting engine. This allows for example to hand out custom IPv6 gateway ranges depending on the location of the requestor, enabling the use of NAT64 services close to the user.
+Apart from faking AAAA records, it is also possible to also generate the
+associated PTR records. This makes sure that reverse lookup of DNS64-generated
+IPv6 addresses generate the right name. The procedure is similar, a request for
+an IPv6 PTR is converted into one for the corresponding IPv4 address.
 
-To setup DNS64, create the following Lua script and save it to a file called dns64.lua:
+To setup DNS64, with both forward and reverse records, create the following Lua
+script and save it to a file called `dns64.lua`
 
 ```
-    function nodata ( remoteip, domain, qtype, records )
-             if qtype ~= pdns.AAAA then return pdns.PASS, {} end  --  only AAAA records
-             setvariable()
-             return "getFakeAAAARecords", domain, "fe80::21b:77ff:0:0"
-        end
+!!include ../pdns/dns64.lua
 ```
 
-Where fe80::21b::77ff:0:0 is your "Pref64" translation prefix. Next, make sure your script gets loaded by specifying it with `lua-dns-script=dns64.lua`.
+Where fe80::21b::77ff:0:0 is your "Pref64" translation prefix and the "ip6.arpa"
+string is the reversed form of this Pref64 address. Now ensure your script gets
+loaded by specifying it with [`lua-dns-script=dns64.lua`](#settings.md#lua-dns-script).
 
-In addition, since PowerDNS Recursor 3.6, it is also possible to also generate the associated PTR records. This makes sure that reverse lookup of DNS64-generated IPv6 addresses generate the right name. The procedure is similar, a request for an IPv6 PTR is converted into one for the corresponding IPv4 address.
-
-To hook up the generation of PTR records, include:
-
-```
-      function endswith(s, send)
-             return #s >= #send and s:find(send, #s-#send+1, true) and true or false
-      end
-
-      function preresolve ( remoteip, domain, qtype )
-         if qtype ==pdns.PTR and endswith(domain, "f.f.7.7.b.1.2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.e.f.ip6.arpa.")
-             then
-                return "getFakePTRRecords", domain, "fe80::21b::77ff:0:0"
-             end
-         return pdns.PASS, {}
-      end
-```
-
-Where the "ip6.arpa" string is the reversed form of your Pref64 address.
+To enhance DNS64, see the [Lua scripting](scripting.md) documentation.
index f81ff82de7fb15f2134ba86b8d114e3c8f85db4b..a00f6d83ddccca6275932ca7b1d65c03088a4162 100644 (file)
@@ -1,21 +1,25 @@
 # Scripting The Recursor
-As of version 3.1.7 of the PowerDNS Recursor, it is possible to modify
-resolving behaviour using simple scripts written in the
-[Lua](http://www.lua.org) programming language. This page documents the Recursor 4.x and beyond
-version of the scripting API.
+In the PowerDNS recursor, it is possible to modify resolving behaviour using
+simple scripts written in the [Lua](http://www.lua.org) programming language.
+This page documents the Recursor 4.0.0 and beyond version of the scripting API.
+
+**Note**: This describes the Lua scripts as supported by 4.x. They are very
+different than the ones from 3.x, but tend to be faster and more correct.
 
 These scripts can be used to quickly override dangerous domains, fix things
-that are wrong, for load balancing or for legal or commercial purposes. The scripts can also protect 
-you or your users from malicious traffic.
+that are wrong, for load balancing or for legal or commercial purposes. The
+scripts can also protect you or your users from malicious traffic.
 
-Lua is extremely fast and lightweight, easily supporting hundreds of
-thousands of queries per second. The Lua language is explained very
-well in the excellent book [Programming in Lua](http://www.amazon.com/exec/obidos/ASIN/859037985X/lua-pilindex-20). If you already have programming experience, [Learn Lua in 15 Minutes](http://tylerneylon.com/a/learn-lua/) is a great primer.
+Lua is extremely fast and lightweight, easily supporting hundreds of thousands
+of queries per second. The Lua language is explained very well in the excellent
+book [Programming in Lua](http://www.amazon.com/exec/obidos/ASIN/859037985X/lua-pilindex-20).
+If you already have programming experience,
+[Learn Lua in 15 Minutes](http://tylerneylon.com/a/learn-lua/) is a great primer.
 
 For extra performance, a Just In Time compiled version of Lua called
 [LuaJIT](http://luajit.org/) is supported.
 
-Queries can be intercepted in many places: 
+Queries can be intercepted in many places:
 
 * before any packet parsing begins (`ipfilter`)
 * before the resolving logic starts to work (`preresolve`)
@@ -24,13 +28,10 @@ Queries can be intercepted in many places:
 * before an outgoing query is made to an authoritative server (`preoutquery`)
 
 ## Configuring Lua scripts
-
 In order to load scripts, the PowerDNS Recursor must have Lua support built
 in.  The packages distributed from the PowerDNS website have this language
-enabled, other distributions may differ.  To compile with Lua support, use:
-`LUA=1 make` or `LUA=1 gmake` as the case may be.  Paths to the Lua include
-files and binaries may be found near the top of the `Makefile`, or passed 
-to `configure`.
+enabled, other distributions may differ. By default, the Recursor's configure
+script will attempt to detect if Lua is available.
 
 **note**: Only one script can be loaded at the same time. If you load a different
 script, the current one will be replaced (safely)!
@@ -41,7 +42,8 @@ be reloaded or unloaded at runtime with no interruption in operations.  If a
 new script contains syntax errors, the old script remains in force.
 
 On the command line, or in the configuration file, the setting
-`lua-dns-script` can be used to supply a full path to a 'lua' script.
+[`lua-dns-script`](settings.md#lua-dns-script) can be used to supply a full path
+to the Lua script.
 
 At runtime, `rec_control reload-lua-script` can be used to either reload the
 script from its current location, or, when passed a new file name, load one
@@ -54,24 +56,53 @@ also that you'll be informed about syntax errors at compile time.
 
 Finally, `rec_control unload-lua-script` can be used to remove the currently
 installed script, and revert to unmodified behaviour.
-
-## Writing Lua PowerDNS Recursor scripts for version 4.x
-**Note**: This describes the Lua scripts as supported by 4.x. They are very different than the ones from 3.x, but
-tend to be faster and more correct.
 
+# Writing Lua PowerDNS Recursor scripts
 To get a quick start, we have supplied a sample script that showcases all functionality described below. Please
 find it [here](https://github.com/PowerDNS/pdns/blob/master/pdns/powerdns-example-script.lua). 
 
-In the 4.x API, addresses and DNS Names are not passed as strings but as native objects. This allows for
-easy checking against netmasks and domain sets. It also means that to print such names, the `:toString` 
-method must be used (or even `:toStringWithPort` for addresses).
+Addresses and DNS Names are not passed as strings but as native objects. This
+allows for easy checking against [netmasks](#netmask-groups) and [domain sets]().
+It also means that to print such names, the `:toString` method must be used
+(or even `:toStringWithPort` for addresses).
 
 Comparing IP addresses and DNSNames is not done with '==' but with the `:equal` method. 
 
-Once a script is loaded, PowerDNS looks for several functions, as detailed below. All of these functions are optional.
+Once a script is loaded, PowerDNS looks for several functions, as detailed below.
+All of these functions are optional.
+
+## The DNSQuestion (`dq`) object
+Apart from the `ipfilter`-function, all functions work on a `dq` (DNSQuestion)
+object. This object contains details about the current state of the question.
+This state can be modified from the various hooks. If a function returns 'true',
+it will indicate that it handled a query. If it returns false, the Recursor will
+continue processing unchanged (with one minor exception).
+
+The DNSQuestion object contains at least the following fields:
+
+* qname - DNS native version of the name this query is for
+* qtype - type this query is for, can be compared against pdns.A, pdns.AAAA etc
+* rcode - current DNS Result Code, which can be overridden, including to several magical values
+* remoteaddr - address of the requestor
+* localaddr - address this query was received on
+* variable - a boolean which, if set, indicates the recursor should not packet cache this answer. Honored even when returning 'false'! Important when providing answers that vary over time or based on sender details.
+* followupFunction - a string that signals the nameserver to take one of the following additional actions:
+  * followCNAMERecords: When adding a CNAME to the answer, this tells the recursor to follow that CNAME. See [CNAME chain resolution](#cname-chain-resolution)
+  * getFakeAAAARecords: Get a fake AAAA record, see [DNS64](#dns64)
+  * getFakePTRRecords: Get a fake PTR record, see [DNS64](#dns64)
+  * udpQueryResponse: Do a UDP query and call a handler, see [`udpQueryResponse`](#udpqueryresponse)
+
+It also supports the following methods:
+
+* `addAnswer(type, content, [ttl, name])`: add an answer to the record of `type` with `content`. Optionally supply TTL and the name of
+  the answer too, which defaults to the name of the question
+* `getRecords()`: get a table of DNS Records in this DNS Question (or answer by now)
+* `setRecords(records)`: after your edits, update the answers of this question
+* `getEDNSOption(num)`: get the EDNS Option with number `num`
+* `getEDNSOptions()`: get a map of all EDNS Options
+* `getEDNSSubnet()`: returns the netmask specified in the EDNSSubnet option, or empty if there was none
 
-### `function ipfilter ( remoteip, localip, dh )`
+## `function ipfilter ( remoteip, localip, dh )`
 This hook gets queried immediately after consulting the packet cache, but before
 parsing the DNS packet. If this hook returns something else than false, the packet is dropped. 
 However, because this check is after the packet cache, the IP address might still receive answers
@@ -99,32 +130,21 @@ function ipfilter(rem, loc, dh)
 end
 ```
 
-This hook does not get the full DNSQuestion object as described below, since filling out the fields
+This hook does not get the full DNSQuestion object, since filling out the fields
 would require packet parsing, which is what we are trying to prevent with `ipfilter`.
 
-### The DNSQuestion object
-The following functions all receive a DNSQuestion object, which contains details about
-the current state of the question. This state can be modified from the various hooks. If
-a function returns 'true', it will indicate that it handled a query. If it returns false,
-the Recursor will continue processing unchanged (with one minor exception).
-
-The DNSQuestion object contains at least the following fields:
-
-* qname - DNS native version of the name this query is for
-* qtype - type this query is for, can be compared against pdns.A, pdns.AAAA etc
-* rcode - current DNS Result Code, which can be overridden, including to several magical values
-* remoteaddr - address of the requestor
-* localaddr - address this query was received on
-* variable - a boolean which, if set, indicates the recursor should not packet cache this answer. Honored even when returning 'false'! Important when providing answers that vary over time or based on sender details.
-
-It also supports the following methods:
+### `function gettag(remote, ednssubnet, local, qname, qtype)`
+The `gettag` function is invoked when `dq.tag` is called on a dq object or when
+the Recursor attempts to discover in which packetcache an answer is available.
+This function must return an integer, which is the tag number of the packetcache.
 
-* `addAnswer(type, content, [ttl, name])`: add an answer to the record of `type` with `content`. Optionally supply TTL and the name of 
-  the answer too, which defaults to the name of the question
-* `getRecords()`: get a table of DNS Records in this DNS Question (or answer by now)
-* `setRecords(records)`: after your edits, update the answers of this question 
+The tagged packetcache can e.g. be used to answer queries from cache that have
+e.g. been filtered for certain IPs (this logic should be implemented in the
+`gettag` function). This ensure that queries are answered quickly compared to
+setting dq.variable to `true`. In the latter case, repeated queries will pass
+through the entire Lua script.
 
-### `function preresolve ( dq )`
+### `function preresolve(dq)`
 is called before any DNS resolution is attempted, and if this function
 indicates it, it can supply a direct answer to the DNS query, overriding the
 internet.  This is useful to combat botnets, or to disable domains
@@ -133,24 +153,22 @@ unacceptable to an organization for whatever reason.
 The rcode can be set to pdns.DROP to drop the query. Other statuses are normal DNS
 return codes, like no error, NXDOMDAIN etc.
 
-### `function postresolve (dq)`
-
+### `function postresolve(dq)`
 is called right before returning a response to a client (and, unless
 `variable` is set, to the packet cache too).  It allows inspection
-and modification of almost any detail in the return packet.  
-
-### `function nxdomain ( dq )`
+and modification of almost any detail in the return packet.
 
+### `function nxdomain(dq)`
 is called after the DNS resolution process has run its course, but ended in
 an 'NXDOMAIN' situation, indicating that the domain or the specific record
-does not exist.  Works entirely like postresolve, but saves a trip through Lua for 
+does not exist.  Works entirely like postresolve, but saves a trip through Lua for
 answers which are not NXDOMAIN.
 
-### `function nodata ( dq )`
+### `function nodata(dq)`
 is just like `nxdomain`, except it gets called when a domain exists, but the
-requested type does not.  This is where one would implement DNS64. 
+requested type does not.  This is where one would implement DNS64.
 
-### `function preoutquery (dq)`
+### `function preoutquery(dq)`
 This hook is not called in response to a client packet, but fires when the Recursor
 wants to talk to an authoritative server. When this hook sets the special result code -3,
 the whole DNS client query causing this outquery gets dropped.
@@ -158,20 +176,19 @@ the whole DNS client query causing this outquery gets dropped.
 However, this function can also return records like the preresolve query above.
 
 ## Semantics
-
-All these functions are passed the IP address of the requester. Most also
-get passed the name and type being requested.  In return, these functions
-indicate if they have taken over the request, or want to let normal
-proceedings take their course.
+The functions must return `true` if they have taken over the query and wish that
+the nameserver should not proceed with its regular query-processing. When a
+function returns `false`, the nameserver will process the query normally until
+a new function is called.
 
 If a function has taken over a request, it should set an rcode (usually 0),
 and specify a table with records to be put in the answer section of a
 packet.  An interesting rcode is NXDOMAIN (3, or `pdns.NXDOMAIN`), which
-specifies the non-existence of a domain.  Returning false signifies that the
-function chose not to intervene.
+specifies the non-existence of a domain.
 
-The `ipfilter` and `preoutquery` hooks are different, in that `ipfilter` can only return a true of false value, and
-that `preoutquery` can also set rcode -3 to signify that the whole query should be terminated.
+The `ipfilter` and `preoutquery` hooks are different, in that `ipfilter` can
+only return a true of false value, and that `preoutquery` can also set rcode -3
+to signify that the whole query should be terminated.
 
 A minimal sample script:
 
@@ -210,59 +227,71 @@ if nmg:match(dq.remote) then
 end
 ```
 
-### DNSName 
+### IP Addresses
+We move IP addresses around in native format, called ComboAddress within PowerDNS.
+ComboAddresses can be IPv4 or IPv6, and unless you want to know, you don't need
+to. You can make a ComboAddress with: `newCA("::1")`, and you can compare
+it against a NetmaskGroup as described above.
+
+To compare the address (so not the port) of two ComboAddresses, use `:equal`.
+
+To convert an address to human-friendly representation, use `:toString()` or
+`:toStringWithPort()`. To get only the port number, use `:getPort()`.
+
+### DNSName
 DNSNames are passed to various functions, and they sport the following methods:
 
 * `:equal`: use this to compare two DNSNames in DNS native fashion. So 'PoWeRdNs.COM' matches 'powerdns.com'
 * `:isPartOf`: returns true if a is a part of b. So: `newDN("www.powerdns.com"):isPartOf(newDN("CoM."))` returns true
 
-To make your own DNSName, use newDN("domain.name").
+To make your own DNSName, use `newDN("domain.name")`.
 
-### IP Addresses
-We move IP addresses around in native format, called ComboAddress within PowerDNS. ComboAddresses can be IPv4 or IPv6,
-and unless you want to know, you don't need to. You can make a ComboAddress with: `newCA("::1")`, and you can compare 
-it against a NetmaskGroup as described above.
+### DNS Suffix Match groups
+The `newDS` function creates a "Suffix Match group" that allows fast checking if
+a DNSName is part of a group. Add domains to this group with the `:add(domain)`
+function of the object: `myDS:add("example.net")`, or with a list:
+`myDS:add({"example.net", "example.com"}).
 
-To compare the address (so not the port) of two ComboAddresses, use `:equal`. 
+To check e.g. the dq.qname against this list, use `:check(dq.qname)`. This will
+be `true` if dq.qname is part of any of the Suffix Match group domains.
 
-To convert an address to human-friendly representation, use `:toString()` or `:toStringWithPort()`. To 
-get only the port number, use `:getPort()`.
+This could e.g. be used to answer questions for known malware domains.
 
 ### Metrics
-You can custom metrics which will be shown in the output of 'rec_control get-all' and sent to the metrics server over the Carbon protocol,
-and also appear in the JSON HTTP API. 
+You can custom metrics which will be shown in the output of 'rec_control get-all'
+and sent to the metrics server over the Carbon protocol, and also appear in the
+JSON HTTP API.
 
-Create a custom metric with: `myMetric= getMetric("name")`. This metric sports the following metrics:
+Create a custom metric with: `myMetric= getMetric("name")`. This metric sports
+the following metrics:
 
 * `:inc()`: increase metric by 1
 * `:incBy(amount)`: increase metric by amount
 * `:set(to)`: set metric to value to
 * `:get()`: get value of metric
 
-Metrics are shared across all of PowerDNS and are fully atomic and high performance. The myMetric object is effectively a
-pointer to an atomic value. 
+Metrics are shared across all of PowerDNS and are fully atomic and high
+performance. The myMetric object is effectively a pointer to an atomic value.
 
-Note that metrics live in the same namespace as 'system' metrics. So if you generate one that overlaps with a PowerDNS stock
-metric, you will get double output and weird results. 
+Note that metrics live in the same namespace as 'system' metrics. So if you
+generate one that overlaps with a PowerDNS stock metric, you will get double
+output and weird results.
 
 ### Logging
-To log messages with the main PowerDNS Recursor process, use
-`pdnslog(message)`.  Available since version 3.2.  pdnslog can also write
-out to a syslog loglevel if specified.  Use `pdnslog(message,
-pdns.loglevels.LEVEL)` with the correct pdns.loglevels entry.  Entries are
-listed in the following table:
-
-|&nbsp;|&nbsp;|
-|:--||:--|
-|All|pdns.loglevels.All|
-|Alert|pdns.loglevels.Alert|
-|Critical|pdns.loglevels.Critical|
-|Error|pdns.loglevels.Error|
-|Warning|pdns.loglevels.Warning|
-|Notice|pdns.loglevels.Notice|
-|Info|pdns.loglevels.Info|
-|Debug|pdns.loglevels.Debug|
-|None|pdns.loglevels.None|
+To log messages with the main PowerDNS Recursor process, use `pdnslog(message)`.
+pdnslog can also write out to a syslog loglevel if specified.
+Use `pdnslog(message, pdns.loglevels.LEVEL)` with the correct pdns.loglevels
+entry.  Entries are listed in the following table:
+
+* All - `pdns.loglevels.All`
+* Alert - `pdns.loglevels.Alert`
+* Critical - `pdns.loglevels.Critical`
+* Error - `pdns.loglevels.Error`
+* Warning - `pdns.loglevels.Warning`
+* Notice - `pdns.loglevels.Notice`
+* Info - `pdns.loglevels.Info`
+* Debug - `pdns.loglevels.Debug`
+* None - `pdns.loglevels.None`
 
 `pdnslog(message)` will write out to Info by default.
 
@@ -270,19 +299,53 @@ listed in the following table:
 Public Suffix List. In general it will tell you the 'registered domain' for a given
 name.
 
-To get fake AAAA records for DNS64 usage, set dq.followupFunction to "getFakeAAAARecords",
-dq.followupPrefix to (say) "fe80::21b:77ff:0:0" and dq.followupName to the name you want to 
-synthesise an IPv6 address for.
+## DNS64
+The `getFakeAAAARecords` and `getFakePTRRecords` followupFunctions can be used
+to implement DNS64. See [DNS64 support in the PowerDNS Recursor](dns64.md) for
+more information.
+
+To get fake AAAA records for DNS64 usage, set dq.followupFunction to `getFakeAAAARecords`,
+dq.followupPrefix to e.g. "64:ff9b::" and dq.followupName to the name you want to
+synthesize an IPv6 address for.
+
+For fake reverse (PTR) records, set dq.followupFunction to `getFakePTRRecords`
+and set dq.followupName to the name to look up and dq.followupPrefix to the
+same prefix as used with `getFakeAAAARecords`.
 
 ## CNAME chain resolution
 It may be useful to return a CNAME record for Lua, and then have the
 PowerDNS Recursor continue resolving that CNAME.  This can be achieved by
-setting dq.followupFunction to "followCNAMERecords" and followupDomain to 
+setting dq.followupFunction to `followCNAMERecords` and dq.followupDomain to
 "www.powerdns.com". PowerDNS will do the rest.
 
+## `udpQueryResponse`
+The `udpQueryResponse` dq.followupFunction allows you to query a simple key-value
+store over UDP asynchronously.
+
+Several dq variables can be set:
+
+* `udpQueryDest`: destination IP address to send the UDP packet to
+* `udpQuery`: The content of the UDP payload
+* `udpCallback`: The name of the callback function that is called when an answer is received
+
+The callback function must accept the `dq` object and can find the response to
+the UDP query in `dq.udpAnswer`.
+
+In this callback function, `dq.followupFunction` can be set again to any of the
+available functions for further processing.
+
+This example script queries a simple key/value store over UDP to decide on whether
+or not to filter a query:
+
+```
+!!include=../pdns/kv-example-script.lua
+```
 
-## Some sample scripts
-The full sample script can be found [here](https://github.com/PowerDNS/pdns/blob/master/pdns/powerdns-example-script.lua).
+## Example Script
+
+```
+!!include=../pdns/powerdns-example-script.lua
+```
 
 ### Dropping all traffic from botnet-infected users
 Frequently, DoS attacks are performed where specific IP addresses are attacked, 
@@ -310,6 +373,4 @@ function preoutquery(dq)
        end
        return false
 end
-
-
 ```
index 62a486495554857d16ff37068e34cbc202c440f1..0ab8bb1ec6421897b397b80843a7a4f773227903 100644 (file)
@@ -1,23 +1,24 @@
 -- this small script implements dns64 without any specials or customization
--- the pref64 is "fe80::21b::77ff:0:0", and it appears twice, plus once reversed
+prefix = "fe80::21b:77ff:0:0"
 
-function nodata ( remoteip, domain, qtype, records )
-       if qtype ~= pdns.AAAA then return -1, {} end  --  only AAAA records
-        setvariable()
-        return "getFakeAAAARecords", domain, "fe80::21b:77ff:0:0"
-end     
+function nodata ( dq )
+  if dq.qtype ~= pdns.AAAA then
+    return false
+  end  --  only AAAA records
 
-function endswith(s, send)
-       return #s >= #send and s:find(send, #s-#send+1, true) and true or false
+  dq.followupFunction = "getFakeAAAARecords"
+  dq.followupPrefix = prefix
+  dq.followupName = dq.qname
+  return true
 end
 
--- note that the ip6.arpa string ends on a .
--- it is the reverse of the pref64 address above
-
-function preresolve ( remoteip, domain, qtype )
-       if qtype ==pdns.PTR and endswith(domain, "f.f.7.7.b.1.2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.e.f.ip6.arpa.")
-        then
-               return "getFakePTRRecords", domain, "fe80::21b::77ff:0:0"
-       end
-       return -1, {}
+-- the ip6.arpa address is the reverse of the prefix address above
+function preresolve ( dq )
+  if dq.qtype == pdns.PTR and dq.qname:isPartOf(newDN("f.f.7.7.b.1.2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.e.f.ip6.arpa.")) then
+    dq.followupFunction = "getFakePTRRecords"
+    dq.followupPrefix = prefix
+    dq.followupName = dq.qname
+    return true
+  end
+  return false
 end