/*
 * Copyright (c)2013-2021 ZeroTier, Inc.
 *
 * Use of this software is governed by the Business Source License included
 * in the LICENSE.TXT file in the project's root directory.
 *
 * Change Date: 2026-01-01
 *
 * On the date above, in accordance with the Business Source License, use
 * of this software will be governed by version 2.0 of the Apache License.
 */
/****/

/**
 * @file
 *
 * ZeroTier Node Service
 */

#include "NodeService.hpp"

#include "../version.h"
#include "Events.hpp"
#include "InetAddress.hpp"
#include "Mutex.hpp"
#include "Node.hpp"
#include "Utilities.hpp"
#include "VirtualTap.hpp"

#if defined(__WINDOWS__)
#include <shlobj.h>
#include <winsock2.h>
#include <windows.h>
#include <iphlpapi.h>
#include <netioapi.h>
#define stat _stat
#endif

namespace ZeroTier {

static int SnodeVirtualNetworkConfigFunction(
    ZT_Node* node,
    void* uptr,
    void* tptr,
    uint64_t net_id,
    void** nuptr,
    enum ZT_VirtualNetworkConfigOperation op,
    const ZT_VirtualNetworkConfig* nwconf)
{
    ZTS_UNUSED_ARG(node);
    ZTS_UNUSED_ARG(tptr);
    return reinterpret_cast<NodeService*>(uptr)->nodeVirtualNetworkConfigFunction(net_id, nuptr, op, nwconf);
}

static void SnodeEventCallback(ZT_Node* node, void* uptr, void* tptr, enum ZT_Event event, const void* metaData)
{
    ZTS_UNUSED_ARG(node);
    ZTS_UNUSED_ARG(tptr);
    reinterpret_cast<NodeService*>(uptr)->nodeEventCallback(event, metaData);
}

static void SnodeStatePutFunction(
    ZT_Node* node,
    void* uptr,
    void* tptr,
    enum ZT_StateObjectType type,
    const uint64_t id[2],
    const void* data,
    int len)
{
    ZTS_UNUSED_ARG(node);
    ZTS_UNUSED_ARG(tptr);
    reinterpret_cast<NodeService*>(uptr)->nodeStatePutFunction(type, id, data, len);
}

static int SnodeStateGetFunction(
    ZT_Node* node,
    void* uptr,
    void* tptr,
    enum ZT_StateObjectType type,
    const uint64_t id[2],
    void* data,
    unsigned int maxlen)
{
    ZTS_UNUSED_ARG(node);
    ZTS_UNUSED_ARG(tptr);
    return reinterpret_cast<NodeService*>(uptr)->nodeStateGetFunction(type, id, data, maxlen);
}

static int SnodeWirePacketSendFunction(
    ZT_Node* node,
    void* uptr,
    void* tptr,
    int64_t localSocket,
    const struct sockaddr_storage* addr,
    const void* data,
    unsigned int len,
    unsigned int ttl)
{
    ZTS_UNUSED_ARG(node);
    ZTS_UNUSED_ARG(tptr);
    return reinterpret_cast<NodeService*>(uptr)->nodeWirePacketSendFunction(localSocket, addr, data, len, ttl);
}

static void SnodeVirtualNetworkFrameFunction(
    ZT_Node* node,
    void* uptr,
    void* tptr,
    uint64_t net_id,
    void** nuptr,
    uint64_t sourceMac,
    uint64_t destMac,
    unsigned int etherType,
    unsigned int vlanId,
    const void* data,
    unsigned int len)
{
    ZTS_UNUSED_ARG(node);
    ZTS_UNUSED_ARG(tptr);
    reinterpret_cast<NodeService*>(uptr)
        ->nodeVirtualNetworkFrameFunction(net_id, nuptr, sourceMac, destMac, etherType, vlanId, data, len);
}

static int SnodePathCheckFunction(
    ZT_Node* node,
    void* uptr,
    void* tptr,
    uint64_t ztaddr,
    int64_t localSocket,
    const struct sockaddr_storage* remoteAddr)
{
    ZTS_UNUSED_ARG(node);
    ZTS_UNUSED_ARG(tptr);
    return reinterpret_cast<NodeService*>(uptr)->nodePathCheckFunction(ztaddr, localSocket, remoteAddr);
}

static int SnodePathLookupFunction(
    ZT_Node* node,
    void* uptr,
    void* tptr,
    uint64_t ztaddr,
    int family,
    struct sockaddr_storage* result)
{
    ZTS_UNUSED_ARG(node);
    ZTS_UNUSED_ARG(tptr);
    return reinterpret_cast<NodeService*>(uptr)->nodePathLookupFunction(ztaddr, family, result);
}

static void StapFrameHandler(
    void* uptr,
    void* tptr,
    uint64_t net_id,
    const MAC& from,
    const MAC& to,
    unsigned int etherType,
    unsigned int vlanId,
    const void* data,
    unsigned int len)
{
    ZTS_UNUSED_ARG(tptr);
    reinterpret_cast<NodeService*>(uptr)->tapFrameHandler(net_id, from, to, etherType, vlanId, data, len);
}

NodeService::NodeService()
    : _phy(this, false, true)
    , _node((Node*)0)
    , _nodeId(0x0)
    , _primaryPort()
    , _secondaryPort(0)
    , _tertiaryPort(0)
    , _randomPortRangeStart(0)
    , _randomPortRangeEnd(0)
    , _udpPortPickerCounter(0)
    , _lastDirectReceiveFromGlobal(0)
    , _lastRestart(0)
    , _nextBackgroundTaskDeadline(0)
    , _run(false)
    , _termReason(ONE_STILL_RUNNING)
    , _allowPortMapping(true)
#ifdef ZT_USE_MINIUPNPC
    , _portMapper((PortMapper*)0)
#endif
    , _allowSecondaryPort(true)
    , _allowNetworkCaching(true)
    , _allowPeerCaching(true)
    , _allowIdentityCaching(true)
    , _allowRootSetCaching(true)
    , _userDefinedWorld(false)
    , _nodeIsOnline(false)
    , _eventsEnabled(false)
    , _homePath("")
    , _events(NULL)
{
}

NodeService::~NodeService()
{
    _binder.closeAll(_phy);
#ifdef ZT_USE_MINIUPNPC
    delete _portMapper;
#endif
}

NodeService::ReasonForTermination NodeService::run()
{
    _run = true;
    try {
        // Create home path (if necessary)
        // By default, _homePath is empty and nothing is written to storage
        if (_homePath.length() > 0) {
            std::vector<std::string> hpsp(OSUtils::split(_homePath.c_str(), ZT_PATH_SEPARATOR_S, "", ""));
            std::string ptmp;
            if (_homePath[0] == ZT_PATH_SEPARATOR) {
                ptmp.push_back(ZT_PATH_SEPARATOR);
            }
            for (std::vector<std::string>::iterator pi(hpsp.begin()); pi != hpsp.end(); ++pi) {
                if (ptmp.length() > 0) {
                    ptmp.push_back(ZT_PATH_SEPARATOR);
                }
                ptmp.append(*pi);
                if ((*pi != ".") && (*pi != "..")) {
                    OSUtils::mkdir(ptmp);
                }
            }
            if (OSUtils::mkdir(ptmp) == false) {
                Mutex::Lock _l(_termReason_m);
                _termReason = ONE_UNRECOVERABLE_ERROR;
                _fatalErrorMessage = "home path could not be created";
                return _termReason;
            }
        }

        // Set callbacks for ZT Node
        {
            struct ZT_Node_Callbacks cb;
            cb.version = 0;
            cb.stateGetFunction = SnodeStateGetFunction;
            cb.statePutFunction = SnodeStatePutFunction;
            cb.wirePacketSendFunction = SnodeWirePacketSendFunction;
            cb.virtualNetworkFrameFunction = SnodeVirtualNetworkFrameFunction;
            cb.virtualNetworkConfigFunction = SnodeVirtualNetworkConfigFunction;
            cb.eventCallback = SnodeEventCallback;
            cb.pathCheckFunction = SnodePathCheckFunction;
            cb.pathLookupFunction = SnodePathLookupFunction;
            _node = new Node(this, (void*)0, &cb, OSUtils::now());
        }

        unsigned int minPort = (_randomPortRangeStart ? _randomPortRangeStart : 20000);
        unsigned int maxPort = (_randomPortRangeEnd ? _randomPortRangeEnd : 45500);

        // Make sure we can use the primary port, and hunt for one if
        // configured to do so
        const int portTrials = (_primaryPort == 0) ? 256 : 1;   // if port is 0, pick random
        for (int k = 0; k < portTrials; ++k) {
            if (_primaryPort == 0) {
                unsigned int randp = 0;
                Utils::getSecureRandom(&randp, sizeof(randp));
                _primaryPort = (randp % (maxPort - minPort + 1)) + minPort;
            }
            if (_trialBind(_primaryPort)) {
                _ports[0] = _primaryPort;
                break;
            }
            else {
                _primaryPort = 0;
            }
        }
        if (_ports[0] == 0) {
            Mutex::Lock _l(_termReason_m);
            _termReason = ONE_UNRECOVERABLE_ERROR;
            _fatalErrorMessage = "cannot bind to local control interface port";
            return _termReason;
        }

        // Attempt to bind to a secondary port.
        // This exists because there are buggy NATs out there that fail if more
        // than one device behind the same NAT tries to use the same internal
        // private address port number. Buggy NATs are a running theme.
        //
        // This used to pick the secondary port based on the node ID until we
        // discovered another problem: buggy routers and malicious traffic
        // "detection".  A lot of routers have such things built in these days
        // and mis-detect ZeroTier traffic as malicious and block it resulting
        // in a node that appears to be in a coma.  Secondary ports are now
        // randomized on startup.
        if (_allowSecondaryPort) {
            unsigned int randp = 0;
            Utils::getSecureRandom(&randp, sizeof(randp));
            _ports[1] = (_secondaryPort == 0) ? ((randp % (maxPort - minPort + 1)) + minPort) : _secondaryPort;
            for (int i = 0;; ++i) {
                if (i > 1000) {
                    _ports[1] = 0;
                    break;
                }
                else if (++_ports[1] >= maxPort) {
                    _ports[1] = minPort;
                }
                if (_trialBind(_ports[1])) {
                    _secondaryPort = _ports[1];
                    break;
                }
            }
        }
#ifdef ZT_USE_MINIUPNPC
        if (_allowPortMapping) {
            // If we're running uPnP/NAT-PMP, bind a *third* port for that.
            // We can't use the other two ports for that because some NATs
            // do really funky stuff with ports that are explicitly mapped
            // that breaks things.
            maxPort = (_randomPortRangeEnd ? _randomPortRangeEnd : 65536);

            if (_ports[1]) {
                _ports[2] = (_tertiaryPort == 0) ? _ports[1] : _tertiaryPort;
                for (int i = 0;; ++i) {
                    if (i > 1000) {
                        _ports[2] = 0;
                        break;
                    }
                    else if (++_ports[2] >= maxPort) {
                        _ports[2] = minPort;
                    }
                    if (_trialBind(_ports[2])) {
                        _tertiaryPort = _ports[2];
                        break;
                    }
                }
                if (_ports[2]) {
                    char uniqueName[64] = { 0 };
                    OSUtils::ztsnprintf(
                        uniqueName,
                        sizeof(uniqueName),
                        "ZeroTier/%.10llx@%u",
                        _node->address(),
                        _ports[2]);
                    _portMapper = new PortMapper(_ports[2], uniqueName);
                }
            }
        }
#endif
        // Join existing networks in networks.d
        if (_allowNetworkCaching) {
            std::vector<std::string> networksDotD(
                OSUtils::listDirectory((_homePath + ZT_PATH_SEPARATOR_S "networks.d").c_str()));
            for (std::vector<std::string>::iterator f(networksDotD.begin()); f != networksDotD.end(); ++f) {
                std::size_t dot = f->find_last_of('.');
                if ((dot == 16) && (f->substr(16) == ".conf")) {
                    _node->join(Utils::hexStrToU64(f->substr(0, dot).c_str()), (void*)0, (void*)0);
                }
            }
        }
        // Main I/O loop
        _nextBackgroundTaskDeadline = 0;
        int64_t clockShouldBe = OSUtils::now();
        _lastRestart = clockShouldBe;
        int64_t lastTapMulticastGroupCheck = 0;
        int64_t lastBindRefresh = 0;
        int64_t lastCleanedPeersDb = 0;
        int64_t lastLocalInterfaceAddressCheck =
            (clockShouldBe - ZT_LOCAL_INTERFACE_CHECK_INTERVAL) + 15000;   // do this in 15s to give portmapper time to
                                                                           // configure and other things time to settle
        for (;;) {
            _run_m.lock();
            if (! _run) {
                _run_m.unlock();
                _termReason_m.lock();
                _termReason = ONE_NORMAL_TERMINATION;
                _termReason_m.unlock();
                break;
            }
            else {
                _run_m.unlock();
            }

            const int64_t now = OSUtils::now();

            // Attempt to detect sleep/wake events by detecting delay
            // overruns
            bool restarted = false;
            if ((now > clockShouldBe) && ((now - clockShouldBe) > 10000)) {
                _lastRestart = now;
                restarted = true;
            }

            // Refresh bindings in case device's interfaces have changed,
            // and also sync routes to update any shadow routes (e.g. shadow
            // default)
            if (((now - lastBindRefresh) >= ZT_BINDER_REFRESH_PERIOD) || (restarted)) {
                lastBindRefresh = now;
                unsigned int p[3] = { 0 };
                unsigned int pc = 0;
                for (int i = 0; i < 3; ++i) {
                    if (_ports[i]) {
                        p[pc++] = _ports[i];
                    }
                }
                _binder.refresh(_phy, p, pc, explicitBind, *this);
            }

            // Generate callback messages for user application
            generateSyntheticEvents();

            // Run background task processor in core if it's time to do so
            int64_t dl = _nextBackgroundTaskDeadline;
            if (dl <= now) {
                _node->processBackgroundTasks((void*)0, now, &_nextBackgroundTaskDeadline);
                dl = _nextBackgroundTaskDeadline;
            }

            // Sync multicast group memberships
            if ((now - lastTapMulticastGroupCheck) >= ZT_TAP_CHECK_MULTICAST_INTERVAL) {
                lastTapMulticastGroupCheck = now;
                std::vector<std::pair<uint64_t, std::pair<std::vector<MulticastGroup>, std::vector<MulticastGroup> > > >
                    mgChanges;
                {
                    Mutex::Lock _l(_nets_m);
                    mgChanges.reserve(_nets.size() + 1);
                    for (std::map<uint64_t, NetworkState>::const_iterator n(_nets.begin()); n != _nets.end(); ++n) {
                        if (n->second.tap) {
                            mgChanges.push_back(std::pair<
                                                uint64_t,
                                                std::pair<std::vector<MulticastGroup>, std::vector<MulticastGroup> > >(
                                n->first,
                                std::pair<std::vector<MulticastGroup>, std::vector<MulticastGroup> >()));
                            n->second.tap->scanMulticastGroups(
                                mgChanges.back().second.first,
                                mgChanges.back().second.second);
                        }
                    }
                }
                for (std::vector<
                         std::pair<uint64_t, std::pair<std::vector<MulticastGroup>, std::vector<MulticastGroup> > > >::
                         iterator c(mgChanges.begin());
                     c != mgChanges.end();
                     ++c) {
                    auto mgpair = c->second;
                    for (std::vector<MulticastGroup>::iterator m(mgpair.first.begin()); m != mgpair.first.end(); ++m) {
                        _node->multicastSubscribe((void*)0, c->first, m->mac().toInt(), m->adi());
                    }
                    for (std::vector<MulticastGroup>::iterator m(mgpair.second.begin()); m != mgpair.second.end();
                         ++m) {
                        _node->multicastUnsubscribe(c->first, m->mac().toInt(), m->adi());
                    }
                }
            }

            // Sync information about physical network interfaces
            if ((now - lastLocalInterfaceAddressCheck) >= ZT_LOCAL_INTERFACE_CHECK_INTERVAL) {
                lastLocalInterfaceAddressCheck = now;

                _node->clearLocalInterfaceAddresses();

#ifdef ZT_USE_MINIUPNPC
                if (_portMapper) {
                    std::vector<InetAddress> mappedAddresses(_portMapper->get());
                    for (std::vector<InetAddress>::const_iterator ext(mappedAddresses.begin());
                         ext != mappedAddresses.end();
                         ++ext)
                        _node->addLocalInterfaceAddress(reinterpret_cast<const struct sockaddr_storage*>(&(*ext)));
                }
#endif

                std::vector<InetAddress> boundAddrs(_binder.allBoundLocalInterfaceAddresses());
                for (std::vector<InetAddress>::const_iterator i(boundAddrs.begin()); i != boundAddrs.end(); ++i)
                    _node->addLocalInterfaceAddress(reinterpret_cast<const struct sockaddr_storage*>(&(*i)));
            }

            // Clean peers.d periodically
            if ((now - lastCleanedPeersDb) >= 3600000) {
                lastCleanedPeersDb = now;
                OSUtils::cleanDirectory(
                    (_homePath + ZT_PATH_SEPARATOR_S "peers.d").c_str(),
                    now - 2592000000LL);   // delete older than 30 days
            }

            const unsigned long delay = (dl > now) ? (unsigned long)(dl - now) : 100;
            clockShouldBe = now + (uint64_t)delay;
            _phy.poll(delay);
        }
    }
    catch (std::exception& e) {
        Mutex::Lock _l(_termReason_m);
        _termReason = ONE_UNRECOVERABLE_ERROR;
        _fatalErrorMessage = std::string("unexpected exception in main thread: ") + e.what();
    }
    catch (...) {
        Mutex::Lock _l(_termReason_m);
        _termReason = ONE_UNRECOVERABLE_ERROR;
        _fatalErrorMessage = "unexpected exception in main thread: unknown exception";
    }

    {
        Mutex::Lock _l(_nets_m);
        for (std::map<uint64_t, NetworkState>::iterator n(_nets.begin()); n != _nets.end(); ++n) {
            delete n->second.tap;
        }
        _nets.clear();
    }

    switch (_termReason) {
        case ONE_NORMAL_TERMINATION:
            nodeEventCallback(ZT_EVENT_DOWN, NULL);
            break;
        case ONE_UNRECOVERABLE_ERROR:
            nodeEventCallback(ZT_EVENT_FATAL_ERROR_IDENTITY_COLLISION, NULL);
            break;
        default:
            break;
    }
    delete _node;
    _node = (Node*)0;
    return _termReason;
}

NodeService::ReasonForTermination NodeService::reasonForTermination() const
{
    Mutex::Lock _l(_termReason_m);
    return _termReason;
}

std::string NodeService::fatalErrorMessage() const
{
    Mutex::Lock _l(_termReason_m);
    return _fatalErrorMessage;
}

void NodeService::terminate()
{
    _run_m.lock();
    _run = false;
    _run_m.unlock();
    _nodeId = 0x0;
    _primaryPort = 0;
    _homePath.clear();
    _allowNetworkCaching = true;
    _allowPeerCaching = true;
    _allowIdentityCaching = true;
    _allowRootSetCaching = true;
    memset(_publicIdStr, 0, ZT_IDENTITY_STRING_BUFFER_LENGTH);
    memset(_secretIdStr, 0, ZT_IDENTITY_STRING_BUFFER_LENGTH);
    _interfacePrefixBlacklist.clear();
    _events->disable();
    _phy.whack();
}

void NodeService::syncManagedStuff(NetworkState& n)
{
    char ipbuf[64] = { 0 };
    // assumes _nets_m is locked
    std::vector<InetAddress> newManagedIps;
    newManagedIps.reserve(n.config.assignedAddressCount);
    for (unsigned int i = 0; i < n.config.assignedAddressCount; ++i) {
        const InetAddress* ii = reinterpret_cast<const InetAddress*>(&(n.config.assignedAddresses[i]));
        newManagedIps.push_back(*ii);
    }
    std::sort(newManagedIps.begin(), newManagedIps.end());
    newManagedIps.erase(std::unique(newManagedIps.begin(), newManagedIps.end()), newManagedIps.end());
    for (std::vector<InetAddress>::iterator ip(n.managedIps.begin()); ip != n.managedIps.end(); ++ip) {
        if (std::find(newManagedIps.begin(), newManagedIps.end(), *ip) == newManagedIps.end()) {
            if (! n.tap->removeIp(*ip)) {
                fprintf(stderr, "ERROR: unable to remove ip address %s" ZT_EOL_S, ip->toString(ipbuf));
            }
            else {
                zts_addr_info_t* ad = new zts_addr_info_t();
                ad->net_id = n.tap->_net_id;
                if ((*ip).isV4()) {
                    struct sockaddr_in* in4 = (struct sockaddr_in*)&(ad->addr);
                    memcpy(&(in4->sin_addr.s_addr), (*ip).rawIpData(), 4);
                    in4->sin_family = ZTS_AF_INET;
                    sendEventToUser(ZTS_EVENT_ADDR_REMOVED_IP4, (void*)ad);
                }
                if ((*ip).isV6()) {
                    struct sockaddr_in6* in6 = (struct sockaddr_in6*)&(ad->addr);
                    memcpy(&(in6->sin6_addr.s6_addr), (*ip).rawIpData(), 16);
                    in6->sin6_family = ZTS_AF_INET6;
                    sendEventToUser(ZTS_EVENT_ADDR_REMOVED_IP6, (void*)ad);
                }
            }
        }
    }
    for (std::vector<InetAddress>::iterator ip(newManagedIps.begin()); ip != newManagedIps.end(); ++ip) {
        if (std::find(n.managedIps.begin(), n.managedIps.end(), *ip) == n.managedIps.end()) {
            if (! n.tap->addIp(*ip)) {
                fprintf(stderr, "ERROR: unable to add ip address %s" ZT_EOL_S, ip->toString(ipbuf));
            }
            else {
                zts_addr_info_t* ad = new zts_addr_info_t();
                ad->net_id = n.tap->_net_id;
                if ((*ip).isV4()) {
                    struct sockaddr_in* in4 = (struct sockaddr_in*)&(ad->addr);
                    memcpy(&(in4->sin_addr.s_addr), (*ip).rawIpData(), 4);
                    in4->sin_family = ZTS_AF_INET;
                    sendEventToUser(ZTS_EVENT_ADDR_ADDED_IP4, (void*)ad);
                }
                if ((*ip).isV6()) {
                    struct sockaddr_in6* in6 = (struct sockaddr_in6*)&(ad->addr);
                    memcpy(&(in6->sin6_addr.s6_addr), (*ip).rawIpData(), 16);
                    in6->sin6_family = ZTS_AF_INET6;
                    sendEventToUser(ZTS_EVENT_ADDR_ADDED_IP6, (void*)ad);
                }
            }
        }
    }
    n.managedIps.swap(newManagedIps);
}

void NodeService::phyOnDatagram(
    PhySocket* sock,
    void** uptr,
    const struct sockaddr* localAddr,
    const struct sockaddr* from,
    void* data,
    unsigned long len)
{
    ZTS_UNUSED_ARG(uptr);
    ZTS_UNUSED_ARG(localAddr);
    if ((len >= 16) && (reinterpret_cast<const InetAddress*>(from)->ipScope() == InetAddress::IP_SCOPE_GLOBAL))
        _lastDirectReceiveFromGlobal = OSUtils::now();
    const ZT_ResultCode rc = _node->processWirePacket(
        (void*)0,
        OSUtils::now(),
        reinterpret_cast<int64_t>(sock),
        reinterpret_cast<const struct sockaddr_storage*>(from),   // Phy<> uses sockaddr_storage, so
                                                                  // it'll always be that big
        data,
        len,
        &_nextBackgroundTaskDeadline);
    if (ZT_ResultCode_isFatal(rc)) {
        char tmp[256] = { 0 };
        OSUtils::ztsnprintf(tmp, sizeof(tmp), "fatal error code from processWirePacket: %d", (int)rc);
        Mutex::Lock _l(_termReason_m);
        _termReason = ONE_UNRECOVERABLE_ERROR;
        _fatalErrorMessage = tmp;
        this->terminate();
    }
}

int NodeService::nodeVirtualNetworkConfigFunction(
    uint64_t net_id,
    void** nuptr,
    enum ZT_VirtualNetworkConfigOperation op,
    const ZT_VirtualNetworkConfig* nwc)
{
    Mutex::Lock _l(_nets_m);
    NetworkState& n = _nets[net_id];

    switch (op) {
        case ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP:
            if (! n.tap) {
                n.tap = new VirtualTap(
                    _homePath.c_str(),
                    MAC(nwc->mac),
                    nwc->mtu,
                    (unsigned int)ZT_IF_METRIC,
                    net_id,
                    StapFrameHandler,
                    (void*)this);
                *nuptr = (void*)&n;
                n.tap->setUserEventSystem(_events);
            }
            // After setting up tap, fall through to CONFIG_UPDATE since we
            // also want to do this...
        case ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE:
            memcpy(&(n.config), nwc, sizeof(ZT_VirtualNetworkConfig));
            if (n.tap) {   // sanity check
                syncManagedStuff(n);
                n.tap->setMtu(nwc->mtu);
            }
            else {
                _nets.erase(net_id);
                return -999;   // tap init failed
            }
            if (op == ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE) {
                sendEventToUser(ZTS_EVENT_NETWORK_UPDATE, (void*)&n);
            }
            break;
        case ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DOWN:
        case ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY:
            sendEventToUser(ZTS_EVENT_NETWORK_DOWN, (void*)&n);
            if (n.tap) {   // sanity check
                *nuptr = (void*)0;
                delete n.tap;
                _nets.erase(net_id);
                if (_allowNetworkCaching) {
                    if (op == ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY) {
                        char nlcpath[256] = { 0 };
                        OSUtils::ztsnprintf(
                            nlcpath,
                            sizeof(nlcpath),
                            "%s" ZT_PATH_SEPARATOR_S "networks.d" ZT_PATH_SEPARATOR_S "%.16llx.local.conf",
                            _homePath.c_str(),
                            net_id);
                        OSUtils::rm(nlcpath);
                    }
                }
            }
            else {
                _nets.erase(net_id);
            }
            break;
    }
    return 0;
}

void NodeService::nodeEventCallback(enum ZT_Event event, const void* metaData)
{
    ZTS_UNUSED_ARG(metaData);

    int event_code = 0;
    _nodeIsOnline = (event == ZT_EVENT_ONLINE) ? true : false;
    _nodeId = _node ? _node->address() : 0x0;

    switch (event) {
        case ZT_EVENT_UP:
            event_code = ZTS_EVENT_NODE_UP;
            break;
        case ZT_EVENT_ONLINE:
            event_code = ZTS_EVENT_NODE_ONLINE;
            break;
        case ZT_EVENT_OFFLINE:
            event_code = ZTS_EVENT_NODE_OFFLINE;
            break;
        case ZT_EVENT_DOWN:
            event_code = ZTS_EVENT_NODE_DOWN;
            break;
        case ZT_EVENT_FATAL_ERROR_IDENTITY_COLLISION: {
            Mutex::Lock _l(_termReason_m);
            _termReason = ONE_IDENTITY_COLLISION;
            event_code = ZTS_EVENT_NODE_FATAL_ERROR;
            this->terminate();
        } break;
        default:
            break;
    }
    if (event_code) {
        sendEventToUser(event_code, NULL);
    }
}

void NodeService::sendEventToUser(unsigned int zt_event_code, const void* obj, unsigned int len)
{
    if (! _events) {
        return;
    }

    // Convert raw ZT object into ZTS counterpart

    void* objptr = NULL;

    switch (zt_event_code) {
        case ZTS_EVENT_NODE_UP:
        case ZTS_EVENT_NODE_ONLINE:
        case ZTS_EVENT_NODE_OFFLINE:
        case ZTS_EVENT_NODE_DOWN:
        case ZTS_EVENT_NODE_FATAL_ERROR: {
            zts_node_info_t* nd = new zts_node_info_t;
            nd->node_id = _nodeId;
            nd->ver_major = ZEROTIER_ONE_VERSION_MAJOR;
            nd->ver_minor = ZEROTIER_ONE_VERSION_MINOR;
            nd->ver_rev = ZEROTIER_ONE_VERSION_REVISION;
            nd->port_primary = _primaryPort;
            nd->port_secondary = _secondaryPort;
            nd->port_tertiary = _tertiaryPort;
            objptr = (void*)nd;
            break;
        }
        case ZTS_EVENT_NETWORK_NOT_FOUND:
        case ZTS_EVENT_NETWORK_CLIENT_TOO_OLD:
        case ZTS_EVENT_NETWORK_REQ_CONFIG:
        case ZTS_EVENT_NETWORK_ACCESS_DENIED:
        case ZTS_EVENT_NETWORK_DOWN: {
            NetworkState* ns = (NetworkState*)obj;
            zts_net_info_t* nd = new zts_net_info_t();
            nd->net_id = ns->config.nwid;
            objptr = (void*)nd;
            break;
        }
        case ZTS_EVENT_NETWORK_UPDATE:
        case ZTS_EVENT_NETWORK_READY_IP4:
        case ZTS_EVENT_NETWORK_READY_IP6:
        case ZTS_EVENT_NETWORK_OK: {
            NetworkState* ns = (NetworkState*)obj;
            zts_net_info_t* nd = new zts_net_info_t();
            nd->net_id = ns->config.nwid;
            nd->mac = ns->config.mac;
            strncpy(nd->name, ns->config.name, sizeof(ns->config.name));
            nd->status = (zts_network_status_t)ns->config.status;
            nd->type = (zts_net_info_type_t)ns->config.type;
            nd->mtu = ns->config.mtu;
            nd->dhcp = ns->config.dhcp;
            nd->bridge = ns->config.bridge;
            nd->broadcast_enabled = ns->config.broadcastEnabled;
            nd->port_error = ns->config.portError;
            nd->netconf_rev = ns->config.netconfRevision;
            // Copy and convert address structures
            nd->assigned_addr_count = ns->config.assignedAddressCount;
            for (unsigned int i = 0; i < ns->config.assignedAddressCount; i++) {
                native_ss_to_zts_ss(&(nd->assigned_addrs[i]), &(ns->config.assignedAddresses[i]));
            }
            nd->route_count = ns->config.routeCount;
            for (unsigned int i = 0; i < ns->config.routeCount; i++) {
                native_ss_to_zts_ss(&(nd->routes[i].target), &(ns->config.routes[i].target));
                native_ss_to_zts_ss(&(nd->routes[i].via), &(ns->config.routes[i].via));
                nd->routes[i].flags = ns->config.routes[i].flags;
                nd->routes[i].metric = ns->config.routes[i].metric;
            }
            nd->multicast_sub_count = ns->config.multicastSubscriptionCount;
            memcpy(nd->multicast_subs, &(ns->config.multicastSubscriptions), sizeof(ns->config.multicastSubscriptions));
            objptr = (void*)nd;
            break;
        }
        case ZTS_EVENT_ADDR_ADDED_IP4:
            objptr = (void*)obj;
            break;
        case ZTS_EVENT_ADDR_ADDED_IP6:
            objptr = (void*)obj;
            break;
        case ZTS_EVENT_ADDR_REMOVED_IP4:
            objptr = (void*)obj;
            break;
        case ZTS_EVENT_ADDR_REMOVED_IP6:
            objptr = (void*)obj;
            break;
        case ZTS_EVENT_STORE_IDENTITY_PUBLIC:
            objptr = (void*)obj;
            break;
        case ZTS_EVENT_STORE_IDENTITY_SECRET:
            objptr = (void*)obj;
            break;
        case ZTS_EVENT_STORE_PLANET:
            objptr = (void*)obj;
            break;
        case ZTS_EVENT_STORE_PEER:
            objptr = (void*)obj;
            break;
        case ZTS_EVENT_STORE_NETWORK:
            objptr = (void*)obj;
            break;
        case ZTS_EVENT_PEER_DIRECT:
        case ZTS_EVENT_PEER_RELAY:
        case ZTS_EVENT_PEER_UNREACHABLE:
        case ZTS_EVENT_PEER_PATH_DISCOVERED:
        case ZTS_EVENT_PEER_PATH_DEAD: {
            zts_peer_info_t* pd = new zts_peer_info_t();
            ZT_Peer* peer = (ZT_Peer*)obj;
            pd->peer_id = peer->address;
            pd->ver_major = peer->versionMajor;
            pd->ver_minor = peer->versionMinor;
            pd->ver_rev = peer->versionRev;
            pd->latency = peer->latency;
            pd->role = static_cast<zts_peer_role_t>(peer->role);
            pd->path_count = peer->pathCount;
            for (unsigned int j = 0; j < peer->pathCount; j++) {
                native_ss_to_zts_ss(&(pd->paths[j].address), &(peer->paths[j].address));
                pd->paths[j].last_tx = peer->paths[j].lastSend;
                pd->paths[j].last_rx = peer->paths[j].lastReceive;
                pd->paths[j].trusted_path_id = peer->paths[j].trustedPathId;
                pd->paths[j].latency = peer->paths[j].latencyMean;
                // I doubt there is any guarantee that the ZT_Peer
                // instance will outlive the zts_peer_info_t instance
                //pd->paths[j].ifname = peer->paths[j].ifname;
                pd->paths[j].expired = peer->paths[j].expired;
                pd->paths[j].preferred = peer->paths[j].preferred;
            }
            objptr = (void*)pd;
            break;
        }
        default:
            break;
    }

    // Send event

    if (objptr) {
        _events->enqueue(zt_event_code, objptr, len);
    }
}

void NodeService::generateSyntheticEvents()
{
    // Force the ordering of callback messages, these messages are
    // only useful if the node and stack are both up and running
    if (! _node->online() || ! zts_lwip_is_up()) {
        return;
    }
    // Generate messages to be dequeued by the callback message thread
    Mutex::Lock _l(_nets_m);
    for (std::map<uint64_t, NetworkState>::iterator n(_nets.begin()); n != _nets.end(); ++n) {
        auto netState = n->second;
        int mostRecentStatus = netState.config.status;
        VirtualTap* tap = netState.tap;
        // uint64_t net_id = n->first;
        if (netState.tap->_networkStatus == mostRecentStatus) {
            continue;   // No state change
        }
        switch (mostRecentStatus) {
            case ZT_NETWORK_STATUS_NOT_FOUND:
                sendEventToUser(ZTS_EVENT_NETWORK_NOT_FOUND, (void*)&netState);
                break;
            case ZT_NETWORK_STATUS_CLIENT_TOO_OLD:
                sendEventToUser(ZTS_EVENT_NETWORK_CLIENT_TOO_OLD, (void*)&netState);
                break;
            case ZT_NETWORK_STATUS_REQUESTING_CONFIGURATION:
                sendEventToUser(ZTS_EVENT_NETWORK_REQ_CONFIG, (void*)&netState);
                break;
            case ZT_NETWORK_STATUS_OK:
                if (tap->hasIpv4Addr() && zts_lwip_is_netif_up(tap->netif4)) {
                    sendEventToUser(ZTS_EVENT_NETWORK_READY_IP4, (void*)&netState);
                }
                if (tap->hasIpv6Addr() && zts_lwip_is_netif_up(tap->netif6)) {
                    sendEventToUser(ZTS_EVENT_NETWORK_READY_IP6, (void*)&netState);
                }
                // In addition to the READY messages, send one OK message
                sendEventToUser(ZTS_EVENT_NETWORK_OK, (void*)&netState);
                break;
            case ZT_NETWORK_STATUS_ACCESS_DENIED:
                sendEventToUser(ZTS_EVENT_NETWORK_ACCESS_DENIED, (void*)&netState);
                break;
            default:
                break;
        }
        netState.tap->_networkStatus = mostRecentStatus;
    }
    ZT_PeerList* pl = _node->peers();
    if (pl) {
        for (unsigned long i = 0; i < pl->peerCount; ++i) {
            if (! peerCache.count(pl->peers[i].address)) {
                // New peer, add status
                if (pl->peers[i].pathCount > 0) {
                    sendEventToUser(ZTS_EVENT_PEER_DIRECT, (void*)&(pl->peers[i]));
                }
                if (pl->peers[i].pathCount == 0) {
                    sendEventToUser(ZTS_EVENT_PEER_RELAY, (void*)&(pl->peers[i]));
                }
            }
            else {   // Previously known peer, update status
                if (peerCache[pl->peers[i].address] < pl->peers[i].pathCount) {
                    sendEventToUser(ZTS_EVENT_PEER_PATH_DISCOVERED, (void*)&(pl->peers[i]));
                }
                if (peerCache[pl->peers[i].address] > pl->peers[i].pathCount) {
                    sendEventToUser(ZTS_EVENT_PEER_PATH_DEAD, (void*)&(pl->peers[i]));
                }
                if (peerCache[pl->peers[i].address] == 0 && pl->peers[i].pathCount > 0) {
                    sendEventToUser(ZTS_EVENT_PEER_DIRECT, (void*)&(pl->peers[i]));
                }
                if (peerCache[pl->peers[i].address] > 0 && pl->peers[i].pathCount == 0) {
                    sendEventToUser(ZTS_EVENT_PEER_RELAY, (void*)&(pl->peers[i]));
                }
            }
            // Update our cache with most recently observed path count
            peerCache[pl->peers[i].address] = pl->peers[i].pathCount;
        }
    }
    _node->freeQueryResult((void*)pl);
}

int NodeService::join(uint64_t net_id)
{
    if (! net_id) {
        return ZTS_ERR_ARG;
    }
    _node->join(net_id, NULL, NULL);
    return ZTS_ERR_OK;
}

int NodeService::leave(uint64_t net_id)
{
    if (! net_id) {
        return ZTS_ERR_ARG;
    }
    _node->leave(net_id, NULL, NULL);
    return ZTS_ERR_OK;
}

void NodeService::obtainLock() const
{
    _nets_m.lock();
}

void NodeService::releaseLock() const
{
    _nets_m.unlock();
}

bool NodeService::networkIsReady(uint64_t net_id) const
{
    if (! net_id) {
        return false;
    }
    Mutex::Lock _l(_nets_m);
    std::map<uint64_t, NetworkState>::const_iterator n(_nets.find(net_id));
    if (n == _nets.end()) {
        return false;
    }
    auto netState = n->second;
    return netState.config.assignedAddressCount > 0;
}

int NodeService::addressCount(uint64_t net_id) const
{
    std::map<uint64_t, NetworkState>::const_iterator n(_nets.find(net_id));
    if (n == _nets.end()) {
        return ZTS_ERR_NO_RESULT;
    }
    return n->second.config.assignedAddressCount;
}

int NodeService::routeCount(uint64_t net_id) const
{
    std::map<uint64_t, NetworkState>::const_iterator n(_nets.find(net_id));
    if (n == _nets.end()) {
        return ZTS_ERR_NO_RESULT;
    }
    return n->second.config.routeCount;
}

int NodeService::multicastSubCount(uint64_t net_id) const
{
    std::map<uint64_t, NetworkState>::const_iterator n(_nets.find(net_id));
    if (n == _nets.end()) {
        return ZTS_ERR_NO_RESULT;
    }
    return n->second.config.multicastSubscriptionCount;
}

int NodeService::getPeer(uint64_t net_id, uint64_t mac, zts_peer_info_t* peerInfo) const
{
    ZT_Peer* peer = _node->peer((void*)0, net_id, mac);
    if (!peer)
        return ZTS_ERR_NO_RESULT;
    peerInfo->peer_id = peer->address;
    peerInfo->ver_major = peer->versionMajor;
    peerInfo->ver_minor = peer->versionMinor;
    peerInfo->ver_rev = peer->versionRev;
    peerInfo->latency = peer->latency;
    peerInfo->role = static_cast<zts_peer_role_t>(peer->role);
    peerInfo->path_count = peer->pathCount;
    for (unsigned int j = 0; j < peer->pathCount; j++) {
        native_ss_to_zts_ss(&(peerInfo->paths[j].address), &(peer->paths[j].address));
        peerInfo->paths[j].last_tx = peer->paths[j].lastSend;
        peerInfo->paths[j].last_rx = peer->paths[j].lastReceive;
        peerInfo->paths[j].trusted_path_id = peer->paths[j].trustedPathId;
        peerInfo->paths[j].latency = peer->paths[j].latencyMean;
        peerInfo->paths[j].expired = peer->paths[j].expired;
        peerInfo->paths[j].preferred = peer->paths[j].preferred;
    }
    _node->freeQueryResult(peer);
    return ZTS_ERR_OK;
}

int NodeService::pathCount(uint64_t peer_id) const
{
    return ZTS_ERR_NO_RESULT;   // TODO
}

int NodeService::getAddrAtIdx(uint64_t net_id, unsigned int idx, char* dst, unsigned int len)
{
    std::map<uint64_t, NetworkState>::const_iterator n(_nets.find(net_id));
    if (n == _nets.end()) {
        return 0;
    }
    auto netState = n->second;
    if (idx >= netState.config.assignedAddressCount) {
        return ZTS_ERR_ARG;
    }
    struct sockaddr* sa = (struct sockaddr*)&(netState.config.assignedAddresses[idx]);

    if (sa->sa_family == AF_INET) {
        struct sockaddr_in* in4 = (struct sockaddr_in*)sa;
        inet_ntop(AF_INET, &(in4->sin_addr), dst, ZTS_INET6_ADDRSTRLEN);
    }
    if (sa->sa_family == AF_INET6) {
        struct sockaddr_in6* in6 = (struct sockaddr_in6*)sa;
        inet_ntop(AF_INET6, &(in6->sin6_addr), dst, ZTS_INET6_ADDRSTRLEN);
    }
    return ZTS_ERR_OK;
}

int NodeService::getRouteAtIdx(
    uint64_t net_id,
    unsigned int idx,
    char* target,
    char* via,
    unsigned int len,
    uint16_t* flags,
    uint16_t* metric)
{
    // We want to use strlen later so let's ensure there's no junk first.
    memset(target, 0, len);
    memset(via, 0, len);
    std::map<uint64_t, NetworkState>::const_iterator n(_nets.find(net_id));
    if (n == _nets.end()) {
        return 0;
    }
    auto netState = n->second;
    if (idx >= netState.config.routeCount) {
        return ZTS_ERR_ARG;
    }
    // target
    const char* err = NULL;
    struct sockaddr* sa = (struct sockaddr*)&(netState.config.routes[idx].target);
    if (sa->sa_family == AF_INET) {
        struct sockaddr_in* in4 = (struct sockaddr_in*)sa;
        err = inet_ntop(AF_INET, &(in4->sin_addr), target, ZTS_INET6_ADDRSTRLEN);
    }
    if (sa->sa_family == AF_INET6) {
        struct sockaddr_in6* in6 = (struct sockaddr_in6*)sa;
        err = inet_ntop(AF_INET6, &(in6->sin6_addr), target, ZTS_INET6_ADDRSTRLEN);
    }
    // via
    struct sockaddr* sa_via = (struct sockaddr*)&(netState.config.routes[idx].via);
    if (sa_via->sa_family == AF_INET) {
        struct sockaddr_in* in4 = (struct sockaddr_in*)sa_via;
        err = inet_ntop(AF_INET, &(in4->sin_addr), via, ZTS_INET6_ADDRSTRLEN);
    }
    if (sa_via->sa_family == AF_INET6) {
        struct sockaddr_in6* in6 = (struct sockaddr_in6*)sa_via;
        err = inet_ntop(AF_INET6, &(in6->sin6_addr), via, ZTS_INET6_ADDRSTRLEN);
    }
    if (strlen(via) == 0) {
        strncpy(via, "0.0.0.0", 7);
        // TODO: Double check
    }
    *flags = netState.config.routes[idx].flags;
    *metric = netState.config.routes[idx].metric;
    return ZTS_ERR_OK;
}

int NodeService::getMulticastSubAtIdx(uint64_t net_id, unsigned int idx, uint64_t* mac, uint32_t* adi)
{
    std::map<uint64_t, NetworkState>::const_iterator n(_nets.find(net_id));
    if (n == _nets.end()) {
        return 0;
    }
    auto netState = n->second;
    if (idx >= netState.config.multicastSubscriptionCount) {
        return ZTS_ERR_ARG;
    }
    *mac = netState.config.multicastSubscriptions[idx].mac;
    *adi = netState.config.multicastSubscriptions[idx].adi;
    return ZTS_ERR_OK;
}

int NodeService::getPathAtIdx(uint64_t peer_id, unsigned int idx, char* path, unsigned int len)
{
    return ZTS_ERR_NO_RESULT;   // TODO
}

int NodeService::getFirstAssignedAddr(uint64_t net_id, unsigned int family, struct zts_sockaddr_storage* addr)
{
    if (net_id == 0 || ((family != ZTS_AF_INET) && (family != ZTS_AF_INET6)) || ! addr) {
        return ZTS_ERR_ARG;
    }
    Mutex::Lock _l(_nets_m);
    std::map<uint64_t, NetworkState>::const_iterator n(_nets.find(net_id));
    if (n == _nets.end()) {
        return ZTS_ERR_NO_RESULT;
    }
    auto netState = n->second;
    if (netState.config.assignedAddressCount == 0) {
        return ZTS_ERR_NO_RESULT;
    }
    for (unsigned int i = 0; i < netState.config.assignedAddressCount; i++) {
        struct sockaddr* sa = (struct sockaddr*)&(netState.config.assignedAddresses[i]);
        // Family values may vary across platforms, thus the following
        if (sa->sa_family == AF_INET && family == ZTS_AF_INET) {
            native_ss_to_zts_ss(addr, &(netState.config.assignedAddresses[i]));
            return ZTS_ERR_OK;
        }
        if (sa->sa_family == AF_INET6 && family == ZTS_AF_INET6) {
            native_ss_to_zts_ss(addr, &(netState.config.assignedAddresses[i]));
            return ZTS_ERR_OK;
        }
    }
    return ZTS_ERR_NO_RESULT;
}

int NodeService::getAllAssignedAddr(uint64_t net_id, struct zts_sockaddr_storage* addr, unsigned int* count)
{
    if (net_id == 0 || ! addr || ! count || *count != ZTS_MAX_ASSIGNED_ADDRESSES) {
        return ZTS_ERR_ARG;
    }
    Mutex::Lock _l(_nets_m);
    std::map<uint64_t, NetworkState>::const_iterator n(_nets.find(net_id));
    if (n == _nets.end()) {
        return ZTS_ERR_NO_RESULT;
    }
    memset(addr, 0, sizeof(struct zts_sockaddr_storage) * ZTS_MAX_ASSIGNED_ADDRESSES);
    auto netState = n->second;
    if (netState.config.assignedAddressCount == 0) {
        return ZTS_ERR_NO_RESULT;
    }
    for (unsigned int i = 0; i < netState.config.assignedAddressCount; i++) {
        native_ss_to_zts_ss(&addr[i], &(netState.config.assignedAddresses[i]));
    }
    *count = netState.config.assignedAddressCount;
    return ZTS_ERR_OK;
}

int NodeService::addrIsAssigned(uint64_t net_id, unsigned int family)
{
    if (net_id == 0) {
        return ZTS_ERR_ARG;
    }
    struct zts_sockaddr_storage addr;   // unused
    return getFirstAssignedAddr(net_id, family, &addr) != ZTS_ERR_NO_RESULT;
}

int NodeService::networkHasRoute(uint64_t net_id, unsigned int family)
{
    Mutex::Lock _l(_nets_m);
    std::map<uint64_t, NetworkState>::const_iterator n(_nets.find(net_id));
    if (n == _nets.end()) {
        return ZTS_ERR_NO_RESULT;
    }
    auto netState = n->second;
    for (unsigned int i = 0; i < netState.config.routeCount; i++) {
        struct sockaddr* sa = (struct sockaddr*)&(netState.config.routes[i].target);
        if (sa->sa_family == AF_INET && family == ZTS_AF_INET) {
            return true;
        }
        if (sa->sa_family == AF_INET6 && family == ZTS_AF_INET6) {
            return true;
        }
    }
    return false;
}

int NodeService::orbit(uint64_t moon_roots_id, uint64_t moon_seed)
{
    if (! moon_roots_id || ! moon_seed) {
        return ZTS_ERR_ARG;
    }
    Mutex::Lock _lr(_run_m);
    if (! _run) {
        return ZTS_ERR_SERVICE;
    }
    return _node->orbit(NULL, moon_roots_id, moon_seed);
}

int NodeService::deorbit(uint64_t moon_roots_id)
{
    if (! moon_roots_id) {
        return ZTS_ERR_ARG;
    }
    Mutex::Lock _lr(_run_m);
    if (! _run) {
        return ZTS_ERR_SERVICE;
    }
    return _node->deorbit(NULL, moon_roots_id);
}

uint64_t NodeService::getNodeId()
{
    Mutex::Lock _lr(_run_m);
    if (! _run) {
        return 0x0;
    }
    return _node ? _node->address() : 0x0;
}

int NodeService::setIdentity(const char* keypair, unsigned int len)
{
    if (keypair == NULL || len < ZT_IDENTITY_STRING_BUFFER_LENGTH) {
        return ZTS_ERR_ARG;
    }
    // Double check user-provided keypair
    Identity id;
    if ((strlen(keypair) > 32) && (keypair[10] == ':')) {
        if (! id.fromString(keypair)) {
            return id.locallyValidate();
        }
    }
    Mutex::Lock _lr(_run_m);
    if (_run) {
        return ZTS_ERR_SERVICE;
    }
    Mutex::Lock _ls(_store_m);
    memcpy(_secretIdStr, keypair, len);
    return ZTS_ERR_OK;
}

int NodeService::getIdentity(char* keypair, unsigned int* len)
{
    if (keypair == NULL || *len < ZT_IDENTITY_STRING_BUFFER_LENGTH) {
        return ZTS_ERR_ARG;
    }
    if (_node) {
        _node->identity().toString(true, keypair);
    }
    else {
        return ZTS_ERR_GENERAL;
    }
    *len = strnlen(keypair, ZT_IDENTITY_STRING_BUFFER_LENGTH);
    return ZTS_ERR_OK;
}

void NodeService::nodeStatePutFunction(
    enum ZT_StateObjectType type,
    const uint64_t id[2],
    const void* data,
    unsigned int len)
{
    char p[1024] = { 0 };
    FILE* f;
    bool secure = false;
    char dirname[1024] = { 0 };
    dirname[0] = 0;

    Mutex::Lock _ls(_store_m);

    switch (type) {
        case ZT_STATE_OBJECT_IDENTITY_PUBLIC:
            sendEventToUser(ZTS_EVENT_STORE_IDENTITY_PUBLIC, data, len);
            memcpy(_publicIdStr, data, len);
            if (_homePath.length() > 0 && _allowIdentityCaching) {
                OSUtils::ztsnprintf(p, sizeof(p), "%s" ZT_PATH_SEPARATOR_S "identity.public", _homePath.c_str());
            }
            else {
                return;
            }
            break;
        case ZT_STATE_OBJECT_IDENTITY_SECRET:
            sendEventToUser(ZTS_EVENT_STORE_IDENTITY_SECRET, data, len);
            memcpy(_secretIdStr, data, len);
            if (_homePath.length() > 0 && _allowIdentityCaching) {
                OSUtils::ztsnprintf(p, sizeof(p), "%s" ZT_PATH_SEPARATOR_S "identity.secret", _homePath.c_str());
                secure = true;
            }
            else {
                return;
            }
            break;
        case ZT_STATE_OBJECT_PLANET:
            sendEventToUser(ZTS_EVENT_STORE_PLANET, data, len);
            memcpy(_rootsData, data, len);
            if (_homePath.length() > 0 && _allowRootSetCaching) {
                OSUtils::ztsnprintf(p, sizeof(p), "%s" ZT_PATH_SEPARATOR_S "roots", _homePath.c_str());
            }
            else {
                return;
            }
            break;
        case ZT_STATE_OBJECT_NETWORK_CONFIG:
            if (_homePath.length() > 0 && _allowNetworkCaching) {
                OSUtils::ztsnprintf(dirname, sizeof(dirname), "%s" ZT_PATH_SEPARATOR_S "networks.d", _homePath.c_str());
                OSUtils::ztsnprintf(
                    p,
                    sizeof(p),
                    "%s" ZT_PATH_SEPARATOR_S "%.16llx.conf",
                    dirname,
                    (unsigned long long)id[0]);
                secure = true;
            }
            else {
                return;
            }
            break;
        case ZT_STATE_OBJECT_PEER:
            if (_homePath.length() > 0 && _allowPeerCaching) {
                OSUtils::ztsnprintf(dirname, sizeof(dirname), "%s" ZT_PATH_SEPARATOR_S "peers.d", _homePath.c_str());
                OSUtils::ztsnprintf(
                    p,
                    sizeof(p),
                    "%s" ZT_PATH_SEPARATOR_S "%.10llx.peer",
                    dirname,
                    (unsigned long long)id[0]);
            }
            else {
                return;
            }
            break;
        default:
            return;
    }

    if (len >= 0) {
        // Check to see if we've already written this first. This reduces
        // redundant writes and I/O overhead on most platforms and has
        // little effect on others.
        f = fopen(p, "rb");
        if (f) {
            char buf[65535] = { 0 };
            long l = (long)fread(buf, 1, sizeof(buf), f);
            fclose(f);
            if ((l == (long)len) && (memcmp(data, buf, l) == 0)) {
                return;
            }
        }

        f = fopen(p, "wb");
        if ((! f) && (dirname[0])) {   // create subdirectory if it does not exist
            OSUtils::mkdir(dirname);
            f = fopen(p, "wb");
        }
        if (f) {
            if (fwrite(data, len, 1, f) != 1)
                fprintf(
                    stderr,
                    "WARNING: unable to write to file: %s (I/O "
                    "error)" ZT_EOL_S,
                    p);
            fclose(f);
            if (secure) {
                OSUtils::lockDownFile(p, false);
            }
        }
        else {
            fprintf(
                stderr,
                "WARNING: unable to write to file: %s (unable to "
                "open)" ZT_EOL_S,
                p);
        }
    }
    else {
        OSUtils::rm(p);
    }
}

int NodeService::nodeStateGetFunction(
    enum ZT_StateObjectType type,
    const uint64_t id[2],
    void* data,
    unsigned int maxlen)
{
    char p[4096] = { 0 };
    unsigned int keylen = 0;
    switch (type) {
        case ZT_STATE_OBJECT_IDENTITY_PUBLIC:
            keylen = strlen(_publicIdStr);
            if (keylen > 0 && keylen <= maxlen) {
                memcpy(data, _publicIdStr, keylen);
                return keylen;
            }
            if (_homePath.length() > 0) {
                OSUtils::ztsnprintf(p, sizeof(p), "%s" ZT_PATH_SEPARATOR_S "identity.public", _homePath.c_str());
            }
            break;
        case ZT_STATE_OBJECT_IDENTITY_SECRET:
            keylen = strlen(_secretIdStr);
            if (keylen > 0 && keylen <= maxlen) {
                memcpy(data, _secretIdStr, keylen);
                return keylen;
            }
            if (_homePath.length() > 0) {
                OSUtils::ztsnprintf(p, sizeof(p), "%s" ZT_PATH_SEPARATOR_S "identity.secret", _homePath.c_str());
            }
            break;
        case ZT_STATE_OBJECT_PLANET:
            if (_userDefinedWorld) {
                memcpy(data, _rootsData, _rootsDataLen);
                return _rootsDataLen;
            }
            OSUtils::ztsnprintf(p, sizeof(p), "%s" ZT_PATH_SEPARATOR_S "roots", _homePath.c_str());
            break;
        case ZT_STATE_OBJECT_NETWORK_CONFIG:
            OSUtils::ztsnprintf(
                p,
                sizeof(p),
                "%s" ZT_PATH_SEPARATOR_S "networks.d" ZT_PATH_SEPARATOR_S "%.16llx.conf",
                _homePath.c_str(),
                (unsigned long long)id[0]);
            break;
        case ZT_STATE_OBJECT_PEER:
            OSUtils::ztsnprintf(
                p,
                sizeof(p),
                "%s" ZT_PATH_SEPARATOR_S "peers.d" ZT_PATH_SEPARATOR_S "%.10llx.peer",
                _homePath.c_str(),
                (unsigned long long)id[0]);
            break;
        default:
            return -1;
    }
    FILE* f = fopen(p, "rb");
    if (f) {
        int n = (int)fread(data, 1, maxlen, f);
        fclose(f);
        if (n >= 0) {
            return n;
        }
    }
    return -1;
}

int NodeService::nodeWirePacketSendFunction(
    const int64_t localSocket,
    const struct sockaddr_storage* addr,
    const void* data,
    unsigned int len,
    unsigned int ttl)
{
    // Even when relaying we still send via UDP. This way if UDP starts
    // working we can instantly "fail forward" to it and stop using TCP
    // proxy fallback, which is slow.

    if ((localSocket != -1) && (localSocket != 0) && (_binder.isUdpSocketValid((PhySocket*)((uintptr_t)localSocket)))) {
        if ((ttl) && (addr->ss_family == AF_INET))
            _phy.setIp4UdpTtl((PhySocket*)((uintptr_t)localSocket), ttl);
        const bool r = _phy.udpSend((PhySocket*)((uintptr_t)localSocket), (const struct sockaddr*)addr, data, len);
        if ((ttl) && (addr->ss_family == AF_INET))
            _phy.setIp4UdpTtl((PhySocket*)((uintptr_t)localSocket), 255);
        return ((r) ? 0 : -1);
    }
    else {
        return ((_binder.udpSendAll(_phy, addr, data, len, ttl)) ? 0 : -1);
    }
}

void NodeService::nodeVirtualNetworkFrameFunction(
    uint64_t net_id,
    void** nuptr,
    uint64_t sourceMac,
    uint64_t destMac,
    unsigned int etherType,
    unsigned int vlanId,
    const void* data,
    unsigned int len)
{
    ZTS_UNUSED_ARG(vlanId);
    ZTS_UNUSED_ARG(net_id);
    NetworkState* n = reinterpret_cast<NetworkState*>(*nuptr);
    if ((! n) || (! n->tap)) {
        return;
    }
    n->tap->put(MAC(sourceMac), MAC(destMac), etherType, data, len);
}

int NodeService::nodePathCheckFunction(
    uint64_t ztaddr,
    const int64_t localSocket,
    const struct sockaddr_storage* remoteAddr)
{
    ZTS_UNUSED_ARG(localSocket);
    // Make sure we're not trying to do ZeroTier-over-ZeroTier
    {
        Mutex::Lock _l(_nets_m);
        for (std::map<uint64_t, NetworkState>::const_iterator n(_nets.begin()); n != _nets.end(); ++n) {
            if (n->second.tap) {
                std::vector<InetAddress> ips(n->second.tap->ips());
                for (std::vector<InetAddress>::const_iterator i(ips.begin()); i != ips.end(); ++i) {
                    if (i->containsAddress(*(reinterpret_cast<const InetAddress*>(remoteAddr)))) {
                        return 0;
                    }
                }
            }
        }
    }

    /* Note: I do not think we need to scan for overlap with managed routes
     * because of the "route forking" and interface binding that we do. This
     * ensures (we hope) that ZeroTier traffic will still take the physical
     * path even if its managed routes this for other traffic. Will
     * revisit if we see recursion problems. */

    // Check blacklists
    const Hashtable<uint64_t, std::vector<InetAddress> >* blh =
        (const Hashtable<uint64_t, std::vector<InetAddress> >*)0;
    const std::vector<InetAddress>* gbl = (const std::vector<InetAddress>*)0;
    if (remoteAddr->ss_family == AF_INET) {
        blh = &_v4Blacklists;
        gbl = &_globalV4Blacklist;
    }
    else if (remoteAddr->ss_family == AF_INET6) {
        blh = &_v6Blacklists;
        gbl = &_globalV6Blacklist;
    }
    if (blh) {
        Mutex::Lock _l(_localConfig_m);
        const std::vector<InetAddress>* l = blh->get(ztaddr);
        if (l) {
            for (std::vector<InetAddress>::const_iterator a(l->begin()); a != l->end(); ++a) {
                if (a->containsAddress(*reinterpret_cast<const InetAddress*>(remoteAddr))) {
                    return 0;
                }
            }
        }
    }
    if (gbl) {
        for (std::vector<InetAddress>::const_iterator a(gbl->begin()); a != gbl->end(); ++a) {
            if (a->containsAddress(*reinterpret_cast<const InetAddress*>(remoteAddr))) {
                return 0;
            }
        }
    }
    return 1;
}

int NodeService::nodePathLookupFunction(uint64_t ztaddr, unsigned int family, struct sockaddr_storage* result)
{
    const Hashtable<uint64_t, std::vector<InetAddress> >* lh = (const Hashtable<uint64_t, std::vector<InetAddress> >*)0;
    if (family < 0) {
        lh = (_node->prng() & 1) ? &_v4Hints : &_v6Hints;
    }
    else if (family == AF_INET) {
        lh = &_v4Hints;
    }
    else if (family == AF_INET6) {
        lh = &_v6Hints;
    }
    else {
        return 0;
    }
    const std::vector<InetAddress>* l = lh->get(ztaddr);
    if ((l) && (l->size() > 0)) {
        memcpy(result, &((*l)[(unsigned long)_node->prng() % l->size()]), sizeof(struct sockaddr_storage));
        return 1;
    }
    else {
        return 0;
    }
}

void NodeService::tapFrameHandler(
    uint64_t net_id,
    const MAC& from,
    const MAC& to,
    unsigned int etherType,
    unsigned int vlanId,
    const void* data,
    unsigned int len)
{
    _node->processVirtualNetworkFrame(
        (void*)0,
        OSUtils::now(),
        net_id,
        from.toInt(),
        to.toInt(),
        etherType,
        vlanId,
        data,
        len,
        &_nextBackgroundTaskDeadline);
}

int NodeService::shouldBindInterface(const char* ifname, const InetAddress& ifaddr)
{
#if defined(__linux__) || defined(linux) || defined(__LINUX__) || defined(__linux)
    if ((ifname[0] == 'l') && (ifname[1] == 'o')) {
        return false;   // loopback
    }
    if ((ifname[0] == 'z') && (ifname[1] == 't')) {
        return false;   // sanity check: zt#
    }
    if ((ifname[0] == 't') && (ifname[1] == 'u') && (ifname[2] == 'n')) {
        return false;   // tun# is probably an OpenVPN tunnel or similar
    }
    if ((ifname[0] == 't') && (ifname[1] == 'a') && (ifname[2] == 'p')) {
        return false;   // tap# is probably an OpenVPN tunnel or similar
    }
#endif

#ifdef __APPLE__
    if ((ifname[0] == 'f') && (ifname[1] == 'e') && (ifname[2] == 't') && (ifname[3] == 'h')) {
        return false;   // ... as is feth#
    }
    if ((ifname[0] == 'l') && (ifname[1] == 'o')) {
        return false;   // loopback
    }
    if ((ifname[0] == 'z') && (ifname[1] == 't')) {
        return false;   // sanity check: zt#
    }
    if ((ifname[0] == 't') && (ifname[1] == 'u') && (ifname[2] == 'n')) {
        return false;   // tun# is probably an OpenVPN tunnel or similar
    }
    if ((ifname[0] == 't') && (ifname[1] == 'a') && (ifname[2] == 'p')) {
        return false;   // tap# is probably an OpenVPN tunnel or similar
    }
    if ((ifname[0] == 'u') && (ifname[1] == 't') && (ifname[2] == 'u') && (ifname[3] == 'n')) {
        return false;   // ... as is utun#
    }
#endif

    {
        Mutex::Lock _l(_localConfig_m);
        for (std::vector<std::string>::const_iterator p(_interfacePrefixBlacklist.begin());
             p != _interfacePrefixBlacklist.end();
             ++p) {
            if (! strncmp(p->c_str(), ifname, p->length())) {
                return false;
            }
        }
    }
    {
        // Check global blacklists
        const std::vector<InetAddress>* gbl = (const std::vector<InetAddress>*)0;
        if (ifaddr.ss_family == AF_INET) {
            gbl = &_globalV4Blacklist;
        }
        else if (ifaddr.ss_family == AF_INET6) {
            gbl = &_globalV6Blacklist;
        }
        if (gbl) {
            Mutex::Lock _l(_localConfig_m);
            for (std::vector<InetAddress>::const_iterator a(gbl->begin()); a != gbl->end(); ++a) {
                if (a->containsAddress(ifaddr)) {
                    return false;
                }
            }
        }
    }
    {
        Mutex::Lock _l(_nets_m);
        for (std::map<uint64_t, NetworkState>::const_iterator n(_nets.begin()); n != _nets.end(); ++n) {
            if (n->second.tap) {
                std::vector<InetAddress> ips(n->second.tap->ips());
                for (std::vector<InetAddress>::const_iterator i(ips.begin()); i != ips.end(); ++i) {
                    if (i->ipsEqual(ifaddr)) {
                        return false;
                    }
                }
            }
        }
    }
    return true;
}

int NodeService::_trialBind(unsigned int port)
{
    struct sockaddr_in in4;
    struct sockaddr_in6 in6;
    PhySocket* tb;

    memset(&in4, 0, sizeof(in4));
    in4.sin_family = AF_INET;
    in4.sin_port = Utils::hton((uint16_t)port);
    tb = _phy.udpBind(reinterpret_cast<const struct sockaddr*>(&in4), (void*)0, 0);
    if (tb) {
        _phy.close(tb, false);
        return true;
    }

    memset(&in6, 0, sizeof(in6));
    in6.sin6_family = AF_INET6;
    in6.sin6_port = Utils::hton((uint16_t)port);
    tb = _phy.udpBind(reinterpret_cast<const struct sockaddr*>(&in6), (void*)0, 0);
    if (tb) {
        _phy.close(tb, false);
        return true;
    }
    return false;
}

int NodeService::isRunning() const
{
    return _run;
}

int NodeService::nodeIsOnline() const
{
    return _nodeIsOnline;
}

int NodeService::setHomePath(const char* homePath)
{
    if (! homePath) {
        return ZTS_ERR_ARG;
    }
    Mutex::Lock _lr(_run_m);
    if (_run) {
        return ZTS_ERR_SERVICE;
    }
    _homePath = std::string(homePath);
    return ZTS_ERR_OK;
}

int NodeService::setPrimaryPort(unsigned short primaryPort)
{
    Mutex::Lock _lr(_run_m);
    if (_run) {
        return ZTS_ERR_SERVICE;
    }
    _primaryPort = primaryPort;
    return ZTS_ERR_OK;
}

int NodeService::setRandomPortRange(unsigned short startPort, unsigned short endPort)
{
    Mutex::Lock _lr(_run_m);
    if (_run) {
        return ZTS_ERR_SERVICE;
    }
    _randomPortRangeStart = startPort;
    _randomPortRangeEnd = endPort;
    return ZTS_ERR_OK;
}

unsigned short NodeService::getPrimaryPort() const
{
    return _primaryPort;
}

int NodeService::allowPortMapping(unsigned int allowed)
{
    Mutex::Lock _lr(_run_m);
    if (_run) {
        return ZTS_ERR_SERVICE;
    }
    _allowPortMapping = allowed;
    return ZTS_ERR_OK;
}

int NodeService::allowSecondaryPort(unsigned int allowed)
{
    Mutex::Lock _lr(_run_m);
    if (_run) {
        return ZTS_ERR_SERVICE;
    }
    _allowSecondaryPort = allowed;
    return ZTS_ERR_OK;
}

int NodeService::setUserEventSystem(Events* events)
{
    Mutex::Lock _lr(_run_m);
    if (_run) {
        return ZTS_ERR_SERVICE;
    }
    _events = events;
    return ZTS_ERR_OK;
}

void NodeService::enableEvents()
{
    Mutex::Lock _lr(_run_m);
    if (! _events) {
        return;
    }
    _events->enable();
}

int NodeService::setRoots(const void* rootsData, unsigned int len)
{
    if (! rootsData || len <= 0 || len > ZTS_STORE_DATA_LEN) {
        return ZTS_ERR_ARG;
    }
    Mutex::Lock _lr(_run_m);
    if (_run) {
        return ZTS_ERR_SERVICE;
    }
    Mutex::Lock _ls(_store_m);
    memcpy(_rootsData, rootsData, len);
    _rootsDataLen = len;
    _userDefinedWorld = true;
    return ZTS_ERR_OK;
}

int NodeService::addInterfacePrefixToBlacklist(const char* prefix, unsigned int len)
{
    if (! prefix || len == 0 || len > 15) {
        return ZTS_ERR_ARG;
    }
    Mutex::Lock _lr(_run_m);
    if (_run) {
        return ZTS_ERR_SERVICE;
    }
    Mutex::Lock _l(_localConfig_m);
    _interfacePrefixBlacklist.push_back(std::string(prefix));
    return ZTS_ERR_OK;
}

uint64_t NodeService::getMACAddress(uint64_t net_id) const
{
    if (net_id == 0) {
        return ZTS_ERR_ARG;
    }
    Mutex::Lock _lr(_run_m);
    if (! _run) {
        return ZTS_ERR_SERVICE;
    }
    Mutex::Lock _ln(_nets_m);
    std::map<uint64_t, NetworkState>::const_iterator n(_nets.find(net_id));
    if (n == _nets.end()) {
        return ZTS_ERR_NO_RESULT;
    }
    return n->second.config.mac;
}

int NodeService::getNetworkName(uint64_t net_id, char* dst, unsigned int len) const
{
    if (net_id == 0 || ! dst || len != ZTS_MAX_NETWORK_SHORT_NAME_LENGTH) {
        return ZTS_ERR_ARG;
    }
    Mutex::Lock _lr(_run_m);
    if (! _run) {
        return ZTS_ERR_SERVICE;
    }
    if (! _nodeIsOnline) {
        return ZTS_ERR_SERVICE;
    }
    Mutex::Lock _ln(_nets_m);
    std::map<uint64_t, NetworkState>::const_iterator n(_nets.find(net_id));
    if (n == _nets.end()) {
        return ZTS_ERR_NO_RESULT;
    }
    auto netState = n->second;
    strncpy(dst, netState.config.name, ZTS_MAX_NETWORK_SHORT_NAME_LENGTH);
    return ZTS_ERR_OK;
}

int NodeService::allowPeerCaching(unsigned int allowed)
{
    Mutex::Lock _lr(_run_m);
    if (_run) {
        return ZTS_ERR_SERVICE;
    }
    _allowPeerCaching = allowed;
    return ZTS_ERR_OK;
}

int NodeService::allowNetworkCaching(unsigned int allowed)
{
    Mutex::Lock _lr(_run_m);
    if (_run) {
        return ZTS_ERR_SERVICE;
    }
    _allowNetworkCaching = allowed;
    return ZTS_ERR_OK;
}

int NodeService::allowIdentityCaching(unsigned int allowed)
{
    Mutex::Lock _lr(_run_m);
    if (_run) {
        return ZTS_ERR_SERVICE;
    }
    _allowIdentityCaching = allowed;
    return ZTS_ERR_OK;
}

int NodeService::allowRootSetCaching(unsigned int allowed)
{
    Mutex::Lock _lr(_run_m);
    if (_run) {
        return ZTS_ERR_SERVICE;
    }
    _allowRootSetCaching = allowed;
    return ZTS_ERR_OK;
}
int NodeService::getNetworkBroadcast(uint64_t net_id)
{
    if (net_id == 0) {
        return ZTS_ERR_ARG;
    }
    Mutex::Lock _lr(_run_m);
    if (! _run) {
        return ZTS_ERR_SERVICE;
    }
    Mutex::Lock _ln(_nets_m);
    std::map<uint64_t, NetworkState>::const_iterator n(_nets.find(net_id));
    if (n == _nets.end()) {
        return ZTS_ERR_NO_RESULT;
    }
    return n->second.config.broadcastEnabled;
}

int NodeService::getNetworkMTU(uint64_t net_id)
{
    Mutex::Lock _lr(_run_m);
    if (! _run) {
        return ZTS_ERR_SERVICE;
    }
    Mutex::Lock _ln(_nets_m);
    std::map<uint64_t, NetworkState>::const_iterator n(_nets.find(net_id));
    if (n == _nets.end()) {
        return ZTS_ERR_NO_RESULT;
    }
    return n->second.config.mtu;
}

int NodeService::getNetworkType(uint64_t net_id)
{
    Mutex::Lock _lr(_run_m);
    if (! _run) {
        return ZTS_ERR_SERVICE;
    }
    Mutex::Lock _ln(_nets_m);
    std::map<uint64_t, NetworkState>::const_iterator n(_nets.find(net_id));
    if (n == _nets.end()) {
        return ZTS_ERR_NO_RESULT;
    }
    return n->second.config.type;
}

int NodeService::getNetworkStatus(uint64_t net_id)
{
    Mutex::Lock _lr(_run_m);
    if (! _run) {
        return ZTS_ERR_SERVICE;
    }
    Mutex::Lock _ln(_nets_m);
    std::map<uint64_t, NetworkState>::const_iterator n(_nets.find(net_id));
    if (n == _nets.end()) {
        return ZTS_ERR_NO_RESULT;
    }
    return n->second.config.status;
}

}   // namespace ZeroTier
