/*
 * Copyright (c) 2017 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.
 */

#include <TargetConditionals.h>

// DNS64 code is only for iOS, which is currently the only Apple OS that supports DNS proxy network extensions.

#if TARGET_OS_IOS
#include "DNS64.h"

#include <AssertMacros.h>

#if __has_include(<nw/private.h>)
    #include <nw/private.h>
#else
    #include <network/nat64.h>
#endif

#include <stdlib.h>
#include <string.h>

#include "dns_sd.h"
#include "dns_sd_internal.h"
#include "uDNS.h"

//===========================================================================================================================
//  Constants
//===========================================================================================================================

#define kDNS64IPv4OnlyFQDNString    "\x8" "ipv4only" "\x4" "arpa"
#define kDNS64IPv4OnlyFQDN          ((const domainname *) kDNS64IPv4OnlyFQDNString)
#define kDNS64IPv4OnlyFQDNLength    15  // 9 bytes for first label, 5 bytes for second label, and 1 byte for the root label.

#define sizeof_field(TYPE, FIELD)   sizeof(((TYPE *)0)->FIELD)    // From CoreUtils.h

check_compile_time(sizeof(kDNS64IPv4OnlyFQDNString) == kDNS64IPv4OnlyFQDNLength);
check_compile_time(sizeof_field(DNSQuestion, qname) >= kDNS64IPv4OnlyFQDNLength);
check_compile_time(sizeof_field(DNS64, qnameStash)  == kDNS64IPv4OnlyFQDNLength);

//===========================================================================================================================
//  Local Prototypes
//===========================================================================================================================

mDNSlocal mStatus   DNS64GetIPv6Addrs(mDNS *m, mDNSu16 inResGroupID, struct in6_addr **outAddrs, uint32_t *outAddrCount);
mDNSlocal mStatus   DNS64GetPrefixes(mDNS *m, mDNSu16 inResGroupID, nw_nat64_prefix_t **outPrefixes, uint32_t *outPrefixCount);
mDNSlocal mDNSBool  DNS64GetReverseIPv6Addr(const domainname *inQName, struct in6_addr *outAddr);
mDNSlocal mDNSu32   DNS64IPv4OnlyFQDNHash(void);
mDNSlocal void      DNS64RestartQuestion(mDNS *m, DNSQuestion *q, DNS64State newState);
mDNSlocal mDNSBool  DNS64TestIPv6Synthesis(mDNS *m, mDNSu16 inResGroupID, const mDNSv4Addr *inV4Addr);

//===========================================================================================================================
//  DNS64StateMachine
//===========================================================================================================================

mDNSexport mDNSBool DNS64StateMachine(mDNS *m, DNSQuestion *inQ, const ResourceRecord *inRR, QC_result inResult)
{
    // If this is an mDNS question, then exit early. DNS64 is only for unicast DNS questions.

    if (mDNSOpaque16IsZero(inQ->TargetQID)) return (mDNSfalse);

    switch (inQ->dns64.state)
    {
    // If this question is going to be answered with a negative AAAA record and the question is not for "ipv4only.arpa." and
    // the question's DNS server's interface supports NAT64, then restart the question as an "ipv4only.arpa." AAAA question.
    // Otherwise, do nothing.

    case kDNS64State_Initial:
        if ((inRR->RecordType == kDNSRecordTypePacketNegative) && (inResult == QC_add))
        {
            if ((inQ->qtype      == kDNSType_AAAA) &&
                (inRR->rrtype    == kDNSType_AAAA) &&
                (inRR->rrclass   == kDNSClass_IN) &&
                ((inQ->qnamehash != DNS64IPv4OnlyFQDNHash()) || !SameDomainName(&inQ->qname, kDNS64IPv4OnlyFQDN)) &&
                inQ->qDNSServer &&
                nw_nat64_does_interface_index_support_nat64((uint32_t)(uintptr_t)inQ->qDNSServer->interface))
            {
                DNS64RestartQuestion(m, inQ, kDNS64State_PrefixDiscovery);
                return (mDNStrue);
            }
            else if ((inQ->qtype == kDNSType_PTR) &&
                (inRR->rrtype    == kDNSType_PTR) &&
                (inRR->rrclass   == kDNSClass_IN) &&
                inQ->qDNSServer &&
                nw_nat64_does_interface_index_support_nat64((uint32_t)(uintptr_t)inQ->qDNSServer->interface) &&
                DNS64GetReverseIPv6Addr(&inQ->qname, NULL))
            {
                DNS64RestartQuestion(m, inQ, kDNS64State_PrefixDiscoveryPTR);
                return (mDNStrue);
            }
        }
        break;

    // If the "ipv4only.arpa." question is going to be answered with a positive AAAA record, then restart it as a question
    // for an A record with the original AAAA qname.
    // Otherwise, restart the question for the original AAAA record.

    case kDNS64State_PrefixDiscovery:
        if ((inRR->RecordType != kDNSRecordTypePacketNegative) &&
            (inResult         == QC_add) &&
            (inRR->rrtype     == kDNSType_AAAA) &&
            (inRR->rrclass    == kDNSClass_IN))
        {
            DNS64RestartQuestion(m, inQ, kDNS64State_QueryA);
            return (mDNStrue);
        }
        else
        {
            DNS64RestartQuestion(m, inQ, kDNS64State_QueryAAAA);
            return (mDNStrue);
        }
        break;

    // The "ipv4only.arpa." question is going to be answered. Restart the question now. DNS64HandleNewQuestion() will decide
    // whether or not to change it to a reverse IPv4 question.

    case kDNS64State_PrefixDiscoveryPTR:
        DNS64RestartQuestion(m, inQ, kDNS64State_QueryPTR);
        return (mDNStrue);
        break;

    // If this question is going to be answered with a CNAME, then do nothing.
    // If this question is going to be answered with a positive A record that's synthesizable, then set the state to
    // QueryARecord2.
    // Otherwise, restart the question for the original AAAA record.

    case kDNS64State_QueryA:
        if (inRR->rrtype != kDNSType_CNAME)
        {
            if ((inRR->RecordType != kDNSRecordTypePacketNegative) &&
                (inResult         == QC_add) &&
                (inRR->rrtype     == kDNSType_A) &&
                (inRR->rrclass    == kDNSClass_IN) &&
                inQ->qDNSServer &&
                DNS64TestIPv6Synthesis(m, inQ->qDNSServer->resGroupID, &inRR->rdata->u.ipv4))
            {
                inQ->dns64.state = kDNS64State_QueryA2;
            }
            else
            {
                DNS64RestartQuestion(m, inQ, kDNS64State_QueryAAAA);
                return (mDNStrue);
            }
        }
        break;

    // For all other states, do nothing.

    case kDNS64State_QueryA2:
    case kDNS64State_QueryAAAA:
    case kDNS64State_QueryPTR:
    case kDNS64State_ReverseIPv4:
    case kDNS64State_ReverseIPv6:
        break;

    default:
        LogMsg("DNS64StateMachine: unrecognized DNS64 state %d", inQ->dns64.state);
        break;
    }

    return (mDNSfalse);
}

//===========================================================================================================================
//  DNS64AnswerQuestion
//===========================================================================================================================

mDNSexport mStatus DNS64AnswerQuestion(mDNS *m, DNSQuestion *inQ, const ResourceRecord *inRR, QC_result inResult)
{
    mStatus                 err;
    ResourceRecord          newRR;
    RData                   rdata;
    nw_nat64_prefix_t *     prefixes = NULL;
    uint32_t                prefixCount;
    uint32_t                i;
    struct in_addr          v4Addr;
    struct in6_addr         synthV6;

    require_action_quiet(inQ->qDNSServer, exit, err = mStatus_BadParamErr);

    err = DNS64GetPrefixes(m, inQ->qDNSServer->resGroupID, &prefixes, &prefixCount);
    require_noerr_quiet(err, exit);

    newRR               = *inRR;
    newRR.rrtype        = kDNSType_AAAA;
    newRR.rdlength      = 16;
    rdata.MaxRDLength   = newRR.rdlength;
    newRR.rdata         = &rdata;

    memcpy(&v4Addr.s_addr, inRR->rdata->u.ipv4.b, 4);
    for (i = 0; i < prefixCount; i++)
    {
        if (nw_nat64_synthesize_v6(&prefixes[i], &v4Addr, &synthV6))
        {
            memcpy(rdata.u.ipv6.b, synthV6.s6_addr, 16);
            inQ->QuestionCallback(m, inQ, &newRR, inResult);
        }
    }
    err = mStatus_NoError;

exit:
    if (prefixes) free(prefixes);
    return (err);
}

//===========================================================================================================================
//  DNS64HandleNewQuestion
//===========================================================================================================================

mDNSexport void DNS64HandleNewQuestion(mDNS *m, DNSQuestion *inQ)
{
    if (inQ->dns64.state == kDNS64State_QueryPTR)
    {
        struct in6_addr     v6Addr;

        inQ->dns64.state = kDNS64State_ReverseIPv6;
        if (inQ->qDNSServer && DNS64GetReverseIPv6Addr(&inQ->qname, &v6Addr))
        {
            mStatus                 err;
            nw_nat64_prefix_t *     prefixes;
            uint32_t                prefixCount;
            uint32_t                i;
            struct in_addr          v4Addr;
            char                    qnameStr[MAX_REVERSE_MAPPING_NAME_V4];

            err = DNS64GetPrefixes(m, inQ->qDNSServer->resGroupID, &prefixes, &prefixCount);
            require_noerr_quiet(err, exit);

            for (i = 0; i < prefixCount; i++)
            {
                if (nw_nat64_extract_v4(&prefixes[i], &v6Addr, &v4Addr))
                {
                    const mDNSu8 * const        a = (const mDNSu8 *)&v4Addr.s_addr;

                    snprintf(qnameStr, sizeof(qnameStr), "%u.%u.%u.%u.in-addr.arpa.", a[3], a[2], a[1], a[0]);
                    MakeDomainNameFromDNSNameString(&inQ->qname, qnameStr);
                    inQ->qnamehash   = DomainNameHashValue(&inQ->qname);
                    inQ->dns64.state = kDNS64State_ReverseIPv4;
                    break;
                }
            }
            free(prefixes);
        }
    }

exit:
    return;
}

//===========================================================================================================================
//  DNS64ResetState
//===========================================================================================================================

// Called from mDNS_StopQuery_internal().

mDNSexport void DNS64ResetState(DNSQuestion *inQ)
{
    switch (inQ->dns64.state)
    {
    case kDNS64State_PrefixDiscoveryPTR:
        inQ->qtype = kDNSType_PTR;  // Restore qtype to PTR and fall through.

    case kDNS64State_PrefixDiscovery:
        memcpy(&inQ->qname, inQ->dns64.qnameStash, sizeof(inQ->dns64.qnameStash));  // Restore the previous qname.
        inQ->qnamehash = DomainNameHashValue(&inQ->qname);
        break;

    case kDNS64State_QueryA:
    case kDNS64State_QueryA2:
        inQ->qtype = kDNSType_AAAA; // Restore qtype to AAAA.
        break;

    // Do nothing for the other states.

    case kDNS64State_Initial:
    case kDNS64State_QueryAAAA:
    case kDNS64State_QueryPTR:
    case kDNS64State_ReverseIPv4:
    case kDNS64State_ReverseIPv6:
        break;

    default:
        LogMsg("DNS64ResetState: unrecognized DNS64 state %d", inQ->dns64.state);
        break;
    }
    inQ->dns64.state = kDNS64State_Initial;
}

//===========================================================================================================================
//  DNS64RestartQuestions
//===========================================================================================================================

mDNSexport void DNS64RestartQuestions(mDNS *m)
{
    DNSQuestion *       q;
    DNSQuestion *       restartList = NULL;
    DNSServer *         newServer;

    m->RestartQuestion = m->Questions;
    while (m->RestartQuestion)
    {
        q = m->RestartQuestion;
        m->RestartQuestion = q->next;
        if (q->dns64.state != kDNS64State_Initial)
        {
            SetValidDNSServers(m, q);
            q->triedAllServersOnce = 0;
            newServer = GetServerForQuestion(m, q);
            if (q->qDNSServer != newServer)
            {
                if (!CacheRecordRmvEventsForQuestion(m, q))
                {
                    LogInfo("DNS64RestartQuestions: Question deleted while delivering RMV events from cache");
                }
                else
                {
                    LogInfo("DNS64RestartQuestions: Stop question %p %##s (%s)", q, q->qname.c, DNSTypeName(q->qtype));
                    mDNS_StopQuery_internal(m, q);
                    q->next = restartList;
                    restartList = q;
                }
            }
        }
    }
    while ((q = restartList) != NULL)
    {
        restartList = restartList->next;
        q->next = NULL;
        LogInfo("DNS64RestartQuestions: Start question %p %##s (%s)", q, q->qname.c, DNSTypeName(q->qtype));
        mDNS_StartQuery_internal(m, q);
    }
}

//===========================================================================================================================
//  DNS64GetIPv6Addrs
//===========================================================================================================================

#define IsPositiveAAAAFromResGroup(RR, RES_GROUP_ID)        \
    ((RR)->rDNSServer &&                                    \
    ((RR)->rDNSServer->resGroupID == RES_GROUP_ID) &&       \
    ((RR)->rrtype == kDNSType_AAAA) &&                      \
    ((RR)->RecordType != kDNSRecordTypePacketNegative) &&   \
    !(RR)->InterfaceID)

mDNSlocal mStatus DNS64GetIPv6Addrs(mDNS *m, const mDNSu16 inResGroupID, struct in6_addr **outAddrs, uint32_t *outAddrCount)
{
    mStatus                 err;
    const CacheGroup *      cg;
    const CacheRecord *     cr;
    struct in6_addr *       addrs = NULL;
    uint32_t                addrCount;
    uint32_t                recordCount;

    cg = CacheGroupForName(m, DNS64IPv4OnlyFQDNHash(), kDNS64IPv4OnlyFQDN);
    require_action_quiet(cg, exit, err = mStatus_NoSuchRecord);

    recordCount = 0;
    for (cr = cg->members; cr; cr = cr->next)
    {
        if (IsPositiveAAAAFromResGroup(&cr->resrec, inResGroupID))
        {
            recordCount++;
        }
    }
    require_action_quiet(recordCount > 0, exit, err = mStatus_NoSuchRecord);

    addrs = (struct in6_addr *)calloc(recordCount, sizeof(*addrs));
    require_action_quiet(addrs, exit, err = mStatus_NoMemoryErr);

    addrCount = 0;
    for (cr = cg->members; cr && (addrCount < recordCount); cr = cr->next)
    {
        if (IsPositiveAAAAFromResGroup(&cr->resrec, inResGroupID))
        {
            memcpy(addrs[addrCount].s6_addr, cr->resrec.rdata->u.ipv6.b, 16);
            addrCount++;
        }
    }

    *outAddrs = addrs;
    addrs = NULL;
    *outAddrCount = addrCount;
    err = mStatus_NoError;

exit:
    if (addrs) free(addrs);
    return (err);
}

//===========================================================================================================================
//  DNS64GetPrefixes
//===========================================================================================================================

mDNSlocal mStatus DNS64GetPrefixes(mDNS *m, mDNSu16 inResGroupID, nw_nat64_prefix_t **outPrefixes, uint32_t *outPrefixCount)
{
    mStatus                 err;
    struct in6_addr *       v6Addrs;
    uint32_t                v6AddrCount;
    nw_nat64_prefix_t *     prefixes;
    int32_t                 prefixCount;

    err = DNS64GetIPv6Addrs(m, inResGroupID, &v6Addrs, &v6AddrCount);
    require_noerr_quiet(err, exit);

    prefixCount = nw_nat64_copy_prefixes_from_ipv4only_records(v6Addrs, v6AddrCount, &prefixes);
    free(v6Addrs);
    require_action_quiet(prefixCount > 0, exit, err = mStatus_UnknownErr);

    *outPrefixes    = prefixes;
    *outPrefixCount = prefixCount;

exit:
    return (err);
}

//===========================================================================================================================
//  DNS64GetReverseIPv6Addr
//===========================================================================================================================

#define kReverseIPv6Domain  ((const domainname *) "\x3" "ip6" "\x4" "arpa")

mDNSlocal mDNSBool DNS64GetReverseIPv6Addr(const domainname *inQName, struct in6_addr *outAddr)
{
    const mDNSu8 *      ptr;
    int                 i;
    unsigned int        c;
    unsigned int        nl;
    unsigned int        nu;

    // If the name is of the form "x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.ip6.arpa.", where each x
    // is a hex digit, then the sequence of 32 hex digit labels represents the nibbles of an IPv6 address in reverse order.
    // See <https://tools.ietf.org/html/rfc3596#section-2.5>.

    ptr = (const mDNSu8 *)inQName;
    for (i = 0; i < 16; i++)
    {
        if (*ptr++ != 1) return (mDNSfalse);                    // If this label's length is not 1, then fail.
        c = *ptr++;                                             // Get label byte.
        if (     (c >= '0') && (c <= '9')) nl =  c - '0';       // If it's a hex digit, get its numeric value.
        else if ((c >= 'a') && (c <= 'f')) nl = (c - 'a') + 10;
        else if ((c >= 'A') && (c <= 'F')) nl = (c - 'A') + 10;
        else                               return (mDNSfalse);  // Otherwise, fail.

        if (*ptr++ != 1) return (mDNSfalse);                    // If this label's length is not 1, then fail.
        c = *ptr++;                                             // Get label byte.
        if (     (c >= '0') && (c <= '9')) nu =  c - '0';       // If it's a hex digit, get its numeric value.
        else if ((c >= 'a') && (c <= 'f')) nu = (c - 'a') + 10;
        else if ((c >= 'A') && (c <= 'F')) nu = (c - 'A') + 10;
        else                               return (mDNSfalse);  // Otherwise, fail.

        if (outAddr) outAddr->s6_addr[15 - i] = (mDNSu8)((nu << 4) | nl);
    }

    // The rest of the name needs to be "ip6.arpa.". If it isn't, fail.

    if (!SameDomainName((const domainname *)ptr, kReverseIPv6Domain)) return (mDNSfalse);

    return (mDNStrue);
}

//===========================================================================================================================
//  DNS64IPv4OnlyFQDNHash
//===========================================================================================================================

mDNSlocal mDNSu32 DNS64IPv4OnlyFQDNHash(void)
{
    static dispatch_once_t      sHashOnce;
    static mDNSu32              sHash;

    dispatch_once(&sHashOnce, ^{ sHash = DomainNameHashValue(kDNS64IPv4OnlyFQDN); });

    return (sHash);
}

//===========================================================================================================================
//  DNS64RestartQuestion
//===========================================================================================================================

mDNSlocal void DNS64RestartQuestion(mDNS *const m, DNSQuestion *inQ, DNS64State inNewState)
{
    mDNS_StopQuery_internal(m, inQ);

    inQ->dns64.state = inNewState;
    switch (inQ->dns64.state)
    {
    case kDNS64State_Initial:
        break;

    case kDNS64State_PrefixDiscovery:
    case kDNS64State_PrefixDiscoveryPTR:
        // Save the first 15 bytes from the original qname that are displaced by setting qname to "ipv4only.arpa.".

        memcpy(inQ->dns64.qnameStash, &inQ->qname, sizeof(inQ->dns64.qnameStash));
        AssignDomainName(&inQ->qname, kDNS64IPv4OnlyFQDN);
        inQ->qnamehash = DNS64IPv4OnlyFQDNHash();
        inQ->qtype = kDNSType_AAAA;
        break;

    case kDNS64State_QueryA:
    case kDNS64State_QueryA2:
        inQ->qtype = kDNSType_A;
        break;

    case kDNS64State_QueryPTR:
    case kDNS64State_ReverseIPv4:
    case kDNS64State_ReverseIPv6:
        inQ->qtype = kDNSType_PTR;
        break;

    case kDNS64State_QueryAAAA:
        inQ->qtype = kDNSType_AAAA;
        break;

    default:
        LogMsg("DNS64RestartQuestion: unrecognized DNS64 state %d", inQ->dns64.state);
        break;
    }

    mDNS_StartQuery_internal(m, inQ);
}

//===========================================================================================================================
//  DNS64TestIPv6Synthesis
//===========================================================================================================================

mDNSlocal mDNSBool DNS64TestIPv6Synthesis(mDNS *m, mDNSu16 inResGroupID, const mDNSv4Addr *inV4Addr)
{
    mStatus                 err;
    nw_nat64_prefix_t *     prefixes    = NULL;
    uint32_t                prefixCount;
    uint32_t                i;
    struct in_addr          v4Addr;
    struct in6_addr         synthV6;
    mDNSBool                result      = mDNSfalse;

    err = DNS64GetPrefixes(m, inResGroupID, &prefixes, &prefixCount);
    require_noerr_quiet(err, exit);

    memcpy(&v4Addr.s_addr, inV4Addr->b, 4);
    for (i = 0; i < prefixCount; i++)
    {
        if (nw_nat64_synthesize_v6(&prefixes[i], &v4Addr, &synthV6))
        {
            result = mDNStrue;
            break;
        }
    }

exit:
    if (prefixes) free(prefixes);
    return (result);
}
#endif  // TARGET_OS_IOS
