Commit bde0e945 authored by JINMEI Tatuya's avatar JINMEI Tatuya
Browse files

[master] Merge branch 'trac2934'

parents e5094ef9 a29c0e18
......@@ -1332,6 +1332,7 @@ AC_CONFIG_FILES([Makefile
tests/tools/perfdhcp/Makefile
tests/tools/perfdhcp/tests/Makefile
tests/tools/perfdhcp/tests/testdata/Makefile
tests/lettuce/Makefile
m4macros/Makefile
dns++.pc
])
......
......@@ -18,6 +18,8 @@
import unittest
import os
import socket
import fcntl
from isc.testutils.tsigctx_mock import MockTSIGContext
from isc.testutils.ccsession_mock import MockModuleCCSession
from isc.cc.session import *
......@@ -190,6 +192,24 @@ class Dbserver:
def decrease_transfers_counter(self):
self.transfer_counter -= 1
class TestUtility(unittest.TestCase):
"""Test some utility functions."""
def test_make_blockign(self):
def is_blocking(fd):
return (fcntl.fcntl(fd, fcntl.F_GETFL) & os.O_NONBLOCK) == 0
with socket.socket(socket.AF_INET, socket.SOCK_STREAM,
socket.IPPROTO_TCP) as sock:
# By default socket is made blocking
self.assertTrue(is_blocking(sock.fileno()))
# make_blocking(False) makes it non blocking
xfrout.make_blocking(sock.fileno(), False)
self.assertFalse(is_blocking(sock.fileno()))
# make_blocking(True) makes it blocking again
xfrout.make_blocking(sock.fileno(), True)
self.assertTrue(is_blocking(sock.fileno()))
class TestXfroutSessionBase(unittest.TestCase):
'''Base class for tests related to xfrout sessions
......@@ -269,6 +289,12 @@ class TestXfroutSessionBase(unittest.TestCase):
self.xfrsess._request_typestr = 'IXFR'
def setUp(self):
# xfrout.make_blocking won't work with faked socket, and we'd like to
# examine how it's called, so we replace it with our mock.
self.__orig_make_blocking = xfrout.make_blocking
self._make_blocking_history = []
xfrout.make_blocking = lambda x, y: self.__make_blocking(x, y)
self.sock = MySocket(socket.AF_INET,socket.SOCK_STREAM)
self.xfrsess = MyXfroutSession(self.sock, None, Dbserver(),
TSIGKeyRing(),
......@@ -285,7 +311,11 @@ class TestXfroutSessionBase(unittest.TestCase):
# original is used elsewhere.
self.orig_get_rrset_len = xfrout.get_rrset_len
def __make_blocking(self, fd, on):
self._make_blocking_history.append((fd, on))
def tearDown(self):
xfrout.make_blocking = self.__orig_make_blocking
xfrout.get_rrset_len = self.orig_get_rrset_len
# transfer_counter must be always be reset no matter happens within
# the XfroutSession object. We check the condition here.
......@@ -306,7 +336,12 @@ class TestXfroutSession(TestXfroutSessionBase):
def test_quota_ok(self):
'''The default case in terms of the xfrout quota.
It also confirms that the socket is made blocking.
'''
# make_blocking shouldn't be called yet.
self.assertEqual([], self._make_blocking_history)
# set up a bogus request, which should result in FORMERR. (it only
# has to be something that is different from the previous case)
self.xfrsess._request_data = \
......@@ -316,6 +351,26 @@ class TestXfroutSession(TestXfroutSessionBase):
XfroutSession._handle(self.xfrsess)
self.assertEqual(self.sock.read_msg().get_rcode(), Rcode.FORMERR)
# make_blocking should have been called in _handle(). Note that
# in the test fixture we handle fileno as a faked socket object, not
# as integer.
self.assertEqual([(self.sock, True)], self._make_blocking_history)
def test_make_blocking_fail(self):
"""Check what if make_blocking() raises an exception."""
# make_blocking() can fail due to OSError. It shouldn't cause
# disruption, and xfrout_start shouldn't be called.
def raise_exception():
raise OSError('faked error')
xfrout.make_blocking = lambda x, y: raise_exception()
self.start_arg = []
self.xfrsess.dns_xfrout_start = \
lambda s, x, y: self.start_arg.append((x, y))
XfroutSession._handle(self.xfrsess)
self.assertEqual([], self.start_arg)
def test_exception_from_session(self):
'''Test the case where the main processing raises an exception.
......
......@@ -30,6 +30,7 @@ from isc.cc import SessionError, SessionTimeout
from isc.statistics import Counters
from isc.notify import notify_out
import isc.util.process
import fcntl
import socket
import select
import errno
......@@ -152,6 +153,27 @@ def get_soa_serial(soa_rdata):
'''
return Serial(int(soa_rdata.to_text().split()[2]))
def make_blocking(filenum, on):
"""A helper function to change blocking mode of the given socket.
It sets the mode of blocking I/O for the socket associated with filenum
(descriptor of the socket) according to parameter 'on': if it's True the
file will be made blocking; otherwise it will be made non-blocking.
The given filenum must be a descriptor of a socket (not an ordinary file
etc), but this function doesn't check that condition.
filenum(int): file number (descriptor) of the socket to update.
on(bool): whether enable (True) or disable (False) blocking I/O.
"""
flags = fcntl.fcntl(filenum, fcntl.F_GETFL)
if on: # make it blocking
flags &= ~os.O_NONBLOCK
else: # make it non blocking
flags |= os.O_NONBLOCK
fcntl.fcntl(filenum, fcntl.F_SETFL, flags)
class XfroutSession():
def __init__(self, sock_fd, request_data, server, tsig_key_ring, remote,
default_acl, zone_config, client_class=DataSourceClient):
......@@ -190,6 +212,10 @@ class XfroutSession():
quota_ok = self._server.increase_transfers_counter()
ex = None
try:
# Before start, make sure the socket uses blocking I/O because
# responses will be sent in the blocking mode; otherwise it could
# result in EWOULDBLOCK and disrupt the session.
make_blocking(self._sock_fd, True)
self.dns_xfrout_start(self._sock_fd, self._request_data, quota_ok)
except Exception as e:
# To avoid resource leak we need catch all possible exceptions
......
......@@ -103,6 +103,12 @@ would be better to not be too verbose, but you might want to increase
the log level and make sure there's no resource leak or other system
level troubles when it's logged.
% ASIODNS_TCP_CLOSE_NORESP_FAIL failed to close DNS/TCP socket with a client: %1
A TCP DNS server tried to close a TCP socket used to communicate with
a client without returning an answer (which normally happens for zone
transfer requests), but it failed to do that. See ASIODNS_TCP_CLOSE_FAIL
for more details.
% ASIODNS_TCP_GETREMOTE_FAIL failed to get remote address of a DNS TCP connection: %1
A TCP DNS server tried to get the address and port of a remote client
on a connected socket but failed. It's expected to be rare but can
......
......@@ -238,8 +238,15 @@ TCPServer::operator()(asio::error_code ec, size_t length) {
// The 'done_' flag indicates whether we have an answer
// to send back. If not, exit the coroutine permanently.
if (!done_) {
// Explicitly close() isn't necessary for most cases. But for the
// very connection, socket_ is shared with the original owner of
// the server object and would stay open.
// TODO: should we keep the connection open for a short time
// to see if new requests come in?
socket_->close(ec);
if (ec) {
LOG_DEBUG(logger, 0, ASIODNS_TCP_CLOSE_FAIL).arg(ec.message());
}
return;
}
......
SUBDIRS = tools
SUBDIRS = tools lettuce
noinst_SCRIPTS = setup_intree_bind10.sh
......@@ -6,7 +6,8 @@ these tests are specific for BIND10, but we are keeping in mind that RFC-related
tests could be separated, so that we can test other systems as well.
Prerequisites:
- BIND 10 must be compiled or installed
- BIND 10 must be compiled or installed (even when testing in-tree build;
see below)
- dig
- lettuce (http://lettuce.it)
......@@ -32,7 +33,9 @@ By default it will use the build tree, but you can use an installed version
of bind10 by passing -I as the first argument of run_lettuce.sh
The tool 'dig' must be in the default search path of your environment. If
you specified -I, so must the main bind10 program, as well as bindctl.
you specified -I, so must the main BIND 10 programs. And, with or without
-I, some BIND 10 programs still have to be installed as they are invoked
from test tools. Those include bindctl and b10-loadzone.
Due to the default way lettuce prints its output, it is advisable to run it
in a terminal that is wide than the default. If you see a lot of lines twice
......
{
"version": 3,
"Logging": {
"loggers": [ {
"debuglevel": 99,
"severity": "DEBUG",
"name": "*"
} ]
},
"Auth": {
"database_file": "data/xfrout.sqlite3",
"listen_on": [ {
"address": "::1",
"port": 47806
} ]
},
"data_sources": {
"classes": {
"IN": [{
"type": "sqlite3",
"params": {
"database_file": "data/xfrout.sqlite3"
}
}]
}
},
"Xfrout": {
"zone_config": [ {
"origin": "example.org"
} ]
},
"Init": {
"components": {
"b10-auth": { "kind": "needed", "special": "auth" },
"b10-xfrout": { "address": "Xfrout", "kind": "dispensable" },
"b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
"b10-stats": { "address": "Stats", "kind": "dispensable" },
"b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
}
}
}
# Copyright (C) 2013 Internet Systems Consortium.
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
from lettuce import *
import subprocess
import tempfile
def run_loadzone(zone, zone_file, db_file):
"""Run b10-loadzone.
It currently only works for an SQLite3-based data source, and takes
its DB file as a parameter.
Parameters:
zone (str): the zone name
zone_file (str): master zone file for the zone
db_file (str): SQLite3 DB file to load the zone into
"""
sqlite_datasrc_cfg = '{"database_file": "' + db_file + '"}'
args = ['b10-loadzone', '-c', sqlite_datasrc_cfg, zone, zone_file]
loadzone = subprocess.Popen(args, 1, None, None,
subprocess.PIPE, subprocess.PIPE)
(stdout, stderr) = loadzone.communicate()
result = loadzone.returncode
world.last_loadzone_stdout = stdout
world.last_loadzone_stderr = stderr
assert result == 0, "loadzone exit code: " + str(result) +\
"\nstdout:\n" + str(stdout) +\
"stderr:\n" + str(stderr)
@step('load zone (\S+) to DB file (\S+) from (\S+)')
def load_zone_to_dbfile(step, zone, db_file, zone_file):
"""Load a zone into a data source from a zone file.
It currently only works for an SQLite3-based data source. Its
DB file name should be specified.
Step definition:
load zone <zone_name> to DB file <db_file> from <zone_file>
"""
run_loadzone(zone, zone_file, db_file)
@step('load (\d+) records for zone (\S+) to DB file (\S+)')
def load_zone_rr_to_dbfile(step, num_records, zone, db_file):
"""Load a zone with a specified number of RRs into a data source.
It currently only works for an SQLite3-based data source. Its
DB file name should be specified.
It creates the content of the zone dynamically so the total number of
RRs of the zone is the specified number, including mandatory SOA and NS.
Step definition:
load zone <zone_name> to DB file <db_file> from <zone_file>
"""
num_records = int(num_records)
assert num_records >= 2, 'zone must have at least 2 RRs: SOA and NS'
num_records -= 2
with tempfile.NamedTemporaryFile(mode='w', prefix='zone-file',
dir='data/', delete=True) as f:
filename = f.name
f.write('$TTL 3600\n')
f.write('$ORIGIN .\n') # so it'll work whether or not zone ends with .
f.write(zone + ' IN SOA . . 0 0 0 0 0\n')
f.write(zone + ' IN NS 0.' + zone + '\n')
count = 0
while count < num_records:
f.write(str(count) + '.' + zone + ' IN A 192.0.2.1\n')
count += 1
f.flush()
run_loadzone(zone, f.name, db_file)
......@@ -83,7 +83,9 @@ copylist = [
["data/xfrin-notify.sqlite3.orig",
"data/xfrin-notify.sqlite3"],
["data/ddns/example.org.sqlite3.orig",
"data/ddns/example.org.sqlite3"]
"data/ddns/example.org.sqlite3"],
["data/empty_db.sqlite3",
"data/xfrout.sqlite3"]
]
# This is a list of files that, if present, will be removed before a scenario
......
......@@ -18,7 +18,8 @@
# and inspect the results.
#
# Like querying.py, it uses dig to do the transfers, and
# places its output in a result structure
# places its output in a result structure. It also uses a custom client
# implementation for less normal operations.
#
# This is done in a different file with different steps than
# querying, because the format of dig's output is
......@@ -58,7 +59,16 @@ class TransferResult(object):
if len(line) > 0 and line[0] != ';':
self.records.append(line)
@step('An AXFR transfer of ([\w.]+)(?: from ([^:]+|\[[0-9a-fA-F:]+\])(?::([0-9]+))?)?')
def parse_addr_port(address, port):
if address is None:
address = "::1" # default address
# convert [IPv6_addr] to IPv6_addr:
address = re.sub(r"\[(.+)\]", r"\1", address)
if port is None:
port = 47806 # default port
return (address, port)
@step('An AXFR transfer of ([\w.]+)(?: from ([\d.]+|\[[0-9a-fA-F:]+\])(?::([0-9]+))?)?')
def perform_axfr(step, zone_name, address, port):
"""
Perform an AXFR transfer, and store the result as an instance of
......@@ -70,15 +80,60 @@ def perform_axfr(step, zone_name, address, port):
Address defaults to ::1
Port defaults to 47806
"""
if address is None:
address = "::1"
# convert [IPv6_addr] to IPv6_addr:
address = re.sub(r"\[(.+)\]", r"\1", address)
if port is None:
port = 47806
(address, port) = parse_addr_port(address, port)
args = [ 'dig', 'AXFR', '@' + str(address), '-p', str(port), zone_name ]
world.transfer_result = TransferResult(args)
@step('A customized AXFR transfer of ([\w.]+)(?: from ([\d.]+|\[[0-9a-fA-F:]+\])(?::([0-9]+))?)?(?: with pose of (\d+) seconds?)?')
def perform_custom_axfr(step, zone_name, address, port, delay):
"""Checks AXFR transfer, and store the result in the form of internal
CustomTransferResult class, which is compatible with TransferResult.
Step definition:
A customized AXFR transfer of <zone_name> [from <address>:<port>] [with pose of <delay> second]
If optional delay is specified (not None), it waits for the specified
seconds after sending the AXFR query before starting receiving
responses. This emulates a slower AXFR client.
"""
class CustomTransferResult:
"""Store transfer result only on the number of received answer RRs.
To be compatible with TransferResult it stores the result in the
'records' attribute, which is a list. But its content is
meaningless; its only use is to be used with
check_transfer_result_count where its length is of concern.
"""
def __init__(self):
self.records = []
# Build arguments and run xfr-client.py. On success, it simply dumps
# the number of received answer RRs to stdout.
(address, port) = parse_addr_port(address, port)
args = ['/bin/sh', 'run_python-tool.sh', 'tools/xfr-client.py',
'-s', address, '-p', str(port)]
if delay is not None:
args.extend(['-d', delay])
args.append(zone_name)
client = subprocess.Popen(args, 1, None, None, subprocess.PIPE,
subprocess.PIPE)
(stdout, stderr) = client.communicate()
result = client.returncode
world.last_client_stdout = stdout
world.last_client_stderr = stderr
assert result == 0, "xfr-client exit code: " + str(result) +\
"\nstdout:\n" + str(stdout) +\
"stderr:\n" + str(stderr)
num_rrs = int(stdout.strip())
# Make the result object, storing dummy value (None) for the number of
# answer RRs in the records list.
world.transfer_result = CustomTransferResult()
world.transfer_result.records = [None for _ in range(0, num_rrs)]
@step('An IXFR transfer of ([\w.]+) (\d+)(?: from ([^:]+)(?::([0-9]+))?)?(?: over (tcp|udp))?')
def perform_ixfr(step, zone_name, serial, address, port, protocol):
"""
......
Feature: Xfrout
Tests for Xfrout, specific for BIND 10 behaviour.
Scenario: normal transfer with a moderate number of RRs
Load 100 records for zone example.org to DB file data/xfrout.sqlite3
Given I have bind10 running with configuration xfrout_master.conf
And wait for bind10 stderr message BIND10_STARTED_CC
And wait for bind10 stderr message CMDCTL_STARTED
And wait for bind10 stderr message AUTH_SERVER_STARTED
And wait for bind10 stderr message XFROUT_STARTED
And wait for bind10 stderr message ZONEMGR_STARTED
# The transferred zone should have the generated 100 RRs plush one
# trailing SOA.
When I do a customized AXFR transfer of example.org
Then transfer result should have 101 rrs
# Similar to the previous one, but using a much larger zone, and with
# a small delay at the client side. It should still succeed.
# The specific delay (5 seconds) was chosen for an environment that
# revealed a bug which is now fixed to reproduce the issue; shorter delays
# didn't trigger the problem. Depending on the OS implementation, machine
# speed, etc, the same delay may be too long or too short, but in any case
# the test should succeed now.
Scenario: transfer a large zone
Load 50000 records for zone example.org to DB file data/xfrout.sqlite3
Given I have bind10 running with configuration xfrout_master.conf
And wait for bind10 stderr message BIND10_STARTED_CC
And wait for bind10 stderr message CMDCTL_STARTED
And wait for bind10 stderr message AUTH_SERVER_STARTED
And wait for bind10 stderr message XFROUT_STARTED
And wait for bind10 stderr message ZONEMGR_STARTED
When I do a customized AXFR transfer of example.org from [::1]:47806 with pose of 5 seconds
Then transfer result should have 50001 rrs
#!/bin/sh
# Copyright (C) 2013 Internet Systems Consortium.
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
# This script runs the specified python program, referring to the in-tree
# BIND 10 Python libraries (in case the program needs them)
# usage example: run_python-tool.sh tools/xfr-client.py -p 5300 example.org
. ./setup_intree_bind10.sh
$PYTHON_EXEC $*
......@@ -20,7 +20,7 @@ export PYTHON_EXEC
BIND10_PATH=@abs_top_builddir@/src/bin/bind10
PATH=@abs_top_builddir@/src/bin/bind10:@abs_top_builddir@/src/bin/bindctl:@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/resolver:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/stats:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:@abs_top_builddir@/src/bin/zonemgr:@abs_top_builddir@/src/bin/ddns:@abs_top_builddir@/src/bin/dhcp6:@abs_top_builddir@/src/bin/sockcreator:$PATH
PATH=@abs_top_builddir@/src/bin/bind10:@abs_top_builddir@/src/bin/bindctl:@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/resolver:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/stats:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:@abs_top_builddir@/src/bin/zonemgr:@abs_top_builddir@/src/bin/ddns:@abs_top_builddir@/src/bin/dhcp6:@abs_top_builddir@/src/bin/sockcreator:@abs_top_builddir@/src/bin/loadzone:$PATH
export PATH
PYTHONPATH=@abs_top_builddir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python/isc/cc:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/xfr/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/python/isc/config:@abs_top_builddir@/src/lib/python/isc/acl/.libs:@abs_top_builddir@/src/lib/python/isc/datasrc/.libs:$PYTHONPATH
......
#!/usr/bin/env python3
# Copyright (C) 2013 Internet Systems Consortium.
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
# A simple XFR client program with some customized behavior.
# This is intended to provide some less common or even invalid client behavior
# for some specific tests on outbound zone transfer.
# It currently only supports AXFR, but can be extended to support IXFR
# as we see the need for it.
#
# For command line usage, run this program with -h option.
from isc.dns import *
import sys
import socket
import struct
import time
from optparse import OptionParser
parser = OptionParser(usage='usage: %prog [options] zone_name')
parser.add_option('-d', '--delay', dest='delay', action='store', default=None,
help='delay (sec) before receiving responses, ' +
'emulating slow clients')
parser.add_option('-s', '--server', dest='server_addr', action='store',
default='::1',
help="master server's address [default: %default]")
parser.add_option('-p', '--port', dest='server_port', action='store',
default=53,
help="master server's TCP port [default: %default]")
(options, args) = parser.parse_args()
if len(args) == 0:
parser.error('missing argument')
# Parse arguments and options, and creates client socket.
zone_name = Name(args[0])
gai = socket.getaddrinfo(options.server_addr, int(options.server_port), 0,
socket.SOCK_STREAM, socket.IPPROTO_TCP,
socket.AI_NUMERICHOST|socket.AI_NUMERICSERV)
server_family, server_socktype, server_proto, server_sockaddr = \
(gai[0][0], gai[0][1], gai[0][2], gai[0][4])
s = socket.socket(server_family, server_socktype, server_proto)
s.connect(server_sockaddr)
s.settimeout(60) # safety net in case of hangup situation
# Build XFR query.
axfr_qry = Message(Message.RENDER)
axfr_qry.set_rcode(Rcode.NOERROR)
axfr_qry.set_opcode(Opcode.QUERY)
axfr_qry.add_question(Question(zone_name, RRClass.IN, RRType.AXFR))
renderer = MessageRenderer()
axfr_qry.to_wire(renderer)
qry_data = renderer.get_data()
# Send the query
hlen_data = struct.pack('H', socket.htons(len(qry_data)))
s.send(hlen_data)
s.send(qry_data)
# If specified wait for the given period
if options.delay is not None:
time.sleep(int(options.delay))
def get_request_response(s, size):
"""A helper function to receive data of specified length from a socket."""
recv_size = 0
data = b''
while recv_size < size:
need_recv_size = size - recv_size
tmp_data = s.recv(need_recv_size)
if len(tmp_data) == 0:
return None
recv_size += len(tmp_data)
data += tmp_data
return data
# Receive responses until the connection is terminated, and dump the
# number of received answer RRs to stdout.
num_rrs = 0
while True:
hlen_data = get_request_response(s, 2)
if hlen_data is None:
break