diff options
Diffstat (limited to 'mDNSResponder/unittests/CNameRecordTests.c')
-rw-r--r-- | mDNSResponder/unittests/CNameRecordTests.c | 400 |
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, ¬Used); + 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, ¬Used); + 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; +} + + |