| /* -*- Mode: C; tab-width: 4 -*- |
| * |
| * Copyright (c) 2011-2024 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 |
| * |
| * https://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. |
| */ |
| |
| #include "dnsproxy.h" |
| #include "mrcs_dns_proxy.h" |
| #include "mrcs_server.h" |
| #include "mDNSMacOSX.h" |
| #include <AssertMacros.h> |
| |
| #if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PROXY_DNS64) |
| #include <nw/private.h> |
| #endif |
| |
| #include "mdns_strict.h" |
| |
| #ifndef UNICAST_DISABLED |
| |
| extern mDNS mDNSStorage; |
| |
| // Implementation Notes |
| // |
| // DNS Proxy listens on port 53 (UDPv4v6 & TCPv4v6) for DNS queries. It handles only |
| // the "Query" opcode of the DNS protocol described in RFC 1035. For all other opcodes, it returns |
| // "Not Implemented" error. The platform interface mDNSPlatformInitDNSProxySkts |
| // sets up the sockets and whenever it receives a packet, it calls ProxyTCPCallback or ProxyUDPCallback |
| // defined here. For TCP socket, the platform does the "accept" and only sends the received packets |
| // on the newly accepted socket. A single UDP socket (per address family) is used to send/recv |
| // requests/responses from all clients. For TCP, there is one socket per request. Hence, there is some |
| // extra state that needs to be disposed at the end. |
| // |
| // When a DNS request is received, ProxyCallbackCommon checks for malformed packet etc. and also checks |
| // for duplicates, before creating DNSProxyClient state and starting a question with the "core" |
| // (mDNS_StartQuery). When the callback for the question happens, it gathers all the necessary |
| // resource records, constructs a response and sends it back to the client. |
| // |
| // - Question callback is called with only one resource record at a time. We need all the resource |
| // records to construct the response. Hence, we lookup all the records ourselves. |
| // |
| // - The response may not fit the client's buffer size. In that case, we need to set the truncate bit |
| // and the client would retry using TCP. |
| // |
| // - The client may have set the DNSSEC OK bit in the EDNS0 option and that means we also have to |
| // return the RRSIGs or the NSEC records with the RRSIGs in the Additional section. We need to |
| // ask the "core" to fetch the DNSSEC records and do the validation if the CD bit is not set. |
| // |
| // Once the response is sent to the client, the client state is disposed. When there is no response |
| // from the "core", it eventually times out and we will not find any answers in the cache and we send a |
| // "NXDomain" response back. Thus, we don't need any special timers to reap the client state in the case |
| // of errors. |
| |
| typedef struct DNSProxyClient_struct DNSProxyClient; |
| |
| #if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PROXY_DNS64) |
| typedef enum |
| { |
| kDNSProxyDNS64State_Initial = 0, // Initial state. |
| kDNSProxyDNS64State_AAAASynthesis = 1, // Querying for A record for AAAA record synthesis. |
| kDNSProxyDNS64State_PTRSynthesisTrying = 2, // Querying for in-addr.arpa PTR record to map from ip6.arpa PTR. |
| kDNSProxyDNS64State_PTRSynthesisSuccess = 3, // in-addr.arpa PTR query got non-negative non-CNAME answer. |
| kDNSProxyDNS64State_PTRSynthesisNXDomain = 4 // in-addr.arpa PTR query produced no useful result. |
| |
| } DNSProxyDNS64State; |
| #endif |
| |
| // Note: This needs to maintain compatibility with struct DNSMessage defined in mDNSEmbeddedAPI.h |
| typedef struct |
| { |
| DNSMessageHeader h; // Note: Size 12 bytes |
| mDNSu8 data[]; // Flexible storage. Start at NormalUDPDNSMessageData, |
| // then try rcvBufSize if present or AbsoluteMaxDNSMessageData if tcp |
| } _DNSMessage; |
| |
| struct DNSProxyClient_struct |
| { |
| DNSProxyClient *next; |
| mDNSAddr addr; // Client's IP address |
| mDNSIPPort port; // Client's port number |
| mDNSOpaque16 msgid; // DNS msg id |
| mDNSInterfaceID interfaceID; // Interface on which we received the request |
| void *socket; // Return socket |
| mDNSBool tcp; // TCP or UDP ? |
| mDNSOpaque16 requestFlags; // second 16 bit word in the DNSMessageHeader of the request |
| mDNSu8 *optRR; // EDNS0 option |
| mDNSu32 optLen; // Total Length of the EDNS0 option |
| mDNSu16 rcvBufSize; // How much can the client receive ? |
| void *context; // Platform context to be disposed if non-NULL |
| domainname qname; // q->qname can't be used for duplicate check |
| DNSQuestion q; // as it can change underneath us for CNAMEs |
| mDNSu16 qtype; |
| #if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PROXY_DNS64) |
| DNSProxyDNS64State dns64state; |
| #endif |
| mDNSu8 *omsg_ptr; // Where we are in the omsg->data |
| mDNSu16 omsg_size; // The current size of omsg->data |
| _DNSMessage *omsg; // Outgoing message we're building |
| mrcs_dns_proxy_t proxy; // A reference to the DNS proxy that this client request belongs to. |
| }; |
| |
| typedef struct |
| { |
| DNSMessageHeader h; |
| size_t omsg_offset; |
| } _MsgResourceState; |
| |
| // 12 (DNS message header) + 500 (DNS message body) = 512 total |
| #define NormalUDPDNSMessageData 500 |
| |
| // OPT pseudo-resource record fixed-length fields without RDATA |
| // See <https://tools.ietf.org/html/rfc6891#section-6.1.2> |
| typedef struct |
| { |
| mDNSu8 name[1]; |
| mDNSu8 type[2]; |
| mDNSu8 udpPayloadSize[2]; |
| mDNSu8 extendedRCode[1]; |
| mDNSu8 version[1]; |
| mDNSu8 extendedFlags[2]; |
| mDNSu8 rdLen[2]; |
| } OPTRecordFixedFields; |
| |
| extern int sizecheck_OPTRecordFixedFields[(sizeof(OPTRecordFixedFields) == 11) ? 1 : -1]; |
| |
| static DNSProxyClient *DNSProxyClients; |
| |
| mDNSlocal void FreeDNSProxyClient(DNSProxyClient *pc) |
| { |
| if (pc->optRR) |
| mDNSPlatformMemFree(pc->optRR); |
| if (pc->omsg) |
| mDNSPlatformMemFree(pc->omsg); |
| mrcs_forget(&pc->proxy); |
| mDNSPlatformMemFree(pc); |
| } |
| |
| mDNSlocal mDNSBool DNSProxyPrepareOmsg(const mDNSu16 size, DNSProxyClient *const pc) |
| { |
| mDNSu32 allocation_size = sizeof(_DNSMessage) + size; // Handle values up to UINT16_MAX + sizeof(_DNSMessage) |
| size_t offset = 0; |
| if (!pc->omsg) |
| { |
| pc->omsg = mDNSPlatformMemAllocateClear(allocation_size); |
| if (!pc->omsg) |
| { |
| return mDNSfalse; |
| } |
| } |
| else |
| { |
| void * new_ptr = mDNSPlatformMemAllocateClear(allocation_size); |
| if (!new_ptr) |
| { |
| return mDNSfalse; |
| } |
| offset = pc->omsg_ptr - pc->omsg->data; |
| LogInfo("DNSProxyPrepareOmsg: Preserving offset %ld in size %d", offset, pc->omsg_size); |
| memcpy(new_ptr, pc->omsg, MIN(sizeof(_DNSMessage) + pc->omsg_size, allocation_size)); |
| mDNSPlatformMemFree(pc->omsg); |
| pc->omsg = new_ptr; |
| } |
| pc->omsg_size = size; |
| pc->omsg_ptr = pc->omsg->data + offset; |
| return mDNStrue; |
| } |
| |
| mDNSlocal mDNSBool DNSProxyMsgCanGrow(const DNSProxyClient *const pc, mDNSu16 *out_size) |
| { |
| mDNSu16 max_size; |
| if (!pc->tcp) |
| { |
| if (!pc->rcvBufSize) |
| { |
| max_size = NormalUDPDNSMessageData; |
| } |
| else |
| { |
| mDNSu16 bufSize = (pc->rcvBufSize > sizeof(_DNSMessage)) ? (pc->rcvBufSize - sizeof(_DNSMessage)) : NormalUDPDNSMessageData; |
| max_size = (bufSize > AbsoluteMaxDNSMessageData ? AbsoluteMaxDNSMessageData : bufSize); |
| } |
| } |
| else |
| { |
| // For TCP, max_size is not determined by EDNS0 but by 16 bit rdlength field and |
| // AbsoluteMaxDNSMessageData is smaller than 64k. |
| max_size = AbsoluteMaxDNSMessageData; |
| } |
| if (out_size) |
| { |
| *out_size = max_size; |
| } |
| return (pc->omsg_size < max_size); |
| } |
| |
| mDNSlocal void DNSMsgStateSave(const DNSProxyClient *const pc, _MsgResourceState *const state) |
| { |
| mDNSPlatformMemCopy(&state->h, &pc->omsg->h, sizeof(DNSMessageHeader)); |
| state->omsg_offset = pc->omsg_ptr - pc->omsg->data; |
| } |
| |
| mDNSlocal void DNSMsgStateRestore(DNSProxyClient *const pc, const _MsgResourceState *const state) |
| { |
| mDNSPlatformMemCopy(&pc->omsg->h, &state->h, sizeof(DNSMessageHeader)); |
| pc->omsg_ptr = pc->omsg->data + state->omsg_offset; |
| } |
| |
| mDNSexport mDNSu8 *DNSProxySetAttributes(DNSQuestion *q, DNSMessageHeader *h, DNSMessage *msg, mDNSu8 *ptr, mDNSu8 *limit) |
| { |
| DNSProxyClient *pc = (DNSProxyClient *)q->QuestionContext; |
| |
| (void) msg; |
| |
| h->flags = pc->requestFlags; |
| if (pc->optRR) |
| { |
| if (ptr + pc->optLen > limit) |
| { |
| LogInfo("DNSProxySetAttributes: Cannot set EDNS0 option start %p, OptLen %d, end %p", ptr, pc->optLen, limit); |
| return ptr; |
| } |
| h->numAdditionals++; |
| mDNSPlatformMemCopy(ptr, pc->optRR, pc->optLen); |
| ptr += pc->optLen; |
| } |
| return ptr; |
| } |
| |
| mDNSlocal mDNSu8 *AddEDNS0Option(_DNSMessage *const msg, mDNSu8 *ptr, mDNSu8 *limit) |
| { |
| int len = 4096; |
| |
| if (ptr + 11 > limit) |
| { |
| LogInfo("AddEDNS0Option: not enough space"); |
| return mDNSNULL; |
| } |
| msg->h.numAdditionals++; |
| ptr[0] = 0; |
| ptr[1] = (mDNSu8) (kDNSType_OPT >> 8); |
| ptr[2] = (mDNSu8) (kDNSType_OPT & 0xFF); |
| ptr[3] = (mDNSu8) (len >> 8); |
| ptr[4] = (mDNSu8) (len & 0xFF); |
| ptr[5] = 0; // rcode |
| ptr[6] = 0; // version |
| ptr[7] = 0; |
| ptr[8] = 0; // flags |
| ptr[9] = 0; // rdlength |
| ptr[10] = 0; // rdlength |
| |
| LogInfo("AddEDNS0 option added to response"); |
| |
| return (ptr + 11); |
| } |
| |
| // Currently RD and CD bit should be copied if present in the request or cleared if |
| // not present in the request. RD bit is normally set in the response and hence the |
| // cache reflects the right value. CD bit behaves differently. If the CD bit is set |
| // the first time, the cache retains it, if it is present in response (assuming the |
| // upstream server does it right). Next time through we should not use the cached |
| // value of the CD bit blindly. It depends on whether it was in the request or not. |
| mDNSlocal mDNSOpaque16 SetResponseFlags(DNSProxyClient *pc, const mDNSOpaque16 responseFlags) |
| { |
| mDNSOpaque16 rFlags = responseFlags; |
| |
| if (pc->requestFlags.b[0] & kDNSFlag0_RD) |
| rFlags.b[0] |= kDNSFlag0_RD; |
| else |
| rFlags.b[0] &= ~kDNSFlag0_RD; |
| |
| if (pc->requestFlags.b[1] & kDNSFlag1_CD) |
| rFlags.b[1] |= kDNSFlag1_CD; |
| else |
| rFlags.b[1] &= ~kDNSFlag1_CD; |
| |
| return rFlags; |
| } |
| |
| mDNSlocal mDNSu8 *AddResourceRecord(DNSProxyClient *pc, mDNSu8 **prevptr, mStatus *error, mDNSBool final_answer) |
| { |
| mDNS *const m = &mDNSStorage; |
| CacheGroup *cg; |
| CacheRecord *cr; |
| int len = sizeof(DNSMessageHeader); |
| mDNSu8 *ptr = mDNSNULL; |
| mDNSs32 now; |
| mDNSs32 ttl; |
| const CacheRecord *soa = mDNSNULL; |
| mDNSu8 *limit; |
| |
| *error = mStatus_NoError; |
| *prevptr = mDNSNULL; |
| |
| mDNS_Lock(m); |
| now = m->timenow; |
| mDNS_Unlock(m); |
| |
| if (pc->tcp || !pc->rcvBufSize || pc->rcvBufSize > pc->omsg_size) |
| { |
| limit = pc->omsg->data + pc->omsg_size; |
| } |
| else |
| { |
| limit = pc->omsg->data + pc->rcvBufSize; |
| } |
| LogInfo("AddResourceRecord: Limit is %d", limit - pc->omsg_ptr); |
| |
| cg = CacheGroupForName(m, pc->q.qnamehash, &pc->q.qname); |
| if (!cg) |
| { |
| LogInfo("AddResourceRecord: CacheGroup not found for %##s", pc->q.qname.c); |
| *error = mStatus_NoSuchRecord; |
| return mDNSNULL; |
| } |
| for (cr = cg->members; cr; cr = cr->next) |
| { |
| if (SameNameCacheRecordAnswersQuestion(cr, &pc->q)) |
| { |
| if (pc->omsg->h.numQuestions == 0) |
| { |
| // If this is the first time, initialize the header and the question. |
| // This code needs to be here so that we can use the responseFlags from the |
| // cache record |
| mDNSOpaque16 responseFlags = SetResponseFlags(pc, cr->responseFlags); |
| InitializeDNSMessage(&pc->omsg->h, pc->msgid, responseFlags); |
| ptr = putQuestion((DNSMessage*)pc->omsg, pc->omsg->data, limit, &pc->qname, pc->qtype, pc->q.qclass); |
| if (!ptr) |
| { |
| LogInfo("AddResourceRecord: putQuestion NULL for %##s (%s)", &pc->qname.c, DNSTypeName(pc->qtype)); |
| return mDNSNULL; |
| } |
| len += (ptr - pc->omsg_ptr); |
| pc->omsg_ptr = ptr; |
| #if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PROXY_DNS64) |
| if (pc->dns64state == kDNSProxyDNS64State_PTRSynthesisSuccess) |
| { |
| // For the first answer record, synthesize a CNAME record to map the originally requested ip6.arpa |
| // domain name to the in-addr.arpa domain name. |
| // See <https://tools.ietf.org/html/rfc6147#section-5.3.1>. |
| RData rdata; |
| ResourceRecord newRR; |
| mDNSPlatformMemZero(&newRR, (mDNSu32)sizeof(newRR)); |
| newRR.RecordType = kDNSRecordTypePacketAns; |
| newRR.rrtype = kDNSType_CNAME; |
| newRR.rrclass = kDNSClass_IN; |
| newRR.name = &pc->qname; |
| AssignDomainName(&rdata.u.name, &pc->q.qname); |
| rdata.MaxRDLength = (mDNSu32)sizeof(rdata.u); |
| newRR.rdata = &rdata; |
| ptr = PutResourceRecordTTLWithLimit((DNSMessage*)pc->omsg, ptr, &pc->omsg->h.numAnswers, &newRR, 0, limit); |
| if (!ptr) |
| { |
| *prevptr = pc->omsg_ptr; |
| return mDNSNULL; |
| } |
| len += (ptr - pc->omsg_ptr); |
| pc->omsg_ptr = ptr; |
| } |
| #endif |
| } |
| else if (!ptr) |
| { |
| ptr = pc->omsg_ptr; |
| } |
| // - For NegativeAnswers there is nothing to add |
| // - If DNSSECOK is set, we also automatically lookup the RRSIGs which |
| // will also be returned. If the client is explicitly looking up |
| // a DNSSEC record (e.g., DNSKEY, DS) we should return the response. |
| // DNSSECOK bit only influences whether we add the RRSIG or not. |
| mDNSBool addedCNAMERecord = mDNSfalse; |
| if (cr->resrec.RecordType != kDNSRecordTypePacketNegative) |
| { |
| const ResourceRecord *rr; |
| #if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PROXY_DNS64) |
| RData rdata; |
| ResourceRecord newRR; |
| const nw_nat64_prefix_t *const nat64_prefix = mrcs_dns_proxy_get_nat64_prefix(pc->proxy); |
| if (nat64_prefix && (pc->dns64state == kDNSProxyDNS64State_AAAASynthesis) && (cr->resrec.rrtype == kDNSType_A)) |
| { |
| struct in_addr addrV4; |
| struct in6_addr addrV6; |
| |
| newRR = cr->resrec; |
| newRR.rrtype = kDNSType_AAAA; |
| newRR.rdlength = 16; |
| rdata.MaxRDLength = newRR.rdlength; |
| newRR.rdata = &rdata; |
| |
| memcpy(&addrV4.s_addr, cr->resrec.rdata->u.ipv4.b, 4); |
| if (nw_nat64_synthesize_v6(nat64_prefix, &addrV4, &addrV6)) |
| { |
| memcpy(rdata.u.ipv6.b, addrV6.s6_addr, 16); |
| rr = &newRR; |
| } |
| else |
| { |
| continue; |
| } |
| } |
| else |
| #endif |
| { |
| rr = &cr->resrec; |
| } |
| LogInfo("AddResourceRecord: Answering question with %s", RRDisplayString(m, rr)); |
| ttl = cr->resrec.rroriginalttl - (now - cr->TimeRcvd) / mDNSPlatformOneSecond; |
| ptr = PutResourceRecordTTLWithLimit((DNSMessage*)pc->omsg, ptr, &pc->omsg->h.numAnswers, rr, ttl, limit); |
| if (!ptr) |
| { |
| *prevptr = pc->omsg_ptr; |
| return mDNSNULL; |
| } |
| len += (ptr - pc->omsg_ptr); |
| pc->omsg_ptr = ptr; |
| if (cr->resrec.rrtype == kDNSType_CNAME) |
| { |
| addedCNAMERecord = mDNStrue; |
| } |
| } |
| if (cr->soa) |
| { |
| LogInfo("AddResourceRecord: soa set for %s", CRDisplayString(m ,cr)); |
| soa = cr->soa; |
| } |
| // If we are using CNAME to answer a question and CNAME is not the type we |
| // are looking for, note down the CNAME record so that we can follow them |
| // later. Before we follow the CNAME, print the RRSIGs and any nsec (wildcard |
| // expanded) if any. |
| if ((pc->q.qtype != cr->resrec.rrtype) && cr->resrec.rrtype == kDNSType_CNAME) |
| { |
| LogInfo("AddResourceRecord: cname set for %s", CRDisplayString(m ,cr)); |
| } |
| // If a CNAME record was just added to the response for the current QNAME, break out of the for-loop. |
| // As described in <https://datatracker.ietf.org/doc/html/rfc2181#section-10.1>, there cannot be more |
| // than one CNAME record for a given domain name. This is a defensive measure because because it's |
| // currently possible for two CNAME records with the same name to end up in mDNSResponder's cache. |
| // To avoid "garbage in, garbage out", which can confuse clients, the DNS proxy shouldn't create |
| // responses with multiple CNAME records with the same name. See rdar://117823387 for more details. |
| if (addedCNAMERecord) |
| { |
| break; |
| } |
| } |
| } |
| // Along with the nsec records, we also cache the SOA record. For non-DNSSEC question, we need |
| // to send the SOA back. Normally we either cache the SOA record (non-DNSSEC question) pointed |
| // to by "cr->soa" or the NSEC/SOA records along with their RRSIGs (DNSSEC question) pointed to |
| // by "cr->nsec". Two cases: |
| // |
| // - if we issue a DNSSEC question followed by non-DNSSEC question for the same name, |
| // we only have the nsec records and we need to filter the SOA record alone for the |
| // non-DNSSEC questions. |
| // |
| // - if we issue a non-DNSSEC question followed by DNSSEC question for the same name, |
| // the "core" flushes the cache entry and re-issue the question with EDNS0/DOK bit and |
| // in this case we return all the DNSSEC records we have. |
| if (soa) |
| { |
| LogInfo("AddResourceRecord: SOA Answering question with %s", CRDisplayString(m, soa)); |
| ptr = PutResourceRecordTTLWithLimit((DNSMessage*)pc->omsg, ptr, &pc->omsg->h.numAuthorities, &soa->resrec, soa->resrec.rroriginalttl, limit); |
| if (!ptr) |
| { |
| *prevptr = pc->omsg_ptr; |
| return mDNSNULL; |
| } |
| len += (ptr - pc->omsg_ptr); |
| pc->omsg_ptr = ptr; |
| } |
| if (!ptr) |
| { |
| LogInfo("AddResourceRecord: Did not find any valid ResourceRecords"); |
| *error = mStatus_NoSuchRecord; |
| return mDNSNULL; |
| } |
| if (final_answer && pc->rcvBufSize) |
| { |
| ptr = AddEDNS0Option(pc->omsg, ptr, limit); |
| if (!ptr) |
| { |
| *prevptr = pc->omsg_ptr; |
| return mDNSNULL; |
| } |
| len += (ptr - pc->omsg_ptr); |
| pc->omsg_ptr = ptr; |
| } |
| LogInfo("AddResourceRecord: Added %d bytes to the packet", len); |
| return ptr; |
| } |
| |
| mDNSlocal void ProxyClientCallback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord) |
| { |
| DNSProxyClient *pc = question->QuestionContext; |
| DNSProxyClient **ppc = &DNSProxyClients; |
| mDNSu8 *ptr; |
| mDNSu8 *prevptr = mDNSNULL; |
| mStatus error; |
| mDNSBool final_answer = ((answer->RecordType == kDNSRecordTypePacketNegative) || (answer->rrtype == question->qtype)); |
| |
| if (!AddRecord) |
| return; |
| |
| LogInfo("ProxyClientCallback: %##s (%s)", &pc->qname.c, DNSTypeName(pc->qtype)); |
| |
| #if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PROXY_DNS64) |
| if (mrcs_dns_proxy_get_nat64_prefix(pc->proxy)) |
| { |
| if (pc->dns64state == kDNSProxyDNS64State_Initial) |
| { |
| // If we get a negative AAAA answer, then retry the query as an A record query. |
| // See <https://tools.ietf.org/html/rfc6147#section-5.1.6>. |
| if ((answer->RecordType == kDNSRecordTypePacketNegative) && (question->qtype == kDNSType_AAAA) && |
| (answer->rrtype == kDNSType_AAAA) && (answer->rrclass == kDNSClass_IN)) |
| { |
| mDNS_StopQuery(m, question); |
| pc->dns64state = kDNSProxyDNS64State_AAAASynthesis; |
| question->qtype = kDNSType_A; |
| mDNS_StartQuery(m, question); |
| return; |
| } |
| } |
| else if (pc->dns64state == kDNSProxyDNS64State_PTRSynthesisTrying) |
| { |
| // If we get a non-negative non-CNAME answer, then this is the answer we give to the client. |
| // Otherwise, just respond with NXDOMAIN. |
| // See <https://tools.ietf.org/html/rfc6147#section-5.3.1>. |
| if ((answer->RecordType != kDNSRecordTypePacketNegative) && (question->qtype == kDNSType_PTR) && |
| (answer->rrtype == kDNSType_PTR) && (answer->rrclass == kDNSClass_IN)) |
| { |
| pc->dns64state = kDNSProxyDNS64State_PTRSynthesisSuccess; |
| } |
| else |
| { |
| pc->dns64state = kDNSProxyDNS64State_PTRSynthesisNXDomain; |
| } |
| } |
| } |
| if (pc->dns64state == kDNSProxyDNS64State_PTRSynthesisNXDomain) |
| { |
| const mDNSOpaque16 flags = { { kDNSFlag0_QR_Response | kDNSFlag0_OP_StdQuery, kDNSFlag1_RC_NXDomain } }; |
| InitializeDNSMessage(&pc->omsg->h, pc->msgid, flags); |
| ptr = putQuestion((DNSMessage*)pc->omsg, pc->omsg->data, pc->omsg->data + pc->omsg_size, &pc->qname, pc->qtype, |
| pc->q.qclass); |
| if (!ptr) |
| { |
| LogInfo("ProxyClientCallback: putQuestion NULL for %##s (%s)", &pc->qname.c, DNSTypeName(pc->qtype)); |
| } |
| } |
| else |
| #endif |
| { |
| for(;;) |
| { |
| _MsgResourceState save; |
| DNSMsgStateSave(pc, &save); |
| ptr = AddResourceRecord(pc, &prevptr, &error, final_answer); |
| if (!ptr) |
| { |
| mDNSu16 max_size; |
| if (DNSProxyMsgCanGrow(pc, &max_size)) |
| { |
| LogInfo("ProxyClientCallback: Increase omsg buffer size to %d for %##s (%s)", max_size, &pc->qname.c, DNSTypeName(pc->qtype)); |
| if (!DNSProxyPrepareOmsg(max_size, pc)) |
| { |
| LogMsg("ProxyClientCallback: AbsoluteMaxDNSMessageData memory failure for %##s (%s)", &pc->qname.c, DNSTypeName(pc->qtype)); |
| } |
| else |
| { |
| DNSMsgStateRestore(pc, &save); |
| continue; // try again |
| } |
| } |
| } |
| break; |
| } |
| if (!ptr) |
| { |
| LogInfo("ProxyClientCallback: AddResourceRecord NULL for %##s (%s)", &pc->qname.c, DNSTypeName(pc->qtype)); |
| if (error == mStatus_NoError && prevptr) |
| { |
| // No space to add the record. Set the Truncate bit for UDP. |
| // |
| // TBD: For TCP, we need to send the rest of the data. But finding out what is left |
| // is harder. We should allocate enough buffer in the first place to send all |
| // of the data. |
| if (!pc->tcp) |
| { |
| pc->omsg->h.flags.b[0] |= kDNSFlag0_TC; |
| ptr = prevptr; |
| } |
| else |
| { |
| LogInfo("ProxyClientCallback: ERROR!! Not enough space to return in TCP for %##s (%s)", &pc->qname.c, DNSTypeName(pc->qtype)); |
| ptr = prevptr; |
| } |
| } |
| else |
| { |
| mDNSOpaque16 flags = { { kDNSFlag0_QR_Response | kDNSFlag0_OP_StdQuery, kDNSFlag1_RC_ServFail } }; |
| // We could not find the record for some reason. Return a response, so that the client |
| // is not waiting forever. |
| LogInfo("ProxyClientCallback: No response"); |
| if (!mDNSOpaque16IsZero(pc->q.responseFlags)) |
| flags = pc->q.responseFlags; |
| InitializeDNSMessage(&pc->omsg->h, pc->msgid, flags); |
| ptr = putQuestion((DNSMessage*)pc->omsg, pc->omsg->data, pc->omsg->data + pc->omsg_size, &pc->qname, pc->qtype, pc->q.qclass); |
| if (!ptr) |
| { |
| LogInfo("ProxyClientCallback: putQuestion NULL for %##s (%s)", &pc->qname.c, DNSTypeName(pc->qtype)); |
| goto done; |
| } |
| } |
| } |
| if (!final_answer) |
| { |
| // Wait till we get called for the real response |
| LogInfo("ProxyClientCallback: Received %s, not answering yet", RRDisplayString(m, answer)); |
| return; |
| } |
| } |
| debugf("ProxyClientCallback: InterfaceID is %p for response to client", pc->interfaceID); |
| |
| if (!pc->tcp) |
| { |
| mDNSSendDNSMessage(m, (DNSMessage*)pc->omsg, ptr, pc->interfaceID, mDNSNULL, (UDPSocket *)pc->socket, &pc->addr, pc->port, mDNSNULL, mDNSfalse); |
| } |
| else |
| { |
| mDNSSendDNSMessage(m, (DNSMessage*)pc->omsg, ptr, pc->interfaceID, (TCPSocket *)pc->socket, mDNSNULL, &pc->addr, pc->port, mDNSNULL, mDNSfalse); |
| } |
| |
| done: |
| mDNS_StopQuery(m, question); |
| |
| while (*ppc && *ppc != pc) |
| ppc=&(*ppc)->next; |
| if (!*ppc) |
| { |
| LogMsg("ProxyClientCallback: question %##s (%s) not found", question->qname.c, DNSTypeName(question->qtype)); |
| return; |
| } |
| *ppc = pc->next; |
| mDNSPlatformDisposeProxyContext(pc->context); |
| FreeDNSProxyClient(pc); |
| } |
| |
| mDNSlocal void SendError(void *socket, DNSMessage *const msg, const mDNSu8 *const end, const mDNSAddr *dstaddr, |
| const mDNSIPPort dstport, const mDNSInterfaceID InterfaceID, mDNSBool tcp, void *context, mDNSu8 rcode) |
| { |
| mDNS *const m = &mDNSStorage; |
| int pktlen = (int)(end - (mDNSu8 *)msg); |
| |
| // RFC 1035 requires that we copy the question back and RFC 2136 is okay with sending nothing |
| // in the body or send back whatever we get for updates. It is easy to return whatever we get |
| // in the question back to the responder. We return as much as we can fit in our standard |
| // output packet. |
| if (pktlen > AbsoluteMaxDNSMessageData) |
| pktlen = AbsoluteMaxDNSMessageData; |
| |
| mDNSPlatformMemCopy(&m->omsg.h, &msg->h, sizeof(DNSMessageHeader)); |
| m->omsg.h.flags.b[0] |= kDNSFlag0_QR_Response; |
| m->omsg.h.flags.b[1] = rcode; |
| mDNSPlatformMemCopy(m->omsg.data, (mDNSu8 *)&msg->data, (pktlen - sizeof(DNSMessageHeader))); |
| |
| if (!tcp) |
| { |
| mDNSSendDNSMessage(m, &m->omsg, (mDNSu8 *)&m->omsg + pktlen, InterfaceID, mDNSNULL, socket, dstaddr, dstport, mDNSNULL, mDNSfalse); |
| } |
| else |
| { |
| mDNSSendDNSMessage(m, &m->omsg, (mDNSu8 *)&m->omsg + pktlen, InterfaceID, (TCPSocket *)socket, mDNSNULL, dstaddr, dstport, mDNSNULL, mDNSfalse); |
| } |
| mDNSPlatformDisposeProxyContext(context); |
| } |
| |
| mDNSlocal DNSQuestion *IsDuplicateClient(const mDNSAddr *const addr, const mDNSIPPort port, const mDNSOpaque16 id, |
| const DNSQuestion *const question) |
| { |
| DNSProxyClient *pc; |
| |
| for (pc = DNSProxyClients; pc; pc = pc->next) |
| { |
| if (mDNSSameAddress(&pc->addr, addr) && |
| mDNSSameIPPort(pc->port, port) && |
| mDNSSameOpaque16(pc->msgid, id) && |
| pc->qtype == question->qtype && |
| pc->q.qclass == question->qclass && |
| SameDomainName(&pc->qname, &question->qname)) |
| { |
| LogInfo("IsDuplicateClient: Found a duplicate client in the list"); |
| return(&pc->q); |
| } |
| } |
| return(mDNSNULL); |
| } |
| |
| static mrcs_dns_proxy_manager_t gProxyManager = mDNSNULL; |
| |
| mDNSlocal mrcs_dns_proxy_t DNSProxyGetDNSProxyInstance(mDNSInterfaceID InterfaceID) |
| { |
| const mDNSu32 index = (mDNSu32)(uintptr_t)InterfaceID; |
| return (gProxyManager ? mrcs_dns_proxy_manager_get_proxy_by_input_interface(gProxyManager, index) : mDNSNULL); |
| } |
| |
| mDNSlocal void ProxyCallbackCommon(void *socket, DNSMessage *const msg, const mDNSu8 *const end, const mDNSAddr *const srcaddr, |
| const mDNSIPPort srcport, const mDNSAddr *dstaddr, const mDNSIPPort dstport, const mDNSInterfaceID InterfaceID, mDNSBool tcp, void *context) |
| { |
| mDNS *const m = &mDNSStorage; |
| mDNSu8 QR_OP; |
| const mDNSu8 *ptr; |
| DNSQuestion q, *qptr; |
| DNSProxyClient *pc; |
| const mDNSu8 *optRR = mDNSNULL; |
| mDNSu32 optLen = 0; |
| DNSProxyClient **ppc = &DNSProxyClients; |
| |
| (void) dstaddr; |
| (void) dstport; |
| |
| debugf("ProxyCallbackCommon: DNS Query coming from InterfaceID %p", InterfaceID); |
| // Ignore if the DNS Query is not from a Valid Input InterfaceID |
| const mrcs_dns_proxy_t proxy = DNSProxyGetDNSProxyInstance(InterfaceID); |
| if (!proxy) |
| { |
| LogMsg("ProxyCallbackCommon: Rejecting DNS Query coming from InterfaceID %p", InterfaceID); |
| return; |
| } |
| |
| if ((unsigned)(end - (mDNSu8 *)msg) < sizeof(DNSMessageHeader)) |
| { |
| debugf("ProxyCallbackCommon: DNS Message from %#a:%d to %#a:%d length %d too short", srcaddr, mDNSVal16(srcport), dstaddr, mDNSVal16(dstport), (int)(end - (mDNSu8 *)msg)); |
| return; |
| } |
| |
| // Read the integer parts which are in IETF byte-order (MSB first, LSB second) |
| ptr = (mDNSu8 *)&msg->h.numQuestions; |
| msg->h.numQuestions = (mDNSu16)((mDNSu16)ptr[0] << 8 | ptr[1]); |
| msg->h.numAnswers = (mDNSu16)((mDNSu16)ptr[2] << 8 | ptr[3]); |
| msg->h.numAuthorities = (mDNSu16)((mDNSu16)ptr[4] << 8 | ptr[5]); |
| msg->h.numAdditionals = (mDNSu16)((mDNSu16)ptr[6] << 8 | ptr[7]); |
| |
| QR_OP = (mDNSu8)(msg->h.flags.b[0] & kDNSFlag0_QROP_Mask); |
| if (QR_OP != kDNSFlag0_QR_Query) |
| { |
| LogInfo("ProxyCallbackCommon: Not a query(%d) for pkt from %#a:%d", QR_OP, srcaddr, mDNSVal16(srcport)); |
| SendError(socket, msg, end, srcaddr, srcport, InterfaceID, tcp, context, kDNSFlag1_RC_NotImpl); |
| return; |
| } |
| |
| if (msg->h.numQuestions != 1 || msg->h.numAnswers || msg->h.numAuthorities) |
| { |
| LogInfo("ProxyCallbackCommon: Malformed pkt from %#a:%d, Q:%d, An:%d, Au:%d", srcaddr, mDNSVal16(srcport), |
| msg->h.numQuestions, msg->h.numAnswers, msg->h.numAuthorities); |
| SendError(socket, msg, end, srcaddr, srcport, InterfaceID, tcp, context, kDNSFlag1_RC_FormErr); |
| return; |
| } |
| ptr = msg->data; |
| ptr = getQuestion(msg, ptr, end, InterfaceID, &q); |
| if (!ptr) |
| { |
| LogInfo("ProxyCallbackCommon: Question cannot be parsed for pkt from %#a:%d", srcaddr, mDNSVal16(srcport)); |
| SendError(socket, msg, end, srcaddr, srcport, InterfaceID, tcp, context, kDNSFlag1_RC_FormErr); |
| return; |
| } |
| else |
| { |
| LogInfo("ProxyCallbackCommon: Question %##s (%s)", q.qname.c, DNSTypeName(q.qtype)); |
| } |
| ptr = LocateOptRR(msg, end, 0); |
| if (ptr) |
| { |
| optRR = ptr; |
| ptr = skipResourceRecord(msg, ptr, end); |
| // Be liberal and ignore the EDNS0 option if we can't parse it properly |
| if (!ptr) |
| { |
| LogInfo("ProxyCallbackCommon: EDNS0 cannot be parsed for pkt from %#a:%d, ignoring", srcaddr, mDNSVal16(srcport)); |
| } |
| else |
| { |
| optLen = (mDNSu32)(ptr - optRR); |
| LogInfo("ProxyCallbackCommon: EDNS0 opt length %u present in Question %##s (%s)", optLen, q.qname.c, DNSTypeName(q.qtype)); |
| } |
| } |
| else |
| { |
| LogInfo("ProxyCallbackCommon: EDNS0 opt not present in Question %##s (%s), ptr %p", q.qname.c, DNSTypeName(q.qtype), ptr); |
| } |
| |
| qptr = IsDuplicateClient(srcaddr, srcport, msg->h.id, &q); |
| if (qptr) |
| { |
| LogInfo("ProxyCallbackCommon: Found a duplicate for pkt from %#a:%d, ignoring this", srcaddr, mDNSVal16(srcport)); |
| return; |
| } |
| pc = (DNSProxyClient *) mDNSPlatformMemAllocateClear(sizeof(*pc)); |
| if (!pc) |
| { |
| LogMsg("ProxyCallbackCommon: Memory failure for pkt from %#a:%d, ignoring this", srcaddr, mDNSVal16(srcport)); |
| return; |
| } |
| if (!DNSProxyPrepareOmsg(NormalUDPDNSMessageData, pc)) |
| { |
| LogMsg("ProxyCallbackCommon: NormalUDPDNSMessageData memory failure for pkt from %#a:%d, ignoring this", srcaddr, mDNSVal16(srcport)); |
| FreeDNSProxyClient(pc); |
| return; |
| } |
| pc->proxy = proxy; |
| mrcs_retain(pc->proxy); |
| pc->addr = *srcaddr; |
| pc->port = srcport; |
| pc->msgid = msg->h.id; |
| pc->interfaceID = InterfaceID; // input interface |
| pc->socket = socket; |
| pc->tcp = tcp; |
| pc->requestFlags = msg->h.flags; |
| pc->context = context; |
| AssignDomainName(&pc->qname, &q.qname); |
| if (optRR) |
| { |
| if (optLen < sizeof(OPTRecordFixedFields)) |
| { |
| LogInfo("ProxyCallbackCommon: Invalid EDNS0 option for pkt from %#a:%d, ignoring this", srcaddr, mDNSVal16(srcport)); |
| } |
| else |
| { |
| const OPTRecordFixedFields *const fields = (const OPTRecordFixedFields *)optRR; |
| pc->rcvBufSize = (mDNSu16)((fields->udpPayloadSize[0] << 8) | fields->udpPayloadSize[1]); |
| pc->optRR = (mDNSu8 *) mDNSPlatformMemAllocate(optLen); |
| if (!pc->optRR) |
| { |
| LogMsg("ProxyCallbackCommon: Memory failure for pkt from %#a:%d, ignoring this", srcaddr, mDNSVal16(srcport)); |
| FreeDNSProxyClient(pc); |
| return; |
| } |
| mDNSPlatformMemCopy(pc->optRR, optRR, optLen); |
| pc->optLen = optLen; |
| } |
| } |
| |
| const mDNSu32 outputIndex = mrcs_dns_proxy_get_output_interface(pc->proxy); |
| debugf("ProxyCallbackCommon: DNS Query forwarding to interface index %u", outputIndex); |
| mDNS_SetupQuestion(&pc->q, (mDNSInterfaceID)(unsigned long)outputIndex, &q.qname, q.qtype, ProxyClientCallback, pc); |
| pc->q.TimeoutQuestion = 1; |
| // Set ReturnIntermed so that we get the negative responses |
| pc->q.ReturnIntermed = mDNStrue; |
| pc->q.ProxyQuestion = mDNStrue; |
| pc->q.responseFlags = zeroID; |
| pc->q.euid = mrcs_dns_proxy_get_euid(pc->proxy); |
| #if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PROXY_DNS64) |
| pc->qtype = pc->q.qtype; |
| const nw_nat64_prefix_t * const nat64_prefix = mrcs_dns_proxy_get_nat64_prefix(pc->proxy); |
| if (nat64_prefix) |
| { |
| if (pc->qtype == kDNSType_PTR) |
| { |
| struct in6_addr v6Addr; |
| struct in_addr v4Addr; |
| if (GetReverseIPv6Addr(&pc->qname, v6Addr.s6_addr) && nw_nat64_extract_v4(nat64_prefix, &v6Addr, &v4Addr)) |
| { |
| const mDNSu8 *const a = (const mDNSu8 *)&v4Addr.s_addr; |
| char qnameStr[MAX_REVERSE_MAPPING_NAME_V4]; |
| mDNS_snprintf(qnameStr, (mDNSu32)sizeof(qnameStr), "%u.%u.%u.%u.in-addr.arpa.", a[3], a[2], a[1], a[0]); |
| MakeDomainNameFromDNSNameString(&pc->q.qname, qnameStr); |
| pc->q.qnamehash = DomainNameHashValue(&pc->q.qname); |
| pc->dns64state = kDNSProxyDNS64State_PTRSynthesisTrying; |
| } |
| } |
| else if ((pc->qtype == kDNSType_AAAA) && mrcs_dns_proxy_forces_aaaa_synthesis(pc->proxy)) |
| { |
| pc->dns64state = kDNSProxyDNS64State_AAAASynthesis; |
| pc->q.qtype = kDNSType_A; |
| } |
| } |
| #endif |
| |
| while (*ppc) |
| ppc = &((*ppc)->next); |
| *ppc = pc; |
| |
| mDNS_StartQuery(m, &pc->q); |
| } |
| |
| mDNSexport void ProxyUDPCallback(void *socket, DNSMessage *const msg, const mDNSu8 *const end, const mDNSAddr *const srcaddr, |
| const mDNSIPPort srcport, const mDNSAddr *dstaddr, const mDNSIPPort dstport, const mDNSInterfaceID InterfaceID, void *context) |
| { |
| LogInfo("ProxyUDPCallback: DNS Message from %#a:%d to %#a:%d length %d", srcaddr, mDNSVal16(srcport), dstaddr, mDNSVal16(dstport), (int)(end - (mDNSu8 *)msg)); |
| ProxyCallbackCommon(socket, msg, end, srcaddr, srcport, dstaddr, dstport, InterfaceID, mDNSfalse, context); |
| } |
| |
| mDNSexport void ProxyTCPCallback(void *socket, DNSMessage *const msg, const mDNSu8 *const end, const mDNSAddr *const srcaddr, |
| const mDNSIPPort srcport, const mDNSAddr *dstaddr, const mDNSIPPort dstport, const mDNSInterfaceID InterfaceID, void *context) |
| { |
| LogInfo("ProxyTCPCallback: DNS Message from %#a:%d to %#a:%d length %d", srcaddr, mDNSVal16(srcport), dstaddr, mDNSVal16(dstport), (int)(end - (mDNSu8 *)msg)); |
| |
| // If the connection was closed from the other side or incoming packet does not match stored input interface list, locate the client |
| // state and free it. |
| if (((end - (mDNSu8 *)msg) == 0) || (!DNSProxyGetDNSProxyInstance(InterfaceID))) |
| { |
| DNSProxyClient **ppc = &DNSProxyClients; |
| DNSProxyClient **prevpc; |
| |
| prevpc = ppc; |
| while (*ppc && (*ppc)->socket != socket) |
| { |
| prevpc = ppc; |
| ppc=&(*ppc)->next; |
| } |
| if (!*ppc) |
| { |
| mDNSPlatformDisposeProxyContext(socket); |
| LogMsg("ProxyTCPCallback: socket cannot be found"); |
| return; |
| } |
| *prevpc = (*ppc)->next; |
| LogInfo("ProxyTCPCallback: free"); |
| mDNSPlatformDisposeProxyContext(socket); |
| FreeDNSProxyClient(*ppc); |
| return; |
| } |
| ProxyCallbackCommon(socket, msg, end, srcaddr, srcport, dstaddr, dstport, InterfaceID, mDNStrue, context); |
| } |
| |
| mDNSlocal OSStatus DNSProxyStart(const mrcs_dns_proxy_t proxy) |
| { |
| OSStatus err; |
| if (!gProxyManager) |
| { |
| gProxyManager = mrcs_dns_proxy_manager_create(&err); |
| require_noerr_quiet(err, exit); |
| } |
| const size_t previousCount = mrcs_dns_proxy_manager_get_count(gProxyManager); |
| err = mrcs_dns_proxy_manager_add_proxy(gProxyManager, proxy); |
| if (previousCount == 0) |
| { |
| if (mrcs_dns_proxy_manager_get_count(gProxyManager) > 0) |
| { |
| mDNSPlatformInitDNSProxySkts(ProxyUDPCallback, ProxyTCPCallback); |
| } |
| } |
| |
| exit: |
| return err; |
| } |
| |
| mDNSlocal OSStatus DNSProxyStop(const mrcs_dns_proxy_t proxy) |
| { |
| OSStatus err; |
| require_action_quiet(gProxyManager, exit, err = mStatus_BadStateErr); |
| |
| const size_t previousCount = mrcs_dns_proxy_manager_get_count(gProxyManager); |
| err = mrcs_dns_proxy_manager_remove_proxy(gProxyManager, proxy); |
| require_noerr_quiet(err, exit); |
| |
| if (previousCount > 0) |
| { |
| if (mrcs_dns_proxy_manager_get_count(gProxyManager) == 0) |
| { |
| mDNSPlatformCloseDNSProxySkts(&mDNSStorage); |
| } |
| } |
| |
| exit: |
| return err; |
| } |
| |
| mDNSlocal OSStatus DNSProxyStartHandler(const mrcs_dns_proxy_t proxy) |
| { |
| KQueueLock(); |
| const OSStatus err = DNSProxyStart(proxy); |
| KQueueUnlock("DNSProxyStartHandler"); |
| return err; |
| } |
| |
| mDNSlocal OSStatus DNSProxyStopHandler(const mrcs_dns_proxy_t proxy) |
| { |
| KQueueLock(); |
| const OSStatus err = DNSProxyStop(proxy); |
| KQueueUnlock("DNSProxyStopHandler"); |
| return err; |
| } |
| |
| mDNSlocal char *DNSProxyGetState(void) |
| { |
| char *state; |
| if (gProxyManager && (mrcs_dns_proxy_manager_get_count(gProxyManager) > 0)) |
| { |
| state = mrcs_copy_description(gProxyManager); |
| } |
| else |
| { |
| state = mdns_strdup("‹No DNS Proxies›"); |
| } |
| return state; |
| } |
| |
| mDNSlocal char *DNSProxyGetStateHandler(void) |
| { |
| KQueueLock(); |
| char *state = DNSProxyGetState(); |
| KQueueUnlock("DNSProxyGetStateHandler"); |
| return state; |
| } |
| |
| const struct mrcs_server_dns_proxy_handlers_s kMRCSServerDNSProxyHandlers = |
| { |
| .start = DNSProxyStartHandler, |
| .stop = DNSProxyStopHandler, |
| .get_state = DNSProxyGetStateHandler |
| }; |
| |
| #else // UNICAST_DISABLED |
| |
| mDNSexport void ProxyUDPCallback(void *socket, DNSMessage *const msg, const mDNSu8 *const end, const mDNSAddr *const srcaddr, const mDNSIPPort srcport, const mDNSAddr *dstaddr, const mDNSIPPort dstport, const mDNSInterfaceID InterfaceID, void *context) |
| { |
| (void) socket; |
| (void) msg; |
| (void) end; |
| (void) srcaddr; |
| (void) srcport; |
| (void) dstaddr; |
| (void) dstport; |
| (void) InterfaceID; |
| (void) context; |
| } |
| |
| mDNSexport void ProxyTCPCallback(void *socket, DNSMessage *const msg, const mDNSu8 *const end, const mDNSAddr *const srcaddr, const mDNSIPPort srcport, const mDNSAddr *dstaddr, const mDNSIPPort dstport, const mDNSInterfaceID InterfaceID, void *context) |
| { |
| (void) socket; |
| (void) msg; |
| (void) end; |
| (void) srcaddr; |
| (void) srcport; |
| (void) dstaddr; |
| (void) dstport; |
| (void) InterfaceID; |
| (void) context; |
| } |
| |
| mDNSexport void DNSProxyInit(mDNSu32 IpIfArr[MaxIp], mDNSu32 OpIf) |
| { |
| (void) IpIfArr; |
| (void) OpIf; |
| } |
| extern void DNSProxyTerminate(void) |
| { |
| } |
| |
| |
| #endif // UNICAST_DISABLED |