summaryrefslogtreecommitdiff
path: root/node/PacketDecoder.cpp
diff options
context:
space:
mode:
authorAdam Ierymenko <adam.ierymenko@gmail.com>2013-08-05 12:16:25 -0400
committerAdam Ierymenko <adam.ierymenko@gmail.com>2013-08-05 12:16:25 -0400
commitb9aeec9f298415746f92da8444014093bb2a33c2 (patch)
tree8f73cf1516ddd6c25f983564950b646150c55828 /node/PacketDecoder.cpp
parentbf5c07f79a0f78e714fe47a5d1e09330022261ea (diff)
downloadinfinitytier-b9aeec9f298415746f92da8444014093bb2a33c2.tar.gz
infinitytier-b9aeec9f298415746f92da8444014093bb2a33c2.zip
Documentation improvements and some very minor pre-emptive security stuff.
Diffstat (limited to 'node/PacketDecoder.cpp')
-rw-r--r--node/PacketDecoder.cpp133
1 files changed, 86 insertions, 47 deletions
diff --git a/node/PacketDecoder.cpp b/node/PacketDecoder.cpp
index 8d02b425..0262c2a8 100644
--- a/node/PacketDecoder.cpp
+++ b/node/PacketDecoder.cpp
@@ -35,6 +35,21 @@
#include "Filter.hpp"
#include "Service.hpp"
+/*
+ * The big picture:
+ *
+ * tryDecode() gets called for a given fully-assembled packet until it returns
+ * true or the packet's time to live has been exceeded. The state machine must
+ * therefore be re-entrant if it ever returns false. Take care here!
+ *
+ * Stylistic note:
+ *
+ * There's a lot of unnecessary if nesting. It's mostly to allow TRACE to
+ * print informative messages on every possible reason something gets
+ * rejected or fails. Sometimes it also makes code more explicit and thus
+ * easier to understand.
+ */
+
namespace ZeroTier {
bool PacketDecoder::tryDecode(const RuntimeEnvironment *_r)
@@ -50,14 +65,15 @@ bool PacketDecoder::tryDecode(const RuntimeEnvironment *_r)
SharedPtr<Peer> peer = _r->topology->getPeer(source());
if (peer) {
- if (_step == DECODE_STEP_WAITING_FOR_ORIGINAL_SUBMITTER_LOOKUP) {
- // This means we've already decoded, decrypted, decompressed, and
- // validated, and we're processing a MULTICAST_FRAME. We're waiting
- // for a lookup on the frame's original submitter. So try again and
- // see if we have it.
+ // Resume saved state?
+ if (_step == DECODE_WAITING_FOR_MULTICAST_FRAME_ORIGINAL_SENDER_LOOKUP) {
+ // In this state we have already authenticated and decrypted the
+ // packet and are waiting for the lookup of the original sender
+ // for a multicast frame. So check to see if we've got it.
return _doMULTICAST_FRAME(_r,peer);
}
+ // No saved state? Verify MAC before we proceed.
if (!hmacVerify(peer->macKey())) {
TRACE("dropped packet from %s(%s), HMAC authentication failed (size: %u)",source().toString().c_str(),_remoteAddress.toString().c_str(),size());
return true;
@@ -80,8 +96,9 @@ bool PacketDecoder::tryDecode(const RuntimeEnvironment *_r)
Packet::Verb v = verb();
- // Validated packets that have passed HMAC can result in us learning a new
- // path to this peer.
+ // Once a packet is determined to be basically valid, it can be used
+ // to passively learn a new network path to the sending peer. It
+ // also results in statistics updates.
peer->onReceive(_r,_localPort,_remoteAddress,hops(),v,Utils::now());
switch(v) {
@@ -89,7 +106,7 @@ bool PacketDecoder::tryDecode(const RuntimeEnvironment *_r)
TRACE("NOP from %s(%s)",source().toString().c_str(),_remoteAddress.toString().c_str());
return true;
case Packet::VERB_HELLO:
- return _doHELLO(_r);
+ return _doHELLO(_r); // encrypted HELLO is technically allowed, but kind of pointless... :)
case Packet::VERB_ERROR:
return _doERROR(_r,peer);
case Packet::VERB_OK:
@@ -118,7 +135,7 @@ bool PacketDecoder::tryDecode(const RuntimeEnvironment *_r)
return true;
}
} else {
- _step = DECODE_STEP_WAITING_FOR_SENDER_LOOKUP;
+ _step = DECODE_WAITING_FOR_SENDER_LOOKUP; // should already be this...
_r->sw->requestWhois(source());
return false;
}
@@ -213,27 +230,26 @@ bool PacketDecoder::_doHELLO(const RuntimeEnvironment *_r)
uint64_t timestamp = at<uint64_t>(ZT_PROTO_VERB_HELLO_IDX_TIMESTAMP);
Identity id(*this,ZT_PROTO_VERB_HELLO_IDX_IDENTITY);
+ // Create a new candidate peer that we might decide to add to our
+ // database. We create it now since we want its keys to send replies
+ // even in the error case, and the code for keying is in Peer.
SharedPtr<Peer> candidate(new Peer(_r->identity,id));
candidate->setPathAddress(_remoteAddress,false);
- // Initial sniff test
- if (id.address().isReserved()) {
- TRACE("rejected HELLO from %s(%s): identity has reserved address",source().toString().c_str(),_remoteAddress.toString().c_str());
- Packet outp(source(),_r->identity.address(),Packet::VERB_ERROR);
- outp.append((unsigned char)Packet::VERB_HELLO);
- outp.append(packetId());
- outp.append((unsigned char)Packet::ERROR_IDENTITY_INVALID);
- outp.encrypt(candidate->cryptKey());
- outp.hmacSet(candidate->macKey());
- _r->demarc->send(_localPort,_remoteAddress,outp.data(),outp.size(),-1);
- return true;
- }
- if (id.address() != source()) {
- TRACE("rejected HELLO from %s(%s): identity is not for sender of packet (HELLO is a self-announcement)",source().toString().c_str(),_remoteAddress.toString().c_str());
+ // The initial sniff test... is the identity valid, and is it
+ // the sender's identity?
+ if ((id.address().isReserved())||(id.address() != source())) {
+#ifdef ZT_TRACE
+ if (id.address().isReserved()) {
+ TRACE("rejected HELLO from %s(%s): identity has reserved address",source().toString().c_str(),_remoteAddress.toString().c_str());
+ } else {
+ TRACE("rejected HELLO from %s(%s): identity is not for sender of packet (HELLO is a self-announcement)",source().toString().c_str(),_remoteAddress.toString().c_str());
+ }
+#endif
Packet outp(source(),_r->identity.address(),Packet::VERB_ERROR);
outp.append((unsigned char)Packet::VERB_HELLO);
outp.append(packetId());
- outp.append((unsigned char)Packet::ERROR_INVALID_REQUEST);
+ outp.append((unsigned char)((id.address().isReserved()) ? Packet::ERROR_IDENTITY_INVALID : Packet::ERROR_INVALID_REQUEST));
outp.encrypt(candidate->cryptKey());
outp.hmacSet(candidate->macKey());
_r->demarc->send(_localPort,_remoteAddress,outp.data(),outp.size(),-1);
@@ -257,7 +273,9 @@ bool PacketDecoder::_doHELLO(const RuntimeEnvironment *_r)
return true;
}
- // Otherwise we call addPeer() and set up a callback to handle the verdict
+ // Otherwise we call addPeer() and set up a callback to handle the verdict.
+ // Topology evaluates the peer in the background, possibly doing the entire
+ // expensive analysis before determining whether to add it to the database.
_CBaddPeerFromHello_Data *arg = new _CBaddPeerFromHello_Data;
arg->renv = _r;
arg->source = source();
@@ -288,19 +306,21 @@ bool PacketDecoder::_doOK(const RuntimeEnvironment *_r,const SharedPtr<Peer> &pe
TRACE("%s(%s): OK(HELLO), latency: %u",source().toString().c_str(),_remoteAddress.toString().c_str(),latency);
peer->setLatency(_remoteAddress,latency);
} break;
- case Packet::VERB_WHOIS:
- // Right now we only query supernodes for WHOIS and only accept
- // OK back from them. If we query other nodes, we'll have to
- // do something to prevent WHOIS cache poisoning such as
- // using the packet ID field in the OK packet to match with the
- // original query. Technically we should be doing this anyway.
+ case Packet::VERB_WHOIS: {
TRACE("%s(%s): OK(%s)",source().toString().c_str(),_remoteAddress.toString().c_str(),Packet::verbString(inReVerb));
- if (_r->topology->isSupernode(source()))
+ if (_r->topology->isSupernode(source())) {
+ // Right now, only supernodes are queried for WHOIS so we only
+ // accept OK(WHOIS) from supernodes. Otherwise peers could
+ // potentially cache-poison. A more elegant but memory-intensive
+ // solution would be to remember packet IDs of WHOIS requests.
_r->topology->addPeer(SharedPtr<Peer>(new Peer(_r->identity,Identity(*this,ZT_PROTO_VERB_WHOIS__OK__IDX_IDENTITY))),&PacketDecoder::_CBaddPeerFromWhois,const_cast<void *>((const void *)_r));
- break;
+ }
+ } break;
case Packet::VERB_NETWORK_CONFIG_REQUEST: {
SharedPtr<Network> nw(_r->nc->network(at<uint64_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_NETWORK_ID)));
if ((nw)&&(nw->controller() == source())) {
+ // Only accept OK(NETWORK_CONFIG_REQUEST) from masters for
+ // networks we have.
unsigned int dictlen = at<uint16_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT_LEN);
std::string dict((const char *)field(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT,dictlen),dictlen);
if (dict.length()) {
@@ -357,20 +377,38 @@ bool PacketDecoder::_doWHOIS(const RuntimeEnvironment *_r,const SharedPtr<Peer>
bool PacketDecoder::_doRENDEZVOUS(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer)
{
try {
- Address with(field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ZTADDRESS,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH);
- SharedPtr<Peer> withPeer(_r->topology->getPeer(with));
- if (withPeer) {
- unsigned int port = at<uint16_t>(ZT_PROTO_VERB_RENDEZVOUS_IDX_PORT);
- unsigned int addrlen = (*this)[ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRLEN];
- if ((port > 0)&&((addrlen == 4)||(addrlen == 16))) {
- InetAddress atAddr(field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRESS,addrlen),addrlen,port);
- TRACE("RENDEZVOUS from %s says %s might be at %s, starting NAT-t",source().toString().c_str(),with.toString().c_str(),atAddr.toString().c_str());
- _r->sw->contact(withPeer,atAddr);
+ //
+ // At the moment, we only obey RENDEZVOUS if it comes from a designated
+ // supernode. If relay offloading is implemented to scale the net, this
+ // will need reconsideration.
+ //
+ // The reason is that RENDEZVOUS could technically be used to cause a
+ // peer to send a weird encrypted UDP packet to an arbitrary IP:port.
+ // The sender of RENDEZVOUS has no control over the content of this
+ // packet, but it's still maybe something we want to not allow just
+ // anyone to order due to possible DDOS or network forensic implications.
+ // So if we diversify relays, we'll need some way of deciding whether the
+ // sender is someone we should trust with a RENDEZVOUS hint. Or maybe
+ // we just need rate limiting to prevent DDOS and amplification attacks.
+ //
+ if (_r->topology->isSupernode(source())) {
+ Address with(field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ZTADDRESS,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH);
+ SharedPtr<Peer> withPeer(_r->topology->getPeer(with));
+ if (withPeer) {
+ unsigned int port = at<uint16_t>(ZT_PROTO_VERB_RENDEZVOUS_IDX_PORT);
+ unsigned int addrlen = (*this)[ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRLEN];
+ if ((port > 0)&&((addrlen == 4)||(addrlen == 16))) {
+ InetAddress atAddr(field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRESS,addrlen),addrlen,port);
+ TRACE("RENDEZVOUS from %s says %s might be at %s, starting NAT-t",source().toString().c_str(),with.toString().c_str(),atAddr.toString().c_str());
+ _r->sw->contact(withPeer,atAddr);
+ } else {
+ TRACE("dropped corrupt RENDEZVOUS from %s(%s) (bad address or port)",source().toString().c_str(),_remoteAddress.toString().c_str());
+ }
} else {
- TRACE("dropped corrupt RENDEZVOUS from %s(%s) (bad address or port)",source().toString().c_str(),_remoteAddress.toString().c_str());
+ TRACE("ignored RENDEZVOUS from %s(%s) to meet unknown peer %s",source().toString().c_str(),_remoteAddress.toString().c_str(),with.toString().c_str());
}
} else {
- TRACE("ignored RENDEZVOUS from %s(%s) to meet unknown peer %s",source().toString().c_str(),_remoteAddress.toString().c_str(),with.toString().c_str());
+ TRACE("ignored RENDEZVOUS from %s(%s): source not supernode",source().toString().c_str(),_remoteAddress.toString().c_str());
}
} catch (std::exception &ex) {
TRACE("dropped RENDEZVOUS from %s(%s): %s",source().toString().c_str(),_remoteAddress.toString().c_str(),ex.what());
@@ -487,7 +525,7 @@ bool PacketDecoder::_doMULTICAST_FRAME(const RuntimeEnvironment *_r,const Shared
if (!originalSubmitter) {
TRACE("requesting WHOIS on original multicast frame submitter %s",originalSubmitterAddress.toString().c_str());
_r->sw->requestWhois(originalSubmitterAddress);
- _step = DECODE_STEP_WAITING_FOR_ORIGINAL_SUBMITTER_LOOKUP;
+ _step = DECODE_WAITING_FOR_MULTICAST_FRAME_ORIGINAL_SENDER_LOOKUP;
return false; // try again if/when we get OK(WHOIS)
} else if (Multicaster::verifyMulticastPacket(originalSubmitter->identity(),network->id(),fromMac,mg,etherType,dataAndSignature,datalen,dataAndSignature + datalen,signaturelen)) {
_r->multicaster->addToDedupHistory(mccrc,now);
@@ -538,7 +576,7 @@ bool PacketDecoder::_doMULTICAST_FRAME(const RuntimeEnvironment *_r,const Shared
//TRACE("terminating MULTICAST_FRAME propagation from %s(%s): max depth reached",source().toString().c_str(),_remoteAddress.toString().c_str());
}
} else {
- LOG("rejected MULTICAST_FRAME from %s(%s) due to failed signature check (claims original sender %s)",source().toString().c_str(),_remoteAddress.toString().c_str(),originalSubmitterAddress.toString().c_str());
+ LOG("rejected MULTICAST_FRAME from %s(%s) due to failed signature check (falsely claims origin %s)",source().toString().c_str(),_remoteAddress.toString().c_str(),originalSubmitterAddress.toString().c_str());
}
} else {
TRACE("dropped redundant MULTICAST_FRAME from %s(%s)",source().toString().c_str(),_remoteAddress.toString().c_str());
@@ -575,9 +613,10 @@ bool PacketDecoder::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *_r,const
#ifndef __WINDOWS__
if (_r->netconfService) {
unsigned int dictLen = at<uint16_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN);
- std::string dict((const char *)field(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT,dictLen),dictLen);
Dictionary request;
+ if (dictLen)
+ request["meta"] = std::string((const char *)field(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT,dictLen),dictLen);
request["type"] = "netconf-request";
request["peerId"] = peer->identity().toString(false);
sprintf(tmp,"%llx",(unsigned long long)nwid);