Xek
Xeon
XForwarded
+XFR
Xiang
xorbooter
xpf
dnsdist-ecs.cc dnsdist-ecs.hh \
dnsdist-edns.cc dnsdist-edns.hh \
dnsdist-healthchecks.cc dnsdist-healthchecks.hh \
- dnsdist-idstate.hh \
+ dnsdist-idstate.cc dnsdist-idstate.hh \
dnsdist-internal-queries.cc dnsdist-internal-queries.hh \
dnsdist-kvs.hh dnsdist-kvs.cc \
dnsdist-lbpolicies.cc dnsdist-lbpolicies.hh \
dnsdist-dynbpf.cc dnsdist-dynbpf.hh \
dnsdist-ecs.cc dnsdist-ecs.hh \
dnsdist-edns.cc dnsdist-edns.hh \
- dnsdist-idstate.hh \
+ dnsdist-idstate.cc dnsdist-idstate.hh \
dnsdist-kvs.cc dnsdist-kvs.hh \
dnsdist-lbpolicies.cc dnsdist-lbpolicies.hh \
dnsdist-lua-bindings-dnsquestion.cc \
{"addMaintenanceCallback", true, "callback", "register a function to be called as part of the maintenance hook, every second"},
{"addResponseAction", true, R"(DNS rule, DNS response action [, {uuid="UUID", name="name"}}])", "add a response rule"},
{"addSelfAnsweredResponseAction", true, R"(DNS rule, DNS response action [, {uuid="UUID", name="name"}}])", "add a self-answered response rule"},
+ {"addXFRResponseAction", true, R"(DNS rule, DNS response action [, {uuid="UUID", name="name"}}])", "add a XFR response rule"},
{"addTLSLocal", true, "addr, certFile(s), keyFile(s) [,params]", "listen to incoming DNS over TLS queries on the specified address using the specified certificate (or list of) and key (or list of). The last parameter is a table"},
{"AllowAction", true, "", "let these packets go through"},
{"AllowResponseAction", true, "", "let these packets go through"},
{"getTopResponseRules", true, "[top]", "return the `top` response rules"},
{"getTopRules", true, "[top]", "return the `top` rules"},
{"getTopSelfAnsweredResponseRules", true, "[top]", "return the `top` self-answered response rules"},
+ {"getTopXFRResponseRules", true, "[top]", "return the `top` XFR response rules"},
{"getTLSContext", true, "n", "returns the TLS context with index n"},
{"getTLSFrontend", true, "n", "returns the TLS frontend with index n"},
{"getTLSFrontendCount", true, "", "returns the number of DoT listeners"},
{"getVerbose", true, "", "get whether log messages at the verbose level will be logged"},
+ {"getXFRResponseRule", true, "selector", "Return the XFR response rule corresponding to the selector, if any"},
{"grepq", true, R"(Netmask|DNS Name|100ms|{"::1", "powerdns.com", "100ms"} [, n] [,options])", "shows the last n queries and responses matching the specified client address or range (Netmask), or the specified DNS Name, or slower than 100ms"},
{"hashPassword", true, "password [, workFactor]", "Returns a hashed and salted version of the supplied password, usable with 'setWebserverConfig()'"},
{"HTTPHeaderRule", true, "name, regex", "matches DoH queries with a HTTP header 'name' whose content matches the regular expression 'regex'"},
{"mvRuleToTop", true, "", "move the last rule to the first position"},
{"mvSelfAnsweredResponseRule", true, "from, to", "move self-answered response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule"},
{"mvSelfAnsweredResponseRuleToTop", true, "", "move the last self-answered response rule to the first position"},
+ {"mvXFRResponseRule", true, "from, to", "move XFR response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule"},
+ {"mvXFRResponseRuleToTop", true, "", "move the last XFR response rule to the first position"},
{"NetmaskGroupRule", true, "nmg[, src]", "Matches traffic from/to the network range specified in nmg. Set the src parameter to false to match nmg against destination address instead of source address. This can be used to differentiate between clients"},
{"newBPFFilter", true, "{ipv4MaxItems=int, ipv4PinnedPath=string, ipv6MaxItems=int, ipv6PinnedPath=string, cidr4MaxItems=int, cidr4PinnedPath=string, cidr6MaxItems=int, cidr6PinnedPath=string, qnamesMaxItems=int, qnamesPinnedPath=string, external=bool}", "Return a new eBPF socket filter with specified options."},
{"newCA", true, "address", "Returns a ComboAddress based on `address`"},
{"rmRule", true, "id", "remove rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID"},
{"rmSelfAnsweredResponseRule", true, "id", "remove self-answered response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID"},
{"rmServer", true, "id", "remove server with index 'id' or whose uuid matches if 'id' is an UUID string"},
+ {"rmXFRResponseRule", true, "id", "remove XFR response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID"},
{"roundrobin", false, "", "Simple round robin over available servers"},
{"sendCustomTrap", true, "str", "send a custom `SNMP` trap from Lua, containing the `str` string"},
{"setACL", true, "{netmask, netmask}", "replace the ACL set with these netmasks. Use `setACL({})` to reset the list, meaning no one can use us"},
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "dnsdist-idstate.hh"
+#include "dnsdist-doh-common.hh"
+#include "doh3.hh"
+#include "doq.hh"
+
+InternalQueryState InternalQueryState::partialCloneForXFR() const
+{
+ /* for XFR responses we cannot move the state from the query
+ because we usually have more than one response packet per query,
+ so we need to do a partial clone.
+ */
+ InternalQueryState ids;
+ ids.qtype = qtype;
+ ids.qclass = qclass;
+ ids.qname = qname;
+ ids.poolName = poolName;
+ ids.queryRealTime = queryRealTime;
+ ids.protocol = protocol;
+ ids.subnet = subnet;
+ ids.origRemote = origRemote;
+ ids.origDest = origDest;
+ ids.hopRemote = hopRemote;
+ ids.hopLocal = hopLocal;
+ if (qTag) {
+ ids.qTag = std::make_unique<QTag>(*qTag);
+ }
+ if (d_protoBufData) {
+ ids.d_protoBufData = std::make_unique<InternalQueryState::ProtoBufData>(*d_protoBufData);
+ }
+ ids.cs = cs;
+ return ids;
+}
#endif /* HAVE_XSK */
}
+ InternalQueryState partialCloneForXFR() const;
+
boost::optional<Netmask> subnet{boost::none}; // 40
ComboAddress origRemote; // 28
ComboAddress origDest; // 28
GlobalStateHolder<std::vector<ResponseRuleAction>> s_cachehitrespruleactions;
GlobalStateHolder<std::vector<ResponseRuleAction>> s_selfansweredrespruleactions;
GlobalStateHolder<std::vector<ResponseRuleAction>> s_cacheInsertedRespRuleActions;
+GlobalStateHolder<std::vector<ResponseRuleAction>> s_XFRRespRuleActions;
static const std::vector<ResponseRuleChainDescription> s_responseRuleChains{
{"", "response-rules", s_respruleactions},
{"CacheHit", "cache-hit-response-rules", s_cachehitrespruleactions},
{"CacheInserted", "cache-inserted-response-rules", s_selfansweredrespruleactions},
{"SelfAnswered", "self-answered-response-rules", s_cacheInsertedRespRuleActions},
+ {"XFR", "xfr-response-rules", s_XFRRespRuleActions},
};
const std::vector<ResponseRuleChainDescription>& getResponseRuleChains()
CacheHitResponseRules = 1,
CacheInsertedResponseRules = 2,
SelfAnsweredResponseRules = 3,
- ResponseRuleChainsCount = 4
+ XFRResponseRules = 4,
};
struct RuleAction
response.d_buffer = std::move(d_responseBuffer);
response.d_connection = conn;
response.d_ds = conn->d_ds;
- /* we don't move the whole IDS because we will need for the responses to come */
- response.d_idstate.qtype = it->second.d_query.d_idstate.qtype;
- response.d_idstate.qname = it->second.d_query.d_idstate.qname;
+ const auto& queryIDS = it->second.d_query.d_idstate;
+ /* we don't move the whole IDS because we will need it for the responses to come */
+ response.d_idstate = queryIDS.partialCloneForXFR();
DEBUGLOG("passing XFRresponse to client connection for "<<response.d_idstate.qname);
it->second.d_query.d_xfrStarted = true;
{
public:
TCPClientThreadData():
- localRespRuleActions(dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::ResponseRules).getLocal()), localCacheInsertedRespRuleActions(dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::CacheInsertedResponseRules).getLocal()), mplexer(std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent()))
+ localRespRuleActions(dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::ResponseRules).getLocal()), localCacheInsertedRespRuleActions(dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::CacheInsertedResponseRules).getLocal()), localXFRRespRuleActions(dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::XFRResponseRules).getLocal()), mplexer(std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent()))
{
}
LocalHolders holders;
LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> localRespRuleActions;
LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> localCacheInsertedRespRuleActions;
+ LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> localXFRRespRuleActions;
std::unique_ptr<FDMultiplexer> mplexer{nullptr};
pdns::channel::Receiver<ConnectionInfo> queryReceiver;
pdns::channel::Receiver<CrossProtocolQuery> crossProtocolQueryReceiver;
#include "dnsdist-concurrent-connections.hh"
#include "dnsdist-dnsparser.hh"
#include "dnsdist-ecs.hh"
+#include "dnsdist-edns.hh"
#include "dnsdist-nghttp2-in.hh"
#include "dnsdist-proxy-protocol.hh"
#include "dnsdist-rings.hh"
}
}
+static bool processXFRResponse(PacketBuffer& response, const std::vector<dnsdist::rules::ResponseRuleAction>& xfrRespRuleActions, DNSResponse& dnsResponse)
+{
+ if (!applyRulesToResponse(xfrRespRuleActions, dnsResponse)) {
+ return false;
+ }
+
+ if (dnsResponse.isAsynchronous()) {
+ return true;
+ }
+
+ if (dnsResponse.ids.d_extendedError) {
+ dnsdist::edns::addExtendedDNSError(dnsResponse.getMutableData(), dnsResponse.getMaximumSize(), dnsResponse.ids.d_extendedError->infoCode, dnsResponse.ids.d_extendedError->extraText);
+ }
+
+ return true;
+}
+
void IncomingTCPConnectionState::handleXFRResponse(const struct timeval& now, TCPResponse&& response)
{
if (std::this_thread::get_id() != d_creatorThreadID) {
}
std::shared_ptr<IncomingTCPConnectionState> state = shared_from_this();
+ auto& ids = response.d_idstate;
+ std::shared_ptr<DownstreamState> backend = response.d_ds ? response.d_ds : (response.d_connection ? response.d_connection->getDS() : nullptr);
+ DNSResponse dnsResponse(ids, response.d_buffer, backend);
+ dnsResponse.d_incomingTCPState = state;
+ memcpy(&response.d_cleartextDH, dnsResponse.getHeader().get(), sizeof(response.d_cleartextDH));
+
+ if (!processXFRResponse(response.d_buffer, *state->d_threadData.localXFRRespRuleActions, dnsResponse)) {
+ state->terminateClientConnection();
+ return;
+ }
+
queueResponse(state, now, std::move(response), true);
}
}
#endif /* HAVE_DNSCRYPT */
-static bool applyRulesToResponse(const std::vector<dnsdist::rules::ResponseRuleAction>& respRuleActions, DNSResponse& dnsResponse)
+bool applyRulesToResponse(const std::vector<dnsdist::rules::ResponseRuleAction>& respRuleActions, DNSResponse& dnsResponse)
{
DNSResponseAction::Action action = DNSResponseAction::Action::None;
std::string ruleresult;
bool processRulesResult(const DNSAction::Action& action, DNSQuestion& dnsQuestion, std::string& ruleresult, bool& drop);
bool processResponseAfterRules(PacketBuffer& response, const std::vector<dnsdist::rules::ResponseRuleAction>& cacheInsertedRespRuleActions, DNSResponse& dnsResponse, bool muted);
bool processResponderPacket(std::shared_ptr<DownstreamState>& dss, PacketBuffer& response, const std::vector<dnsdist::rules::ResponseRuleAction>& localRespRuleActions, const std::vector<dnsdist::rules::ResponseRuleAction>& cacheInsertedRespRuleActions, InternalQueryState&& ids);
+bool applyRulesToResponse(const std::vector<dnsdist::rules::ResponseRuleAction>& respRuleActions, DNSResponse& dnsResponse);
bool assignOutgoingUDPQueryToBackend(std::shared_ptr<DownstreamState>& downstream, uint16_t queryID, DNSQuestion& dnsQuestion, PacketBuffer& query, bool actuallySend = true);
- :func:`DnstapLogResponseAction`
- :func:`LimitTTLResponseAction`
- :func:`LogAction`
+- :func:`LogResponseAction`
- :func:`NoneAction`
- :func:`RemoteLogAction`
- :func:`RemoteLogResponseAction`
.. versionchanged:: 1.6.0
Replaced by :func:`mvSelfAnsweredResponseRuleToTop`
- Before 1.6.0 this function used to move the last cache hit response rule to the first position, which is now handled by :func:`mvSelfAnsweredResponseRuleToTop`.
+ Before 1.6.0 this function used to move the last self-answered response rule to the first position, which is now handled by :func:`mvSelfAnsweredResponseRuleToTop`.
Move the last self answered response rule to the first position.
+XFR
+---
+
+Functions for manipulating zone transfer (AXFR, IXFR) Response Rules:
+
+.. note::
+ Please remember that a zone transfer (XFR) can and will often contain
+ several response packets to a single query packet.
+
+.. warning::
+ While almost all existing selectors and Response actions should be usable from
+ the XFR response rules, it is strongly advised to only inspect the content of
+ XFR response packets, and not modify them.
+ Logging the content of response packets can be done via:
+
+ - :func:`DnstapLogResponseAction`
+ - :func:`LogResponseAction`
+ - :func:`RemoteLogResponseAction`
+
+.. function:: addXFRResponseAction(DNSRule, action [, options])
+
+ .. versionadded:: 1.10
+
+ Add a Rule and ResponseAction for zone transfers (XFR) to the existing rules.
+ If a string (or list of) is passed as the first parameter instead of a :class:`DNSRule`, it behaves as if the string or list of strings was passed to :func:`NetmaskGroupRule` or :func:`SuffixMatchNodeRule`.
+
+ :param DNSrule rule: A :class:`DNSRule`, e.g. an :func:`AllRule`, or a compounded bunch of rules using e.g. :func:`AndRule`.
+ :param action: The action to take
+ :param table options: A table with key: value pairs with options.
+
+ Options:
+
+ * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
+ * ``name``: string - Name to assign to the new rule.
+
+.. function:: mvXFRResponseRule(from, to)
+
+ .. versionadded:: 1.10
+
+ Move XFR response rule ``from`` to a position where it is in front of ``to``.
+ ``to`` can be one larger than the largest rule, in which case the rule will be moved to the last position.
+
+ :param int from: Rule number to move
+ :param int to: Location to more the Rule to
+
+.. function:: mvXFRResponseRuleToTop()
+
+ .. versionadded:: 1.10
+
+ This function moves the last XFR response rule to the first position.
+
+.. function:: rmXFRResponseRule(id)
+
+ .. versionadded:: 1.10
+
+ :param int id: The position of the rule to remove if ``id`` is numerical, its UUID or name otherwise
+
+.. function:: showXFRResponseRules([options])
+
+ .. versionadded:: 1.10
+
+ Show all defined XFR response rules, optionally displaying their UUIDs.
+
+ :param table options: A table with key: value pairs with display options.
+
+ Options:
+
+ * ``showUUIDs=false``: bool - Whether to display the UUIDs, defaults to false.
+ * ``truncateRuleWidth=-1``: int - Truncate rules output to ``truncateRuleWidth`` size. Defaults to ``-1`` to display the full rule.
+
Convenience Functions
---------------------
return false;
}
+bool applyRulesToResponse(const std::vector<dnsdist::rules::ResponseRuleAction>& respRuleActions, DNSResponse& dnsResponse)
+{
+ return true;
+}
+
bool sendUDPResponse(int origFD, const PacketBuffer& response, const int delayMsec, const ComboAddress& origDest, const ComboAddress& origRemote)
{
return false;
_config_template = """
newServer{address="127.0.0.1:%s"}
"""
- _verboseMode = True
@classmethod
def startResponders(cls):
receivedQuery.id = query.id
self.assertEqual(query, receivedQuery)
self.assertEqual(len(receivedResponses), len(responses) - 1)
-
- # def testFourNoFirstSOAAXFR(self):
- # """
- # AXFR: Four messages, no SOA in the first one
- # """
- # name = 'fournosoainfirst.axfr.tests.powerdns.com.'
- # query = dns.message.make_query(name, 'AXFR', 'IN')
- # responses = []
- # soa = dns.rrset.from_text(name,
- # 60,
- # dns.rdataclass.IN,
- # dns.rdatatype.SOA,
- # 'ns.' + name + ' hostmaster.' + name + ' 1 3600 3600 3600 60')
- # response = dns.message.make_response(query)
- # response.answer.append(dns.rrset.from_text(name,
- # 60,
- # dns.rdataclass.IN,
- # dns.rdatatype.A,
- # '192.0.2.1'))
- # responses.append(response)
-
- # response = dns.message.make_response(query)
- # rrset = dns.rrset.from_text(name,
- # 60,
- # dns.rdataclass.IN,
- # dns.rdatatype.AAAA,
- # '2001:DB8::1')
- # response.answer.append(soa)
- # response.answer.append(rrset)
- # responses.append(response)
-
- # response = dns.message.make_response(query)
- # rrset = dns.rrset.from_text('dummy.' + name,
- # 60,
- # dns.rdataclass.IN,
- # dns.rdatatype.AAAA,
- # '2001:DB8::1')
- # response.answer.append(rrset)
- # responses.append(response)
-
- # response = dns.message.make_response(query)
- # rrset = dns.rrset.from_text(name,
- # 60,
- # dns.rdataclass.IN,
- # dns.rdatatype.TXT,
- # 'dummy')
- # response.answer.append(rrset)
- # response.answer.append(soa)
- # responses.append(response)
-
- # (receivedQuery, receivedResponses) = self.sendTCPQueryWithMultipleResponses(query, responses)
- # receivedQuery.id = query.id
- # self.assertEqual(query, receivedQuery)
- # self.assertEqual(len(receivedResponses), 1)
-
- # def testFourLastSOAInSecondAXFR(self):
- # """
- # AXFR: Four messages, SOA in the first one and the second one
- # """
- # name = 'foursecondsoainsecond.axfr.tests.powerdns.com.'
- # query = dns.message.make_query(name, 'AXFR', 'IN')
- # responses = []
- # soa = dns.rrset.from_text(name,
- # 60,
- # dns.rdataclass.IN,
- # dns.rdatatype.SOA,
- # 'ns.' + name + ' hostmaster.' + name + ' 1 3600 3600 3600 60')
-
- # response = dns.message.make_response(query)
- # response.answer.append(soa)
- # response.answer.append(dns.rrset.from_text(name,
- # 60,
- # dns.rdataclass.IN,
- # dns.rdatatype.A,
- # '192.0.2.1'))
- # responses.append(response)
-
- # response = dns.message.make_response(query)
- # response.answer.append(soa)
- # rrset = dns.rrset.from_text(name,
- # 60,
- # dns.rdataclass.IN,
- # dns.rdatatype.AAAA,
- # '2001:DB8::1')
- # response.answer.append(rrset)
- # responses.append(response)
-
- # response = dns.message.make_response(query)
- # rrset = dns.rrset.from_text('dummy.' + name,
- # 60,
- # dns.rdataclass.IN,
- # dns.rdatatype.AAAA,
- # '2001:DB8::1')
- # response.answer.append(rrset)
- # responses.append(response)
-
- # response = dns.message.make_response(query)
- # rrset = dns.rrset.from_text(name,
- # 60,
- # dns.rdataclass.IN,
- # dns.rdatatype.TXT,
- # 'dummy')
- # response.answer.append(rrset)
- # responses.append(response)
-
- # (receivedQuery, receivedResponses) = self.sendTCPQueryWithMultipleResponses(query, responses)
- # receivedQuery.id = query.id
- # self.assertEqual(query, receivedQuery)
- # self.assertEqual(len(receivedResponses), 2)
self.assertEqual(msg.httpVersion, dnsmessage_pb2.PBDNSMessage.HTTPVersion.HTTP3)
self.checkProtobufQuery(msg, pbMessageType, query, dns.rdataclass.IN, dns.rdatatype.A, name)
+
+class TestProtobufAXFR(DNSDistProtobufTest):
+ # this test suite uses a different responder port
+ # because, contrary to the other ones, its
+ # TCP responder allows multiple responses and we don't want
+ # to mix things up.
+ _testServerPort = pickAvailablePort()
+
+ @classmethod
+ def startResponders(cls):
+ print("Launching responders..")
+
+ cls._UDPResponder = threading.Thread(name='UDP Protobuf AXFR Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
+ cls._UDPResponder.daemon = True
+ cls._UDPResponder.start()
+ cls._TCPResponder = threading.Thread(name='TCP Protobuf AXFR Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue, False, True, None, None, True])
+ cls._TCPResponder.daemon = True
+ cls._TCPResponder.start()
+ cls._protobufListener = threading.Thread(name='Protobuf Listener', target=cls.ProtobufListener, args=[cls._protobufServerPort])
+ cls._protobufListener.daemon = True
+ cls._protobufListener.start()
+
+ _config_template = """
+ newServer{address="127.0.0.1:%d"}
+ rl = newRemoteLogger('127.0.0.1:%d')
+
+ addXFRResponseAction(AllRule(), RemoteLogResponseAction(rl, nil, false, {serverID='dnsdist-server-1'}))
+ """
+ _config_params = ['_testServerPort', '_protobufServerPort']
+
+ def testProtobufAXFR(self):
+ """
+ Protobuf: Check the logging of multiple messages for AXFR responses
+ """
+ # first query is NOT an AXFR, we should not log anything
+ name = 'axfr.protobuf.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ ttl = 60
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ ttl,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+ response.answer.append(rrset)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (receivedQuery, receivedResponse) = sender(query, response)
+
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+
+ self.assertTrue(self._protobufQueue.empty())
+
+ query = dns.message.make_query(name, 'AXFR', 'IN')
+ responses = []
+ soa = dns.rrset.from_text(name,
+ ttl,
+ dns.rdataclass.IN,
+ dns.rdatatype.SOA,
+ 'ns.' + name + ' hostmaster.' + name + ' 1 3600 3600 3600 60')
+ response = dns.message.make_response(query)
+ response.answer.append(soa)
+ responses.append(response)
+
+ response = dns.message.make_response(query)
+ response.answer.append(dns.rrset.from_text(name,
+ ttl,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.1'))
+ responses.append(response)
+
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ ttl,
+ dns.rdataclass.IN,
+ dns.rdatatype.AAAA,
+ '2001:db8::1')
+ response.answer.append(rrset)
+ responses.append(response)
+
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ ttl,
+ dns.rdataclass.IN,
+ dns.rdatatype.TXT,
+ 'dummy')
+ response.answer.append(rrset)
+ responses.append(response)
+
+ response = dns.message.make_response(query)
+ response.answer.append(soa)
+ responses.append(response)
+
+ # UDP would not make sense since it does not support multiple messages
+ (receivedQuery, receivedResponses) = self.sendTCPQueryWithMultipleResponses(query, responses)
+
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponses)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(len(receivedResponses), len(responses))
+
+ if self._protobufQueue.empty():
+ # let the protobuf messages the time to get there
+ time.sleep(1)
+
+ # check the protobuf messages corresponding to the responses
+ count = 0
+ while not self._protobufQueue.empty():
+ msg = self.getFirstProtobufMessage()
+ count = count + 1
+ pbMessageType = dnsmessage_pb2.PBDNSMessage.TCP
+ self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.TCP, responses[count-1])
+
+ expected = responses[count-1].answer[0]
+ if expected.rdtype in [dns.rdatatype.A, dns.rdatatype.AAAA]:
+ rr = msg.response.rrs[0]
+ self.checkProtobufResponseRecord(rr, expected.rdclass, expected.rdtype, name, ttl)
+ if expected.rdtype == dns.rdatatype.A:
+ self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.1')
+ else:
+ self.assertEqual(socket.inet_ntop(socket.AF_INET6, rr.rdata), '2001:db8::1')
+
+ self.assertEqual(count, len(responses))