Commit d0e51e3b authored by Grigorii Demidov's avatar Grigorii Demidov

without address translation; knotd support added

parent 2a72d8fd
......@@ -26,11 +26,12 @@ def del_files(path_to):
DEFAULT_IFACE = 0
CHILD_IFACE = 0
TMPDIR = ""
INSTALLDIR = os.path.dirname(os.path.abspath(__file__))
if "SOCKET_WRAPPER_DEFAULT_IFACE" in os.environ:
DEFAULT_IFACE = int(os.environ["SOCKET_WRAPPER_DEFAULT_IFACE"])
if DEFAULT_IFACE < 2 or DEFAULT_IFACE > 254 :
DEFAULT_IFACE = 10
DEFAULT_IFACE = 2
os.environ["SOCKET_WRAPPER_DEFAULT_IFACE"]="{}".format(DEFAULT_IFACE)
if "KRESD_WRAPPER_DEFAULT_IFACE" in os.environ:
......@@ -42,6 +43,7 @@ if CHILD_IFACE < 2 or CHILD_IFACE > 254 or CHILD_IFACE == DEFAULT_IFACE:
CHILD_IFACE = 2
os.environ["KRESD_WRAPPER_DEFAULT_IFACE"] = "{}".format(CHILD_IFACE)
if "SOCKET_WRAPPER_DIR" in os.environ:
TMPDIR = os.environ["SOCKET_WRAPPER_DIR"]
if TMPDIR == "" or os.path.isdir(TMPDIR) is False:
......@@ -169,7 +171,8 @@ def find_objects(path):
def write_timestamp_file(path, tst):
time_file = open(path, 'w')
time_file.write(datetime.fromtimestamp(tst).strftime('%Y-%m-%d %H:%M:%S'))
time_file.write(datetime.fromtimestamp(tst).strftime('@%Y-%m-%d %H:%M:%S'))
time_file.flush()
time_file.close()
def setup_env(child_env, config, config_name, j2template):
......@@ -181,7 +184,7 @@ def setup_env(child_env, config, config_name, j2template):
os.environ["FAKETIME_TIMESTAMP_FILE"] = '%s/.time' % TMPDIR
child_env["FAKETIME_NO_CACHE"] = "1"
child_env["FAKETIME_TIMESTAMP_FILE"] = '%s/.time' % TMPDIR
write_timestamp_file(child_env["FAKETIME_TIMESTAMP_FILE"], 0)
write_timestamp_file(child_env["FAKETIME_TIMESTAMP_FILE"], int (time.time()))
# Set up child process env()
child_env["SOCKET_WRAPPER_DEFAULT_IFACE"] = "%i" % CHILD_IFACE
child_env["SOCKET_WRAPPER_DIR"] = TMPDIR
......@@ -219,6 +222,7 @@ def setup_env(child_env, config, config_name, j2template):
"NO_MINIMIZE" : no_minimize,
"TRUST_ANCHOR" : trust_anchor_str,
"WORKING_DIR" : TMPDIR,
"INSTALL_DIR" : INSTALLDIR
}
cfg_rendered = j2template.render(j2template_ctx)
f = open(os.path.join(TMPDIR,config_name), 'w')
......@@ -240,6 +244,10 @@ def play_object(path, binary_name, config_name, j2template, binary_additional_pa
# Setup daemon environment
daemon_env = os.environ.copy()
setup_env(daemon_env, config, config_name, j2template)
server = testserver.TestServer(scenario, config, DEFAULT_IFACE, CHILD_IFACE)
server.start()
# Start binary
daemon_proc = None
daemon_log = open('%s/server.log' % TMPDIR, 'w')
......@@ -260,9 +268,9 @@ def play_object(path, binary_name, config_name, j2template, binary_additional_pa
sock.connect((testserver.get_local_addr_str(socket.AF_INET, CHILD_IFACE), 53))
except: continue
break
sock.close()
# Play scenario
server = testserver.TestServer(scenario, config, DEFAULT_IFACE, CHILD_IFACE)
server.start()
try:
server.play()
finally:
......@@ -272,7 +280,7 @@ def play_object(path, binary_name, config_name, j2template, binary_additional_pa
if 'VERBOSE' in os.environ:
print('[ LOG ]\n%s' % open('%s/server.log' % TMPDIR).read())
# Do not clear files if the server crashed (for analysis)
del_files(TMPDIR)
# del_files(TMPDIR)
def test_platform(*args):
if sys.platform == 'windows':
......
import os
import threading
dprint_lock = threading.Lock()
def dprint(tag, msg):
""" Verbose logging (if enabled). """
if 'VERBOSE' in os.environ:
dprint_lock.acquire()
print tag, msg
dprint_lock.release()
#!/bin/bash
# Path to scenario files
TESTS=sets/knotd/master
# Path to daemon
DAEMON=knotd
# Template file name
TEMPLATE=template/knotd_master.j2
# Config file name
CONFIG=knotd.conf
# Additional parameter for knotd
# it means configuration file can be found in working directory
ADDITIONAL="-c ./knotd.conf"
export TESTS DAEMON TEMPLATE CONFIG ADDITIONAL
make
#!/bin/bash
# Path to scenario files
TESTS=sets/knotd/slave
# Path to daemon
DAEMON=knotd
# Template file name
TEMPLATE=template/knotd_slave.j2
# Config file name
CONFIG=knotd.conf
# Additional parameter for knotd
# it means configuration file can be found in working directory
ADDITIONAL="-c ./knotd.conf"
export TESTS DAEMON TEMPLATE CONFIG ADDITIONAL
make
#!/bin/bash
# Path to scenario files
TESTS=sets/resolver
# Path to daemon
DAEMON=kresd
# Template file name
TEMPLATE=template/kresd.j2
# Config file name
CONFIG=config
export TESTS DAEMON TEMPLATE CONFIG
make
#!/bin/bash
# Path to scenario files
TESTS=sets/resolver
# Path to daemon
DAEMON=pdns_recursor
# Template file name
TEMPLATE=template/recursor.j2
# Config file name
CONFIG=recursor.conf
# Additional parameter for pdns_recursor
# it means configuration file can be found in working directory
ADDITIONAL=--config-dir=./
export TESTS DAEMON TEMPLATE CONFIG ADDITIONAL
make
......@@ -8,11 +8,7 @@ import os
import itertools
import time
from datetime import datetime
def dprint(msg):
""" Verbose logging (if enabled). """
if 'VERBOSE' in os.environ:
print(msg)
from dprint import dprint
class Entry:
"""
......@@ -100,7 +96,7 @@ class Entry:
def adjust_reply(self, query):
""" Copy scripted reply and adjust to received query. """
answer = dns.message.from_text(self.message.to_text())
answer = self.message
answer.use_edns(query.edns, query.ednsflags)
if 'copy_id' in self.adjust_fields:
answer.id = query.id
......@@ -168,11 +164,14 @@ class Entry:
def __rr_add(self, section, rr):
""" Merge record to existing RRSet, or append to given section. """
for existing_rr in section:
if existing_rr.match(rr.name, rr.rdclass, rr.rdtype, 0):
existing_rr += rr
return
section.append(rr)
if rr.rdtype != dns.rdatatype.SOA:
for existing_rr in section:
if existing_rr.match(rr.name, rr.rdclass, rr.rdtype, 0):
existing_rr += rr
return
section.append(rr)
def __rr_from_str(self, owner, args):
""" Parse RR from tokenized string. """
......@@ -280,20 +279,25 @@ class Step:
def play(self, ctx, peeraddr):
""" Play one step from a scenario. """
dprint('[ STEP %03d ] %s' % (self.id, self.type))
dtag = '[ STEP %03d ] %s' % (self.id, self.type)
if self.type == 'QUERY':
dprint(self.data[0].message.to_text())
dprint(dtag, self.data[0].message.to_text())
return self.__query(ctx, peeraddr)
elif self.type == 'CHECK_OUT_QUERY':
pass # Ignore
dprint(dtag, '')
pass # Ignore
elif self.type == 'CHECK_ANSWER':
dprint(dtag, '')
return self.__check_answer(ctx)
elif self.type == 'TIME_PASSES':
dprint(dtag, '')
return self.__time_passes(ctx)
elif self.type == 'REPLY':
dprint(dtag, '')
pass
else:
raise Exception('step %s unsupported' % self.type)
raise Exception('step id %03d type %s unsupported' % (self.id, self.type))
def __check_answer(self, ctx):
""" Compare answer from previously resolved query. """
......@@ -301,12 +305,12 @@ class Step:
raise Exception("response definition required")
expected = self.data[0]
if expected.is_raw_data_entry is True:
dprint(ctx.last_raw_answer.to_text())
dprint("[ __check_answer ]", ctx.last_raw_answer.to_text())
expected.cmp_raw(ctx.last_raw_answer)
else:
if ctx.last_answer is None:
raise Exception("no answer from preceding query")
dprint(ctx.last_answer.to_text())
dprint("[ __check_answer ]", ctx.last_answer.to_text())
expected.match(ctx.last_answer)
def __query(self, ctx, peeraddr):
......@@ -345,10 +349,11 @@ class Step:
time_file = open(os.environ["FAKETIME_TIMESTAMP_FILE"], 'r')
line = time_file.readline().strip()
time_file.close()
t = time.mktime(datetime.strptime(line, '%Y-%m-%d %H:%M:%S').timetuple())
t = time.mktime(datetime.strptime(line, '@%Y-%m-%d %H:%M:%S').timetuple())
t += int(self.args[1])
time_file = open(os.environ["FAKETIME_TIMESTAMP_FILE"], 'w')
time_file.write(datetime.fromtimestamp(t).strftime('%Y-%m-%d %H:%M:%S') + "\n")
time_file.write(datetime.fromtimestamp(t).strftime('@%Y-%m-%d %H:%M:%S') + "\n")
time_file.flush()
time_file.close()
class Scenario:
......
......@@ -6,16 +6,42 @@ import time
import dns.message
import dns.rdatatype
import itertools
import struct
import binascii
from dprint import dprint
def recvfrom_msg(stream):
""" Receive DNS/UDP message. """
data, addr = stream.recvfrom(4096)
""" Receive DNS/UDP/TCP message. """
if stream.type == socket.SOCK_DGRAM:
data, addr = stream.recvfrom(4096)
elif stream.type == socket.SOCK_STREAM:
data = stream.recv(2)
if len(data) == 0:
return None, None
msg_len = struct.unpack_from("!H",data)[0]
data = ""
received = 0
while received < msg_len:
next_chunk = stream.recv(4096)
if len(next_chunk) == 0:
return None, None
data += next_chunk
received += len (next_chunk)
addr = stream.getpeername()[0]
else:
raise Exception ("[recvfrom_msg]: unknown socket type '%i'" % stream.type)
return dns.message.from_wire(data), addr
def sendto_msg(stream, message, addr):
""" Send DNS/UDP message. """
""" Send DNS/UDP/TCP message. """
try:
stream.sendto(message, addr)
if stream.type == socket.SOCK_DGRAM:
stream.sendto(message, addr)
elif stream.type == socket.SOCK_STREAM:
data = struct.pack("!H",len(message)) + message
stream.send(data)
else:
raise Exception ("[recvfrom_msg]: unknown socket type '%i'" % stream.type)
except: # Failure to respond is OK, resolver should recover
pass
......@@ -29,12 +55,6 @@ def get_local_addr_str(family, iface):
raise Exception("[get_local_addr_str] family not supported '%i'" % family)
return addr_local_pattern.format(iface)
class SrvSock (socket.socket):
""" Socket with some additional info """
def __init__(self, client_address, family=socket.AF_INET, type=socket.SOCK_DGRAM, proto=socket.IPPROTO_UDP):
self.client_address = client_address
socket.socket.__init__(self, family, type, proto)
class AddrMapInfo:
""" Saves mapping info between adresses from rpl and cwrap adresses """
def __init__(self, family, local, external):
......@@ -50,6 +70,7 @@ class TestServer:
self.thread = None
self.srv_socks = []
self.client_socks = []
self.connections = []
self.active = False
self.scenario = scenario
self.config = config
......@@ -60,7 +81,7 @@ class TestServer:
self.kroot_family = None
self.default_iface = d_iface
self.peer_iface = p_iface
self.map_adresses()
self.set_initial_address()
def __del__(self):
""" Cleanup after deletion. """
......@@ -73,83 +94,35 @@ class TestServer:
raise Exception('TestServer already started')
self.active = True
self.start_srv(self.kroot_local, self.kroot_family)
self.start_srv(self.kroot_local, self.kroot_family, socket.IPPROTO_TCP)
def stop(self):
""" Stop socket server operation. """
self.active = False
self.thread.join()
for conn in self.connections:
conn.close()
for srv_sock in self.srv_socks:
srv_sock.close()
for client_sock in self.client_socks:
client_sock.close()
self.client_socks = []
self.srv_socks = []
self.connections = []
self.scenario = None
def map_to_local(self, addr, family, iface):
""" Maps arbitrary IP to localhost for using with cwrap """
addr_external = None
addr_local = None
addr_local_pattern = None
new_entry = None
def check_family (self, addr, family):
""" Determines if address matches family """
test_addr = None
try:
n = socket.inet_pton(family, addr)
addr_external = socket.inet_ntop(family, n)
test_addr = socket.inet_ntop(family, n)
except socket.error:
return addr_local, new_entry
for am in self.addr_map:
if am.family == family and am.external == addr_external:
addr_local = am.local
new_entry = False
if addr_local is None:
# Do not remap addresses already in local range
if addr.startswith('127.0.0.') or addr.startswith('::'):
addr_local = addr
else:
addr_local = get_local_addr_str(family, iface)
am = AddrMapInfo(family,addr_local,addr_external)
self.addr_map.append(am)
new_entry = True
return addr_local, new_entry
def get_local(self, addr, root):
""" Maps arbitrary IP4 or IP6 addres to local address, """
""" saves mapping info and returns local address to caller"""
local_address = None
iface = None
is_new_entry = None
family = None
if root is True:
iface = self.default_iface
else:
if self.cur_iface == self.default_iface or self.cur_iface == self.peer_iface:
self.cur_iface = self.cur_iface + 1
iface = self.cur_iface
family = socket.AF_INET
local_address, is_new_entry = self.map_to_local(addr, family, iface)
if local_address is None:
family = socket.AF_INET6
local_address, is_new_entry = self.map_to_local(addr, family, iface);
if local_address is None:
family = None
if root is False and is_new_entry is True:
self.cur_iface = self.cur_iface + 1
while self.cur_iface == self.default_iface or self.cur_iface == self.peer_iface:
self.cur_iface = self.cur_iface + 1
return local_address, family
def map_entries(self, entrylist):
""" Translate addresses for A and AAAA records"""
for entry in entrylist :
for rr in itertools.chain(entry.message.answer,entry.message.additional,entry.message.question,entry.message.authority):
for rd in rr:
if rd.rdtype == dns.rdatatype.A or rd.rdtype == dns.rdatatype.AAAA:
rd_local_address, family = self.get_local(rd.address,False)
rd.address = rd_local_address
return False
return True
def map_adresses(self):
""" Translate addresses for whole scenario """
""" Raw data not translated """
def set_initial_address(self):
""" Set address for starting thread """
if self.config is None:
self.kroot_family = socket.AF_INET
self.kroot_local = get_local_addr_str(self.kroot_family, self.default_iface)
......@@ -159,18 +132,18 @@ class TestServer:
if k == 'stub-addr':
kroot_addr = v
if kroot_addr is not None:
self.kroot_local, self.kroot_family = self.get_local(kroot_addr, True)
if self.kroot_local is None:
raise Exception("[map_adresses] Invalid K.ROOT-SERVERS.NET. address, check the config")
for rng in self.scenario.ranges :
range_local_address, family = self.get_local(rng.address, False)
if range_local_address is None:
raise Exception("[map_adresses] Error translating address '%s', check the config" % rng.address)
rng.address = range_local_address
self.map_entries(rng.stored)
for stp in self.scenario.steps :
self.map_entries(stp.data)
if self.check_family (kroot_addr, socket.AF_INET):
self.kroot_family = socket.AF_INET
self.kroot_local = kroot_addr
elif self.check_family (kroot_addr, socket.AF_INET6):
self.kroot_family = socket.AF_INET6
self.kroot_local = kroot_addr
else:
raise Exception("[set_initial_adress] Invalid 'stub-addr' address (%s), must be IPv4 or IPv6, check the config")
else:
self.kroot_family = socket.AF_INET
self.kroot_local = get_local_addr_str(self.kroot_family, self.default_iface)
def address(self):
""" Returns opened sockets list """
addrlist = [];
......@@ -180,10 +153,11 @@ class TestServer:
def handle_query(self, client):
""" Handle incoming queries. """
client_address = client.client_address
client_address = client.getsockname()[0]
query, addr = recvfrom_msg(client)
if query is None:
return False
dprint ("[ handle_query ]", "incoming query\n%s" % query)
response = dns.message.make_response(query)
is_raw_data = False
if self.scenario is not None:
......@@ -196,42 +170,69 @@ class TestServer:
self.start_srv(rd.address, socket.AF_INET)
elif rd.rdtype == dns.rdatatype.AAAA:
self.start_srv(rd.address, socket.AF_INET6)
sendto_msg(client, response.to_wire(), addr)
data_to_wire = response.to_wire()
dprint ("[ handle_query ]", "response\n%s" % response)
else:
sendto_msg(client, response, addr)
return True
data_to_wire = response
dprint ("[ handle_query ]", "raw response found")
else:
response = dns.message.make_response(query)
response.rcode = dns.rcode.SERVFAIL
sendto_msg(client, response.to_wire(), addr)
return False
data_to_wire = response.to_wire()
dprint ("[ handle_query ]", "response failed, SERVFAIL")
sendto_msg(client, data_to_wire, addr)
return True
def query_io(self):
""" Main server process """
if self.active is False:
raise Exception("[query_io] Test server not active")
while self.active is True:
to_read, _, to_error = select.select(self.srv_socks, [], self.srv_socks, 0.1)
objects = self.srv_socks + self.connections
to_read, _, to_error = select.select(objects, [], objects, 0.1)
for sock in to_read:
self.handle_query(sock)
if sock in self.srv_socks:
if (sock.proto == socket.IPPROTO_TCP):
conn, addr = sock.accept()
self.connections.append(conn)
else:
self.handle_query(sock)
elif sock in self.connections:
if not self.handle_query(sock):
sock.close()
self.connections.remove(sock)
else:
raise Exception("[query_io] Socket IO internal error {}, exit".format(sock.getsockname()))
for sock in to_error:
raise Exception("[query_io] Socket IO error {}, exit".format(sock.getsockname()))
def start_srv(self, address = None, family = socket.AF_INET, port = 53):
def start_srv(self, address = None, family = socket.AF_INET, proto = socket.IPPROTO_UDP, port = 53):
""" Starts listening thread if necessary """
if family == None:
family = socket.AF_INET
if family == socket.AF_INET:
if address == '' or address is None:
address = "127.0.0.{}".format(self.default_iface)
address = get_local_addr_str(family, self.default_iface)
elif family == socket.AF_INET6:
if socket.has_ipv6 is not True:
raise Exception("[start_srv] IPV6 is not supported")
if address == '' or address is None:
address = "::1"
address = get_local_addr_str(family, self.default_iface)
else:
raise Exception("[start_srv] unsupported protocol family {family}".format(family=family))
if proto == None:
proto = socket.IPPROTO_UDP
if proto == socket.IPPROTO_TCP:
socktype = socket.SOCK_STREAM
elif proto == socket.IPPROTO_UDP:
socktype = socket.SOCK_DGRAM
else:
raise Exception("[start_srv] unsupported socket type {sock_type}".format(sock_type=type))
raise Exception("[start_srv] unsupported protocol {protocol}".format(protocol=proto))
if port == 0 or port is None:
port = 53
......@@ -240,18 +241,19 @@ class TestServer:
self.thread.start()
for srv_sock in self.srv_socks:
if srv_sock.family == family and srv_sock.client_address == address :
if srv_sock.family == family and srv_sock.getsockname()[0] == address and srv_sock.proto == proto:
return srv_sock.getsockname()
addr_info = socket.getaddrinfo(address,port,family,0,socket.IPPROTO_UDP)
sock = SrvSock(address, family, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
addr_info_entry0 = addr_info[0]
sockaddr = addr_info_entry0[4]
addr_info = socket.getaddrinfo(address,port,family,socktype,proto)
sock = socket.socket(family, socktype, proto)
sockaddr = addr_info[0][-1]
sock.bind(sockaddr)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
if proto == socket.IPPROTO_TCP:
sock.listen(5)
self.srv_socks.append(sock)
sockname = sock.getsockname()
return sockname
return sockname, proto
def play(self):
saddr = get_local_addr_str(socket.AF_INET,self.default_iface)
......
example.com. 3600 SOA dns1.example.com. hostmaster.example.com. 2010111213 21600 3600 604800 86400
example.com. 3600 NS dns1.example.com.
example.com. 3600 NS dns2.example.com.
example.com. 3600 MX 10 mail.example.com.
dns1.example.com. 3600 A 127.0.0.10