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
|
local ffi = require('ffi')
-- test if constants work properly
local function test_constants()
same(kres.class.IN, 1, 'class constants work')
same(kres.type.NS, 2, 'record type constants work')
same(kres.type.TYPE2, 2, 'unnamed record type constants work')
same(kres.type.BADTYPE, nil, 'non-existent type constants are checked')
same(kres.section.ANSWER, 0, 'section constants work')
same(kres.rcode.SERVFAIL, 2, 'rcode constants work')
same(kres.opcode.UPDATE, 5, 'opcode constants work')
-- Test inverset tables to convert constants to text
same(kres.tostring.class[1], 'IN', 'text class constants work')
same(kres.tostring.type[2], 'NS', 'text record type constants work')
same(kres.tostring.type[65535], 'TYPE65535', 'text record type undefined constants work')
same(kres.tostring.section[0], 'ANSWER', 'text section constants work')
same(kres.tostring.rcode[2], 'SERVFAIL', 'text rcode constants work')
same(kres.tostring.opcode[5], 'UPDATE', 'text opcode constants work')
end
-- test globals
local function test_globals()
ok(mode('strict'), 'changing strictness mode')
boom(mode, {'badmode'}, 'changing to non-existent strictness mode')
same(reorder_RR(true), true, 'answer section reordering')
same(option('REORDER_RR', false), false, 'generic option call')
boom(option, {'REORDER_RR', 'potato'}, 'generic option call argument check')
boom(option, {'MARS_VACATION', false}, 'generic option check name')
same(table_print('crabdiary'), 'crabdiary\n', 'table print works')
same(table_print({fakepizza=1}), '[fakepizza] => 1\n', 'table print works on tables')
end
-- test if dns library functions work
local function test_rrset_functions()
local rr = {owner = '\3com\0', ttl = 1, type = kres.type.TXT, rdata = '\5hello'}
local rr_text = tostring(kres.rr2str(rr))
same(rr_text:gsub('%s+', ' '), 'com. 1 TXT "hello"', 'rrset to text works')
same(kres.dname2str(todname('com.')), 'com.', 'domain name conversion works')
-- test creating rrset
rr = kres.rrset(todname('com.'), kres.type.A, kres.class.IN, 66)
ok(ffi.istype(kres.rrset, rr), 'created an empty RR')
same(rr:owner(), '\3com\0', 'created RR has correct owner')
same(rr:class(), kres.class.IN, 'created RR has correct class')
same(rr:class(kres.class.CH), kres.class.CH, 'can set a different class')
same(rr:class(kres.class.IN), kres.class.IN, 'can restore a class')
same(rr.type, kres.type.A, 'created RR has correct type')
-- test adding rdata
same(rr:wire_size(), 0, 'empty RR wire size is zero')
ok(rr:add_rdata('\1\2\3\4', 4), 'adding RDATA works')
same(rr:wire_size(), 5 + 4 + 4 + 2 + 4, 'RR wire size works after adding RDATA')
-- test conversion to text
local expect = 'com. 66 A 1.2.3.4\n'
same(rr:txt_dump(), expect, 'RR to text works')
-- create a dummy rrsig
local rrsig = kres.rrset(todname('com.'), kres.type.RRSIG, kres.class.IN, 0)
rrsig:add_rdata('\0\1', 2)
same(rr:rdcount(), 1, 'add_rdata really added RDATA')
-- check rrsig matching
same(rr.type, rrsig:type_covered(), 'rrsig type covered matches covered RR type')
ok(rr:is_covered_by(rrsig), 'rrsig is covering a record')
-- test rrset merging
local copy = kres.rrset(rr:owner(), rr.type, kres.class.IN, 66)
ok(copy:add_rdata('\4\3\2\1', 4), 'adding second RDATA works')
ok(rr:merge_rdata(copy), 'merge_rdata works')
same(rr:rdcount(), 2, 'RDATA count is correct after merge_rdata')
expect = 'com. 66 A 1.2.3.4\n' ..
'com. 66 A 4.3.2.1\n'
same(rr:txt_dump(), expect, 'merge_rdata actually merged RDATA')
end
-- test dns library packet interface
local function test_packet_functions()
local pkt = kres.packet(512)
isnt(pkt, nil, 'creating packets works')
-- Test manipulating header
ok(pkt:rcode(kres.rcode.NOERROR), 'setting rcode works')
same(pkt:rcode(), 0, 'getting rcode works')
same(pkt:opcode(), 0, 'getting opcode works')
is(pkt:aa(), false, 'packet is created without AA')
is(pkt:ra(), false, 'packet is created without RA')
is(pkt:ad(), false, 'packet is created without AD')
ok(pkt:rd(true), 'setting RD bit works')
is(pkt:rd(), true, 'getting RD bit works')
ok(pkt:tc(true), 'setting TC bit works')
is(pkt:tc(), true, 'getting TC bit works')
ok(pkt:tc(false), 'disabling TC bit works')
is(pkt:tc(), false, 'getting TC bit after disable works')
is(pkt:cd(), false, 'getting CD bit works')
is(pkt:id(1234), 1234, 'setting MSGID works')
is(pkt:id(), 1234, 'getting MSGID works')
-- Test manipulating question
is(pkt:qname(), nil, 'reading name from empty question')
is(pkt:qtype(), 0, 'reading type from empty question')
is(pkt:qclass(), 0, 'reading class from empty question')
ok(pkt:question(todname('hello'), kres.class.IN, kres.type.A), 'setting question section works')
same(pkt:qname(), todname('hello'), 'reading QNAME works')
same(pkt:qtype(), kres.type.A, 'reading QTYPE works')
same(pkt:qclass(), kres.class.IN, 'reading QCLASS works')
-- Test manipulating sections
ok(pkt:begin(kres.section.ANSWER), 'switching sections works')
local res, err = pkt:put(nil, 0, 0, 0, '')
isnt(res, true, 'inserting nil entry doesnt work')
isnt(err.code, 0, 'error code is non-zero')
isnt(tostring(res), '', 'inserting nil returns invalid parameter')
ok(pkt:put(pkt:qname(), 900, pkt:qclass(), kres.type.A, '\1\2\3\4'), 'adding rrsets works')
boom(pkt.begin, {pkt, 10}, 'switching to invalid section doesnt work')
ok(pkt:begin(kres.section.ADDITIONAL), 'switching to different section works')
boom(pkt.begin, {pkt, 0}, 'rewinding sections doesnt work')
local before_insert = pkt:remaining_bytes()
ok(pkt:put(pkt:qname(), 900, pkt:qclass(), kres.type.A, '\4\3\2\1'), 'adding rrsets to different section works')
same(pkt:remaining_bytes(), before_insert - (2 + 4 + 4 + 2 + 4), 'remaining bytes count goes down with insertions')
-- Test conversions to text
like(pkt:tostring(), '->>HEADER<<-', 'packet to text works')
-- Test deserialization
local wire = pkt:towire()
same(#wire, 55, 'packet serialization works')
local parsed = kres.packet(#wire, wire)
isnt(parsed, nil, 'creating packet from wire works')
ok(parsed:parse(), 'parsing packet from wire works')
same(parsed:qname(), pkt:qname(), 'parsed packet has same QNAME')
same(parsed:qtype(), pkt:qtype(), 'parsed packet has same QTYPE')
same(parsed:qclass(), pkt:qclass(), 'parsed packet has same QCLASS')
same(parsed:opcode(), pkt:opcode(), 'parsed packet has same opcode')
same(parsed:rcode(), pkt:rcode(), 'parsed packet has same rcode')
same(parsed:rd(), pkt:rd(), 'parsed packet has same RD')
same(parsed:id(), pkt:id(), 'parsed packet has same MSGID')
same(parsed:qdcount(), pkt:qdcount(), 'parsed packet has same question count')
same(parsed:ancount(), pkt:ancount(), 'parsed packet has same answer count')
same(parsed:nscount(), pkt:nscount(), 'parsed packet has same authority count')
same(parsed:arcount(), pkt:arcount(), 'parsed packet has same additional count')
same(parsed:tostring(), pkt:tostring(), 'parsed packet is equal to source packet')
-- Test adding RR sets directly
local copy = kres.packet(23)
copy:question(todname('hello'), kres.class.IN, kres.type.A)
copy:begin(kres.section.ANSWER)
local rr = kres.rrset(pkt:qname(), kres.type.A, kres.class.IN, 66)
rr:add_rdata('\4\3\2\1', 4)
ok(not copy:put_rr(rr), 'adding RR sets checks for available space')
ok(copy:resize(512), 'resizing packet works')
ok(copy:put_rr(rr), 'adding RR sets directly works')
ok(copy:recycle(), 'recycling packet works')
-- Test recycling of packets
-- Clear_payload keeps header + question intact
local cleared = kres.packet(#wire, wire) -- same as "parsed" above
ok(cleared:parse(), 'parsing packet from wire works')
ok(cleared:clear_payload(), 'clear_payload works')
same(cleared:id(), pkt:id(), 'cleared packet has same MSGID')
same(cleared:qr(), pkt:qr(), 'cleared packet has same QR')
same(cleared:opcode(), pkt:opcode(), 'cleared packet has same OPCODE')
same(cleared:aa(), pkt:aa(), 'cleared packet has same AA')
same(cleared:tc(), pkt:tc(), 'cleared packet has same TC')
same(cleared:rd(), pkt:rd(), 'cleared packet has same RD')
same(cleared:ra(), pkt:ra(), 'cleared packet has same RA')
same(cleared:ad(), pkt:ad(), 'cleared packet has same AD')
same(cleared:cd(), pkt:cd(), 'cleared packet has same CD')
same(cleared:rcode(), pkt:rcode(), 'cleared packet has same RCODE')
same(cleared:qdcount(), pkt:qdcount(), 'cleared packet has same question count')
same(cleared:ancount(), 0, 'cleared packet has no answers')
same(cleared:nscount(), 0, 'cleared packet has no authority')
same(cleared:arcount(), 0, 'cleared packet has no additional')
same(cleared:qname(), pkt:qname(), 'cleared packet has same QNAME')
same(cleared:qtype(), pkt:qtype(), 'cleared packet has same QTYPE')
same(cleared:qclass(), pkt:qclass(), 'cleared packet has same QCLASS')
-- Recycle clears question as well
ok(pkt:recycle(), 'recycle() works')
is(pkt:ancount(), 0, 'recycle() clears records')
is(pkt:qname(), nil, 'recycle() clears question')
is(#pkt:towire(), 12, 'recycle() clears the packet wireformat')
end
-- test JSON encode/decode functions
local function test_json_functions()
for msg, obj in pairs({
['number'] = 0,
['string'] = 'ok',
['list'] = {1, 2, 3},
['map'] = {foo='bar'},
['nest structure'] = {foo='bar', baz={1,2,3}},
}) do
same(fromjson(tojson(obj)), obj, 'json test: ' .. msg)
end
for _, str in ipairs({
'{', '}',
'[', ']',
'x,',
'[1,2,3,]',
}) do
boom(fromjson, {'{'}, 'json test: invalid \'' .. str .. '\'')
end
end
return {
test_constants,
test_globals,
test_rrset_functions,
test_packet_functions,
test_json_functions,
}
|