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
|
-- SPDX-License-Identifier: GPL-3.0-or-later
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 inverse 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'", 'table print works')
same(table_print({fakepizza=1}), "{\n ['fakepizza'] = 1,\n}", 'table print works on tables')
end
-- test global API functions
local function test_global_functions()
boom(kres.parse_rdata, {'A 127.0.0.1'}, 'parse_rdata with non-table argument')
boom(kres.parse_rdata, {{1}}, 'parse_rdata with non-string data in arg table')
same(kres.parse_rdata({'A 127.0.0.1'}), {string.char(127, 0, 0, 1)}, 'parse_rdata with single record')
same(kres.parse_rdata({'A 127.0.0.1', 'A 127.0.0.2'}),
{string.char(127, 0, 0, 1), string.char(127, 0, 0, 2)}, 'parse_rdata with multiple records')
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(512)
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(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_global_functions,
test_rrset_functions,
test_packet_functions,
test_json_functions,
}
|