| /* |
| * Copyright (c) 2017-2023 Apple Inc. All rights reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #import "unittest_common.h" |
| #import "mDNSMacOSX.h" |
| #import <XCTest/XCTest.h> |
| |
| // 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 }}; |
| |
| @interface CNameRecordTest : XCTestCase |
| { |
| UDPSocket* local_socket; |
| request_state* client_request_message;} |
| @end |
| |
| @implementation CNameRecordTest |
| |
| // 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. |
| - (void)setUp |
| { |
| // Init unit test environment and verify no error occurred. |
| mStatus result = init_mdns_environment(mDNStrue); |
| XCTAssertEqual(result, mStatus_NoError); |
| |
| // Add one DNS server and verify it was added. |
| AddDNSServer_ut(); |
| XCTAssertEqual(CountOfUnicastDNSServers(&mDNSStorage), 1); |
| |
| // Create memory for a socket that is never used or opened. |
| local_socket = (UDPSocket *) mDNSPlatformMemAllocateClear(sizeof(*local_socket)); |
| |
| // Create memory for a request that is used to make this unit test's client request. |
| client_request_message = calloc(1, sizeof(request_state)); |
| } |
| |
| - (void)tearDown |
| { |
| 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); |
| } |
| } |
| |
| - (void)testCNameRecordTestSeries |
| { |
| [self _startClientQueryRequest]; |
| [self _populateCacheWithClientResponseRecords]; |
| [self _simulateNetworkChangeAndVerify]; |
| } |
| |
| // 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. |
| - (void)_startClientQueryRequest |
| { |
| mDNS *const m = &mDNSStorage; |
| request_state* req = client_request_message; |
| const uint8_t *const msgptr = query_client_msgbuf; |
| const uint32_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); |
| XCTAssertEqual(err, mStatus_NoError); |
| |
| // Verify the request fields were set as expected |
| XCTAssertNil((__bridge id)req->next); |
| XCTAssertNil((__bridge id)req->primary); |
| XCTAssertEqual(req->sd, client_req_sd); |
| XCTAssertEqual(req->process_id, client_req_process_id); |
| XCTAssertFalse(strcmp(req->pid_name, client_req_pid_name)); |
| XCTAssertEqual(req->validUUID, mDNSfalse); |
| XCTAssertEqual(req->errsd, 0); |
| XCTAssertEqual(req->uid, client_req_uid); |
| XCTAssertEqual(req->ts, t_complete); |
| XCTAssertGreaterThan((mDNSs32)req->data_bytes, min_size); |
| XCTAssertEqual(req->msgend, msgptr+msgsz); |
| XCTAssertNil((__bridge id)(void*)req->msgbuf); |
| XCTAssertEqual(req->hdr.version, VERSION); |
| XCTAssertNil((__bridge id)req->replies); |
| XCTAssertNotEqual(req->terminate, (req_termination_fn)0); |
| XCTAssertEqual(req->flags, kDNSServiceFlagsReturnIntermediates); |
| XCTAssertEqual(req->interfaceIndex, kDNSServiceInterfaceIndexAny); |
| |
| // Verify the query fields were set as expected |
| q = &req->queryrecord->op.q; |
| XCTAssertNotEqual(q, (DNSQuestion *)mDNSNULL); |
| XCTAssertEqual(q, m->Questions); |
| XCTAssertEqual(q, m->NewQuestions); |
| XCTAssertEqual(q->SuppressUnusable, mDNSfalse); |
| XCTAssertEqual(q->ReturnIntermed, mDNStrue); |
| XCTAssertEqual(q->Suppressed, mDNSfalse); |
| |
| ConvertDomainNameToCString(&q->qname, qname_cstr); |
| XCTAssertFalse(strcmp(qname_cstr, udns_original_domainname_cstr)); |
| XCTAssertEqual(q->qnamehash, DomainNameHashValue(&q->qname)); |
| |
| XCTAssertEqual(q->InterfaceID, mDNSInterface_Any); |
| XCTAssertEqual(q->flags, req->flags); |
| XCTAssertEqual(q->qtype, 1); |
| XCTAssertEqual(q->qclass, 1); |
| XCTAssertEqual(q->LongLived, 0); |
| XCTAssertEqual(q->ExpectUnique, mDNSfalse); |
| XCTAssertEqual(q->ForceMCast, 0); |
| XCTAssertEqual(q->TimeoutQuestion, 0); |
| XCTAssertEqual(q->WakeOnResolve, 0); |
| XCTAssertEqual(q->UseBackgroundTraffic, 0); |
| XCTAssertEqual(q->ProxyQuestion, 0); |
| XCTAssertNotEqual((void*)q->QuestionCallback, (void*)mDNSNULL); |
| XCTAssertEqual(q->AppendSearchDomains, 0); |
| XCTAssertNil((__bridge id)q->DuplicateOf); |
| |
| // 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. |
| XCTAssertNil((__bridge id)m->NewQuestions); |
| |
| // Verify the cache is empty and the request got no reply. |
| XCTAssertEqual(m->rrcache_totalused, 0); |
| XCTAssertNil((__bridge id)req->replies); |
| } |
| |
| // 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. |
| - (void)_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->queryrecord->op.q; |
| const uint8_t *data; |
| const uint8_t *end; |
| char name[kDNSServiceMaxDomainName]; |
| uint16_t rrtype, rrclass, rdlen; |
| const uint8_t *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); |
| XCTAssertEqual(CacheUsed, m->rrcache_totalused); |
| XCTAssertEqual(CacheUsed, 4); // 2 for the CacheGroup object plus 2 for the A and CNAME records |
| XCTAssertEqual(m->PktNum, 1); // one packet was received |
| |
| // Verify question's qname is now set with the A record's domainname |
| ConvertDomainNameToCString(&q->qname, domainname_cstr); |
| XCTAssertEqual(q->qnamehash, DomainNameHashValue(&q->qname)); |
| XCTAssertFalse(strcmp(domainname_cstr, udns_cname_domainname_cstr)); |
| |
| // Verify client's add event for CNAME is properly formed |
| reply = req->replies; |
| XCTAssertNotEqual(reply, (reply_state*)mDNSNULL); |
| XCTAssertNil((__bridge id)reply->next); |
| |
| data = (uint8_t *)&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); |
| |
| XCTAssertEqual(reply->totallen, len + sizeof(ipc_msg_hdr)); |
| XCTAssertEqual(reply->mhdr->version, VERSION); |
| XCTAssertEqual(reply->mhdr->datalen, len); |
| XCTAssertEqual(reply->mhdr->ipc_flags, 0); |
| XCTAssertEqual(reply->mhdr->op, query_reply_op); |
| |
| XCTAssertEqual(reply->rhdr->flags, htonl(kDNSServiceFlagsAdd)); |
| XCTAssertEqual(reply->rhdr->ifi, kDNSServiceInterfaceIndexAny); |
| XCTAssertEqual(reply->rhdr->error, kDNSServiceErr_NoError); |
| |
| XCTAssertEqual(rrtype, kDNSType_CNAME); |
| XCTAssertEqual(rrclass, kDNSClass_IN); |
| ConvertDomainNameToCString((const domainname *const)rdata, domainname_cstr); |
| XCTAssertFalse(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; |
| XCTAssertNotEqual(reply, (reply_state*)mDNSNULL); |
| XCTAssertNotEqual(reply->next, (reply_state*)mDNSNULL); |
| reply = reply->next; |
| |
| data = (uint8_t *)&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); |
| |
| XCTAssertEqual(reply->totallen, len + sizeof(ipc_msg_hdr)); |
| XCTAssertEqual(reply->mhdr->version, VERSION); |
| XCTAssertEqual(reply->mhdr->datalen, len); |
| |
| XCTAssertEqual(reply->mhdr->ipc_flags, 0); |
| XCTAssertEqual(reply->mhdr->op, query_reply_op); |
| |
| XCTAssertEqual(reply->rhdr->flags, htonl(kDNSServiceFlagsAdd)); |
| XCTAssertEqual(reply->rhdr->ifi, kDNSServiceInterfaceIndexAny); |
| XCTAssertEqual(reply->rhdr->error, kDNSServiceErr_NoError); |
| |
| XCTAssertEqual(rrtype, kDNSType_A); |
| XCTAssertEqual(rrclass, kDNSClass_IN); |
| XCTAssertEqual(rdata[0], dns_response_ipv4.b[0]); |
| XCTAssertEqual(rdata[1], dns_response_ipv4.b[1]); |
| XCTAssertEqual(rdata[2], dns_response_ipv4.b[2]); |
| XCTAssertEqual(rdata[3], dns_response_ipv4.b[3]); |
| } |
| |
| // 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. |
| - (void)_simulateNetworkChangeAndVerify |
| { |
| mDNS *const m = &mDNSStorage; |
| request_state* req = client_request_message; |
| DNSQuestion* q = &req->queryrecord->op.q; |
| mDNSu32 CacheUsed =0, notUsed =0; |
| const uint8_t *data; |
| const uint8_t *end; |
| char name[kDNSServiceMaxDomainName]; |
| uint16_t rrtype, rrclass, rdlen; |
| const uint8_t *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. |
| force_uDNS_SetupDNSConfig_ut(m); |
| |
| // Verify the A record query was restarted. This is done indirectly by noticing the transaction id and interval have changed. |
| XCTAssertEqual(q->ThisQInterval, InitialQuestionInterval); |
| XCTAssertNotEqual(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); |
| XCTAssertEqual(CacheUsed, m->rrcache_totalused); |
| XCTAssertEqual(CacheUsed, 0); |
| |
| // Verify the A record's remove event is setup as expected in the reply data |
| struct reply_state *reply; |
| reply = req->replies; |
| XCTAssertNotEqual(reply, (reply_state*)mDNSNULL); |
| XCTAssertNotEqual(reply->next, (reply_state*)mDNSNULL); |
| XCTAssertNotEqual(reply->next->next, (reply_state*)mDNSNULL); |
| |
| reply = reply->next->next; // Get to last event to verify remove event |
| data = (uint8_t *)&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); |
| |
| XCTAssertEqual(reply->totallen, reply->mhdr->datalen + sizeof(ipc_msg_hdr)); |
| XCTAssertEqual(reply->mhdr->version, VERSION); |
| XCTAssertEqual(reply->mhdr->datalen, len); |
| XCTAssertEqual(reply->mhdr->ipc_flags, 0); |
| XCTAssertEqual(reply->mhdr->op, query_reply_op); |
| |
| XCTAssertNotEqual(reply->rhdr->flags, htonl(kDNSServiceFlagsAdd)); |
| XCTAssertEqual(reply->rhdr->ifi, kDNSServiceInterfaceIndexAny); |
| XCTAssertEqual(reply->rhdr->error, kDNSServiceErr_NoError); |
| |
| XCTAssertEqual(rrtype, kDNSType_A); |
| XCTAssertEqual(rrclass, kDNSClass_IN); |
| XCTAssertEqual(rdata[0], dns_response_ipv4.b[0]); |
| XCTAssertEqual(rdata[1], dns_response_ipv4.b[1]); |
| XCTAssertEqual(rdata[2], dns_response_ipv4.b[2]); |
| XCTAssertEqual(rdata[3], dns_response_ipv4.b[3]); |
| } |
| |
| @end |