Compare commits

...
7 Commits
Author SHA1 Message Date
Adam Ierymenko 9f28eec95c VERSION 0.6.7: revert change for GitHub issue #20
This will have to be thought out more. The old version worked fine 99% of the
time so we'll revisit this.
2013-12-31 11:36:13 -08:00
Adam Ierymenko 17126b0c6d VERSION 0.6.6: fix to path discovery
New versions will be coming fast and furious for a bit as bugs get fixed and
testing is done in prep to the first binary release.

This version fixes a problem with WAN path discovery and a possible security
issue in PacketDecoder. (see previous comments)
2013-12-31 11:24:57 -08:00
Adam Ierymenko c37cb60d3c Merge branch 'adamierymenko-dev' of shub-niggurath.zerotier.com:/git/ZeroTierOne into adamierymenko-dev 2013-12-31 14:22:05 -05:00
Adam Ierymenko 6c587b1c57 . 2013-12-31 11:22:54 -08:00
Adam Ierymenko 5917453611 Linux makefile helpful debug line. 2013-12-31 14:21:53 -05:00
Adam Ierymenko cc2a1444ae TRACE output improvements and compile fix. 2013-12-31 11:18:40 -08:00
Adam Ierymenko 10df5dcf70 Fix several things:
(1) The changes to path learning in the two previous releases were poorly thought out,
and this version should remedy that by introducing PROBE. This is basically a kind of
ECHO request and is used to authenticate endpoints that are not learned via a valid
request/response pair. Thus we will still passively learn endpoints, but securely.

(2) Turns out there was a security oversight in _doHELLO() that could have permitted...
well... I'm not sure it was exploitable to do anything particularly interesting since
a bad identity would be discarded anyway, but fix it just the same.
2013-12-31 11:03:45 -08:00
11 changed files with 153 additions and 49 deletions

No files matched your search

+4 -1
View File
@@ -7,9 +7,12 @@ DEFS=
LIBS=
ifeq ($(ZT_AUTO_UPDATE),1)
DEFS+=-DZT_AUTO_UPDATE
DEFS+=-DZT_AUTO_UPDATE
endif
# Uncomment to dump trace and log to stdout
#DEFS+=-DZT_TRACE -DZT_LOG_STDOUT
# Uncomment for a release optimized build
CFLAGS=-Wall -O3 -fvisibility=hidden -fstack-protector -pthread $(INCLUDES) -DNDEBUG $(DEFS)
STRIP=strip --strip-all
+4 -1
View File
@@ -6,9 +6,12 @@ DEFS=
LIBS=
ifeq ($(ZT_AUTO_UPDATE),1)
DEFS+=-DZT_AUTO_UPDATE
DEFS+=-DZT_AUTO_UPDATE
endif
# Uncomment to dump trace and log info to stdout (useful for debug/test)
#DEFS+=-DZT_TRACE -DZT_LOG_STDOUT
# Uncomment for a release optimized universal binary build
CFLAGS=-arch i386 -arch x86_64 -Wall -O4 -fstack-protector -pthread -mmacosx-version-min=10.6 -DNDEBUG -Wno-unused-private-field $(INCLUDES) $(DEFS)
STRIP=strip
+1
View File
@@ -48,6 +48,7 @@ const char *Packet::verbString(Verb v)
case VERB_NETWORK_MEMBERSHIP_CERTIFICATE: return "NETWORK_MEMBERSHIP_CERTIFICATE";
case VERB_NETWORK_CONFIG_REQUEST: return "NETWORK_CONFIG_REQUEST";
case VERB_NETWORK_CONFIG_REFRESH: return "NETWORK_CONFIG_REFRESH";
case VERB_PROBE: return "PROBE";
}
return "(unknown)";
}
+33 -6
View File
@@ -225,6 +225,16 @@
#define ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT_LEN (ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_NETWORK_ID + 8)
#define ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT (ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT_LEN + 2)
#define ZT_PROTO_VERB_PROBE_IDX_TIMESTAMP (ZT_PACKET_IDX_PAYLOAD)
#define ZT_PROTO_VERB_PROBE_LEN_TIMESTAMP 8
#define ZT_PROTO_VERB_PROBE_IDX_MS_SINCE_LAST_SEND (ZT_PROTO_VERB_PROBE_IDX_TIMESTAMP + ZT_PROTO_VERB_PROBE_LEN_TIMESTAMP)
#define ZT_PROTO_VERB_PROBE_LEN_MS_SINCE_LAST_SEND 8
#define ZT_PROTO_VERB_PROBE__OK__IDX_TIMESTAMP (ZT_PACKET_IDX_PAYLOAD)
#define ZT_PROTO_VERB_PROBE__OK__LEN_TIMESTAMP 8
#define ZT_PROTO_VERB_PROBE__OK__IDX_MS_SINCE_LAST_SEND (ZT_PROTO_VERB_PROBE_IDX_TIMESTAMP + ZT_PROTO_VERB_PROBE_LEN_TIMESTAMP)
#define ZT_PROTO_VERB_PROBE__OK__LEN_MS_SINCE_LAST_SEND 8
// ---------------------------------------------------------------------------
namespace ZeroTier {
@@ -457,11 +467,7 @@ public:
* send this to both peers at the same time on a periodic basis, telling
* each where it might find the other on the network.
*
* Upon receipt, a peer sends a message such as NOP or HELLO to the other
* peer. Peers only "learn" one anothers' direct addresses when they
* successfully *receive* a message and authenticate it. Optionally, peers
* will usually preface these messages with one or more firewall openers
* to clear the path.
* Upon receipt a peer sends HELLO to establish a direct link.
*
* Nodes should implement rate control, limiting the rate at which they
* respond to these packets to prevent their use in DDOS attacks. Nodes
@@ -615,7 +621,28 @@ public:
* It does not generate an OK or ERROR message, and is treated only as
* a hint to refresh now.
*/
VERB_NETWORK_CONFIG_REFRESH = 12
VERB_NETWORK_CONFIG_REFRESH = 12,
/* Probe peer connection status:
* <[8] 64-bit timestamp>
* <[8] 64-bit milliseconds since last send to this peer>
*
* This message is sent to probe the status of a peer and to confirm
* new link-layer addresses. Upon receipt an OK is generated which
* echoes the time and responds with the number of milliseconds since
* the recipient has last sent a packet to the sender.
*
* Using these delay times, a peer may determine if its current route
* to another peer is likely dead and default to another route (e.g.
* reverting to relaying).
*
* OK response payload:
* <[8] 64-bit timestamp echoed from request>
* <[8] 64-bit milliseconds since last send to requesitng peer>
*
* ERROR is not generated.
*/
VERB_PROBE = 13
};
/**
+60 -20
View File
@@ -106,11 +106,13 @@ bool PacketDecoder::tryDecode(const RuntimeEnvironment *_r)
return _doNETWORK_CONFIG_REQUEST(_r,peer);
case Packet::VERB_NETWORK_CONFIG_REFRESH:
return _doNETWORK_CONFIG_REFRESH(_r,peer);
case Packet::VERB_PROBE:
return _doPROBE(_r,peer);
default:
// This might be something from a new or old version of the protocol.
// Technically it passed MAC so the packet is still valid, but we
// ignore it.
TRACE("ignored unrecognized verb %.2x from %s(%s)",(unsigned int)v,source().toString().c_str(),_remoteAddress.toString().c_str());
TRACE("ignored unrecognized verb %.2x from %s(%s)",(unsigned int)verb(),source().toString().c_str(),_remoteAddress.toString().c_str());
return true;
}
} else {
@@ -195,16 +197,25 @@ bool PacketDecoder::_doHELLO(const RuntimeEnvironment *_r)
if (peer->identity() != id) {
unsigned char key[ZT_PEER_SECRET_KEY_LENGTH];
if (_r->identity.agree(id,key,ZT_PEER_SECRET_KEY_LENGTH)) {
TRACE("rejected HELLO from %s(%s): address already claimed",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_COLLISION);
outp.armor(key,true);
_r->demarc->send(_localPort,_remoteAddress,outp.data(),outp.size(),-1);
if (dearmor(key)) { // ensure packet is authentic, otherwise drop
TRACE("rejected HELLO from %s(%s): address already claimed",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_COLLISION);
outp.armor(key,true);
_r->demarc->send(_localPort,_remoteAddress,outp.data(),outp.size(),-1);
} else {
LOG("rejected HELLO from %s(%s): packet failed authentication",source().toString().c_str(),_remoteAddress.toString().c_str());
}
} else {
TRACE("rejected HELLO from %s(%s): key agreement failed",source().toString().c_str(),_remoteAddress.toString().c_str());
}
return true;
} // else continue and send OK since we already know thee...
} else if (!dearmor(peer->key())) {
TRACE("rejected HELLO from %s(%s): packet failed authentication",source().toString().c_str(),_remoteAddress.toString().c_str());
return true;
} // else continue and respond
} else {
// If we don't have a peer record on file, check the identity cache (if
// we have one) to see if we have a cached identity. Then check that for
@@ -213,20 +224,30 @@ bool PacketDecoder::_doHELLO(const RuntimeEnvironment *_r)
if ((alreadyHaveCachedId)&&(id != alreadyHaveCachedId)) {
unsigned char key[ZT_PEER_SECRET_KEY_LENGTH];
if (_r->identity.agree(id,key,ZT_PEER_SECRET_KEY_LENGTH)) {
TRACE("rejected HELLO from %s(%s): address already claimed",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_COLLISION);
outp.armor(key,true);
_r->demarc->send(_localPort,_remoteAddress,outp.data(),outp.size(),-1);
if (dearmor(key)) { // ensure packet is authentic, otherwise drop
TRACE("rejected HELLO from %s(%s): address already claimed",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_COLLISION);
outp.armor(key,true);
_r->demarc->send(_localPort,_remoteAddress,outp.data(),outp.size(),-1);
} else {
LOG("rejected HELLO from %s(%s): packet failed authentication",source().toString().c_str(),_remoteAddress.toString().c_str());
}
} else {
TRACE("rejected HELLO from %s(%s): key agreement failed",source().toString().c_str(),_remoteAddress.toString().c_str());
}
return true;
} // else continue since identity is already known and matches
// Learn a new peer if it's new. This also adds it to the identity
// cache if that's enabled.
peer = _r->topology->addPeer(SharedPtr<Peer>(new Peer(_r->identity,id)));
// If this is a new peer, learn it
SharedPtr<Peer> newPeer(new Peer(_r->identity,id));
if (!dearmor(newPeer->key())) {
LOG("rejected HELLO from %s(%s): packet failed authentication",source().toString().c_str(),_remoteAddress.toString().c_str());
return true;
}
peer = _r->topology->addPeer(newPeer);
}
peer->onReceive(_r,_localPort,_remoteAddress,hops(),packetId(),Packet::VERB_HELLO,0,Packet::VERB_NOP,Utils::now());
@@ -256,7 +277,7 @@ bool PacketDecoder::_doOK(const RuntimeEnvironment *_r,const SharedPtr<Peer> &pe
Packet::Verb inReVerb = (Packet::Verb)(*this)[ZT_PROTO_VERB_OK_IDX_IN_RE_VERB];
uint64_t inRePacketId = at<uint64_t>(ZT_PROTO_VERB_OK_IDX_IN_RE_PACKET_ID);
//TRACE("%s(%s): OK(%s)",source().toString().c_str(),_remoteAddress.toString().c_str(),Packet::verbString(inReVerb));
TRACE("%s(%s): OK(%s)",source().toString().c_str(),_remoteAddress.toString().c_str(),Packet::verbString(inReVerb));
switch(inReVerb) {
case Packet::VERB_HELLO: {
@@ -908,4 +929,23 @@ bool PacketDecoder::_doNETWORK_CONFIG_REFRESH(const RuntimeEnvironment *_r,const
return true;
}
bool PacketDecoder::_doPROBE(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer)
{
try {
uint64_t ts = at<uint64_t>(ZT_PROTO_VERB_PROBE_IDX_TIMESTAMP);
//uint64_t msSinceLastSend = at<uint64_t>(ZT_PROTO_VERB_PROBE_IDX_MS_SINCE_LAST_SEND);
Packet outp(source(),_r->identity.address(),Packet::VERB_OK);
outp.append((unsigned char)Packet::VERB_PROBE);
outp.append(ts);
outp.append(peer->lastDirectSend()); // FIXME: need to refactor to also track relayed sends
outp.armor(peer->key(),true);
_r->demarc->send(_localPort,_remoteAddress,outp.data(),outp.size(),-1);
} catch (std::exception &exc) {
TRACE("dropped PROBE from %s(%s): unexpected exception: %s",source().toString().c_str(),_remoteAddress.toString().c_str(),exc.what());
} catch ( ... ) {
TRACE("dropped PROBE from %s(%s): unexpected exception: (unknown)",source().toString().c_str(),_remoteAddress.toString().c_str());
}
return true;
}
} // namespace ZeroTier
+1
View File
@@ -122,6 +122,7 @@ private:
bool _doNETWORK_MEMBERSHIP_CERTIFICATE(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer);
bool _doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer);
bool _doNETWORK_CONFIG_REFRESH(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer);
bool _doPROBE(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer);
uint64_t _receiveTime;
Demarc::Port _localPort;
+6 -11
View File
@@ -40,10 +40,10 @@ Peer::Peer() :
_lastUnicastFrame(0),
_lastMulticastFrame(0),
_lastAnnouncedTo(0),
_latency(0),
_vMajor(0),
_vMinor(0),
_vRevision(0),
_latency(0),
_requestHistoryPtr(0)
{
}
@@ -87,20 +87,15 @@ void Peer::onReceive(
WanPath *const wp = (remoteAddr.isV4() ? &_ipv4p : &_ipv6p);
wp->lastReceive = now;
wp->localPort = ((localPort) ? localPort : Demarc::ANY_PORT);
if (!wp->fixed)
wp->addr = remoteAddr;
// Do things like learn latency or endpoints on OK or ERROR replies
// Learn latency from replies
if (inReVerb != Packet::VERB_NOP) {
for(unsigned int p=0;p<ZT_PEER_REQUEST_HISTORY_LENGTH;++p) {
if ((_requestHistory[p].packetId == inRePacketId)&&(_requestHistory[p].verb == inReVerb)) {
if ((_requestHistory[p].timestamp)&&(_requestHistory[p].packetId == inRePacketId)&&(_requestHistory[p].verb == inReVerb)) {
_latency = std::min((unsigned int)(now - _requestHistory[p].timestamp),(unsigned int)0xffff);
// Only learn paths on replies to packets we have sent, otherwise
// this introduces both an asymmetry problem in NAT-t and a potential
// reply DOS attack.
if (!wp->fixed)
wp->addr = remoteAddr;
_requestHistory[p].packetId = 0;
_requestHistory[p].timestamp = 0;
break;
}
}
+19 -9
View File
@@ -49,7 +49,7 @@
#include "Mutex.hpp"
// Increment if serialization has changed
#define ZT_PEER_SERIALIZATION_VERSION 5
#define ZT_PEER_SERIALIZATION_VERSION 6
namespace ZeroTier {
@@ -129,7 +129,7 @@ public:
uint64_t now);
/**
* Send a UDP packet to this peer
* Send a UDP packet to this peer directly (not via relaying)
*
* @param _r Runtime environment
* @param data Data to send
@@ -236,9 +236,19 @@ public:
}
/**
* @return Lowest of measured latencies of all paths or 0 if unknown
* @return Current latency or 0 if unknown (max: 65535)
*/
inline unsigned int latency() const throw() { return _latency; }
inline unsigned int latency() const
throw()
{
uint64_t now = Utils::now();
uint64_t latestOutstandingReq = 0;
for(unsigned int p=0;p<ZT_PEER_REQUEST_HISTORY_LENGTH;++p)
latestOutstandingReq = std::max(latestOutstandingReq,_requestHistory[p].timestamp);
if (latestOutstandingReq)
return std::min(std::max((unsigned int)(now - latestOutstandingReq),(unsigned int)_latency),(unsigned int)0xffff);
else return _latency;
}
/**
* @return True if this peer has at least one direct IP address path
@@ -513,12 +523,12 @@ private:
WanPath _ipv4p;
WanPath _ipv6p;
uint64_t _lastUsed;
uint64_t _lastUnicastFrame;
uint64_t _lastMulticastFrame;
uint64_t _lastAnnouncedTo;
unsigned int _latency; // milliseconds, 0 if not known
volatile uint64_t _lastUsed;
volatile uint64_t _lastUnicastFrame;
volatile uint64_t _lastMulticastFrame;
volatile uint64_t _lastAnnouncedTo;
unsigned int _vMajor,_vMinor,_vRevision;
volatile unsigned int _latency;
// not persisted
RequestHistoryItem _requestHistory[ZT_PEER_REQUEST_HISTORY_LENGTH];
+14
View File
@@ -226,6 +226,20 @@ bool Switch::sendHELLO(const SharedPtr<Peer> &dest,Demarc::Port localPort,const
} else return false;
}
bool Switch::sendPROBE(const SharedPtr<Peer> &dest,Demarc::Port localPort,const InetAddress &remoteAddr)
{
uint64_t now = Utils::now();
Packet outp(dest->address(),_r->identity.address(),Packet::VERB_PROBE);
outp.append(now);
outp.append(dest->lastDirectSend()); // FIXME: need to refactor to also track relayed sends
outp.armor(dest->key(),true);
if (_r->demarc->send(localPort,remoteAddr,outp.data(),outp.size(),-1)) {
dest->expectResponseTo(outp.packetId(),Packet::VERB_PROBE,localPort,now);
return true;
} else return false;
}
bool Switch::unite(const Address &p1,const Address &p2,bool force)
{
if ((p1 == _r->identity.address())||(p2 == _r->identity.address()))
+10
View File
@@ -129,6 +129,16 @@ public:
*/
bool sendHELLO(const SharedPtr<Peer> &dest,Demarc::Port localPort,const InetAddress &remoteAddr);
/**
* Send a PROBE immediately to the indicated address
*
* @param localPort Originating local port or ANY_PORT to pick
* @param remoteAddr IP address to send to
* @param dest Destination peer
* @return True if send appears successful
*/
bool sendPROBE(const SharedPtr<Peer> &dest,Demarc::Port localPort,const InetAddress &remoteAddr);
/**
* Send RENDEZVOUS to two peers to permit them to directly connect
*
+1 -1
View File
@@ -41,6 +41,6 @@
/**
* Revision
*/
#define ZEROTIER_ONE_VERSION_REVISION 5
#define ZEROTIER_ONE_VERSION_REVISION 7
#endif