// Copyright (C) 2011-2024 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <config.h>

#if defined(OS_BSD)

#include <dhcp/iface_mgr.h>
#include <dhcp/iface_mgr_error_handler.h>
#include <dhcp/pkt_filter_bpf.h>
#include <dhcp/pkt_filter_inet.h>
#include <exceptions/exceptions.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <net/if_dl.h>
#include <net/if.h>
#include <ifaddrs.h>

using namespace std;
using namespace isc;
using namespace isc::asiolink;
using namespace isc::dhcp;

namespace isc {
namespace dhcp {

/// This is a BSD specific interface detection method.
void
IfaceMgr::detectIfaces(bool update_only) {
    if (detect_callback_) {
        if (!detect_callback_(update_only)) {
            return;
        }
    }

    struct ifaddrs* iflist = 0;// The whole interface list
    struct ifaddrs* ifptr = 0; // The interface we're processing now

    // Gets list of ifaddrs struct
    if (getifaddrs(&iflist) != 0) {
        isc_throw(Unexpected, "Network interfaces detection failed.");
    }

    typedef map<string, IfacePtr> IfaceLst;
    IfaceLst::iterator iface_iter;
    IfaceLst ifaces;

    // First lookup for getting interfaces ...
    for (ifptr = iflist; ifptr != 0; ifptr = ifptr->ifa_next) {
        const char * ifname = ifptr->ifa_name;
        uint ifindex = 0;

        if (!(ifindex = if_nametoindex(ifname))) {
            // Interface name does not have corresponding index ...
            freeifaddrs(iflist);
            isc_throw(Unexpected, "Interface " << ifname << " has no index");
        }

        iface_iter = ifaces.find(ifname);
        if (iface_iter != ifaces.end()) {
            continue;
        }

        IfacePtr iface;
        if (update_only) {
            iface = getIface(ifname);
        }
        if (!iface) {
            iface.reset(new Iface(ifname, ifindex));
        }
        iface->setFlags(ifptr->ifa_flags);
        ifaces.insert(pair<string, IfacePtr>(ifname, iface));
    }

    // Second lookup to get MAC and IP addresses
    for (ifptr = iflist; ifptr != 0; ifptr = ifptr->ifa_next) {
        iface_iter = ifaces.find(ifptr->ifa_name);
        if (iface_iter == ifaces.end()) {
            continue;
        }
        // Common byte pointer for following data
        const uint8_t * ptr = 0;
        if (ifptr->ifa_addr->sa_family == AF_LINK) {
            // HWAddr
            struct sockaddr_dl * ldata =
                reinterpret_cast<struct sockaddr_dl *>(ifptr->ifa_addr);
            ptr = reinterpret_cast<uint8_t *>(LLADDR(ldata));

            iface_iter->second->setHWType(ldata->sdl_type);
            iface_iter->second->setMac(ptr, ldata->sdl_alen);
        } else if (ifptr->ifa_addr->sa_family == AF_INET6) {
            // IPv6 Addr
            struct sockaddr_in6 * adata =
                reinterpret_cast<struct sockaddr_in6 *>(ifptr->ifa_addr);
            ptr = reinterpret_cast<uint8_t *>(&adata->sin6_addr);

            IOAddress a = IOAddress::fromBytes(AF_INET6, ptr);
            iface_iter->second->addAddress(a);
        } else {
            // IPv4 Addr
            struct sockaddr_in * adata =
                reinterpret_cast<struct sockaddr_in *>(ifptr->ifa_addr);
            ptr = reinterpret_cast<uint8_t *>(&adata->sin_addr);

            IOAddress a = IOAddress::fromBytes(AF_INET, ptr);
            iface_iter->second->addAddress(a);
        }
    }

    freeifaddrs(iflist);

    // Interfaces registering
    for (auto const& iface_it : ifaces) {
        IfacePtr iface;
        if (update_only) {
            iface = getIface(iface_it.first);
        }
        if (!iface) {
            addInterface(iface_it.second);
        }
    }
}

/// @brief sets flag_*_ fields
///
/// Like Linux version, os specific flags
///
/// @params flags
void Iface::setFlags(uint64_t flags) {
    flags_ = flags;

    flag_loopback_ = flags & IFF_LOOPBACK;
    flag_up_ = flags & IFF_UP;
    flag_running_ = flags & IFF_RUNNING;
    flag_multicast_ = flags & IFF_MULTICAST;
    flag_broadcast_ = flags & IFF_BROADCAST;
}

void
IfaceMgr::setMatchingPacketFilter(const bool direct_response_desired) {
    // If direct response is desired we have to use BPF. If the direct
    // response is not desired we use datagram socket supported by the
    // PktFilterInet class. Note however that on BSD systems binding the
    // datagram socket to the device is not supported and the server would
    // have no means to determine on which interface the packet has been
    // received. Hence, it is discouraged to use PktFilterInet for the
    // server.
    if (direct_response_desired) {
        setPacketFilter(PktFilterPtr(new PktFilterBPF()));

    } else {
        setPacketFilter(PktFilterPtr(new PktFilterInet()));

    }
}

bool
IfaceMgr::openMulticastSocket(Iface& iface,
                              const isc::asiolink::IOAddress& addr,
                              const uint16_t port,
                              IfaceMgrErrorMsgCallback error_handler) {
    try {
        // This should open a socket, bind it to link-local address
        // and join multicast group.
        openSocket(iface.getName(), addr, port, iface.flag_multicast_);

    } catch (const Exception& ex) {
        IFACEMGR_ERROR(SocketConfigError, error_handler, IfacePtr(),
                       "Failed to open link-local socket on "
                       "interface " << iface.getName() << ": "
                       << ex.what());
        return (false);

    }
    return (true);
}

int
IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, uint16_t port,
                      const bool join_multicast) {
    // On BSD, we bind the socket to in6addr_any and join multicast group
    // to receive multicast traffic. So, if the multicast is requested,
    // replace the address specified by the caller with the "unspecified"
    // address.
    IOAddress actual_address = join_multicast ? IOAddress("::") : addr;
    SocketInfo info = packet_filter6_->openSocket(iface, actual_address, port,
                                                  join_multicast);
    iface.addSocket(info);
    return (info.sockfd_);
}

} // end of isc::dhcp namespace
} // end of dhcp namespace

#endif
