/*
 * Copyright (c) 2015-2019, 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.
 */

#include "SymptomReporter.h"

#if MDNSRESPONDER_SUPPORTS(APPLE, SYMPTOMS) && !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
#include <arpa/inet.h>
#include <dlfcn.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/socket.h>
#include <AvailabilityMacros.h>
#include <SymptomReporter/SymptomReporter.h>
#include "mdns_strict.h"

#define SYMPTOM_REPORTER_mDNSResponder_NUMERIC_ID  101
#define SYMPTOM_REPORTER_mDNSResponder_TEXT_ID     "com.apple.mDNSResponder"

#define SYMPTOM_DNS_NO_REPLIES          0x00065001
#define SYMPTOM_DNS_RESUMED_RESPONDING  0x00065002

mDNSu32 NumUnreachableDNSServers;

static symptom_framework_t symptomReporter = mDNSNULL;
static symptom_framework_t (*symptom_framework_init_f)(symptom_ident_t id, const char *originator_string) = mDNSNULL;
static symptom_t (*symptom_new_f)(symptom_framework_t framework, symptom_ident_t id) = mDNSNULL;
static int (*symptom_set_additional_qualifier_f)(symptom_t symptom, uint32_t qualifier_type, size_t qualifier_len, void *qualifier_data) = mDNSNULL;
static int (*symptom_send_f)(symptom_t symptom) = mDNSNULL;

mDNSlocal mStatus SymptomReporterInitCheck(void)
{
    mStatus err;
    static mDNSBool triedInit = mDNSfalse;
    static void *symptomReporterLib = mDNSNULL;
    static const char path[] = "/System/Library/PrivateFrameworks/SymptomReporter.framework/SymptomReporter";

    if (!triedInit)
    {
        triedInit = mDNStrue;

        symptomReporterLib = dlopen(path, RTLD_LAZY | RTLD_LOCAL);
        if (!symptomReporterLib)
            goto exit;

        symptom_framework_init_f = dlsym(symptomReporterLib, "symptom_framework_init");
        if (!symptom_framework_init_f)
            goto exit;

        symptom_new_f = dlsym(symptomReporterLib, "symptom_new");
        if (!symptom_new_f)
            goto exit;

        symptom_set_additional_qualifier_f = dlsym(symptomReporterLib, "symptom_set_additional_qualifier");
        if (!symptom_set_additional_qualifier_f)
            goto exit;

        symptom_send_f = dlsym(symptomReporterLib, "symptom_send");
        if (!symptom_send_f)
            goto exit;

        symptomReporter = symptom_framework_init_f(SYMPTOM_REPORTER_mDNSResponder_NUMERIC_ID, SYMPTOM_REPORTER_mDNSResponder_TEXT_ID);
    }

exit:
    err = symptomReporter ? mStatus_NoError : mStatus_NotInitializedErr;
    return err;
}

mDNSlocal mStatus SymptomReporterReportDNSReachability(const mDNSAddr *addr, mDNSBool isReachable)
{
    mStatus err;
    symptom_t symptom;
    struct sockaddr_storage sockAddr;
    size_t sockAddrSize;

    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
        "SymptomReporterReportDNSReachability: DNS server " PRI_IP_ADDR " is " PUB_S "reachable", addr, isReachable ? "" : "un");

    if (addr->type == mDNSAddrType_IPv4)
    {
        struct sockaddr_in *sin = (struct sockaddr_in *)&sockAddr;
        sockAddrSize = sizeof(*sin);
        mDNSPlatformMemZero(sin, sockAddrSize);
        sin->sin_len = sockAddrSize;
        sin->sin_family = AF_INET;
        sin->sin_addr.s_addr = addr->ip.v4.NotAnInteger;
    }
    else if (addr->type == mDNSAddrType_IPv6)
    {
        struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&sockAddr;
        sockAddrSize = sizeof(*sin6);
        mDNSPlatformMemZero(sin6, sockAddrSize);
        sin6->sin6_len = sockAddrSize;
        sin6->sin6_family = AF_INET6;
        sin6->sin6_addr = *(struct in6_addr *)&addr->ip.v6;
    }
    else
    {
        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
            "SymptomReporterReportDNSReachability: addr is not an IPv4 or IPv6 address!");
        err = mStatus_BadParamErr;
        goto exit;
    }

    symptom = symptom_new_f(symptomReporter, isReachable ? SYMPTOM_DNS_RESUMED_RESPONDING : SYMPTOM_DNS_NO_REPLIES);
    symptom_set_additional_qualifier_f(symptom, 1, sockAddrSize, (void *)&sockAddr);
    symptom_send_f(symptom);
    err = mStatus_NoError;

exit:
    return err;
}

mDNSexport mStatus SymptomReporterDNSServerReachable(mDNS *const m, const mDNSAddr *addr)
{
    mStatus err;
    DNSServer *s;
    mDNSBool found = mDNSfalse;

    err = SymptomReporterInitCheck();
    if (err != mStatus_NoError)
        goto exit;

    for (s = m->DNSServers; s; s = s->next)
    {
        if (s->flags & DNSServerFlag_Delete)
            continue;
        if ((s->flags & DNSServerFlag_Unreachable) && mDNSSameAddress(addr, &s->addr))
        {
            s->flags &= ~DNSServerFlag_Unreachable;
            NumUnreachableDNSServers--;
            found = mDNStrue;
        }
    }

    if (!found)
    {
        err = mStatus_NoSuchNameErr;
        goto exit;
    }

    err = SymptomReporterReportDNSReachability(addr, mDNStrue);

exit:
    return err;
}

mDNSexport mStatus SymptomReporterDNSServerUnreachable(DNSServer *s)
{
    mStatus err;

    err = SymptomReporterInitCheck();
    if (err != mStatus_NoError)
        goto exit;

    if ((s->flags & DNSServerFlag_Delete) || (s->flags & DNSServerFlag_Unreachable))
        goto exit;

    s->flags |= DNSServerFlag_Unreachable;
    NumUnreachableDNSServers++;

    err = SymptomReporterReportDNSReachability(&s->addr, mDNSfalse);

exit:
    return err;
}
#endif // MDNSRESPONDER_SUPPORTS(APPLE, SYMPTOMS)
