0
0
mirror of https://github.com/cjdelisle/cjdns synced 2025-10-06 00:32:50 +02:00
Files
cjdns/net/SwitchPinger.c

475 lines
19 KiB
C

/* vim: set expandtab ts=4 sw=4: */
/*
* You may redistribute this program and/or modify it under the terms of
* the GNU General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "benc/String.h"
#include "crypto/AddressCalc.h"
#include "net/SwitchPinger.h"
#include "dht/Address.h"
#include "util/Bits.h"
#include "util/Checksum.h"
#include "util/Endian.h"
#include "util/Pinger.h"
#include "util/version/Version.h"
#include "util/Identity.h"
#include "wire/RouteHeader.h"
#include "wire/Control.h"
#include "wire/Error.h"
struct SwitchPinger_pvt
{
struct SwitchPinger pub;
struct Pinger* pinger;
struct Admin* admin;
struct Log* logger;
struct Allocator* allocator;
struct Address* myAddr;
/**
* The label is stored here while the message is sent through the pinger
* and it decides which ping the incoming message belongs to.
*/
uint64_t incomingLabel;
/** The version of the node which sent the message. */
uint32_t incomingVersion;
uint8_t incomingKey[32];
struct Address incomingSnodeAddr;
uint32_t incomingSnodeKbps;
// If it's an rpath message
uint64_t rpath;
struct Control_LlAddr lladdrMsg;
/** The error code if an error has been returned (see Error.h) */
int error;
/** Pings which are currently waiting for responses. */
int outstandingPings;
/** Maximum number of pings which can be outstanding at one time. */
int maxConcurrentPings;
Identity
};
struct Ping
{
struct SwitchPinger_Ping pub;
uint64_t label;
String* data;
struct SwitchPinger_pvt* context;
SwitchPinger_ResponseCallback onResponse;
void* onResponseContext;
struct Pinger_Ping* pingerPing;
Identity
};
// incoming message from network, pointing to the beginning of the switch header.
static Iface_DEFUN messageFromControlHandler(Message_t* msg, struct Iface* iface)
{
struct SwitchPinger_pvt* ctx = Identity_check((struct SwitchPinger_pvt*) iface);
struct RouteHeader rh;
Err(Message_epop(msg, &rh, RouteHeader_SIZE));
ctx->incomingLabel = Endian_bigEndianToHost64(rh.sh.label_be);
ctx->incomingVersion = 0;
Bits_memset(&ctx->incomingSnodeAddr, 0, sizeof ctx->incomingSnodeAddr);
Bits_memset(ctx->incomingKey, 0, sizeof ctx->incomingKey);
ctx->incomingSnodeKbps = 0;
ctx->rpath = 0;
Bits_memset(&ctx->lladdrMsg, 0, sizeof ctx->lladdrMsg);
struct Control* ctrl = (struct Control*) Message_bytes(msg);
if (ctrl->header.type_be == Control_PONG_be) {
Err(Message_eshift(msg, -Control_Header_SIZE));
ctx->error = Error_NONE;
if (Message_getLength(msg) >= Control_Pong_MIN_SIZE) {
struct Control_Ping* pongHeader = (struct Control_Ping*) Message_bytes(msg);
ctx->incomingVersion = Endian_bigEndianToHost32(pongHeader->version_be);
if (pongHeader->magic != Control_Pong_MAGIC) {
Log_debug(ctx->logger, "dropped invalid switch pong");
return Error(msg, "INVALID");
}
Err(Message_eshift(msg, -Control_Pong_HEADER_SIZE));
} else {
Log_debug(ctx->logger, "got runt pong message, length: [%d]", Message_getLength(msg));
return Error(msg, "RUNT");
}
} else if (ctrl->header.type_be == Control_KEYPONG_be) {
Err(Message_eshift(msg, -Control_Header_SIZE));
ctx->error = Error_NONE;
if (Message_getLength(msg) >= Control_KeyPong_HEADER_SIZE && Message_getLength(msg) <= Control_KeyPong_MAX_SIZE) {
struct Control_KeyPing* pongHeader = (struct Control_KeyPing*) Message_bytes(msg);
ctx->incomingVersion = Endian_bigEndianToHost32(pongHeader->version_be);
if (pongHeader->magic != Control_KeyPong_MAGIC) {
Log_debug(ctx->logger, "dropped invalid switch key-pong");
return Error(msg, "INVALID");
}
Bits_memcpy(ctx->incomingKey, pongHeader->key, 32);
Err(Message_eshift(msg, -Control_KeyPong_HEADER_SIZE));
} else if (Message_getLength(msg) > Control_KeyPong_MAX_SIZE) {
Log_debug(ctx->logger, "got overlong key-pong message, length: [%d]", Message_getLength(msg));
return Error(msg, "INVALID");
} else {
Log_debug(ctx->logger, "got runt key-pong message, length: [%d]", Message_getLength(msg));
return Error(msg, "RUNT");
}
} else if (ctrl->header.type_be == Control_GETSNODE_REPLY_be) {
Err(Message_eshift(msg, -Control_Header_SIZE));
ctx->error = Error_NONE;
if (Message_getLength(msg) < Control_GetSnode_HEADER_SIZE) {
Log_debug(ctx->logger, "got runt GetSnode message, length: [%d]", Message_getLength(msg));
return Error(msg, "RUNT");
}
struct Control_GetSnode* hdr = (struct Control_GetSnode*) Message_bytes(msg);
if (hdr->magic != Control_GETSNODE_REPLY_MAGIC) {
Log_debug(ctx->logger, "dropped invalid GetSnode");
return Error(msg, "INVALID");
}
if (Bits_isZero(hdr->snodeKey, 32)) {
Log_debug(ctx->logger, "Peer doesn't have an snode");
return NULL;
}
if (!AddressCalc_addressForPublicKey(ctx->incomingSnodeAddr.ip6.bytes, hdr->snodeKey)) {
Log_debug(ctx->logger, "dropped invalid GetSnode key");
return Error(msg, "INVALID");
}
ctx->incomingVersion = Endian_hostToBigEndian32(hdr->version_be);
Bits_memcpy(ctx->incomingSnodeAddr.key, hdr->snodeKey, 32);
uint64_t pathToSnode_be;
Bits_memcpy(&pathToSnode_be, hdr->pathToSnode_be, 8);
ctx->incomingSnodeAddr.path = Endian_bigEndianToHost64(pathToSnode_be);
ctx->incomingSnodeAddr.protocolVersion = Endian_bigEndianToHost32(hdr->snodeVersion_be);
ctx->incomingSnodeKbps = Endian_bigEndianToHost32(hdr->kbps_be);
Err(Message_eshift(msg, -Control_GetSnode_HEADER_SIZE));
} else if (ctrl->header.type_be == Control_RPATH_REPLY_be) {
Err(Message_eshift(msg, -Control_Header_SIZE));
ctx->error = Error_NONE;
if (Message_getLength(msg) < Control_RPath_HEADER_SIZE) {
Log_debug(ctx->logger, "got runt RPath message, length: [%d]", Message_getLength(msg));
return Error(msg, "RUNT");
}
struct Control_RPath* hdr = (struct Control_RPath*) Message_bytes(msg);
if (hdr->magic != Control_RPATH_REPLY_MAGIC) {
Log_debug(ctx->logger, "dropped invalid RPATH (bad magic)");
return Error(msg, "INVALID");
}
ctx->incomingVersion = Endian_hostToBigEndian32(hdr->version_be);
uint64_t rpath_be;
Bits_memcpy(&rpath_be, hdr->rpath_be, 8);
ctx->rpath = Endian_bigEndianToHost64(rpath_be);
Err(Message_eshift(msg, -Control_RPath_HEADER_SIZE));
} else if (ctrl->header.type_be == Control_ERROR_be) {
Err(Message_eshift(msg, -Control_Header_SIZE));
Assert_true((uint8_t*)&ctrl->content.error.errorType_be == Message_bytes(msg));
if (Message_getLength(msg) < (Control_Error_HEADER_SIZE + SwitchHeader_SIZE + Control_Header_SIZE)) {
Log_debug(ctx->logger, "runt error packet");
return Error(msg, "RUNT");
}
uint32_t err = 0;
Err(Message_epop32be(&err, msg));
ctx->error = err;
Err(Message_epush32be(msg, 0));
Err(Message_eshift(msg, -(Control_Error_HEADER_SIZE + SwitchHeader_SIZE)));
struct Control* origCtrl = (struct Control*) Message_bytes(msg);
Log_debug(ctx->logger, "error [%d] was caused by our [%s]",
ctx->error,
Control_typeString(origCtrl->header.type_be));
int shift;
if (origCtrl->header.type_be == Control_PING_be) {
shift = -(Control_Header_SIZE + Control_Ping_HEADER_SIZE);
} else if (origCtrl->header.type_be == Control_KEYPING_be) {
shift = -(Control_Header_SIZE + Control_KeyPing_HEADER_SIZE);
} else {
Assert_failure("problem in Ducttape.c");
}
if (Message_getLength(msg) < -shift) {
Log_debug(ctx->logger, "runt error packet");
}
Err(Message_eshift(msg, shift));
} else if (ctrl->header.type_be == Control_LlAddr_REPLY_be) {
Err(Message_eshift(msg, -Control_Header_SIZE));
ctx->error = Error_NONE;
if (Message_getLength(msg) < Control_LlAddr_HEADER_SIZE) {
Log_debug(ctx->logger, "got runt LlAddr message, length: [%d]", Message_getLength(msg));
return Error(msg, "RUNT");
}
Err(Message_epop(msg, &ctx->lladdrMsg, sizeof ctx->lladdrMsg));
if (ctx->lladdrMsg.magic != Control_LlAddr_REPLY_MAGIC) {
Log_debug(ctx->logger, "dropped invalid LLADDR reply (bad magic)");
return Error(msg, "INVALID");
}
} else {
// If it gets here then Ducttape.c is failing.
Assert_true(false);
}
String* msgStr = &(String) { .bytes = (char*) Message_bytes(msg), .len = Message_getLength(msg) };
Pinger_pongReceived(msgStr, ctx->pinger);
Bits_memset(ctx->incomingKey, 0, 32);
return NULL;
}
static void onPingResponse(String* data, uint32_t milliseconds, void* vping)
{
struct Ping* p = Identity_check((struct Ping*) vping);
enum SwitchPinger_Result err = SwitchPinger_Result_OK;
uint64_t label = p->context->incomingLabel;
if (data) {
if (label != p->label) {
err = SwitchPinger_Result_LABEL_MISMATCH;
} else if ((p->data || data->len > 0) && !String_equals(data, p->data)) {
err = SwitchPinger_Result_WRONG_DATA;
} else if (p->context->error == Error_LOOP_ROUTE) {
err = SwitchPinger_Result_LOOP_ROUTE;
} else if (p->context->error) {
err = SwitchPinger_Result_ERROR_RESPONSE;
}
} else {
err = SwitchPinger_Result_TIMEOUT;
label = p->label;
}
uint32_t version = p->context->incomingVersion;
struct SwitchPinger_Response* resp =
Allocator_calloc(p->pub.pingAlloc, sizeof(struct SwitchPinger_Response), 1);
resp->res = err;
resp->label = label;
resp->milliseconds = milliseconds;
resp->ping = &p->pub;
if (err != SwitchPinger_Result_TIMEOUT) {
resp->version = p->context->incomingVersion;
resp->data = data;
resp->version = version;
Bits_memcpy(resp->key, p->context->incomingKey, 32);
Bits_memcpy(&resp->snode, &p->context->incomingSnodeAddr, sizeof(struct Address));
resp->kbpsLimit = p->context->incomingSnodeKbps;
resp->rpath = p->context->rpath;
Bits_memcpy(&resp->lladdr, &p->context->lladdrMsg, sizeof p->context->lladdrMsg);
}
p->onResponse(resp, p->pub.onResponseContext);
}
static void sendPing(String* data, void* sendPingContext)
{
struct Ping* p = Identity_check((struct Ping*) sendPingContext);
Message_t* msg = Message_new(0, data->len + 512, p->pub.pingAlloc);
while (((uintptr_t)Message_bytes(msg) - data->len) % 4) {
Err_assert(Message_epush8(msg, 0));
}
Err_assert(Message_truncate(msg, 0));
Err_assert(Message_epush(msg, data->bytes, data->len));
Assert_true(!((uintptr_t)Message_bytes(msg) % 4) && "alignment fault");
if (p->pub.type == SwitchPinger_Type_KEYPING) {
Err_assert(Message_epush(msg, NULL, Control_KeyPing_HEADER_SIZE));
struct Control_KeyPing* keyPingHeader = (struct Control_KeyPing*) Message_bytes(msg);
keyPingHeader->magic = Control_KeyPing_MAGIC;
keyPingHeader->version_be = Endian_hostToBigEndian32(Version_CURRENT_PROTOCOL);
Bits_memcpy(keyPingHeader->key, p->context->myAddr->key, 32);
} else if (p->pub.type == SwitchPinger_Type_PING) {
Err_assert(Message_epush(msg, NULL, Control_Ping_HEADER_SIZE));
struct Control_Ping* pingHeader = (struct Control_Ping*) Message_bytes(msg);
pingHeader->magic = Control_Ping_MAGIC;
pingHeader->version_be = Endian_hostToBigEndian32(Version_CURRENT_PROTOCOL);
} else if (p->pub.type == SwitchPinger_Type_GETSNODE) {
Err_assert(Message_epush(msg, NULL, Control_GetSnode_HEADER_SIZE));
struct Control_GetSnode* hdr = (struct Control_GetSnode*) Message_bytes(msg);
hdr->magic = Control_GETSNODE_QUERY_MAGIC;
hdr->version_be = Endian_hostToBigEndian32(Version_CURRENT_PROTOCOL);
hdr->kbps_be = Endian_hostToBigEndian32(p->pub.kbpsLimit);
Bits_memcpy(hdr->snodeKey, p->pub.snode.key, 32);
uint64_t pathToSnode_be = Endian_hostToBigEndian64(p->pub.snode.path);
Bits_memcpy(hdr->pathToSnode_be, &pathToSnode_be, 8);
hdr->snodeVersion_be = Endian_hostToBigEndian32(p->pub.snode.protocolVersion);
} else if (p->pub.type == SwitchPinger_Type_RPATH) {
Err_assert(Message_epush(msg, NULL, Control_RPath_HEADER_SIZE));
struct Control_RPath* hdr = (struct Control_RPath*) Message_bytes(msg);
hdr->magic = Control_RPATH_QUERY_MAGIC;
hdr->version_be = Endian_hostToBigEndian32(Version_CURRENT_PROTOCOL);
uint64_t path_be = Endian_hostToBigEndian64(p->label);
Bits_memcpy(hdr->rpath_be, &path_be, 8);
} else if (p->pub.type == SwitchPinger_Type_LLADDR) {
struct Control_LlAddr addr = {
.magic = Control_LlAddr_QUERY_MAGIC,
.version_be = Endian_hostToBigEndian32(Version_CURRENT_PROTOCOL),
// Lazy
.addr.payload = { .type = 0, .len = 2, }
};
Err_assert(Message_epush(msg, &addr, sizeof addr));
} else {
Assert_failure("unexpected ping type");
}
Err_assert(Message_eshift(msg, Control_Header_SIZE));
struct Control* ctrl = (struct Control*) Message_bytes(msg);
ctrl->header.checksum_be = 0;
if (p->pub.type == SwitchPinger_Type_PING) {
ctrl->header.type_be = Control_PING_be;
} else if (p->pub.type == SwitchPinger_Type_KEYPING) {
ctrl->header.type_be = Control_KEYPING_be;
} else if (p->pub.type == SwitchPinger_Type_GETSNODE) {
ctrl->header.type_be = Control_GETSNODE_QUERY_be;
} else if (p->pub.type == SwitchPinger_Type_RPATH) {
ctrl->header.type_be = Control_RPATH_QUERY_be;
} else if (p->pub.type == SwitchPinger_Type_LLADDR) {
ctrl->header.type_be = Control_LlAddr_QUERY_be;
} else {
Assert_failure("unexpected type");
}
ctrl->header.checksum_be = Checksum_engine_be(Message_bytes(msg), Message_getLength(msg));
struct RouteHeader rh;
Bits_memset(&rh, 0, RouteHeader_SIZE);
rh.flags |= RouteHeader_flags_CTRLMSG;
rh.sh.label_be = Endian_hostToBigEndian64(p->label);
SwitchHeader_setVersion(&rh.sh, SwitchHeader_CURRENT_VERSION);
Err_assert(Message_epush(msg, &rh, RouteHeader_SIZE));
Iface_send(&p->context->pub.controlHandlerIf, msg);
}
static String* RESULT_STRING_OK = String_CONST_SO("pong");
static String* RESULT_STRING_LABEL_MISMATCH = String_CONST_SO("diff_label");
static String* RESULT_STRING_WRONG_DATA = String_CONST_SO("diff_data");
static String* RESULT_STRING_ERROR_RESPONSE = String_CONST_SO("err_switch");
static String* RESULT_STRING_TIMEOUT = String_CONST_SO("timeout");
static String* RESULT_STRING_UNKNOWN = String_CONST_SO("err_unknown");
static String* RESULT_STRING_LOOP = String_CONST_SO("err_loop");
String* SwitchPinger_resultString(enum SwitchPinger_Result result)
{
switch (result) {
case SwitchPinger_Result_OK:
return RESULT_STRING_OK;
case SwitchPinger_Result_LABEL_MISMATCH:
return RESULT_STRING_LABEL_MISMATCH;
case SwitchPinger_Result_WRONG_DATA:
return RESULT_STRING_WRONG_DATA;
case SwitchPinger_Result_ERROR_RESPONSE:
return RESULT_STRING_ERROR_RESPONSE;
case SwitchPinger_Result_TIMEOUT:
return RESULT_STRING_TIMEOUT;
case SwitchPinger_Result_LOOP_ROUTE:
return RESULT_STRING_LOOP;
default:
return RESULT_STRING_UNKNOWN;
};
}
static void onPingFree(struct Allocator_OnFreeJob* job)
{
struct Ping* ping = Identity_check((struct Ping*)job->userData);
struct SwitchPinger_pvt* ctx = Identity_check(ping->context);
ctx->outstandingPings--;
Assert_true(ctx->outstandingPings >= 0);
}
struct SwitchPinger_Ping* SwitchPinger_newPing(uint64_t label,
String* data,
uint32_t timeoutMilliseconds,
SwitchPinger_ResponseCallback onResponse,
struct Allocator* alloc,
struct SwitchPinger* context)
{
struct SwitchPinger_pvt* ctx = Identity_check((struct SwitchPinger_pvt*)context);
if (data && data->len > Control_Ping_MAX_SIZE) {
return NULL;
}
if (ctx->outstandingPings > ctx->maxConcurrentPings) {
Log_debug(ctx->logger, "Skipping switch ping because there are already [%d] outstanding",
ctx->outstandingPings);
return NULL;
}
struct Pinger_Ping* pp =
Pinger_newPing(data, onPingResponse, sendPing, timeoutMilliseconds, alloc, ctx->pinger);
struct Ping* ping = Allocator_clone(pp->pingAlloc, (&(struct Ping) {
.pub = {
.pingAlloc = pp->pingAlloc
},
.label = label,
.data = String_clone(data, pp->pingAlloc),
.context = ctx,
.onResponse = onResponse,
.pingerPing = pp
}));
Identity_set(ping);
Allocator_onFree(pp->pingAlloc, onPingFree, ping);
pp->context = ping;
ctx->outstandingPings++;
return &ping->pub;
}
struct SwitchPinger* SwitchPinger_new(EventBase_t* eventBase,
struct Random* rand,
struct Log* logger,
struct Address* myAddr,
struct Allocator* allocator)
{
struct Allocator* alloc = Allocator_child(allocator);
struct SwitchPinger_pvt* sp = Allocator_malloc(alloc, sizeof(struct SwitchPinger_pvt));
Bits_memcpy(sp, (&(struct SwitchPinger_pvt) {
.pub = {
.controlHandlerIf = {
.send = messageFromControlHandler
}
},
.pinger = Pinger_new(eventBase, rand, logger, alloc),
.logger = logger,
.allocator = alloc,
.myAddr = myAddr,
.maxConcurrentPings = SwitchPinger_DEFAULT_MAX_CONCURRENT_PINGS,
}), sizeof(struct SwitchPinger_pvt));
Identity_set(sp);
return &sp->pub;
}