_testServerPort = 5350
_toResponderQueue = Queue.Queue()
_fromResponderQueue = Queue.Queue()
+ _queueTimeout = 1
+ _dnsdistStartupDelay = 2
_dnsdist = None
_responsesCounter = {}
_config_template = """
@classmethod
def startResponders(cls):
print("Launching responders..")
- # clear counters
- for key in cls._responsesCounter:
- cls._responsesCounter[key] = 0
cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort])
cls._UDPResponder.setDaemon(True)
if 'DNSDIST_FAST_TESTS' in os.environ:
delay = 0.5
else:
- delay = 2
+ delay = cls._dnsdistStartupDelay
+
time.sleep(delay)
if cls._dnsdist.poll() is not None:
while True:
data, addr = sock.recvfrom(4096)
request = dns.message.from_wire(data)
+ answered = False
if len(request.question) != 1:
print("Skipping query with question count %d" % (len(request.question)))
continue
if str(request.question[0].name).endswith('tests.powerdns.com.') and not cls._toResponderQueue.empty():
- response = cls._toResponderQueue.get()
- response.id = request.id
- cls._fromResponderQueue.put(request)
- cls.ResponderIncrementCounter()
- else:
+ response = cls._toResponderQueue.get(True, cls._queueTimeout)
+ if response:
+ response.id = request.id
+ cls._fromResponderQueue.put(request, True, cls._queueTimeout)
+ cls.ResponderIncrementCounter()
+ answered = True
+
+ if not answered:
# unexpected query, or health check
response = dns.message.make_response(request)
if request.question[0].rdclass == dns.rdataclass.IN:
sock.listen(100)
while True:
+ answered = False
(conn, address) = sock.accept()
conn.settimeout(2.0)
data = conn.recv(2)
print("Skipping query with question count %d" % (len(request.question)))
continue
if str(request.question[0].name).endswith('tests.powerdns.com.') and not cls._toResponderQueue.empty():
- response = cls._toResponderQueue.get()
- response.id = request.id
- cls._fromResponderQueue.put(request)
- cls.ResponderIncrementCounter()
- else:
+ response = cls._toResponderQueue.get(True, cls._queueTimeout)
+ if response:
+ response.id = request.id
+ cls._fromResponderQueue.put(request, True, cls._queueTimeout)
+ cls.ResponderIncrementCounter()
+ answered = True
+
+ if not answered:
# unexpected query, or health check
response = dns.message.make_response(request)
if request.question[0].rdclass == dns.rdataclass.IN:
@classmethod
def sendUDPQuery(cls, query, response, useQueue=True, timeout=2.0):
if useQueue:
- cls._toResponderQueue.put(response)
+ cls._toResponderQueue.put(response, True, timeout)
if timeout:
cls._sock.settimeout(timeout)
receivedQuery = None
message = None
if useQueue and not cls._fromResponderQueue.empty():
- receivedQuery = cls._fromResponderQueue.get(query)
+ receivedQuery = cls._fromResponderQueue.get(True, timeout)
if data:
message = dns.message.from_wire(data)
return (receivedQuery, message)
@classmethod
def sendTCPQuery(cls, query, response, useQueue=True, timeout=2.0):
if useQueue:
- cls._toResponderQueue.put(response)
+ cls._toResponderQueue.put(response, True, timeout)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if timeout:
sock.settimeout(timeout)
receivedQuery = None
message = None
if useQueue and not cls._fromResponderQueue.empty():
- receivedQuery = cls._fromResponderQueue.get(query)
+ receivedQuery = cls._fromResponderQueue.get(True, timeout)
if data:
message = dns.message.from_wire(data)
return (receivedQuery, message)
+
+ def setUp(self):
+ # This function is called before every tests
+
+ # Clear the responses counters
+ for key in self._responsesCounter:
+ self._responsesCounter[key] = 0
+
+ # Make sure the queues are empty, in case
+ # a previous test failed
+ while not self._toResponderQueue.empty():
+ self._toResponderQueue.get(False)
+
+ while not self._fromResponderQueue.empty():
+ self._toResponderQueue.get(False)
def testAdvancedFixupCase(self):
"""
+ Advanced: Fixup Case
+
Send a query with lower and upper chars,
make the backend return a lowercase version,
check that dnsdist fixes the response.
def testAdvancedNoRD(self):
"""
+ Advanced: No RD
+
Send a query with RD,
check that dnsdist clears the RD flag.
"""
def testAdvancedKeepRD(self):
"""
+ Advanced: No RD canary
+
Send a query with RD for a canary domain,
check that dnsdist does not clear the RD flag.
"""
def testAdvancedSetCD(self):
"""
+ Advanced: Set CD
+
Send a query with CD cleared,
check that dnsdist set the CD flag.
"""
def testAdvancedKeepNoCD(self):
"""
+ Advanced: Preserve CD canary
+
Send a query without CD for a canary domain,
check that dnsdist does not set the CD flag.
"""
def testSpoofA(self):
"""
+ Advanced: Spoof A
+
Send an A query to "spoof.tests.powerdns.com.",
check that dnsdist sends a spoofed result.
"""
def testSpoofAAAA(self):
"""
+ Advanced: Spoof AAAA
+
Send an AAAA query to "spoof.tests.powerdns.com.",
check that dnsdist sends a spoofed result.
"""
def testSpoofCNAME(self):
"""
+ Advanced: Spoof CNAME
+
Send an A query for "cnamespoof.tests.powerdns.com.",
check that dnsdist sends a spoofed result.
"""
def testPolicyPool(self):
"""
+ Advanced: Set pool by qname
+
Send an A query to "pool.tests.powerdns.com.",
check that dnsdist routes the query to the "real" pool.
"""
def testDefaultPool(self):
"""
+ Advanced: Set pool by qname canary
+
Send an A query to "notpool.tests.powerdns.com.",
check that dnsdist sends no response (no servers
in the default pool).
def testRR(self):
"""
+ Advanced: Round Robin
+
Send 100 A queries to "rr.tests.powerdns.com.",
check that dnsdist routes half of it to each backend.
"""
'192.0.2.1')
response.answer.append(rrset)
- # clear counters
- for key in TestAdvancedRoundRobinLB._responsesCounter:
- TestAdvancedRoundRobinLB._responsesCounter[key] = 0
-
# the round robin counter is shared for UDP and TCP,
# so we need to do UDP then TCP to have a clean count
for idx in range(numberOfQueries):
def testRRWithOneDown(self):
"""
+ Advanced: Round Robin with one server down
+
Send 100 A queries to "rr.tests.powerdns.com.",
check that dnsdist routes all of it to the only backend up.
"""
def testACLBlocked(self):
"""
+ Advanced: ACL blocked
+
Send an A query to "tests.powerdns.com.",
we expect no response since 127.0.0.1 is not on the
ACL.
def testDelayed(self):
"""
+ Advanced: Delayed
+
Send an A query to "tests.powerdns.com.",
check that the response delay is longer than 1000 ms
over UDP, less than that over TCP.
def testBlockedA(self):
"""
+ Basics: Blocked A query
+
Send an A query for the powerdns.org domain,
which is blocked by configuration. We expect
no response.
def testAWithECS(self):
"""
- Send an A query with an ECS value.
+ Basics: A query with an ECS value
"""
name = 'awithecs.tests.powerdns.com.'
ecso = clientsubnetoption.ClientSubnetOption('1.2.3.4')
def testSimpleA(self):
"""
- Send a simple A query without EDNS.
+ Basics: A query without EDNS
"""
name = 'simplea.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
def testAnyIsTruncated(self):
"""
+ Basics: Truncate ANY query
+
dnsdist is configured to reply with TC to ANY queries,
send an ANY query and check the result.
"""
def testTruncateTC(self):
"""
+ Basics: Truncate TC
+
dnsdist is configured to truncate TC (default),
we make the backend send responses
with TC set and additional content,
def testRegexReturnsRefused(self):
"""
+ Basics: Refuse query matching regex
+
dnsdist is configured to reply 'refused' for query
matching "evil[0-9]{4,}\\.regex\\.tests\\.powerdns\\.com$".
We send a query for evil4242.powerdns.com
def testDomainAndQTypeReturnsNotImplemented(self):
"""
+ Basics: NOTIMPL for specific name and qtype
+
dnsdist is configured to reply 'not implemented' for query
matching "nameAndQtype.tests.powerdns.com." AND qtype TXT/
We send a TXT query for "nameAndQtype.powerdns.com."
def testDomainWithoutQTypeIsNotAffected(self):
"""
+ Basics: NOTIMPL qtype canary
+
dnsdist is configured to reply 'not implemented' for query
matching "nameAndQtype.tests.powerdns.com." AND qtype TXT/
We send a A query for "nameAndQtype.tests.powerdns.com."
def testOtherDomainANDQTypeIsNotAffected(self):
"""
+ Basics: NOTIMPL qname canary
+
dnsdist is configured to reply 'not implemented' for query
matching "nameAndQtype.tests.powerdns.com." AND qtype TXT/
We send a TXT query for "OtherNameAndQtype.tests.powerdns.com."
_resolverCertificateValidFrom = time.time() - 60
_resolverCertificateValidUntil = time.time() + 7200
_config_params = ['_resolverCertificateSerial', '_resolverCertificateValidFrom', '_resolverCertificateValidUntil', '_dnsDistPortDNSCrypt', '_providerName', '_testServerPort']
+ _dnsdistStartupDelay = 10
def testSimpleA(self):
"""
- Send an encrypted A query.
+ DNSCrypt: encrypted A query
"""
client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", 8443)
name = 'a.dnscrypt.tests.powerdns.com.'
def testResponseLargerThanPaddedQuery(self):
"""
+ DNSCrypt: response larger than query
+
Send a small encrypted query (don't forget to take
the padding into account) and check that the response
is truncated.
def testWithoutEDNS(self):
"""
+ ECS: No existing EDNS
+
Send a query without EDNS, check that the query
received by the responder has the correct ECS value
and that the response received from dnsdist does not
def testWithEDNSNoECS(self):
"""
+ ECS: Existing EDNS without ECS
+
Send a query with EDNS but no ECS value.
Check that the query received by the responder
has a valid ECS value and that the response
def testWithEDNSECS(self):
"""
+ ECS: Existing EDNS with ECS
+
Send a query with EDNS and a crafted ECS value.
Check that the query received by the responder
has the initial ECS value (not overwritten)
def testWithoutEDNS(self):
"""
+ ECS Override: No existing EDNS
+
Send a query without EDNS, check that the query
received by the responder has the correct ECS value
and that the response received from dnsdist does not
def testWithEDNSNoECS(self):
"""
+ ECS Override: Existing EDNS without ECS
+
Send a query with EDNS but no ECS value.
Check that the query received by the responder
has a valid ECS value and that the response
def testWithEDNSShorterInitialECS(self):
"""
+ ECS Override: Existing EDNS with ECS (short)
+
Send a query with EDNS and a crafted ECS value.
Check that the query received by the responder
has an overwritten ECS value (not the initial one)
def testWithEDNSLongerInitialECS(self):
"""
+ ECS Override: Existing EDNS with ECS (long)
+
Send a query with EDNS and a crafted ECS value.
Check that the query received by the responder
has an overwritten ECS value (not the initial one)
def testWithEDNSSameSizeInitialECS(self):
"""
+ ECS Override: Existing EDNS with ECS (same)
+
Send a query with EDNS and a crafted ECS value.
Check that the query received by the responder
has an overwritten ECS value (not the initial one)