summaryrefslogtreecommitdiffstats
path: root/mDNSResponder/unittests/CNameRecordTests.c
diff options
context:
space:
mode:
Diffstat (limited to 'mDNSResponder/unittests/CNameRecordTests.c')
-rw-r--r--mDNSResponder/unittests/CNameRecordTests.c400
1 files changed, 400 insertions, 0 deletions
diff --git a/mDNSResponder/unittests/CNameRecordTests.c b/mDNSResponder/unittests/CNameRecordTests.c
new file mode 100644
index 00000000..9b3fb016
--- /dev/null
+++ b/mDNSResponder/unittests/CNameRecordTests.c
@@ -0,0 +1,400 @@
+#include "CNameRecordTests.h"
+#include "unittest_common.h"
+
+mDNSlocal int InitThisUnitTest(void);
+mDNSlocal int StartClientQueryRequest(void);
+mDNSlocal int PopulateCacheWithClientResponseRecords(void);
+mDNSlocal int SimulateNetworkChangeAndVerifyTest(void);
+mDNSlocal int FinalizeUnitTest(void);
+mDNSlocal mStatus AddDNSServer(void);
+
+// This unit test's variables
+static UDPSocket* local_socket;
+static request_state* client_request_message;
+
+struct UDPSocket_struct
+{
+ mDNSIPPort port; // MUST BE FIRST FIELD -- mDNSCoreReceive expects every UDPSocket_struct to begin with mDNSIPPort port
+};
+typedef struct UDPSocket_struct UDPSocket;
+
+// This client request was generated using the following command: "dns-sd -Q 123server.dotbennu.com. A".
+uint8_t query_client_msgbuf[35] = {
+ 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x32, 0x33, 0x73, 0x65, 0x72, 0x76, 0x65,
+ 0x72, 0x2e, 0x64, 0x6f, 0x74, 0x62, 0x65, 0x6e, 0x6e, 0x75, 0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x00,
+ 0x01, 0x00, 0x01
+};
+
+// This uDNS message is a canned response that was originally captured by wireshark.
+uint8_t query_response_msgbuf[108] = {
+ 0x69, 0x41, // transaction id
+ 0x85, 0x80, // flags
+ 0x00, 0x01, // 1 question for 123server.dotbennu.com. Addr
+ 0x00, 0x02, // 2 anwsers: 123server.dotbennu.com. CNAME test212.dotbennu.com., test212.dotbennu.com. Addr 10.100.0.1,
+ 0x00, 0x01, // 1 authorities anwser: dotbennu.com. NS cardinal2.apple.com.
+ 0x00, 0x00, 0x09, 0x31, 0x32, 0x33,
+ 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x08, 0x64, 0x6f, 0x74, 0x62, 0x65, 0x6e, 0x6e, 0x75, 0x03,
+ 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00,
+ 0x02, 0x56, 0x00, 0x0a, 0x07, 0x74, 0x65, 0x73, 0x74, 0x32, 0x31, 0x32, 0xc0, 0x16, 0xc0, 0x34,
+ 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x04, 0x0a, 0x64, 0x00, 0x01, 0xc0, 0x16,
+ 0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0x51, 0x80, 0x00, 0x12, 0x09, 0x63, 0x61, 0x72, 0x64, 0x69,
+ 0x6e, 0x61, 0x6c, 0x32, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x65, 0xc0, 0x1f
+};
+
+// Variables associated with contents of the above uDNS message
+#define uDNS_TargetQID 16745
+char udns_original_domainname_cstr[] = "123server.dotbennu.com.";
+char udns_cname_domainname_cstr[] = "test212.dotbennu.com.";
+static const mDNSv4Addr dns_response_ipv4 = {{ 10, 100, 0, 1 }};
+
+UNITTEST_HEADER(CNameRecordTests)
+ UNITTEST_TEST(InitThisUnitTest)
+ UNITTEST_TEST(StartClientQueryRequest)
+ UNITTEST_TEST(PopulateCacheWithClientResponseRecords)
+ UNITTEST_TEST(SimulateNetworkChangeAndVerifyTest)
+ UNITTEST_TEST(FinalizeUnitTest)
+UNITTEST_FOOTER
+
+// The InitThisUnitTest() initializes the mDNSResponder environment as well as
+// a DNSServer. It also allocates memory for a local_socket and client request.
+// Note: This unit test does not send packets on the wire and it does not open sockets.
+UNITTEST_HEADER(InitThisUnitTest)
+ // Init unit test environment and verify no error occurred.
+ mStatus result = init_mdns_environment(mDNStrue);
+ UNITTEST_ASSERT(result == mStatus_NoError);
+
+ // Add one DNS server and verify it was added.
+ AddDNSServer();
+ UNITTEST_ASSERT(NumUnicastDNSServers == 1);
+
+ // Create memory for a socket that is never used or opened.
+ local_socket = mDNSPlatformMemAllocate(sizeof(UDPSocket));
+ mDNSPlatformMemZero(local_socket, sizeof(UDPSocket));
+
+ // Create memory for a request that is used to make this unit test's client request.
+ client_request_message = calloc(1, sizeof(request_state));
+UNITTEST_FOOTER
+
+// This test simulates a uds client request by setting up a client request and then
+// calling mDNSResponder's handle_client_request. The handle_client_request function
+// processes the request and starts a query. This unit test verifies
+// the client request and query were setup as expected. This unit test also calls
+// mDNS_execute which determines the cache does not contain the new question's
+// answer.
+UNITTEST_HEADER(StartClientQueryRequest)
+ mDNS *const m = &mDNSStorage;
+ request_state* req = client_request_message;
+ char *msgptr = (char *)query_client_msgbuf;
+ size_t msgsz = sizeof(query_client_msgbuf);
+ mDNSs32 min_size = sizeof(DNSServiceFlags) + sizeof(mDNSu32) + 4;
+ DNSQuestion *q;
+ mStatus err = mStatus_NoError;
+ char qname_cstr[MAX_ESCAPED_DOMAIN_NAME];
+
+ // Process the unit test's client request
+ start_client_request(req, msgptr, msgsz, query_request, local_socket);
+ UNITTEST_ASSERT(err == mStatus_NoError);
+
+ // Verify the request fields were set as expected
+ UNITTEST_ASSERT(req->next == mDNSNULL);
+ UNITTEST_ASSERT(req->primary == mDNSNULL);
+ UNITTEST_ASSERT(req->sd == client_req_sd);
+ UNITTEST_ASSERT(req->process_id == client_req_process_id);
+ UNITTEST_ASSERT(!strcmp(req->pid_name, client_req_pid_name));
+ UNITTEST_ASSERT(req->validUUID == mDNSfalse);
+ UNITTEST_ASSERT(req->errsd == 0);
+ UNITTEST_ASSERT(req->uid == client_req_uid);
+ UNITTEST_ASSERT(req->ts == t_complete);
+ UNITTEST_ASSERT((mDNSs32)req->data_bytes > min_size);
+ UNITTEST_ASSERT(req->msgend == msgptr+msgsz);
+ UNITTEST_ASSERT(req->msgbuf == mDNSNULL);
+ UNITTEST_ASSERT(req->hdr.version == VERSION);
+ UNITTEST_ASSERT(req->replies == mDNSNULL);
+ UNITTEST_ASSERT(req->terminate != mDNSNULL);
+ UNITTEST_ASSERT(req->flags == kDNSServiceFlagsReturnIntermediates);
+ UNITTEST_ASSERT(req->interfaceIndex == kDNSServiceInterfaceIndexAny);
+
+ // Verify the query fields were set as expected
+ q = &req->u.queryrecord.q;
+ UNITTEST_ASSERT(q != mDNSNULL);
+ UNITTEST_ASSERT(q == m->Questions);
+ UNITTEST_ASSERT(q == m->NewQuestions);
+ UNITTEST_ASSERT(q->SuppressUnusable == mDNSfalse);
+ UNITTEST_ASSERT(q->ReturnIntermed == mDNStrue);
+ UNITTEST_ASSERT(q->SuppressQuery == mDNSfalse);
+
+ UNITTEST_ASSERT(q->qnameOrig == mDNSNULL);
+ ConvertDomainNameToCString(&q->qname, qname_cstr);
+ UNITTEST_ASSERT(!strcmp(qname_cstr, udns_original_domainname_cstr));
+ UNITTEST_ASSERT(q->qnamehash == DomainNameHashValue(&q->qname));
+
+ UNITTEST_ASSERT(q->InterfaceID == mDNSInterface_Any);
+ UNITTEST_ASSERT(q->flags == req->flags);
+ UNITTEST_ASSERT(q->qtype == 1);
+ UNITTEST_ASSERT(q->qclass == 1);
+ UNITTEST_ASSERT(q->LongLived == 0);
+ UNITTEST_ASSERT(q->ExpectUnique == mDNSfalse);
+ UNITTEST_ASSERT(q->ForceMCast == 0);
+ UNITTEST_ASSERT(q->TimeoutQuestion == 0);
+ UNITTEST_ASSERT(q->WakeOnResolve == 0);
+ UNITTEST_ASSERT(q->UseBackgroundTrafficClass == 0);
+ UNITTEST_ASSERT(q->ValidationRequired == 0);
+ UNITTEST_ASSERT(q->ValidatingResponse == 0);
+ UNITTEST_ASSERT(q->ProxyQuestion == 0);
+ UNITTEST_ASSERT(q->AnonInfo == mDNSNULL);
+ UNITTEST_ASSERT(q->QuestionCallback != mDNSNULL);
+ UNITTEST_ASSERT(q->QuestionContext == req);
+ UNITTEST_ASSERT(q->SearchListIndex == 0);
+ UNITTEST_ASSERT(q->DNSSECAuthInfo == mDNSNULL);
+ UNITTEST_ASSERT(q->DAIFreeCallback == mDNSNULL);
+ UNITTEST_ASSERT(q->RetryWithSearchDomains == 0);
+ UNITTEST_ASSERT(q->AppendSearchDomains == 0);
+ UNITTEST_ASSERT(q->AppendLocalSearchDomains == 0);
+ UNITTEST_ASSERT(q->DuplicateOf == mDNSNULL);
+
+ // Call mDNS_Execute to see if the new question, q, has an answer in the cache.
+ // It won't be yet because the cache is empty.
+ m->NextScheduledEvent = mDNS_TimeNow_NoLock(m);
+ mDNS_Execute(m);
+
+ // Verify mDNS_Execute processed the new question.
+ UNITTEST_ASSERT(m->NewQuestions == mDNSNULL);
+
+ // Verify the cache is empty and the request got no reply.
+ UNITTEST_ASSERT(m->rrcache_totalused == 0);
+ UNITTEST_ASSERT(req->replies == mDNSNULL);
+
+UNITTEST_FOOTER
+
+// This unit test receives a canned uDNS response message by calling the mDNSCoreReceive() function.
+// It then verifies cache entries were added for the CNAME and A records that were contained in the
+// answers of the canned response, query_response_msgbuf. This unit test also verifies that
+// 2 add events were generated for the client.
+UNITTEST_HEADER(PopulateCacheWithClientResponseRecords)
+ mDNS *const m = &mDNSStorage;
+ DNSMessage *msgptr = (DNSMessage *)query_response_msgbuf;
+ size_t msgsz = sizeof(query_response_msgbuf);
+ struct reply_state *reply;
+ request_state* req = client_request_message;
+ DNSQuestion *q = &req->u.queryrecord.q;
+ const char *data;
+ const char *end;
+ char name[kDNSServiceMaxDomainName];
+ uint16_t rrtype, rrclass, rdlen;
+ const char *rdata;
+ size_t len;
+ char domainname_cstr[MAX_ESCAPED_DOMAIN_NAME];
+
+ // Receive and populate the cache with canned response
+ receive_response(req, msgptr, msgsz);
+
+ // Verify 2 cache entries for CName and A record are present
+ mDNSu32 CacheUsed =0, notUsed =0;
+ LogCacheRecords_ut(mDNS_TimeNow(m), &CacheUsed, &notUsed);
+ UNITTEST_ASSERT(CacheUsed == m->rrcache_totalused);
+ UNITTEST_ASSERT(CacheUsed == 4); // 2 for the CacheGroup object plus 2 for the A and CNAME records
+ UNITTEST_ASSERT(m->PktNum == 1); // one packet was received
+
+ // Verify question's qname is now set with the A record's domainname
+ UNITTEST_ASSERT(q->qnameOrig == mDNSNULL);
+ ConvertDomainNameToCString(&q->qname, domainname_cstr);
+ UNITTEST_ASSERT(q->qnamehash == DomainNameHashValue(&q->qname));
+ UNITTEST_ASSERT(!strcmp(domainname_cstr, udns_cname_domainname_cstr));
+
+ // Verify client's add event for CNAME is properly formed
+ reply = req->replies;
+ UNITTEST_ASSERT(reply != mDNSNULL);
+ UNITTEST_ASSERT(reply->next == mDNSNULL);
+
+ data = (char *)&reply->rhdr[1];
+ end = data+reply->totallen;
+ get_string(&data, data+reply->totallen, name, kDNSServiceMaxDomainName);
+ rrtype = get_uint16(&data, end);
+ rrclass = get_uint16(&data, end);
+ rdlen = get_uint16(&data, end);
+ rdata = get_rdata(&data, end, rdlen);
+ len = get_reply_len(name, rdlen);
+
+ UNITTEST_ASSERT(reply->totallen == len + sizeof(ipc_msg_hdr));
+ UNITTEST_ASSERT(reply->mhdr->version == VERSION);
+ UNITTEST_ASSERT(reply->mhdr->datalen == len);
+ UNITTEST_ASSERT(reply->mhdr->ipc_flags == 0);
+ UNITTEST_ASSERT(reply->mhdr->op == query_reply_op);
+
+ UNITTEST_ASSERT(reply->rhdr->flags == htonl(kDNSServiceFlagsAdd));
+ UNITTEST_ASSERT(reply->rhdr->ifi == kDNSServiceInterfaceIndexAny);
+ UNITTEST_ASSERT(reply->rhdr->error == kDNSServiceErr_NoError);
+
+ UNITTEST_ASSERT(rrtype == kDNSType_CNAME);
+ UNITTEST_ASSERT(rrclass == kDNSClass_IN);
+ ConvertDomainNameToCString((const domainname *const)rdata, domainname_cstr);
+ UNITTEST_ASSERT(!strcmp(domainname_cstr, "test212.dotbennu.com."));
+
+ // The mDNS_Execute call generates an add event for the A record
+ m->NextScheduledEvent = mDNS_TimeNow_NoLock(m);
+ mDNS_Execute(m);
+
+ // Verify the client's reply contains a properly formed add event for the A record.
+ reply = req->replies;
+ UNITTEST_ASSERT(reply != mDNSNULL);
+ UNITTEST_ASSERT(reply->next != mDNSNULL);
+ reply = reply->next;
+
+ data = (char *)&reply->rhdr[1];
+ end = data+reply->totallen;
+ get_string(&data, data+reply->totallen, name, kDNSServiceMaxDomainName);
+ rrtype = get_uint16(&data, end);
+ rrclass = get_uint16(&data, end);
+ rdlen = get_uint16(&data, end);
+ rdata = get_rdata(&data, end, rdlen);
+ len = get_reply_len(name, rdlen);
+
+ UNITTEST_ASSERT(reply->totallen == len + sizeof(ipc_msg_hdr));
+ UNITTEST_ASSERT(reply->mhdr->version == VERSION);
+ UNITTEST_ASSERT(reply->mhdr->datalen == len);
+
+ UNITTEST_ASSERT(reply->mhdr->ipc_flags == 0);
+ UNITTEST_ASSERT(reply->mhdr->op == query_reply_op);
+
+ UNITTEST_ASSERT(reply->rhdr->flags == htonl(kDNSServiceFlagsAdd));
+ UNITTEST_ASSERT(reply->rhdr->ifi == kDNSServiceInterfaceIndexAny);
+ UNITTEST_ASSERT(reply->rhdr->error == kDNSServiceErr_NoError);
+
+ UNITTEST_ASSERT(rrtype == kDNSType_A);
+ UNITTEST_ASSERT(rrclass == kDNSClass_IN);
+ UNITTEST_ASSERT(rdata[0] == dns_response_ipv4.b[0]);
+ UNITTEST_ASSERT(rdata[1] == dns_response_ipv4.b[1]);
+ UNITTEST_ASSERT(rdata[2] == dns_response_ipv4.b[2]);
+ UNITTEST_ASSERT(rdata[3] == dns_response_ipv4.b[3]);
+
+UNITTEST_FOOTER
+
+// This function verifies the cache and event handling occurred as expected when a network change happened.
+// The uDNS_SetupDNSConfig is called to simulate a network change and two outcomes occur. First the A record
+// query is restarted and sent to a new DNS server. Second the cache records are purged. Then mDNS_Execute
+// is called and it removes the purged cache records and generates a remove event for the A record.
+// The following are verified:
+// 1.) The restart of query for A record.
+// 2.) The cache is empty after mDNS_Execute removes the cache entres.
+// 3.) The remove event is verified by examining the request's reply data.
+UNITTEST_HEADER(SimulateNetworkChangeAndVerifyTest)
+ mDNS *const m = &mDNSStorage;
+ request_state* req = client_request_message;
+ DNSQuestion* q = &req->u.queryrecord.q;
+ mDNSu32 CacheUsed =0, notUsed =0;
+ const char *data; const char *end;
+ char name[kDNSServiceMaxDomainName];
+ uint16_t rrtype, rrclass, rdlen;
+ const char *rdata;
+ size_t len;
+
+ // The uDNS_SetupDNSConfig reconfigures the resolvers so the A record query is restarted and
+ // both the CNAME and A record are purged.
+ uDNS_SetupDNSConfig(m);
+
+ // Verify the A record query was restarted. This is done indirectly by noticing the transaction id and interval have changed.
+ UNITTEST_ASSERT(q->ThisQInterval == InitialQuestionInterval);
+ UNITTEST_ASSERT(q->TargetQID.NotAnInteger != uDNS_TargetQID);
+
+ // Then mDNS_Execute removes both records from the cache and calls the client back with a remove event for A record.
+ m->NextScheduledEvent = mDNS_TimeNow_NoLock(m);
+ mDNS_Execute(m);
+
+ // Verify the cache entries are removed
+ LogCacheRecords_ut(mDNS_TimeNow(m), &CacheUsed, &notUsed);
+ UNITTEST_ASSERT(CacheUsed == m->rrcache_totalused);
+ UNITTEST_ASSERT(CacheUsed == 0);
+
+ // Verify the A record's remove event is setup as expected in the reply data
+ struct reply_state *reply;
+ reply = req->replies;
+ UNITTEST_ASSERT(reply != mDNSNULL);
+
+ UNITTEST_ASSERT(reply != mDNSNULL);
+ UNITTEST_ASSERT(reply->next != mDNSNULL);
+ UNITTEST_ASSERT(reply->next->next != mDNSNULL);
+
+ reply = reply->next->next; // Get to last event to verify remove event
+ data = (char *)&reply->rhdr[1];
+ end = data+reply->totallen;
+ get_string(&data, data+reply->totallen, name, kDNSServiceMaxDomainName);
+ rrtype = get_uint16(&data, end);
+ rrclass = get_uint16(&data, end);
+ rdlen = get_uint16(&data, end);
+ rdata = get_rdata(&data, end, rdlen);
+ len = get_reply_len(name, rdlen);
+
+ UNITTEST_ASSERT(reply->totallen == reply->mhdr->datalen + sizeof(ipc_msg_hdr));
+ UNITTEST_ASSERT(reply->mhdr->version == VERSION);
+ UNITTEST_ASSERT(reply->mhdr->datalen == len);
+ UNITTEST_ASSERT(reply->mhdr->ipc_flags == 0);
+ UNITTEST_ASSERT(reply->mhdr->op == query_reply_op);
+
+ UNITTEST_ASSERT(reply->rhdr->flags != htonl(kDNSServiceFlagsAdd));
+ UNITTEST_ASSERT(reply->rhdr->ifi == kDNSServiceInterfaceIndexAny);
+ UNITTEST_ASSERT(reply->rhdr->error == kDNSServiceErr_NoError);
+
+ UNITTEST_ASSERT(rrtype == kDNSType_A);
+ UNITTEST_ASSERT(rrclass == kDNSClass_IN);
+ UNITTEST_ASSERT(rdata[0] == dns_response_ipv4.b[0]);
+ UNITTEST_ASSERT(rdata[1] == dns_response_ipv4.b[1]);
+ UNITTEST_ASSERT(rdata[2] == dns_response_ipv4.b[2]);
+ UNITTEST_ASSERT(rdata[3] == dns_response_ipv4.b[3]);
+
+UNITTEST_FOOTER
+
+// This function does memory cleanup and no verification.
+UNITTEST_HEADER(FinalizeUnitTest)
+ mDNS *m = &mDNSStorage;
+ request_state* req = client_request_message;
+ DNSServer *ptr, **p = &m->DNSServers;
+
+ while (req->replies)
+ {
+ reply_state *reply = req->replies;
+ req->replies = req->replies->next;
+ mDNSPlatformMemFree(reply);
+ }
+ mDNSPlatformMemFree(req);
+
+ mDNSPlatformMemFree(local_socket);
+
+ while (*p)
+ {
+ ptr = *p;
+ *p = (*p)->next;
+ LogInfo("FinalizeUnitTest: Deleting server %p %#a:%d (%##s)", ptr, &ptr->addr, mDNSVal16(ptr->port), ptr->domain.c);
+ mDNSPlatformMemFree(ptr);
+ }
+UNITTEST_FOOTER
+
+// The mDNS_AddDNSServer function adds a dns server to mDNSResponder's list.
+mDNSlocal mStatus AddDNSServer(void)
+{
+ mDNS *m = &mDNSStorage;
+ m->timenow = 0;
+ mDNS_Lock(m);
+ domainname d;
+ mDNSAddr addr;
+ mDNSIPPort port;
+ mDNSs32 serviceID = 0;
+ mDNSu32 scoped = 0;
+ mDNSu32 timeout = dns_server_timeout;
+ mDNSBool cellIntf = 0;
+ mDNSBool isExpensive = 0;
+ mDNSu16 resGroupID = dns_server_resGroupID;
+ mDNSBool reqA = mDNStrue;
+ mDNSBool reqAAAA = mDNStrue;
+ mDNSBool reqDO = mDNSfalse;
+ d.c[0] = 0;
+ addr.type = mDNSAddrType_IPv4;
+ addr.ip.v4.NotAnInteger = dns_server_ipv4.NotAnInteger;
+ port.NotAnInteger = client_resp_src_port;
+ mDNS_AddDNSServer(m, &d, primary_interfaceID, serviceID, &addr, port, scoped, timeout,
+ cellIntf, isExpensive, resGroupID,
+ reqA, reqAAAA, reqDO);
+ mDNS_Unlock(m);
+ return mStatus_NoError;
+}
+
+