diff --git a/deckard.py b/deckard.py index defa50e89f6f000b9e71713d114aad3101b043e7..e02d8053a818f265702d4d3c1a875a04b43cf597 100755 --- a/deckard.py +++ b/deckard.py @@ -11,6 +11,7 @@ import signal import stat import errno import jinja2 +import dns.rdatatype from pydnstest import scenario, testserver, test from datetime import datetime import random @@ -276,12 +277,13 @@ def setup_env(scenario, child_env, config, config_name_list, j2template_list): childaddr = testserver.get_local_addr_str(self_sockfamily, CHILD_IFACE) # Prebind to sockets to create necessary files # @TODO: this is probably a workaround for socket_wrapper bug - for sock_type in (socket.SOCK_STREAM, socket.SOCK_DGRAM): - sock = socket.socket(self_sockfamily, sock_type) - sock.setsockopt(self_sockfamily, socket.SO_REUSEADDR, 1) - sock.bind((childaddr, 53)) - if sock_type == socket.SOCK_STREAM: - sock.listen(5) + if 'NOPRELOAD' not in os.environ: + for sock_type in (socket.SOCK_STREAM, socket.SOCK_DGRAM): + sock = socket.socket(self_sockfamily, sock_type) + sock.setsockopt(self_sockfamily, socket.SO_REUSEADDR, 1) + sock.bind((childaddr, 53)) + if sock_type == socket.SOCK_STREAM: + sock.listen(5) # Generate configuration files j2template_loader = jinja2.FileSystemLoader(searchpath=os.path.dirname(os.path.abspath(__file__))) j2template_env = jinja2.Environment(loader=j2template_loader) @@ -305,19 +307,13 @@ def play_object(path, binary_name, config_name, j2template, binary_additional_pa """ Play scenario from a file object. """ # Parse scenario - file_in = fileinput.input(path) - scenario = None - config = None - try: - scenario, config = parse_file(file_in) - finally: - file_in.close() + case, config = scenario.parse_file(fileinput.input(path)) # Setup daemon environment daemon_env = os.environ.copy() - setup_env(scenario, daemon_env, config, config_name, j2template) + setup_env(case, daemon_env, config, config_name, j2template) - server = testserver.TestServer(scenario, config, DEFAULT_IFACE, CHILD_IFACE) + server = testserver.TestServer(case, config, DEFAULT_IFACE) server.start() # Start binary @@ -328,15 +324,18 @@ def play_object(path, binary_name, config_name, j2template, binary_additional_pa daemon_proc = subprocess.Popen(daemon_args, stdout=daemon_log, stderr=daemon_log, cwd=TMPDIR, preexec_fn=os.setsid, env=daemon_env) except Exception as e: + server.stop() raise Exception("Can't start '%s': %s" % (daemon_args, str(e))) + # Wait until the server accepts TCP clients sockfamily = socket.AF_INET - if scenario.force_ipv6 == True: + if case.force_ipv6 == True: sockfamily = socket.AF_INET6 sock = socket.socket(sockfamily, socket.SOCK_STREAM) while True: time.sleep(0.1) if daemon_proc.poll() != None: + server.stop() print(open('%s/server.log' % TMPDIR).read()) raise Exception('process died "%s", logs in "%s"' % (os.path.basename(binary_name), TMPDIR)) try: @@ -345,9 +344,24 @@ def play_object(path, binary_name, config_name, j2template, binary_additional_pa break sock.close() - # Play scenario + # Bind to test servers + for r in case.ranges: + family = socket.AF_INET6 if ':' in r.address else socket.AF_INET + server.start_srv((r.address, 53), family) + # Bind addresses in ad-hoc REPLYs + for s in case.steps: + if s.type == 'REPLY': + reply = s.data[0].message + for rr in itertools.chain(reply.answer,reply.additional,reply.question,reply.authority): + for rd in rr: + if rd.rdtype == dns.rdatatype.A: + server.start_srv((rd.address, 53), socket.AF_INET) + elif rd.rdtype == dns.rdatatype.AAAA: + server.start_srv((rd.address, 53), socket.AF_INET6) + + # Play test scenario try: - server.play() + server.play(CHILD_IFACE) if VERBOSE: print(open('%s/server.log' % TMPDIR).read()) except: diff --git a/pydnstest/scenario.py b/pydnstest/scenario.py index 8713122a234def8c3815134d73371004ee7a9010..8eaca6775bccba58d059b2655bf851cacd6e30bd 100644 --- a/pydnstest/scenario.py +++ b/pydnstest/scenario.py @@ -306,12 +306,12 @@ class Step: """ Append a data entry to this step. """ self.data.append(entry) - def play(self, ctx, peeraddr): + def play(self, ctx): """ Play one step from a scenario. """ dtag = '[ STEP %03d ] %s' % (self.id, self.type) if self.type == 'QUERY': dprint(dtag, self.data[0].message.to_text()) - return self.__query(ctx, peeraddr) + return self.__query(ctx) elif self.type == 'CHECK_OUT_QUERY': dprint(dtag, '') pass # Ignore @@ -342,7 +342,7 @@ class Step: dprint("[ __check_answer ]", ctx.last_answer.to_text()) expected.match(ctx.last_answer) - def __query(self, ctx, peeraddr): + def __query(self, ctx): """ Resolve a query. """ if len(self.data) == 0: raise Exception("query definition required") @@ -416,7 +416,7 @@ class Scenario: return (rng.reply(query), False) # Find any prescripted one-shot replies for step in self.steps: - if step.id <= step_id or step.type != 'REPLY': + if step.id < step_id or step.type != 'REPLY': continue try: candidate = step.data[0] @@ -432,11 +432,11 @@ class Scenario: pass return (None, True) - def play(self, family, saddr, paddr): + def play(self, family, paddr): """ Play given scenario. """ self.child_sock = socket.socket(family, socket.SOCK_DGRAM) self.child_sock.settimeout(3) - self.child_sock.connect((paddr, 53)) + self.child_sock.connect(paddr) if len(self.steps) == 0: raise ('no steps in this scenario') @@ -448,7 +448,7 @@ class Scenario: step = self.steps[i] self.current_step = step try: - step.play(self, paddr) + step.play(self) except Exception as e: if (step.repeat_if_fail > 0): dprint ('[play]',"step %d: exception catched - '%s', retrying step %d (%d left)" % (step.id, e, step.next_if_fail, step.repeat_if_fail)) diff --git a/pydnstest/testserver.py b/pydnstest/testserver.py index 8cb8c8f21c1406d22c9bfb2e5117de02ffa8e411..925f16d91be2b7ab6d6bfdcb8ec362d6d6cea700 100644 --- a/pydnstest/testserver.py +++ b/pydnstest/testserver.py @@ -65,7 +65,7 @@ class AddrMapInfo: class TestServer: """ This simulates UDP DNS server returning scripted or mirror DNS responses. """ - def __init__(self, scenario, config, d_iface, p_iface): + def __init__(self, scenario, config, d_iface): """ Initialize server instance. """ self.thread = None self.srv_socks = [] @@ -78,9 +78,8 @@ class TestServer: self.start_iface = 2 self.cur_iface = self.start_iface self.kroot_local = None - self.kroot_family = None + self.addr_family = None self.default_iface = d_iface - self.peer_iface = p_iface self.set_initial_address() def __del__(self): @@ -88,13 +87,13 @@ class TestServer: if self.active is True: self.stop() - def start(self): + def start(self, port = 53): """ Synchronous start """ if self.active is True: 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) + self.addr, _ = self.start_srv((self.kroot_local, port), self.addr_family) + self.start_srv(self.addr, self.addr_family, socket.IPPROTO_TCP) def stop(self): """ Stop socket server operation. """ @@ -124,25 +123,24 @@ class TestServer: 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) + self.addr_family = socket.AF_INET + self.kroot_local = get_local_addr_str(self.addr_family, self.default_iface) return + # Default address is localhost kroot_addr = None for k, v in self.config: if k == 'stub-addr': kroot_addr = v if kroot_addr is not None: if self.check_family (kroot_addr, socket.AF_INET): - self.kroot_family = socket.AF_INET + self.addr_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.addr_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) + self.addr_family = socket.AF_INET + self.kroot_local = get_local_addr_str(self.addr_family, self.default_iface) def address(self): """ Returns opened sockets list """ @@ -164,13 +162,7 @@ class TestServer: response, is_raw_data = self.scenario.reply(query, client_address) if response: if is_raw_data is False: - for rr in itertools.chain(response.answer,response.additional,response.question,response.authority): - for rd in rr: - if rd.rdtype == dns.rdatatype.A: - self.start_srv(rd.address, socket.AF_INET) - elif rd.rdtype == dns.rdatatype.AAAA: - self.start_srv(rd.address, socket.AF_INET6) - data_to_wire = response.to_wire() + data_to_wire = response.to_wire(max_size = 65535) dprint ("[ handle_query ]", "response\n%s" % response) else: data_to_wire = response @@ -208,19 +200,18 @@ class TestServer: 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, proto = socket.IPPROTO_UDP, port = 53): + def start_srv(self, address = None, family = socket.AF_INET, proto = socket.IPPROTO_UDP): """ Starts listening thread if necessary """ - if family == None: family = socket.AF_INET if family == socket.AF_INET: - if address == '' or address is None: - address = get_local_addr_str(family, self.default_iface) + if address[0] is None: + address = (get_local_addr_str(family, self.default_iface), 53) 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 = get_local_addr_str(family, self.default_iface) + if address[0] is None: + address = (get_local_addr_str(family, self.default_iface), 53) else: raise Exception("[start_srv] unsupported protocol family {family}".format(family=family)) @@ -233,21 +224,16 @@ class TestServer: else: raise Exception("[start_srv] unsupported protocol {protocol}".format(protocol=proto)) - if port == 0 or port is None: - port = 53 - if (self.thread is None): self.thread = threading.Thread(target=self.query_io) self.thread.start() for srv_sock in self.srv_socks: - if srv_sock.family == family and srv_sock.getsockname()[0] == address and srv_sock.proto == proto: + if srv_sock.family == family and srv_sock.getsockname() == address and srv_sock.proto == proto: return srv_sock.getsockname() - addr_info = socket.getaddrinfo(address,port,family,socktype,proto) sock = socket.socket(family, socktype, proto) - sockaddr = addr_info[0][-1] - sock.bind(sockaddr) + sock.bind(address) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) if proto == socket.IPPROTO_TCP: sock.listen(5) @@ -255,13 +241,12 @@ class TestServer: sockname = sock.getsockname() return sockname, proto - def play(self): + def play(self, subject_addr): sockfamily = socket.AF_INET if self.scenario.force_ipv6 == True: sockfamily = socket.AF_INET6 - saddr = get_local_addr_str(sockfamily,self.default_iface) - paddr = get_local_addr_str(sockfamily,self.peer_iface) - self.scenario.play(sockfamily,saddr,paddr) + paddr = get_local_addr_str(sockfamily, subject_addr) + self.scenario.play(sockfamily, (paddr, 53)) if __name__ == '__main__': # Self-test code @@ -273,7 +258,7 @@ if __name__ == '__main__': DEFAULT_IFACE = 10 os.environ["SOCKET_WRAPPER_DEFAULT_IFACE"]="{}".format(DEFAULT_IFACE) # Mirror server - server = TestServer(None,None,DEFAULT_IFACE,DEFAULT_IFACE) + server = TestServer(None,None,DEFAULT_IFACE) server.start() print "[==========] Mirror server running at", server.address() try: diff --git a/sets/resolver/iter_cycle.rpl b/sets/resolver/iter_cycle.rpl index 3683f805b03c01ad37c24cd694d1106a9d9c6a4f..78813b60dc949c5f5acf9b063e55408cc0389f6c 100644 --- a/sets/resolver/iter_cycle.rpl +++ b/sets/resolver/iter_cycle.rpl @@ -7,9 +7,14 @@ CONFIG_END SCENARIO_BEGIN Test resolution with dependency cycle ; query for ns.example.com, needs ns.example.net, needs ns.example.com. +; Invalid server +RANGE_BEGIN 0 100 +ADDRESS 1.2.3.1 +RANGE_END + ; K.ROOT-SERVERS.NET. RANGE_BEGIN 0 100 - ADDRESS 193.0.14.129 + ADDRESS 193.0.14.129 ENTRY_BEGIN MATCH opcode qtype qname ADJUST copy_id