diff --git a/doc/modules.rst b/doc/modules.rst
index bb7128eb85773c3ad9d4e90c582fbb25244d7abb..349763c2364164322dbd4ab073b56acdd1491636 100644
--- a/doc/modules.rst
+++ b/doc/modules.rst
@@ -19,3 +19,4 @@ Knot DNS Resolver modules
 .. include:: ../modules/ketcd/README.rst
 .. include:: ../modules/cachectl/README.rst
 .. include:: ../modules/tinyweb/README.rst
+.. include:: ../modules/dns64/README.rst
diff --git a/lib/utils.h b/lib/utils.h
index 279ab1f7bd1e32fbdc2f0599f4236e9dccda27e8..523f731534f4e702ce3c922a2cf5f6c2a53be7d9 100644
--- a/lib/utils.h
+++ b/lib/utils.h
@@ -72,9 +72,10 @@ static inline long time_diff(struct timeval *begin, struct timeval *end) {
     return res.tv_sec * 1000 + res.tv_usec / 1000;
 }
 
-/** @internal Array types */
+/** @cond Array types */
 struct kr_context;
 typedef array_t(knot_rrset_t *) rr_array_t;
+/* @endcond */
 
 /** @internal Next RDATA shortcut. */
 #define kr_rdataset_next(rd) (rd + knot_rdata_array_size(knot_rdata_rdlen(rd)))
diff --git a/modules/dns64/README.rst b/modules/dns64/README.rst
new file mode 100644
index 0000000000000000000000000000000000000000..30b434bb133b2ee2da70500b3a0e5c1ed84f5ef7
--- /dev/null
+++ b/modules/dns64/README.rst
@@ -0,0 +1,22 @@
+.. _mod-dns64:
+
+DNS64
+-----
+
+The module for :rfc:`6147` DNS64 AAAA-from-A record synthesis, it is used to enable client-server communication between an IPv6-only client and an IPv4-only server. See the well written `introduction`_ in the PowerDNS documentation.
+
+.. tip:: The A record sub-requests will be DNSSEC secured, but the synthetic AAAA records can't be. Make sure the last mile between stub and resolver is secure to avoid spoofing.
+
+Example configuration
+^^^^^^^^^^^^^^^^^^^^^
+
+.. code-block:: lua
+
+	-- Load the module with a NAT64 address
+	modules = { dns64 = 'fe80::21b:77ff:0:0' }
+	-- Reconfigure later
+	dns64.config('fe80::21b:aabb:0:0')
+
+
+.. _RPZ: https://dnsrpz.info/
+.. _introduction: https://doc.powerdns.com/md/recursor/dns64
\ No newline at end of file
diff --git a/modules/dns64/dns64.lua b/modules/dns64/dns64.lua
new file mode 100644
index 0000000000000000000000000000000000000000..2a98b0997bc7308a2281ceefb1d47e3821685b8c
--- /dev/null
+++ b/modules/dns64/dns64.lua
@@ -0,0 +1,46 @@
+-- Module interface
+local ffi = require('ffi')
+local bit = require('bit')
+local mod = {}
+local MARK_DNS64 = bit.lshift(1, 31)
+-- Config
+function mod.config (confstr)
+	if confstr == nil then return end
+	mod.proxy = kres.str2ip(confstr)
+	if mod.proxy == nil then error('[dns64] "'..confstr..'" is not a valid address') end
+end
+-- Layers
+mod.layer = {
+	consume = function (state, req, pkt)
+		pkt = kres.pkt_t(pkt)
+		req = kres.request_t(req)
+		qry = req:current()
+		-- Observe only authoritative answers
+		if mod.proxy == nil or bit.band(qry.flags, kres.query.RESOLVED) == 0 then
+			return state
+		end
+		-- Synthetic AAAA from marked A responses
+		local answer = pkt:section(kres.section.ANSWER)
+		if bit.band(qry.flags, MARK_DNS64) ~= 0 then -- Marked request
+			for i = 1, #answer do
+				local rr = answer[i]
+				-- Synthesise address
+				local rdata = ffi.new('char [16]')
+				ffi.copy(rdata, mod.proxy)
+				ffi.copy(rdata + 12, rr.rdata, 4)
+				rdata = ffi.string(rdata, 16)
+				-- Write to answer
+				req.answer:put(rr.owner, rr.ttl, rr.class, kres.type.AAAA, rdata)
+			end
+			return state
+		end
+		-- Observe AAAA NODATA responses
+		local is_nodata = (pkt:rcode() == kres.rcode.NOERROR) and (#answer == 0)
+		if pkt:qtype() == kres.type.AAAA and is_nodata and pkt:qname() == qry:name() then
+			local next = req:push(pkt:qname(), kres.type.A, kres.class.IN, 0, qry)
+			next.flags = bit.band(qry.flags, kres.query.DNSSEC_WANT) + kres.query.AWAIT_CUT + MARK_DNS64
+		end
+		return state
+	end
+}
+return mod
diff --git a/modules/dns64/dns64.mk b/modules/dns64/dns64.mk
new file mode 100644
index 0000000000000000000000000000000000000000..948188cbb40b265ee62b0d30d025406d04457c8a
--- /dev/null
+++ b/modules/dns64/dns64.mk
@@ -0,0 +1,2 @@
+dns64_SOURCES := dns64.lua
+$(call make_lua_module,dns64)
diff --git a/modules/modules.mk b/modules/modules.mk
index c6821a344943d41683ac25a68235e5fd3727f9c5..5028924591e7350f580d95569dbcec4ddb5ead18 100644
--- a/modules/modules.mk
+++ b/modules/modules.mk
@@ -18,7 +18,8 @@ modules_TARGETS += ketcd \
                    graphite \
                    policy \
                    view \
-                   predict
+                   predict \
+                   dns64
 endif
 
 # List of Golang modules