~samiam/MaraDNS

ref: 3.5.0021 MaraDNS/maradns-win32/coLunacyDNS.txt -rw-r--r-- 11.3 KiB
ca00f282 — Sam Trenholme MaraDNS release 3.5.0021 4 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# coLunacyDNS ############################################################

coLunacyDNS is a simple IPv4 and IPv6 forwarding DNS server controlled
by a Lua script.  It allows a lot of flexibility because it uses
a combination of C for high performance and Lua for maximum
flexibility.

# Getting started ########################################################

Run the following commands as an administrator to start the coLunacyDNS 
service:

	coLunacyDNS.exe --install
	net start coLunacyDNS

Here, we use coLunacyDNS.lua as the configuration file.

# Configration file examples #############################################

In this example, we listen on 127.0.0.1, and, for any IPv4 query,
we return the IP of that query as reported by 9.9.9.9.
--------------------------------------------------------------------------
bindIp = "127.0.0.1" -- We bind the server to the IP 127.0.0.1
function processQuery(Q) -- Called for every DNS query received
   -- Connect to 9.9.9.9 for the query given to this routine
   local t = coDNS.solve({name=Q.coQuery, type="A", upstreamIp4="9.9.9.9"})
   -- Return a "server fail" if we did not get an answer
   if(t.error or t.status ~= 1) then return {co1Type = "serverFail"} end
   -- Otherwise, return the answer
   return {co1Type = "A", co1Data = t.answer}
end
--------------------------------------------------------------------------


As an even simpler example, we always return "10.1.1.1" for any DNS
query given to us:
--------------------------------------------------------------------------
bindIp = "127.0.0.1" -- We bind the server to the IP 127.0.0.1
function processQuery(Q) -- Called for every DNS query received
  return {co1Type = "A", co1Data = "10.1.1.1"}
end
--------------------------------------------------------------------------


We can also set the AA (authoritative answer) flag, the RA 
(recursion available) flag, and the TTL (time to live) for our 
answer.  In this example, both the AA and RA flags are set, and
the answer is given a time to live of one hour (3600 seconds).
--------------------------------------------------------------------------
bindIp = "127.0.0.1" -- We bind the server to the IP 127.0.0.1
function processQuery(Q) -- Called for every DNS query received
  return {co1Type = "A", co1Data = "10.1.1.1", 
          co1AA = 1, co1RA = 1, co1TTL = 3600}
end
--------------------------------------------------------------------------


In this example, we return 10.1.1.1 for all IPv4 A queries, 
2001:db8:4d61:7261:444e:5300::1234 for all IPv6 AAAA queries,
and "not there" for all other query types:
--------------------------------------------------------------------------
bindIp = "127.0.0.1" -- We bind the server to the IP 127.0.0.1
bindIp6 = "::1" -- As well as the IPv6 IP ::1 (IP6 localhost)
function processQuery(Q) -- Called for every DNS query received
  if Q.coQtype == 28 then
    return {co1Type = "ip6",co1Data="2001-0db8-4d61-7261 444e-5300-0000-1234"}
  elseif Q.coQtype == 1 then
    return {co1Type = "A", co1Data = "10.1.1.1"}
  else
    return {co1Type = "notThere"}
  end
end
--------------------------------------------------------------------------


In the same vein, in this example, we contact the DNS server 9.9.9.9 for 
IPv4 queries, and 149.112.112.112 for IPv6 queries:
--------------------------------------------------------------------------
bindIp = "127.0.0.1" -- We bind the server to the IP 127.0.0.1
bindIp6 = "::1" -- And the IP ::1 on IPv6
function processQuery(Q) -- Called for every DNS query received
  local t
  if Q.coQtype == 28 then -- Request for IPv6 IP
    t = coDNS.solve({name=Q.coQuery,type="ip6", upstreamIp4="149.112.112.112"})
  elseif Q.coQtype == 1 then -- Request for IPv4 IP
    t = coDNS.solve({name=Q.coQuery, type="A", upstreamIp4="9.9.9.9"})
  else
    return {co1Type = "notThere"}
  end
  if t.error then
    return {co1Type = "serverFail"}
  end
  if t.status == 28 then
    return {co1Type = "ip6", co1Data = t.answer}
  elseif t.status == 1 then
    return {co1Type = "A", co1Data = t.answer}
  else
    return {co1Type = "notThere"}
  end 
end
--------------------------------------------------------------------------

Here is an example where we can synthesize any IP given to us:
--------------------------------------------------------------------------
-- This script takes a query like 10.1.2.3.ip4.internal. and returns the
-- corresponding IP (e.g. 10.1.2.3 here)
-- We use "internal" because this is the fourth-most commonly used
-- bogus TLD (#1 is "local", #2 is "home", and #3 is "dhcp")

-- Change this is a different top level domain as desired.  So, if this
-- becomes "test", the this configuration script will resolve 
-- "10.1.2.3.ip4.test." names to their IP.
TLD="internal"
-- Change these IPs to the actual IPs the DNS server will run on
bindIp = "127.0.0.1" -- We bind the server to the IP 127.0.0.1
bindIp6 = "::1" -- Localhost for IPv6

function processQuery(Q) -- Called for every DNS query received
  if Q.coQtype == 1 then
    local query = Q.coQuery
    if query:match("^%d+%.%d+%.%d+%.%d+%.ip4%." .. TLD .. "%.$") then
      local ip = query:gsub("%.ip4%." .. TLD .. "%.$","")
      return {co1Type = "A", co1Data = ip}
    end
  else
    return {co1Type = "notThere"}
  end
  return {co1Type = "notThere"}
end
--------------------------------------------------------------------------

Here is a more complicated example:
--------------------------------------------------------------------------
-- coLunacyDNS configuration
bindIp = "127.0.0.1" -- We bind the server to the IP 127.0.0.1

-- Examples of three API calls we have: timestamp, rand32, and rand16
coDNS.log(string.format("Timestamp: %.1f",coDNS.timestamp())) -- timestamp
coDNS.log(string.format("Random32: %08x",coDNS.rand32())) -- random 32-bit num
coDNS.log(string.format("Random16: %04x",coDNS.rand16())) -- random 16-bit num
-- Note that it is *not* possible to use coDNS.solve here; if we attempt
-- to do so, we will get an error with the message
-- "attempt to yield across metamethod/C-call boundary".  

function processQuery(Q) -- Called for every DNS query received
  -- Because this code uses multiple co-routines, always use "local"
  -- variables
  local returnIP = nil
  local upstream = "9.9.9.9"

  -- Log query
  coDNS.log("Got IPv4 query for " .. Q.coQuery .. " from " ..
            Q.coFromIP .. " type " ..  Q.coFromIPtype) 

  -- We will use 8.8.8.8 as the upstream server if the query ends in ".tj"
  if string.match(Q.coQuery,'%.tj%.$') then
    upstream = "8.8.8.8"
  end

  -- We will use 4.2.2.1 as the upstream server if the query comes from 
  -- 192.168.99.X
  if string.match(Q.coFromIP,'^192%.168%.99%.') then
    upstream = "4.2.2.1"
  end

  -- Right now, coLunacyDNS can *only* process "A" (IPv4 IP) queries
  if Q.coQtype ~= 1 then -- If it is not an A (ipv4) query
    -- return {co1Type = "ignoreMe"} -- Ignore the query
    return {co1Type = "notThere"} -- Send "not there" (like NXDOMAIN)
  end

  -- Contact another DNS server to get our answer
  t = coDNS.solve({name=Q.coQuery, type="A", upstreamIp4=upstream})

  -- If coDNS.solve returns an error, the entire processQuery routine is
  -- "on probation" and unable to run coDNS.solve() again (if an attempt
  -- is made, the thread will be aborted and no DNS response sent 
  -- downstream).  
  if t.error then	
    coDNS.log(t.error)
    return {co1Type = "serverFail"} 
  end

  -- Status is 1 when we get an IP from the upstream DNS server, otherwise 0
  if t.status == 1 and t.answer then
    returnIP = t.answer
  end

  if string.match(Q.coQuery,'%.invalid%.$') then
    return {co1Type = "A", co1Data = "10.1.1.1"} -- Answer for anything.invalid
  end
  if returnIP then
    return {co1Type = "A", co1Data = returnIP} 
  end
  return {co1Type = "notThere"} 
end
--------------------------------------------------------------------------

# Security considerations ################################################

Since the Lua file is executed as admin, some effort is made to restrict
what it can do:

* Only the "math", "string", and "bit32" libraries are loaded from
  Lua's standard libs.  (bit32 actually is another Bit library, but with a
  "bit32" interface.)
* A special "coDNS" library is also loaded.

# Reading files #########################################################

We have an API which can be used to read files.  For example:

-------------------------------------------------------------------------
if not coDNS.open1("filename.txt") then
  return {co1Type = "serverFail"}
end
local line = ""
while line do
  if line then coDNS.log("Line: " .. line) end
  line = coDNS.read1()
end
-------------------------------------------------------------------------
    
The calls are: coDNS.open1(filename), coDNS.read1(), and coDNS.close1().
    
Only a single file can be open at a time.  If coDNS.open1() is called
when a file is open, the currently open file is closed before we attempt
to open the new file.  If coDNS.solve() is called while a file is open,
the file is closed before we attempt to solve the DNS query.  If we exit
processQuery() while a file is open, the file is closed as we exit the
function.  Files are also closed when we finish parsing the Lua
configuration file used by coLunacyDNS, before listening to DNS queries.
    
The filename must start with an ASCII letter, number, or the "_"
(underscore) character.  The filename may contain only ASCII letters,
numbers, instances of "." (the dot character), or the "_" character.
In particular, the filename may not contain "/", "\", or any other
commonly used directory separator.

If the file is not present, or the filename contains an illegal
character, or the file can not be opened, coDNS.open1() will return
a false boolean value.  Otherwise, "open1" returns true.
    
The file has to be in the same directory that coLunacyDNS is run
from.  The file may only be read; writing to the file is not possible.
    
coDNS.read1() reads a single line from the file.  Any newline is
stripped from the end (unlike Perl, coLunacyDNS does not require a 
chop); NUL characters in the line also truncate the string read.  If 
a line is read from the file, coDNS.read1() returns the line which 
was read.  Otherwise, coDNS.read1() returns the `false` Lua boolean 
value.

coDNS.read1() assumes that a single line will be under 500 bytes 
in size.  Behavior is undefined when trying to read a longer line.
    
coDNS.close1() closes an open file; a file is also closed when
opening another file, ending processQuery(), or calling coDNS.solve().
It is mainly here to give programmers trained to close open files
a function which does so.

# Limitations ###########################################################

coLunacyDNS, at this time, only processes requests for DNS A queries
and DNS AAAA queries -- queries for IPv4 and IPv6 IP addresses.
Information about other query types is not available to coLunacyDNS,
and it can only return A queries, AAAA queries, “server fail”,
or “this name is not here” in its replies.

coLunacyDNS, likewise, can only send A (IPv4 IP) and AAAA (IPv6 IP)
requests to upstream servers.  While coLunacyDNS can bind to an IPv6
IP and process IPv6 AAAA records, coLunacyDNS can not contact upstream
DNS servers via IPv6.