Exemplo n.º 1
0
static void adminPingOnResponse(struct SwitchPinger_Response* resp, void* vping)
{
    struct Allocator* pingAlloc = resp->ping->pingAlloc;
    struct Ping* ping = vping;

    Dict* rd = Dict_new(pingAlloc);

    if (resp->res == SwitchPinger_Result_LABEL_MISMATCH) {
        uint8_t path[20] = {0};
        AddrTools_printPath(path, resp->label);
        String* pathStr = String_new(path, pingAlloc);
        Dict_putStringC(rd, "rpath", pathStr, pingAlloc);
    }

    Dict_putIntC(rd, "version", resp->version, pingAlloc);
    Dict_putIntC(rd, "ms", resp->milliseconds, pingAlloc);
    Dict_putStringC(rd, "result", SwitchPinger_resultString(resp->res), pingAlloc);
    Dict_putStringC(rd, "path", ping->path, pingAlloc);
    if (resp->data) {
        Dict_putStringC(rd, "data", resp->data, pingAlloc);
    }

    if (!Bits_isZero(resp->key, 32)) {
        Dict_putStringC(rd, "key", Key_stringify(resp->key, pingAlloc), pingAlloc);
    }

    Admin_sendMessage(rd, ping->txid, ping->context->admin);
}
Exemplo n.º 2
0
static void adminPeerStats(Dict* args, void* vcontext, String* txid, struct Allocator* alloc)
{
    struct Context* context = Identity_check((struct Context*)vcontext);
    struct InterfaceController_PeerStats* stats = NULL;

    int64_t* page = Dict_getIntC(args, "page");
    int i = (page) ? *page * ENTRIES_PER_PAGE : 0;

    int count = InterfaceController_getPeerStats(context->ic, alloc, &stats);

    List* list = List_new(alloc);
    for (int counter=0; i < count && counter++ < ENTRIES_PER_PAGE; i++) {
        Dict* d = Dict_new(alloc);
        Dict_putIntC(d, "bytesIn", stats[i].bytesIn, alloc);
        Dict_putIntC(d, "bytesOut", stats[i].bytesOut, alloc);

        Dict_putIntC(d, "recvKbps", stats[i].recvKbps, alloc);
        Dict_putIntC(d, "sendKbps", stats[i].sendKbps, alloc);

        Dict_putStringC(d, "addr", Address_toString(&stats[i].addr, alloc), alloc);

        String* stateString = String_new(InterfaceController_stateString(stats[i].state), alloc);
        Dict_putStringC(d, "state", stateString, alloc);

        Dict_putIntC(d, "last", stats[i].timeOfLastMessage, alloc);

        Dict_putIntC(d, "isIncoming", stats[i].isIncomingConnection, alloc);
        Dict_putIntC(d, "duplicates", stats[i].duplicates, alloc);
        Dict_putIntC(d, "lostPackets", stats[i].lostPackets, alloc);
        Dict_putIntC(d, "receivedOutOfRange", stats[i].receivedOutOfRange, alloc);

        if (stats[i].user) {
            Dict_putStringC(d, "user", stats[i].user, alloc);
        }

        List_addDict(list, d, alloc);
    }

    Dict* resp = Dict_new(alloc);
    Dict_putListC(resp, "peers", list, alloc);
    Dict_putIntC(resp, "total", count, alloc);

    if (i < count) {
        Dict_putIntC(resp, "more", 1, alloc);
    }

    Admin_sendMessage(resp, txid, context->admin);
}
Exemplo n.º 3
0
static void adminInterfaces(Dict* args,
                            void* vcontext,
                            String* txid,
                            struct Allocator* alloc)
{
    struct Context* context = Identity_check((struct Context*)vcontext);

    int64_t* page = Dict_getIntC(args, "page");
    int i = (page) ? *page * ENTRIES_PER_PAGE : 0;

    int count = InterfaceController_ifaceCount(context->ic);
    //int count = InterfaceController_getIface(context->ic, alloc, &stats);

    List* list = List_new(alloc);
    for (int counter = 0; i < count && counter++ < ENTRIES_PER_PAGE; i++) {
        struct InterfaceController_Iface* iface = InterfaceController_getIface(context->ic, i);
        Dict* d = Dict_new(alloc);
        Dict_putIntC(d, "ifNum", iface->ifNum, alloc);
        Dict_putStringC(d, "name", iface->name, alloc);
        char* bs = InterfaceController_beaconStateString(iface->beaconState);
        Dict_putStringCC(d, "beaconState", bs, alloc);
        List_addDict(list, d, alloc);
    }

    Dict* resp = Dict_new(alloc);
    Dict_putListC(resp, "ifaces", list, alloc);
    Dict_putIntC(resp, "total", count, alloc);
    if (i < count) { Dict_putIntC(resp, "more", 1, alloc); }
    Admin_sendMessage(resp, txid, context->admin);
}
Exemplo n.º 4
0
static void searchResponse(struct RouterModule_Promise* promise,
                           uint32_t lag,
                           struct Address* from,
                           Dict* responseDict)
{
    struct Search* search = Identity_check((struct Search*) promise->userData);
    struct Allocator* alloc = Allocator_child(search->alloc);

    Dict* resp = Dict_new(alloc);
    if (!from) {
        Dict_putStringCC(resp, "error", "none", alloc);
        Dict_putIntC(resp, "complete", 1, alloc);
        Admin_sendMessage(resp, search->txid, search->ctx->admin);
        Allocator_free(alloc);
        return;
    }

    String* fromStr = Address_toString(from, alloc);
    Dict_putStringC(resp, "from", fromStr, alloc);

    Dict_putIntC(resp, "ms", lag, alloc);

    struct Address_List* addrs = ReplySerializer_parse(from, responseDict, NULL, true, alloc);
    List* nodes = List_new(alloc);
    for (int i = 0; addrs && i < addrs->length; i++) {
        String* addr = Address_toString(&addrs->elems[i], alloc);
        List_addString(nodes, addr, alloc);
    }
    Dict_putListC(resp, "nodes", nodes, alloc);

    Admin_sendMessage(resp, search->txid, search->ctx->admin);
}
Exemplo n.º 5
0
static void pingerSendPing(String* data, void* context)
{
    struct MsgCore_Promise_pvt* pp = Identity_check((struct MsgCore_Promise_pvt*) context);
    Assert_true(pp->pub.target);
    Assert_true(pp->pub.msg);
    Dict_putStringC(pp->pub.msg, "txid", data, pp->pub.alloc);
    sendMsg(pp->mcp, pp->pub.msg, pp->pub.target, pp->pub.alloc);
}
Exemplo n.º 6
0
static void sendMsg(struct MsgCore_pvt* mcp,
                    Dict* msgDict,
                    struct Address* addr,
                    struct Allocator* allocator)
{
    struct Allocator* alloc = Allocator_child(allocator);

    // Send the encoding scheme definition
    Dict_putString(msgDict, CJDHTConstants_ENC_SCHEME, mcp->schemeDefinition, allocator);

    // And tell the asker which interface the message came from
    int encIdx = EncodingScheme_getFormNum(mcp->scheme, addr->path);
    Assert_true(encIdx != EncodingScheme_getFormNum_INVALID);
    Dict_putInt(msgDict, CJDHTConstants_ENC_INDEX, encIdx, allocator);

    // send the protocol version
    Dict_putInt(msgDict, CJDHTConstants_PROTOCOL, Version_CURRENT_PROTOCOL, allocator);

    if (!Defined(SUBNODE)) {
        String* q = Dict_getStringC(msgDict, "q");
        String* sq = Dict_getStringC(msgDict, "sq");
        if (q || sq) {
            Log_debug(mcp->log, "Send query [%s] to [%s]",
                ((q) ? q->bytes : sq->bytes),
                Address_toString(addr, alloc)->bytes);
            String* txid = Dict_getStringC(msgDict, "txid");
            Assert_true(txid);
            String* newTxid = String_newBinary(NULL, txid->len + 1, alloc);
            Bits_memcpy(&newTxid->bytes[1], txid->bytes, txid->len);
            newTxid->bytes[0] = '1';
            Dict_putStringC(msgDict, "txid", newTxid, alloc);
        }
    }

    struct Message* msg = Message_new(0, 2048, alloc);
    BencMessageWriter_write(msgDict, msg, NULL);

    //Log_debug(mcp->log, "Sending msg [%s]", Escape_getEscaped(msg->bytes, msg->length, alloc));

    // Sanity check (make sure the addr was actually calculated)
    Assert_true(addr->ip6.bytes[0] == 0xfc);

    struct DataHeader data;
    Bits_memset(&data, 0, sizeof(struct DataHeader));
    DataHeader_setVersion(&data, DataHeader_CURRENT_VERSION);
    DataHeader_setContentType(&data, ContentType_CJDHT);
    Message_push(msg, &data, sizeof(struct DataHeader), NULL);

    struct RouteHeader route;
    Bits_memset(&route, 0, sizeof(struct RouteHeader));
    Bits_memcpy(route.ip6, addr->ip6.bytes, 16);
    route.version_be = Endian_hostToBigEndian32(addr->protocolVersion);
    route.sh.label_be = Endian_hostToBigEndian64(addr->path);
    Bits_memcpy(route.publicKey, addr->key, 32);
    Message_push(msg, &route, sizeof(struct RouteHeader), NULL);

    Iface_send(&mcp->pub.interRouterIf, msg);
}
Exemplo n.º 7
0
static void nodeInfo(Dict* args, void* vcontext, String* txid, struct Allocator* requestAlloc)
{
    struct Context* const ctx = Identity_check((struct Context*) vcontext);
    String* myAddr = Address_toString(ctx->nc->myAddress, requestAlloc);
    String* schemeStr = EncodingScheme_serialize(ctx->encodingScheme, requestAlloc);
    List* schemeList = EncodingScheme_asList(ctx->encodingScheme, requestAlloc);
    Dict* out = Dict_new(requestAlloc);
    Dict_putStringC(out, "myAddr", myAddr, requestAlloc);
    char* schemeHex = Hex_print(schemeStr->bytes, schemeStr->len, requestAlloc);
    Dict_putStringCC(out, "compressedSchemeHex", schemeHex, requestAlloc);
    Dict_putListC(out, "encodingScheme", schemeList, requestAlloc);
    Dict_putIntC(out, "version", Version_CURRENT_PROTOCOL, requestAlloc);
    Dict_putStringCC(out, "error", "none", requestAlloc);
    Admin_sendMessage(out, txid, ctx->admin);
}
Exemplo n.º 8
0
static void mkNextRequest(struct ReachabilityCollector_pvt* rcp)
{
    struct PeerInfo* pi = NULL;
    for (int i = 0; i < rcp->piList->length; i++) {
        pi = ArrayList_OfPeerInfo_get(rcp->piList, i);
        if (!pi->querying) { continue; }
    }
    if (!pi || !pi->querying) { return; }
    rcp->msgOnWire = MsgCore_createQuery(rcp->msgCore, TIMEOUT_MILLISECONDS, rcp->alloc);
    rcp->msgOnWire->userData = rcp;
    rcp->msgOnWire->cb = onReply;
    rcp->msgOnWire->target = Address_clone(&pi->addr, rcp->msgOnWire->alloc);
    Dict* d = rcp->msgOnWire->msg = Dict_new(rcp->msgOnWire->alloc);
    Dict_putStringCC(d, "q", "gp", rcp->msgOnWire->alloc);
    uint64_t label_be = Endian_hostToBigEndian64(pi->pathToCheck);
    Dict_putStringC(d, "tar",
        String_newBinary((uint8_t*) &label_be, 8, rcp->msgOnWire->alloc), rcp->msgOnWire->alloc);
    BoilerplateResponder_addBoilerplate(rcp->br, d, &pi->addr, rcp->msgOnWire->alloc);
}
Exemplo n.º 9
0
static void mkNextRequest(struct ReachabilityCollector_pvt* rcp)
{
    struct PeerInfo_pvt* pi = NULL;
    for (int i = 0; i < rcp->piList->length; i++) {
        pi = ArrayList_OfPeerInfo_pvt_get(rcp->piList, i);
        if (pi->pub.querying && !pi->waitForResponse) { break; }
    }
    if (!pi || !pi->pub.querying) {
        Log_debug(rcp->log, "All [%u] peers have been queried", rcp->piList->length);
        return;
    }
    if (pi->waitForResponse) {
        Log_debug(rcp->log, "Peer is waiting for response.");
        return;
    }
    struct MsgCore_Promise* query =
        MsgCore_createQuery(rcp->msgCore, TIMEOUT_MILLISECONDS, rcp->alloc);
    struct Query* q = Allocator_calloc(query->alloc, sizeof(struct Query), 1);
    q->rcp = rcp;
    q->addr = Address_toString(&pi->pub.addr, query->alloc);
    query->userData = q;
    query->cb = onReply;
    Assert_true(AddressCalc_validAddress(pi->pub.addr.ip6.bytes));
    query->target = Address_clone(&pi->pub.addr, query->alloc);
    Dict* d = query->msg = Dict_new(query->alloc);
    Dict_putStringCC(d, "q", "gp", query->alloc);
    uint64_t label_be = Endian_hostToBigEndian64(pi->pathToCheck);
    uint8_t nearbyLabelBytes[8];
    Bits_memcpy(nearbyLabelBytes, &label_be, 8);

    AddrTools_printPath(q->targetPath, pi->pathToCheck);
    Log_debug(rcp->log, "Getting peers for peer [%s] tar [%s]", q->addr->bytes, q->targetPath);

    Dict_putStringC(d, "tar",
        String_newBinary(nearbyLabelBytes, 8, query->alloc), query->alloc);
    BoilerplateResponder_addBoilerplate(rcp->br, d, &pi->pub.addr, query->alloc);

    pi->waitForResponse = true;
}
Exemplo n.º 10
0
/**
 * For serializing and parsing responses to getPeers and search requests.
 */
struct Address_List* ReplySerializer_parse(struct Address* fromNode,
                                           Dict* result,
                                           struct Log* log,
                                           bool splicePath,
                                           struct Allocator* alloc)
{
    String* nodes = Dict_getString(result, CJDHTConstants_NODES);

    if (!nodes) { return NULL; }

    if (nodes->len == 0 || nodes->len % Address_SERIALIZED_SIZE != 0) {
        Log_debug(log, "Dropping unrecognized reply");
        return NULL;
    }

    struct VersionList* versions = NULL;
    String* versionsStr = Dict_getString(result, CJDHTConstants_NODE_PROTOCOLS);
    if (versionsStr) {
        versions = VersionList_parse(versionsStr, alloc);
    }
    if (!versions || versions->length != (nodes->len / Address_SERIALIZED_SIZE)) {
        Log_debug(log, "Reply with missing or invalid versions");
        return NULL;
    }

    struct Address_List* out = Address_List_new(versions->length, alloc);

    uint32_t j = 0;
    for (uint32_t i = 0; nodes && i < nodes->len; i += Address_SERIALIZED_SIZE) {

        struct Address addr = { .path = 0 };
        Address_parse(&addr, (uint8_t*) &nodes->bytes[i]);
        addr.protocolVersion = versions->versions[i / Address_SERIALIZED_SIZE];

        // calculate the ipv6
        Address_getPrefix(&addr);

        if (splicePath) {
            // We need to splice the given address on to the end of the
            // address of the node which gave it to us.
            uint64_t path = LabelSplicer_splice(addr.path, fromNode->path);

            if (path == UINT64_MAX) {
                /* common, lots of noise
                uint8_t discovered[60];
                uint8_t fromAddr[60];
                Address_print(discovered, &addr);
                Address_print(fromAddr, fromNode);
                Log_debug(log,
                          "Dropping response [%s] from [%s] because route could not be spliced",
                          discovered, fromAddr);*/
                continue;
            }

            addr.path = path;
        }

        /*#ifdef Log_DEBUG
            uint8_t printedAddr[60];
            Address_print(printedAddr, &addr);
            Log_debug(log, "discovered node [%s]", printedAddr);
        #endif*/

        Address_getPrefix(&addr);
        if (!AddressCalc_validAddress(addr.ip6.bytes)) {
            struct Allocator* tmpAlloc = Allocator_child(alloc);
            String* printed = Address_toString(&addr, tmpAlloc);
            uint8_t ipPrinted[40];
            Address_printIp(ipPrinted, &addr);
            Log_debug(log, "Was told garbage addr [%s] [%s]", printed->bytes, ipPrinted);
            Allocator_free(tmpAlloc);
            // This should never happen, badnode.
            continue;
        }

        Bits_memcpy(&out->elems[j++], &addr, sizeof(struct Address));
    }
    out->length = j;
    return out;
}

void ReplySerializer_serialize(struct Address_List* addrs,
                               Dict* out,
                               struct Address* convertDirectorFor,
                               struct Allocator* alloc)
{
    if (!addrs->length) { return; }
    String* nodes = String_newBinary(NULL, addrs->length * Address_SERIALIZED_SIZE, alloc);
    struct VersionList* versions = VersionList_new(addrs->length, alloc);
    for (int i = 0; i < addrs->length; i++) {
        versions->versions[i] = addrs->elems[i].protocolVersion;
        if (!convertDirectorFor) {
            Address_serialize(&nodes->bytes[i * Address_SERIALIZED_SIZE], &addrs->elems[i]);
        } else {
            struct Address addr;
            Bits_memcpy(&addr, &addrs->elems[i], sizeof(struct Address));
            addr.path = NumberCompress_getLabelFor(addr.path, convertDirectorFor->path);
            Address_serialize(&nodes->bytes[i * Address_SERIALIZED_SIZE], &addr);
        }
    }
    Dict_putStringC(out, "n", nodes, alloc);
    Dict_putStringC(out, "np", VersionList_stringify(versions, alloc), alloc);
}
Exemplo n.º 11
0
static void pingCycle(void* vsn)
{
    struct SupernodeHunter_pvt* snp = Identity_check((struct SupernodeHunter_pvt*) vsn);

    if (snp->pub.snodeIsReachable) { return; }
    if (!snp->authorizedSnodes->length) { return; }
    if (!snp->peers->length) { return; }

    Log_debug(snp->log, "\n\nping cycle\n\n");

    // We're not handling replies...
    struct MsgCore_Promise* qp = MsgCore_createQuery(snp->msgCore, 0, snp->alloc);
    struct Query* q = Allocator_calloc(qp->alloc, sizeof(struct Query), 1);
    Identity_set(q);
    q->snp = snp;
    q->sendTime = Time_currentTimeMilliseconds(snp->base);

    Dict* msg = qp->msg = Dict_new(qp->alloc);
    qp->cb = onReply;
    qp->userData = q;

    bool isGetPeers = snp->nodeListIndex & 1;
    int idx = snp->nodeListIndex++ >> 1;
    for (;;) {
        if (idx < snp->peers->length) {
            qp->target = AddrSet_get(snp->peers, idx);
            break;
        }
        idx -= snp->peers->length;
        if (idx < snp->nodes->length) {
            qp->target = AddrSet_get(snp->nodes, idx);
            break;
        }
        snp->snodeAddrIdx++;
        idx -= snp->nodes->length;
    }
    struct Address* snode =
        AddrSet_get(snp->authorizedSnodes, snp->snodeAddrIdx % snp->authorizedSnodes->length);

    if (Address_isSameIp(snode, qp->target)) {
        // Supernode is a peer...
        AddrSet_add(snp->snodeCandidates, qp->target);
    }

    if (snp->snodeCandidates->length) {
        qp->target = AddrSet_get(snp->snodeCandidates, snp->snodeCandidates->length - 1);
        Log_debug(snp->log, "Sending getRoute to snode %s",
            Address_toString(qp->target, qp->alloc)->bytes);
        Dict_putStringCC(msg, "sq", "gr", qp->alloc);
        Dict_putStringC(msg, "src", snp->selfAddrStr, qp->alloc);
        String* target = String_newBinary(qp->target->ip6.bytes, 16, qp->alloc);
        Dict_putStringC(msg, "tar", target, qp->alloc);
        q->isGetRoute = true;
        return;
    }

    if (isGetPeers) {
        Log_debug(snp->log, "Sending getPeers to %s",
            Address_toString(qp->target, qp->alloc)->bytes);
        Dict_putStringCC(msg, "q", "gp", qp->alloc);
        Dict_putStringC(msg, "tar", String_newBinary("\0\0\0\0\0\0\0\1", 8, qp->alloc), qp->alloc);
    } else {
        q->searchTar = Address_clone(snode, qp->alloc);
        Log_debug(snp->log, "Sending findNode to %s",
            Address_toString(qp->target, qp->alloc)->bytes);
        Dict_putStringCC(msg, "q", "fn", qp->alloc);
        Dict_putStringC(msg, "tar", String_newBinary(snode->ip6.bytes, 16, qp->alloc), qp->alloc);
    }
}
Exemplo n.º 12
0
static Iface_DEFUN incoming(struct Message* msg, struct Iface* interRouterIf)
{
    struct MsgCore_pvt* mcp =
        Identity_containerOf(interRouterIf, struct MsgCore_pvt, pub.interRouterIf);

    struct Address addr = { .padding = 0 };
    struct RouteHeader* hdr = (struct RouteHeader*) msg->bytes;
    Message_shift(msg, -(RouteHeader_SIZE + DataHeader_SIZE), NULL);
    Bits_memcpy(addr.ip6.bytes, hdr->ip6, 16);
    Bits_memcpy(addr.key, hdr->publicKey, 32);
    addr.protocolVersion = Endian_bigEndianToHost32(hdr->version_be);
    addr.path = Endian_bigEndianToHost64(hdr->sh.label_be);

    Dict* content = NULL;
    uint8_t* msgBytes = msg->bytes;
    int length = msg->length;
    //Log_debug(mcp->log, "Receive msg [%s] from [%s]",
    //    Escape_getEscaped(msg->bytes, msg->length, msg->alloc),
    //    Address_toString(&addr, msg->alloc)->bytes);
    //
    BencMessageReader_readNoExcept(msg, msg->alloc, &content);
    if (!content) {
        char* esc = Escape_getEscaped(msgBytes, length, msg->alloc);
        Log_debug(mcp->log, "DROP Malformed message [%s]", esc);
        return NULL;
    }

    int64_t* verP = Dict_getIntC(content, "p");
    if (!verP) {
        Log_debug(mcp->log, "DROP Message without version");
        return NULL;
    }
    addr.protocolVersion = *verP;

    String* q = Dict_getStringC(content, "q");

    if (!Defined(SUBNODE)) {
        String* txid = Dict_getStringC(content, "txid");
        Assert_true(txid);
        if (q) {
            if (txid->bytes[0] == '0') {
                Log_debug(mcp->log, "DROP query which begins with 0 and is for old pathfinder");
                return NULL;
            }
        } else {
            if (txid->bytes[0] != '1') {
                Log_debug(mcp->log, "DROP reply which does not begin with 1");
                return NULL;
            }
            String* newTxid = String_newBinary(NULL, txid->len - 1, msg->alloc);
            Bits_memcpy(newTxid->bytes, &txid->bytes[1], txid->len - 1);
            Dict_putStringC(content, "txid", newTxid, msg->alloc);
            txid = newTxid;
        }
    }

    if (q) {
        return queryMsg(mcp, content, &addr, msg);
    } else {
        return replyMsg(mcp, content, &addr, msg);
    }
}

struct MsgCore* MsgCore_new(struct EventBase* base,
                            struct Random* rand,
                            struct Allocator* allocator,
                            struct Log* log,
                            struct EncodingScheme* scheme)
{
    struct Allocator* alloc = Allocator_child(allocator);
    struct MsgCore_pvt* mcp = Allocator_calloc(alloc, sizeof(struct MsgCore_pvt), 1);
    Identity_set(mcp);
    mcp->pub.interRouterIf.send = incoming;
    mcp->qh = ArrayList_OfQueryHandlers_new(alloc);
    mcp->pinger = Pinger_new(base, rand, log, alloc);
    mcp->log = log;

    mcp->scheme = scheme;
    mcp->schemeDefinition = EncodingScheme_serialize(scheme, alloc);

    return &mcp->pub;
}
Exemplo n.º 13
0
static void adminPeerStats(Dict* args, void* vcontext, String* txid, struct Allocator* alloc)
{
    struct Context* context = Identity_check((struct Context*)vcontext);
    struct InterfaceController_PeerStats* stats = NULL;

    int64_t* page = Dict_getIntC(args, "page");
    int i = (page) ? *page * ENTRIES_PER_PAGE : 0;

    int count = InterfaceController_getPeerStats(context->ic, alloc, &stats);

    List* list = List_new(alloc);
    for (int counter=0; i < count && counter++ < ENTRIES_PER_PAGE; i++) {
        Dict* d = Dict_new(alloc);
        Dict_putIntC(d, "bytesIn", stats[i].bytesIn, alloc);
        Dict_putIntC(d, "bytesOut", stats[i].bytesOut, alloc);

        Dict_putIntC(d, "recvKbps", stats[i].recvKbps, alloc);
        Dict_putIntC(d, "sendKbps", stats[i].sendKbps, alloc);

        Dict_putStringC(d, "addr", Address_toString(&stats[i].addr, alloc), alloc);

        String* lladdrString;
#ifdef HAS_ETH_INTERFACE
        if (ETHInterface_Sockaddr_SIZE == stats[i].lladdr->addrLen) {
            struct ETHInterface_Sockaddr* eth = (struct ETHInterface_Sockaddr*) stats[i].lladdr;
            uint8_t printedMac[18];
            AddrTools_printMac(printedMac, eth->mac);
            lladdrString = String_new(printedMac, alloc);
        } else {
            lladdrString = String_new(Sockaddr_print(stats[i].lladdr, alloc), alloc);
        }
#else
        lladdrString = String_new(Sockaddr_print(stats[i].lladdr, alloc), alloc);
#endif
        Dict_putStringC(d, "lladdr", lladdrString, alloc);

        String* stateString = String_new(InterfaceController_stateString(stats[i].state), alloc);
        Dict_putStringC(d, "state", stateString, alloc);

        Dict_putIntC(d, "last", stats[i].timeOfLastMessage, alloc);

        Dict_putIntC(d, "isIncoming", stats[i].isIncomingConnection, alloc);
        Dict_putIntC(d, "duplicates", stats[i].duplicates, alloc);
        Dict_putIntC(d, "lostPackets", stats[i].lostPackets, alloc);
        Dict_putIntC(d, "receivedOutOfRange", stats[i].receivedOutOfRange, alloc);

        Dict_putIntC(d, "ifNum", stats[i].ifNum, alloc);

        if (stats[i].user) {
            Dict_putStringC(d, "user", stats[i].user, alloc);
        }

        Dict_putIntC(d, "receivedPackets", stats[i].receivedPackets, alloc);

        List_addDict(list, d, alloc);
    }

    Dict* resp = Dict_new(alloc);
    Dict_putListC(resp, "peers", list, alloc);
    Dict_putIntC(resp, "total", count, alloc);

    if (i < count) {
        Dict_putIntC(resp, "more", 1, alloc);
    }

    Admin_sendMessage(resp, txid, context->admin);
}