/* 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 . */ #include "crypto/AddressCalc.h" #include "crypto/CryptoAuth_pvt.h" #include "net/DefaultInterfaceController.h" #include "memory/Allocator.h" #include "net/SwitchPinger.h" #include "util/Base32.h" #include "util/Bits.h" #include "util/events/Time.h" #include "util/events/Timeout.h" #include "util/Identity.h" #include "util/version/Version.h" #include "wire/Error.h" #include "wire/Message.h" #include // offsetof /** After this number of milliseconds, a node will be regarded as unresponsive. */ #define UNRESPONSIVE_AFTER_MILLISECONDS (20*1024) /** * After this number of milliseconds without a valid incoming message, * a peer is "lazy" and should be pinged. */ #define PING_AFTER_MILLISECONDS (3*1024) /** How often to ping "lazy" peers, "unresponsive" peers are only pinged 20% of the time. */ #define PING_INTERVAL_MILLISECONDS 1024 /** The number of milliseconds to wait for a ping response. */ #define TIMEOUT_MILLISECONDS (2*1024) /** * The number of seconds to wait before an unresponsive peer * making an incoming connection is forgotten. */ #define FORGET_AFTER_MILLISECONDS (256*1024) /*--------------------Structs--------------------*/ struct IFCPeer { /** The interface which is registered with the switch. */ struct Interface switchIf; /** The internal (wrapped by CryptoAuth) interface. */ struct Interface* cryptoAuthIf; /** The external (network side) interface. */ struct Interface* external; /** The label for this endpoint, needed to ping the endpoint. */ uint64_t switchLabel; /** Milliseconds since the epoch when the last *valid* message was received. */ uint64_t timeOfLastMessage; /** The handle which can be used to look up this endpoint in the endpoint set. */ uint32_t handle; /** True if we should forget about the peer if they do not respond. */ bool isIncomingConnection : 1; /** * If InterfaceController_PeerState_UNAUTHENTICATED, no permanent state will be kept. * During transition from HANDSHAKE to ESTABLISHED, a check is done for a registeration of a * node which is already registered in a different switch slot, if there is one and the * handshake completes, it will be moved. */ int state : 31; // traffic counters uint64_t bytesOut; uint64_t bytesIn; Identity }; #define Map_NAME OfIFCPeerByExernalIf #define Map_ENABLE_HANDLES #define Map_KEY_TYPE struct Interface* #define Map_VALUE_TYPE struct IFCPeer* #include "util/Map.h" struct Context { /** Public functions and fields for this ifcontroller. */ struct InterfaceController pub; /** Used to get a peer by its handle. */ struct Map_OfIFCPeerByExernalIf peerMap; struct Allocator* const allocator; struct CryptoAuth* const ca; /** Switch for adding nodes when they are discovered. */ struct SwitchCore* const switchCore; /** Router needed to inject newly added nodes to bootstrap the system. */ struct RouterModule* const routerModule; struct Log* const logger; struct EventBase* const eventBase; /** After this number of milliseconds, a neoghbor will be regarded as unresponsive. */ uint32_t unresponsiveAfterMilliseconds; /** The number of milliseconds to wait before pinging. */ uint32_t pingAfterMilliseconds; /** The number of milliseconds to let a ping go before timing it out. */ uint32_t timeoutMilliseconds; /** After this number of milliseconds, an incoming connection is forgotten entirely. */ uint32_t forgetAfterMilliseconds; /** A counter to allow for 3/4 of all pings to be skipped when a node is definitely down. */ uint32_t pingCount; /** The timeout event to use for pinging potentially unresponsive neighbors. */ struct Timeout* const pingInterval; /** For pinging lazy/unresponsive nodes. */ struct SwitchPinger* const switchPinger; /** A password which is generated per-startup and sent out in beacon messages. */ uint8_t beaconPassword[Headers_Beacon_PASSWORD_LEN]; Identity }; //---------------// static inline struct Context* ifcontrollerForPeer(struct IFCPeer* ep) { return Identity_cast((struct Context*) ep->switchIf.senderContext); } static void onPingResponse(enum SwitchPinger_Result result, uint64_t label, String* data, uint32_t millisecondsLag, uint32_t version, void* onResponseContext) { if (SwitchPinger_Result_OK != result) { return; } struct IFCPeer* ep = Identity_cast((struct IFCPeer*) onResponseContext); struct Context* ic = ifcontrollerForPeer(ep); struct Address addr; Bits_memset(&addr, 0, sizeof(struct Address)); Bits_memcpyConst(addr.key, CryptoAuth_getHerPublicKey(ep->cryptoAuthIf), 32); addr.path = ep->switchLabel; Log_debug(ic->logger, "got switch pong from node with version [%d]", version); RouterModule_addNode(ic->routerModule, &addr, version); #ifdef Log_DEBUG // This will be false if it times out. //Assert_true(label == ep->switchLabel); uint8_t path[20]; AddrTools_printPath(path, label); uint8_t sl[20]; AddrTools_printPath(sl, ep->switchLabel); Log_debug(ic->logger, "Received [%s] from lazy endpoint [%s] [%s]", SwitchPinger_resultString(result)->bytes, path, sl); #endif } // Called from the pingInteral timeout. static void pingCallback(void* vic) { struct Context* ic = Identity_cast((struct Context*) vic); uint64_t now = Time_currentTimeMilliseconds(ic->eventBase); ic->pingCount++; // scan for endpoints have not sent anything recently. for (uint32_t i = 0; i < ic->peerMap.count; i++) { struct IFCPeer* ep = ic->peerMap.values[i]; if (now > ep->timeOfLastMessage + ic->pingAfterMilliseconds) { #ifdef Log_DEBUG uint8_t key[56]; Base32_encode(key, 56, CryptoAuth_getHerPublicKey(ep->cryptoAuthIf), 32); #endif if (ep->isIncomingConnection && now > ep->timeOfLastMessage + ic->forgetAfterMilliseconds) { Log_debug(ic->logger, "Unresponsive peer [%s.k] has not responded in [%u] " "seconds, dropping connection", key, ic->forgetAfterMilliseconds / 1024); Allocator_free(ep->external->allocator); return; } bool unresponsive = (now > ep->timeOfLastMessage + ic->unresponsiveAfterMilliseconds); uint32_t lag = ~0u; if (unresponsive) { // flush the peer from the table... RouterModule_brokenPath(ep->switchLabel, ic->routerModule); // Lets skip 87% of pings when they're really down. if (ic->pingCount % 8) { continue; } ep->state = InterfaceController_PeerState_UNRESPONSIVE; lag = ((now - ep->timeOfLastMessage) / 1024); } else { lag = ((now - ep->timeOfLastMessage) / 1024); } struct SwitchPinger_Ping* ping = SwitchPinger_newPing(ep->switchLabel, String_CONST(""), ic->timeoutMilliseconds, onPingResponse, ic->allocator, ic->switchPinger); if (!ping) { Log_debug(ic->logger, "Failed to ping %s peer [%s.k] lag [%u], out of ping slots.", (unresponsive ? "unresponsive" : "lazy"), key, lag); return; } ping->onResponseContext = ep; SwitchPinger_sendPing(ping); Log_debug(ic->logger, "Pinging %s peer [%s.k] lag [%u]", (unresponsive ? "unresponsive" : "lazy"), key, lag); } } } /** If there's already an endpoint with the same public key, merge the new one with the old one. */ static void moveEndpointIfNeeded(struct IFCPeer* ep, struct Context* ic) { Log_debug(ic->logger, "Checking for old sessions to merge with."); uint8_t* key = CryptoAuth_getHerPublicKey(ep->cryptoAuthIf); for (uint32_t i = 0; i < ic->peerMap.count; i++) { struct IFCPeer* thisEp = ic->peerMap.values[i]; uint8_t* thisKey = CryptoAuth_getHerPublicKey(thisEp->cryptoAuthIf); if (thisEp != ep && !Bits_memcmp(thisKey, key, 32)) { Log_info(ic->logger, "Moving endpoint to merge new session with old."); ep->switchLabel = thisEp->switchLabel; SwitchCore_swapInterfaces(&thisEp->switchIf, &ep->switchIf); Allocator_free(thisEp->external->allocator); return; } } } // Incoming message which has passed through the cryptoauth and needs to be forwarded to the switch. static uint8_t receivedAfterCryptoAuth(struct Message* msg, struct Interface* cryptoAuthIf) { struct IFCPeer* ep = Identity_cast((struct IFCPeer*) cryptoAuthIf->receiverContext); struct Context* ic = ifcontrollerForPeer(ep); ep->bytesIn += msg->length; if (ep->state < InterfaceController_PeerState_ESTABLISHED) { if (CryptoAuth_getState(cryptoAuthIf) >= CryptoAuth_HANDSHAKE3) { moveEndpointIfNeeded(ep, ic); ep->state = InterfaceController_PeerState_ESTABLISHED; } else { ep->state = InterfaceController_PeerState_HANDSHAKE; // prevent some kinds of nasty things which could be done with packet replay. // This is checking the message switch header and will drop it unless the label // directs it to *this* router. if (msg->length < 8 || msg->bytes[7] != 1) { Log_info(ic->logger, "Dropping message because CA is not established."); return Error_NONE; } else { // When a "server" gets a new connection from a "client" the router doesn't // know about that client so if the client sends a packet to the server, the // server will be unable to handle it until the client has sent inter-router // communication to the server. Here we will ping the client so when the // server gets the ping response, it will insert the client into its table // and know its version. // prevent DoS by limiting the number of times this can be called per second // limit it to 7, this will affect innocent packets but it doesn't matter much // since this is mostly just an optimization and for keeping the tests happy. if ((ic->pingCount + 1) % 7) { pingCallback(ic); } } } } else if (ep->state == InterfaceController_PeerState_UNRESPONSIVE && CryptoAuth_getState(cryptoAuthIf) >= CryptoAuth_HANDSHAKE3) { ep->state = InterfaceController_PeerState_ESTABLISHED; } else { ep->timeOfLastMessage = Time_currentTimeMilliseconds(ic->eventBase); } return ep->switchIf.receiveMessage(msg, &ep->switchIf); } // This is directly called from SwitchCore, message is not encrypted. static uint8_t sendFromSwitch(struct Message* msg, struct Interface* switchIf) { struct IFCPeer* ep = Identity_cast((struct IFCPeer*) switchIf); ep->bytesOut += msg->length; // This sucks but cryptoauth trashes the content when it encrypts // and we need to be capable of sending back a coherent error message. uint8_t top[255]; uint8_t* messageBytes = msg->bytes; uint16_t padding = msg->padding; uint16_t len = (msg->length < 255) ? msg->length : 255; Bits_memcpy(top, msg->bytes, len); struct Context* ic = ifcontrollerForPeer(ep); uint8_t ret; uint64_t now = Time_currentTimeMilliseconds(ic->eventBase); if (now - ep->timeOfLastMessage > ic->unresponsiveAfterMilliseconds) { // XXX: This is a hack because if the time of last message exceeds the // unresponsive time, we need to send back an error and that means // mangling the message which would otherwise be in the queue. struct Allocator* tempAlloc = Allocator_child(ic->allocator); struct Message* toSend = Message_clone(msg, tempAlloc); ret = Interface_sendMessage(ep->cryptoAuthIf, toSend); Allocator_free(tempAlloc); } else { ret = Interface_sendMessage(ep->cryptoAuthIf, msg); } // If this node is unresponsive then return an error. if (ret || now - ep->timeOfLastMessage > ic->unresponsiveAfterMilliseconds) { msg->bytes = messageBytes; msg->padding = padding; msg->length = len; Bits_memcpy(msg->bytes, top, len); return ret ? ret : Error_UNDELIVERABLE; } else { /* Way way way too much noise Log_debug(ic->logger, "Sending to neighbor, last message from this node was [%u] ms ago.", (now - ep->timeOfLastMessage)); */ } return Error_NONE; } static int closeInterface(struct Allocator_OnFreeJob* job) { struct IFCPeer* toClose = Identity_cast((struct IFCPeer*) job->userData); struct Context* ic = ifcontrollerForPeer(toClose); // flush the peer from the table... RouterModule_brokenPath(toClose->switchLabel, ic->routerModule); int index = Map_OfIFCPeerByExernalIf_indexForHandle(toClose->handle, &ic->peerMap); Assert_true(index >= 0); Map_OfIFCPeerByExernalIf_remove(index, &ic->peerMap); return 0; } static int registerPeer(struct InterfaceController* ifController, uint8_t herPublicKey[32], String* password, bool requireAuth, bool isIncomingConnection, struct Interface* externalInterface) { struct Context* ic = Identity_cast((struct Context*) ifController); Log_debug(ic->logger, "registerPeer [%p] total [%u]", (void*)externalInterface, ic->peerMap.count); if (Map_OfIFCPeerByExernalIf_indexForKey(&externalInterface, &ic->peerMap) > -1) { Log_debug(ic->logger, "Skipping registerPeer [%p] because peer is already registered", (void*)externalInterface); return 0; } uint8_t ip6[16]; if (herPublicKey) { AddressCalc_addressForPublicKey(ip6, herPublicKey); if (!AddressCalc_validAddress(ip6)) { return InterfaceController_registerPeer_BAD_KEY; } } struct Allocator* epAllocator = externalInterface->allocator; struct IFCPeer* ep = Allocator_calloc(epAllocator, sizeof(struct IFCPeer), 1); ep->bytesOut = 0; ep->bytesIn = 0; ep->external = externalInterface; int setIndex = Map_OfIFCPeerByExernalIf_put(&externalInterface, &ep, &ic->peerMap); ep->handle = ic->peerMap.handles[setIndex]; Identity_set(ep); Allocator_onFree(epAllocator, closeInterface, ep); // If the other end need not supply a valid password to connect // we will set the connection state to HANDSHAKE because we don't // want the connection to be trashed after the first invalid packet. if (!requireAuth) { ep->state = InterfaceController_PeerState_HANDSHAKE; } ep->cryptoAuthIf = CryptoAuth_wrapInterface(externalInterface, herPublicKey, requireAuth, true, ic->ca); ep->cryptoAuthIf->receiveMessage = receivedAfterCryptoAuth; ep->cryptoAuthIf->receiverContext = ep; // Always use authType 1 until something else comes along, then we'll have to refactor. if (password) { CryptoAuth_setAuth(password, 1, ep->cryptoAuthIf); } ep->isIncomingConnection = isIncomingConnection; Bits_memcpyConst(&ep->switchIf, (&(struct Interface) { .sendMessage = sendFromSwitch, // ifcontrollerForPeer uses this. // sendFromSwitch relies on the fact that the // switchIf is the same memory location as the Peer. .senderContext = ic, .allocator = epAllocator }), sizeof(struct Interface)); int ret = SwitchCore_addInterface(&ep->switchIf, 0, &ep->switchLabel, ic->switchCore); if (ret) { return (ret == SwitchCore_addInterface_OUT_OF_SPACE) ? InterfaceController_registerPeer_OUT_OF_SPACE : InterfaceController_registerPeer_INTERNAL; } // We want the node to immedietly be pinged but we don't want it to appear unresponsive because // the pinger will only ping every (PING_INTERVAL * 8) so we set timeOfLastMessage to // (now - pingAfterMilliseconds - 1) so it will be considered a "lazy node". ep->timeOfLastMessage = Time_currentTimeMilliseconds(ic->eventBase) - ic->pingAfterMilliseconds - 1; if (herPublicKey) { #ifdef Log_INFO uint8_t printAddr[60]; AddrTools_printIp(printAddr, ip6); Log_info(ic->logger, "Adding peer [%s]", printAddr); #endif // Kick the ping callback so that the node will be pinged ASAP. pingCallback(ic); } return 0; } static enum InterfaceController_PeerState getPeerState(struct Interface* iface) { struct Interface* cryptoAuthIf = CryptoAuth_getConnectedInterface(iface); struct IFCPeer* p = Identity_cast((struct IFCPeer*) cryptoAuthIf->receiverContext); return p->state; } static void populateBeacon(struct InterfaceController* ifc, struct Headers_Beacon* beacon) { struct Context* ic = Identity_cast((struct Context*) ifc); beacon->version_be = Endian_hostToBigEndian32(Version_CURRENT_PROTOCOL); Bits_memcpyConst(beacon->password, ic->beaconPassword, Headers_Beacon_PASSWORD_LEN); Bits_memcpyConst(beacon->publicKey, ic->ca->publicKey, 32); } static int getPeerStats(struct InterfaceController* ifController, struct Allocator* alloc, struct InterfaceController_peerStats** statsOut) { struct Context* ic = Identity_cast((struct Context*) ifController); int count = ic->peerMap.count; struct InterfaceController_peerStats* stats = Allocator_malloc(alloc, sizeof(struct InterfaceController_peerStats)*count); for (int i = 0; i < count; i++) { struct IFCPeer* peer = ic->peerMap.values[i]; struct InterfaceController_peerStats* s = &stats[i]; s->pubKey = CryptoAuth_getHerPublicKey(peer->cryptoAuthIf); s->bytesOut = peer->bytesOut; s->bytesIn = peer->bytesIn; s->timeOfLastMessage = peer->timeOfLastMessage; s->state = peer->state; s->switchLabel = peer->switchLabel; s->isIncomingConnection = peer->isIncomingConnection; s->user = NULL; if (s->isIncomingConnection) { s->user = CryptoAuth_getUser(peer->cryptoAuthIf); } struct ReplayProtector* rp = CryptoAuth_getReplayProtector(peer->cryptoAuthIf); s->duplicates = rp->duplicates; s->lostPackets = rp->lostPackets; s->receivedOutOfRange = rp->receivedOutOfRange; } *statsOut = stats; return count; } static int disconnectPeer(struct InterfaceController* ifController, uint8_t herPublicKey[32]) { struct Context* ic = Identity_cast((struct Context*) ifController); for (uint32_t i = 0; i < ic->peerMap.count; i++) { struct IFCPeer* peer = ic->peerMap.values[i]; if (!Bits_memcmp(herPublicKey, CryptoAuth_getHerPublicKey(peer->cryptoAuthIf), 32)) { Allocator_free(peer->external->allocator); return 0; } } return InterfaceController_disconnectPeer_NOTFOUND; } struct InterfaceController* DefaultInterfaceController_new(struct CryptoAuth* ca, struct SwitchCore* switchCore, struct RouterModule* routerModule, struct Log* logger, struct EventBase* eventBase, struct SwitchPinger* switchPinger, struct Random* rand, struct Allocator* allocator) { struct Context* out = Allocator_malloc(allocator, sizeof(struct Context)); Bits_memcpyConst(out, (&(struct Context) { .pub = { .registerPeer = registerPeer, .disconnectPeer = disconnectPeer, .getPeerState = getPeerState, .populateBeacon = populateBeacon, .getPeerStats = getPeerStats, }, .peerMap = { .allocator = allocator }, .allocator = allocator, .ca = ca, .switchCore = switchCore, .routerModule = routerModule, .logger = logger, .eventBase = eventBase, .switchPinger = switchPinger, .unresponsiveAfterMilliseconds = UNRESPONSIVE_AFTER_MILLISECONDS, .pingAfterMilliseconds = PING_AFTER_MILLISECONDS, .timeoutMilliseconds = TIMEOUT_MILLISECONDS, .forgetAfterMilliseconds = FORGET_AFTER_MILLISECONDS, .pingInterval = (switchPinger) ? Timeout_setInterval(pingCallback, out, PING_INTERVAL_MILLISECONDS, eventBase, allocator) : NULL }), sizeof(struct Context)); Identity_set(out); // Add the beaconing password. Random_bytes(rand, out->beaconPassword, Headers_Beacon_PASSWORD_LEN); String strPass = { .bytes=(char*)out->beaconPassword, .len=Headers_Beacon_PASSWORD_LEN }; int ret = CryptoAuth_addUser(&strPass, 1, String_CONST("Local Peers"), ca); if (ret) { Log_warn(logger, "CryptoAuth_addUser() returned [%d]", ret); } return &out->pub; }