/* * libretroshare/src/services: p3disc.cc * * Services for RetroShare. * * Copyright 2004-2008 by Robert Fernie. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License Version 2 as published by the Free Software Foundation. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA. * * Please report all bugs and problems to "retroshare@lunamutt.com". * */ #include "rsiface/rsiface.h" #include "rsiface/rsinit.h" /* for PGPSSL flag */ #include "rsiface/rspeers.h" #include "services/p3disc.h" #include "pqi/p3authmgr.h" #include "pqi/p3connmgr.h" #include #include #include const uint32_t AUTODISC_LDI_SUBTYPE_PING = 0x01; const uint32_t AUTODISC_LDI_SUBTYPE_RPLY = 0x02; #include #include "util/rsdebug.h" #include "util/rsprint.h" #include "util/rsversion.h" const int pqidisczone = 2482; static int convertTDeltaToTRange(double tdelta); static int convertTRangeToTDelta(int trange); // Operating System specific includes. #include "pqi/pqinetwork.h" /* DISC FLAGS */ const uint32_t P3DISC_FLAGS_USE_DISC = 0x0001; const uint32_t P3DISC_FLAGS_USE_DHT = 0x0002; const uint32_t P3DISC_FLAGS_EXTERNAL_ADDR = 0x0004; const uint32_t P3DISC_FLAGS_STABLE_UDP = 0x0008; const uint32_t P3DISC_FLAGS_PEER_ONLINE = 0x0010; const uint32_t P3DISC_FLAGS_OWN_DETAILS = 0x0020; const uint32_t P3DISC_FLAGS_PEER_TRUSTS_ME= 0x0040; const uint32_t P3DISC_FLAGS_ASK_VERSION = 0x0080; /***** * #define P3DISC_DEBUG 1 ****/ #define P3DISC_DEBUG 1 /*********** NOTE *************** * * Only need Mutexs for neighbours information */ /****************************************************************************************** ****************************** NEW DISCOVERY ******************************************* ****************************************************************************************** *****************************************************************************************/ p3disc::p3disc(p3AuthMgr *am, p3ConnectMgr *cm) :p3Service(RS_SERVICE_TYPE_DISC), mAuthMgr(am), mConnMgr(cm) { RsStackMutex stack(mDiscMtx); /********** STACK LOCKED MTX ******/ addSerialType(new RsDiscSerialiser()); mRemoteDisc = true; mLocalDisc = false; return; } int p3disc::tick() { #ifdef P3DISC_DEBUG static int count = 0; if (++count % 10 == 0) { idServers(); } #endif return handleIncoming(); } int p3disc::handleIncoming() { RsItem *item = NULL; #ifdef P3DISC_DEBUG std::cerr << "p3disc::handleIncoming()"; std::cerr << std::endl; #endif bool discOn; { RsStackMutex stack(mDiscMtx); /********** STACK LOCKED MTX ******/ discOn = mRemoteDisc; } // if off discard item. if (!mRemoteDisc) { while(NULL != (item = recvItem())) { #ifdef P3DISC_DEBUG std::ostringstream out; out << "p3disc::handleIncoming()"; out << " Deleting - Cos RemoteDisc Off!" << std::endl; item -> print(out); std::cerr << out.str() << std::endl; #endif delete item; } return 0; } int nhandled = 0; // While messages read while(NULL != (item = recvItem())) { RsDiscOwnItem *dio = NULL; RsDiscReply *dri = NULL; RsDiscIssuer *dii = NULL; RsDiscVersion *dvi = NULL; #ifdef TO_REMOVE if (NULL == (di = dynamic_cast (item))) { #ifdef P3DISC_DEBUG std::ostringstream out; out << "p3disc::handleIncoming()"; out << "Deleting Non RsDiscItem Msg" << std::endl; item -> print(out); std::cerr << out.str() << std::endl; #endif // delete and continue to next loop. delete item; continue; } #endif { #ifdef P3DISC_DEBUG std::ostringstream out; out << "p3disc::handleIncoming()"; out << " Received Message!" << std::endl; item -> print(out); std::cerr << out.str() << std::endl; #endif } // if discovery reply then respond if haven't already. if (NULL != (dri = dynamic_cast (item))) { recvPeerFriendMsg(dri); nhandled++; } #ifdef RS_USE_PGPSSL else if (NULL != (dii = dynamic_cast (item))) { recvPeerIssuerMsg(dii); nhandled++; } #endif else if (NULL != (dvi = dynamic_cast (item))) { recvPeerVersionMsg(dvi); nhandled++; } else if (NULL != (dio = dynamic_cast (item))) /* Ping */ { recvPeerOwnMsg(dio); nhandled++; } delete item; } return nhandled; } /************* from pqiMonitor *******************/ void p3disc::statusChange(const std::list &plist) { #ifdef P3DISC_DEBUG std::cerr << "p3disc::statusChange()"; std::cerr << std::endl; #endif /* get a list of all online peers */ std::list onlineIds; mConnMgr->getOnlineList(onlineIds); std::list::const_iterator pit; /* if any have switched to 'connected' then we notify */ for(pit = plist.begin(); pit != plist.end(); pit++) { if ((pit->state & RS_PEER_S_FRIEND) && (pit->actions & RS_PEER_CONNECTED)) { /* send our details to them */ sendOwnDetails(pit->id); } } } void p3disc::respondToPeer(std::string id) { /* get a peer lists */ #ifdef P3DISC_DEBUG std::cerr << "p3disc::respondToPeer() id: " << id; std::cerr << std::endl; #endif std::list friendIds; std::list onlineIds; std::list::iterator it; mConnMgr->getFriendList(friendIds); mConnMgr->getOnlineList(onlineIds); /* Check that they have DISC on */ { /* get details */ peerConnectState detail; if (!mConnMgr->getFriendNetStatus(id, detail)) { /* major error! */ return; } if (detail.visState & RS_VIS_STATE_NODISC) { /* don't have DISC enabled */ return; } } /* send them a list of all friend's details */ for(it = friendIds.begin(); it != friendIds.end(); it++) { /* get details */ peerConnectState detail; if (!mConnMgr->getFriendNetStatus(*it, detail)) { /* major error! */ continue; } if (!(detail.visState & RS_VIS_STATE_NODISC)) { /* send issuer certs ... only do this for friends at initial connections, no need to do with onlineId list. */ #ifdef RS_USE_PGPSSL sendPeerIssuer(id, *it); #endif sendPeerDetails(id, *it); /* (dest (to), source (cert)) */ } } /* send their details to all online peers */ for(it = onlineIds.begin(); it != onlineIds.end(); it++) { peerConnectState detail; if (!mConnMgr->getFriendNetStatus(*it, detail)) { /* major error! */ continue; } if (!(detail.visState & RS_VIS_STATE_NODISC)) { sendPeerDetails(*it, id); /* (dest (to), source (cert)) */ } } } /*************************************************************************************/ /* Output Network Msgs */ /*************************************************************************************/ void p3disc::sendOwnDetails(std::string to) { /* setup: * IP local / external * availability (TCP LOCAL / EXT, UDP ...) */ // Then send message. { #ifdef P3DISC_DEBUG std::ostringstream out; out << "p3disc::sendOwnDetails()"; out << "Constructing a RsDiscItem Message!" << std::endl; out << "Sending to: " << to; std::cerr << out.str() << std::endl; #endif } // Construct a message RsDiscOwnItem *di = new RsDiscOwnItem(); /* components: * laddr * saddr * contact_tf * discFlags */ peerConnectState detail; if (!mConnMgr->getOwnNetStatus(detail)) { /* major error! */ return; } // Fill the message di -> PeerId(to); di -> laddr = detail.localaddr; di -> saddr = detail.serveraddr; di -> contact_tf = 0; /* construct disc flags */ di -> discFlags = 0; if (!(detail.visState & RS_VIS_STATE_NODISC)) { di->discFlags |= P3DISC_FLAGS_USE_DISC; } if (!(detail.visState & RS_VIS_STATE_NODHT)) { di->discFlags |= P3DISC_FLAGS_USE_DHT; } if ((detail.netMode & RS_NET_MODE_EXT) || (detail.netMode & RS_NET_MODE_UPNP)) { di->discFlags |= P3DISC_FLAGS_EXTERNAL_ADDR; } else if (detail.netMode & RS_NET_MODE_UDP) { di->discFlags |= P3DISC_FLAGS_STABLE_UDP; } // set flag - request for version di->discFlags |= P3DISC_FLAGS_ASK_VERSION; di->discFlags |= P3DISC_FLAGS_OWN_DETAILS; /* send msg */ sendItem(di); } /* (dest (to), source (cert)) */ void p3disc::sendPeerDetails(std::string to, std::string about) { /* setup: * Certificate. * IP local / external * availability ... * last connect (0) if online. */ /* send it off */ { #ifdef P3DISC_DEBUG std::ostringstream out; out << "p3disc::sendPeerDetails()"; out << " Sending details of: " << about; out << " to: " << to << std::endl; std::cerr << out.str() << std::endl; #endif } peerConnectState detail; if (!mConnMgr->getFriendNetStatus(about, detail)) { /* major error! */ return; } // Construct a message RsDiscReply *di = new RsDiscReply(); // Fill the message // Set Target as input cert. di -> PeerId(to); di -> aboutId = about; // set the server address. di -> laddr = detail.localaddr; di -> saddr = detail.serveraddr; if (detail.state & RS_PEER_S_CONNECTED) { di -> contact_tf = 0; } else { di -> contact_tf = convertTDeltaToTRange(time(NULL) - detail.lastcontact); } /* construct disc flags */ di->discFlags = 0; /* NOTE we should not be sending packet if NODISC is set.... * checked elsewhere... so don't check. */ di->discFlags |= P3DISC_FLAGS_USE_DISC; if (!(detail.visState & RS_VIS_STATE_NODHT)) { di->discFlags |= P3DISC_FLAGS_USE_DHT; } if (detail.netMode & RS_NET_MODE_EXT) { di->discFlags |= P3DISC_FLAGS_EXTERNAL_ADDR; } else if (detail.netMode & RS_NET_MODE_UDP) { di->discFlags |= P3DISC_FLAGS_STABLE_UDP; } if (detail.state & RS_PEER_S_CONNECTED) { di->discFlags |= P3DISC_FLAGS_PEER_ONLINE; } // Add 3rd party trust info // We look at peers that trust 'to', by looking into 'to''s tigners list. The problem is that // signers are accessible through their names instead of their id, so there is ambiguity if too peers // have the same names. @DrBob: that would be cool to save signers using their ids... // RsPeerDetails pd ; std::string name = rsPeers->getPeerName(about) ; if(rsPeers->getPeerDetails(to,pd)) for(std::list::const_iterator it(pd.signers.begin());it!=pd.signers.end();++it) if(*it == name) { di->discFlags |= P3DISC_FLAGS_PEER_TRUSTS_ME; #ifdef P3DISC_DEBUG std::cerr << " Peer " << about << "(" << name << ")" << " is trusting " << to << ", sending info." << std::endl ; #endif } uint32_t certLen = 0; unsigned char **binptr = (unsigned char **) &(di -> certDER.bin_data); mAuthMgr->SaveCertificateToBinary(about, binptr, &certLen); #ifdef P3DISC_DEBUG std::cerr << "Saved certificate to binary in p3discReply. Length=" << certLen << std::endl ; #endif if (certLen > 0) { di -> certDER.bin_len = certLen; #ifdef P3DISC_DEBUG std::cerr << "Cert Encoded(" << certLen << ")" << std::endl; #endif } else { #ifdef P3DISC_DEBUG std::cerr << "Failed to Encode Cert" << std::endl; #endif di -> certDER.bin_len = 0; } // Send off message sendItem(di); #ifdef P3DISC_DEBUG std::cerr << "Sent DI Message" << std::endl; #endif } /* (dest (to), source (cert)) */ void p3disc::sendPeerIssuer(std::string to, std::string about) { /* this is just a straight certificate (normally pgp). * but can get quite big (>100K) so will use new packet type. */ /* send it off */ { #ifdef P3DISC_DEBUG std::ostringstream out; out << "p3disc::sendPeerIssuer()"; out << " Sending details of: " << about; out << " to: " << to << std::endl; std::cerr << out.str() << std::endl; #endif } std::string aboutIssuerId = mAuthMgr->getIssuerName(about); if (aboutIssuerId == "") { /* major error! */ return; } // Construct a message RsDiscIssuer *di = new RsDiscIssuer(); // Fill the message // Set Target as input cert. di -> PeerId(to); di -> issuerCert = mAuthMgr->SaveCertificateToString(aboutIssuerId); #ifdef P3DISC_DEBUG std::cerr << "Saved certificate to string in RsDiscIssuer. " << std::endl ; #endif // Send off message sendItem(di); #ifdef P3DISC_DEBUG std::cerr << "Sent DI Message" << std::endl; #endif } void p3disc::sendOwnVersion(std::string to) { { #ifdef P3DISC_DEBUG std::ostringstream out; out << "p3disc::sendOwnVersion()"; out << " Sending rs version to: " << to << std::endl; std::cerr << out.str() << std::endl; #endif } RsDiscVersion *di = new RsDiscVersion(); di->PeerId(to); di->version = RsUtil::retroshareVersion(); /* send the message */ sendItem(di); #ifdef P3DISC_DEBUG std::cerr << "Sent DI Message" << std::endl; #endif } /*************************************************************************************/ /* Input Network Msgs */ /*************************************************************************************/ void p3disc::recvPeerOwnMsg(RsDiscOwnItem *item) { #ifdef P3DISC_DEBUG std::cerr << "p3disc::recvPeerOwnMsg() From: " << item->PeerId() << std::endl; #endif /* tells us their exact address (mConnectMgr can ignore if it looks wrong) */ uint32_t type = 0; uint32_t flags = 0; /* translate flags */ if (item->discFlags & P3DISC_FLAGS_USE_DISC) { flags |= RS_NET_FLAGS_USE_DISC; } if (item->discFlags & P3DISC_FLAGS_USE_DHT) { flags |= RS_NET_FLAGS_USE_DHT; } if (item->discFlags & P3DISC_FLAGS_PEER_ONLINE) { flags |= RS_NET_FLAGS_ONLINE; } /* generate type */ type = RS_NET_CONN_TCP_LOCAL; if (item->discFlags & P3DISC_FLAGS_EXTERNAL_ADDR) { type |= RS_NET_CONN_TCP_EXTERNAL; flags |= RS_NET_FLAGS_EXTERNAL_ADDR; } if (item->discFlags & P3DISC_FLAGS_STABLE_UDP) { type |= RS_NET_CONN_UDP_DHT_SYNC; flags |= RS_NET_FLAGS_STABLE_UDP; } mConnMgr->peerStatus(item->PeerId(), item->laddr, item->saddr, type, flags, RS_CB_PERSON); /* also add as potential stun buddy */ std::string hashid1 = RsUtil::HashId(item->PeerId(), false); mConnMgr->stunStatus(hashid1, item->saddr, type, RS_STUN_ONLINE | RS_STUN_FRIEND); /* now reply with all details */ respondToPeer(item->PeerId()); /*sending rs versio if i was asked*/ if (item->discFlags & P3DISC_FLAGS_ASK_VERSION) { sendOwnVersion(item->PeerId()); } addDiscoveryData(item->PeerId(), item->PeerId(), item->laddr, item->saddr, item->discFlags, time(NULL)); /* cleanup (handled by caller) */ } void p3disc::recvPeerFriendMsg(RsDiscReply *item) { #ifdef P3DISC_DEBUG std::cerr << "p3disc::recvPeerFriendMsg() From: " << item->PeerId(); std::cerr << " About " << item->aboutId; std::cerr << std::endl; #endif /* tells us their exact address (mConnectMgr can ignore if it looks wrong) */ /* load certificate */ std::string peerId; uint8_t *certptr = (uint8_t *) item->certDER.bin_data; uint32_t len = item->certDER.bin_len; bool loaded = mAuthMgr->LoadCertificateFromBinary(certptr, len, peerId); uint32_t type = 0; uint32_t flags = 0; /* translate flags */ if (item->discFlags & P3DISC_FLAGS_USE_DISC) flags |= RS_NET_FLAGS_USE_DISC; if (item->discFlags & P3DISC_FLAGS_USE_DHT) flags |= RS_NET_FLAGS_USE_DHT; if (item->discFlags & P3DISC_FLAGS_PEER_ONLINE) flags |= RS_NET_FLAGS_ONLINE; if (item->discFlags & P3DISC_FLAGS_PEER_TRUSTS_ME) { std::cerr << " Found a peer that trust me: " << peerId << " (" << rsPeers->getPeerName(peerId) << ")" << std::endl ; flags |= RS_NET_FLAGS_TRUSTS_ME; mAuthMgr->addTrustingPeer(peerId) ; } /* generate type */ type = RS_NET_CONN_TCP_LOCAL; if (item->discFlags & P3DISC_FLAGS_EXTERNAL_ADDR) { type |= RS_NET_CONN_TCP_EXTERNAL; flags |= RS_NET_FLAGS_EXTERNAL_ADDR; } if (item->discFlags & P3DISC_FLAGS_STABLE_UDP) { type |= RS_NET_CONN_UDP_DHT_SYNC; flags |= RS_NET_FLAGS_STABLE_UDP; } /* only valid certs, and not ourselves */ if ((loaded) && (peerId != mConnMgr->getOwnId())) { mConnMgr->peerStatus(peerId, item->laddr, item->saddr, type, flags, RS_CB_DISC); std::string hashid1 = RsUtil::HashId(peerId, false); mConnMgr->stunStatus(hashid1, item->saddr, type, RS_STUN_FRIEND_OF_FRIEND); } addDiscoveryData(item->PeerId(), peerId, item->laddr, item->saddr, item->discFlags, time(NULL)); rsicontrol->getNotify().notifyListChange(NOTIFY_LIST_NEIGHBOURS, NOTIFY_TYPE_MOD); /* cleanup (handled by caller) */ } void p3disc::recvPeerIssuerMsg(RsDiscIssuer *item) { #ifdef P3DISC_DEBUG std::cerr << "p3disc::recvPeerIssuerMsg() From: " << item->PeerId(); std::cerr << std::endl; std::cerr << "p3disc::recvPeerIssuerMsg() Cert: " << item->issuerCert; std::cerr << std::endl; #endif /* tells us their exact address (mConnectMgr can ignore if it looks wrong) */ /* load certificate */ std::string peerId; bool loaded = mAuthMgr->LoadCertificateFromString(item->issuerCert, peerId); /* cleanup (handled by caller) */ return; } void p3disc::recvPeerVersionMsg(RsDiscVersion *item) { #ifdef P3DISC_DEBUG std::cerr << "p3disc::recvPeerVersionMsg() From: " << item->PeerId(); std::cerr << std::endl; #endif // dont need protection versions[item->PeerId()] = item->version; return; } /*************************************************************************************/ /* Storing Network Graph */ /*************************************************************************************/ int p3disc::addDiscoveryData(std::string fromId, std::string aboutId, struct sockaddr_in laddr, struct sockaddr_in raddr, uint32_t flags, time_t ts) { RsStackMutex stack(mDiscMtx); /********** STACK LOCKED MTX ******/ /* Store Network information */ std::map::iterator it; if (neighbours.end() == (it = neighbours.find(aboutId))) { /* doesn't exist */ autoneighbour an; /* an data */ an.id = aboutId; an.validAddrs = false; an.discFlags = 0; an.ts = 0; neighbours[aboutId] = an; it = neighbours.find(aboutId); } /* it always valid */ /* just update packet */ autoserver as; as.id = fromId; as.localAddr = laddr; as.remoteAddr = raddr; as.discFlags = flags; as.ts = ts; bool authDetails = (as.id == it->second.id); /* KEY decision about address */ if ((authDetails) || ((!(it->second.authoritative)) && (as.ts > it->second.ts))) { /* copy details to an */ it->second.authoritative = authDetails; it->second.ts = as.ts; it->second.validAddrs = true; it->second.localAddr = as.localAddr; it->second.remoteAddr = as.remoteAddr; it->second.discFlags = as.discFlags; } (it->second).neighbour_of[fromId] = as; /* do we update network address info??? */ return 1; } /*************************************************************************************/ /* Extracting Network Graph Details */ /*************************************************************************************/ bool p3disc::potentialproxies(std::string id, std::list &proxyIds) { /* find id -> and extract the neighbour_of ids */ RsStackMutex stack(mDiscMtx); /********** STACK LOCKED MTX ******/ std::map::iterator it; std::map::iterator sit; if (neighbours.end() == (it = neighbours.find(id))) { return false; } for(sit = it->second.neighbour_of.begin(); sit != it->second.neighbour_of.end(); sit++) { proxyIds.push_back(sit->first); } return true; } void p3disc::getversions(std::map &versions) { versions = this->versions; } int p3disc::idServers() { RsStackMutex stack(mDiscMtx); /********** STACK LOCKED MTX ******/ std::map::iterator nit; std::map::iterator sit; int cts = time(NULL); std::ostringstream out; out << "::::AutoDiscovery Neighbours::::" << std::endl; for(nit = neighbours.begin(); nit != neighbours.end(); nit++) { out << "Neighbour: " << (nit->second).id; out << std::endl; out << "-> LocalAddr: "; out << inet_ntoa(nit->second.localAddr.sin_addr); out << ":" << ntohs(nit->second.localAddr.sin_port) << std::endl; out << "-> RemoteAddr: "; out << inet_ntoa(nit->second.remoteAddr.sin_addr); out << ":" << ntohs(nit->second.remoteAddr.sin_port) << std::endl; out << " Last Contact: "; out << cts - (nit->second.ts) << " sec ago"; out << std::endl; out << " -->DiscFlags: 0x" << std::hex << nit->second.discFlags; out << std::dec << std::endl; for(sit = (nit->second.neighbour_of).begin(); sit != (nit->second.neighbour_of).end(); sit++) { out << "\tConnected via: " << (sit->first); out << std::endl; out << "\t\tLocalAddr: "; out << inet_ntoa(sit->second.localAddr.sin_addr); out <<":"<< ntohs(sit->second.localAddr.sin_port); out << std::endl; out << "\t\tRemoteAddr: "; out << inet_ntoa(sit->second.remoteAddr.sin_addr); out <<":"<< ntohs(sit->second.remoteAddr.sin_port); out << std::endl; out << "\t\tLast Contact:"; out << cts - (sit->second.ts) << " sec ago"; out << std::endl; out << "\t\tDiscFlags: 0x" << std::hex << (sit->second.discFlags); out << std::dec << std::endl; } } #ifdef P3DISC_DEBUG std::cerr << "p3disc::idServers()" << std::endl; std::cerr << out.str(); std::cerr << std::endl; #endif return 1; } // tdelta -> trange. // -inf...<0 0 (invalid) // 0.. <9 1 // 9...<99 2 // 99...<999 3 // 999...<9999 4 // etc... int convertTDeltaToTRange(double tdelta) { if (tdelta < 0) return 0; int trange = 1 + (int) log10(tdelta + 1.0); return trange; } // trange -> tdelta // -inf...0 -1 (invalid) // 1 8 // 2 98 // 3 998 // 4 9998 // etc... int convertTRangeToTDelta(int trange) { if (trange <= 0) return -1; return (int) (pow(10.0, trange) - 1.5); // (int) xxx98.5 -> xxx98 }