/* * tcponudp/udprelay.cc * * libretroshare. * * Copyright 2010 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 3 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 "udprelay.h" #include /* * #define DEBUG_UDP_RELAY 1 */ //#define DEBUG_UDP_RELAY 1 #ifdef DEBUG_UDP_RELAY // DEBUG FUNCTION #include #include int displayUdpRelayPacketHeader(const void *data, const int size); #endif /****************** UDP RELAY STUFF **********/ #define MAX_RELAY_UDP_PACKET_SIZE 1024 UdpRelayReceiver::UdpRelayReceiver(UdpPublisher *pub) :UdpSubReceiver(pub), udppeerMtx("UdpSubReceiver"), relayMtx("UdpSubReceiver") { mClassLimit.resize(UDP_RELAY_NUM_CLASS); mClassCount.resize(UDP_RELAY_NUM_CLASS); setRelayTotal(UDP_RELAY_DEFAULT_COUNT_ALL); for(int i = 0; i < UDP_RELAY_NUM_CLASS; i++) { mClassCount[i] = 0; } /* only allocate this space once */ mTmpSendPkt = malloc(MAX_RELAY_UDP_PACKET_SIZE); mTmpSendSize = MAX_RELAY_UDP_PACKET_SIZE; return; } UdpRelayReceiver::~UdpRelayReceiver() { free(mTmpSendPkt); } int UdpRelayReceiver::addUdpPeer(UdpPeer *peer, UdpRelayAddrSet *endPoints, const struct sockaddr_in *proxyaddr) { struct sockaddr_in realPeerAddr = endPoints->mDestAddr; { RsStackMutex stack(relayMtx); /********** LOCK MUTEX *********/ /* check for duplicate */ std::map::iterator it; it = mStreams.find(realPeerAddr); bool ok = (it == mStreams.end()); if (!ok) { #ifdef DEBUG_UDP_RELAY std::cerr << "UdpPeerReceiver::addUdpPeer() ERROR Peer already exists!" << std::endl; #endif return 0; } /* setup a peer */ UdpRelayEnd ure(endPoints, proxyaddr); mStreams[realPeerAddr] = ure; } { RsStackMutex stack(udppeerMtx); /********** LOCK MUTEX *********/ #ifdef DEBUG_UDP_RELAY std::cerr << "UdpPeerReceiver::addUdpPeer() Just installing UdpPeer!" << std::endl; #endif /* just overwrite */ mPeers[realPeerAddr] = peer; } return 1; } int UdpRelayReceiver::removeUdpPeer(UdpPeer *peer) { bool found = false; struct sockaddr_in realPeerAddr; /* cleanup UdpPeer, and get reference for data */ { RsStackMutex stack(udppeerMtx); /********** LOCK MUTEX *********/ std::map::iterator it; for(it = mPeers.begin(); it != mPeers.end(); it++) { if (it->second == peer) { mPeers.erase(it); found = true; realPeerAddr = it->first; break; } } } if (!found) { return 0; } /* now we cleanup the associated data */ { RsStackMutex stack(relayMtx); /********** LOCK MUTEX *********/ std::map::iterator it; it = mStreams.find(realPeerAddr); if (it != mStreams.end()) { mStreams.erase(it); } else { /* ERROR */ } } return 1; } int UdpRelayReceiver::getRelayEnds(std::list &relayEnds) { RsStackMutex stack(relayMtx); /********** LOCK MUTEX *********/ std::map::iterator rit; for(rit = mStreams.begin(); rit != mStreams.end(); rit++) { relayEnds.push_back(rit->second); } return 1; } int UdpRelayReceiver::getRelayProxies(std::list &relayProxies) { RsStackMutex stack(relayMtx); /********** LOCK MUTEX *********/ std::map::iterator rit; for(rit = mRelays.begin(); rit != mRelays.end(); rit++) { relayProxies.push_back(rit->second); } return 1; } #define RELAY_MAX_BANDWIDTH 1000 #define RELAY_TIMEOUT 30 int UdpRelayReceiver::checkRelays() { RsStackMutex stack(relayMtx); /********** LOCK MUTEX *********/ /* iterate through the Relays */ std::cerr << "UdpRelayReceiver::checkRelays()"; std::cerr << std::endl; std::list eraseList; std::map::iterator rit; time_t now = time(NULL); for(rit = mRelays.begin(); rit != mRelays.end(); rit++) { /* calc bandwidth */ rit->second.mBandwidth = rit->second.mDataSize / (float) (now - rit->second.mLastBandwidthTS); rit->second.mDataSize = 0; rit->second.mLastBandwidthTS = now; std::cerr << "UdpRelayReceiver::checkRelays()"; std::cerr << "Relay: " << rit->first; std::cerr << " using bandwidth: " << rit->second.mBandwidth; std::cerr << std::endl; if (rit->second.mBandwidth > rit->second.mBandwidthLimit) { std::cerr << "UdpRelayReceiver::checkRelays() "; std::cerr << "Dropping Relay due to excessive Bandwidth: " << rit->second.mBandwidth; std::cerr << " Exceeding Limit: " << rit->second.mBandwidthLimit; std::cerr << " Relay: " << rit->first; std::cerr << std::endl; /* if exceeding bandwidth -> drop */ eraseList.push_back(rit->first); } else if (now - rit->second.mLastTS > RELAY_TIMEOUT) { /* if haven't transmitted for ages -> drop */ std::cerr << "UdpRelayReceiver::checkRelays() "; std::cerr << "Dropping Relay due to Timeout: " << rit->first; std::cerr << std::endl; eraseList.push_back(rit->first); } else { /* check the length of the relay - we will drop them after a certain amount of time */ int lifetime = 0; switch(rit->second.mRelayClass) { default: case UDP_RELAY_CLASS_GENERAL: lifetime = UDP_RELAY_LIFETIME_GENERAL; break; case UDP_RELAY_CLASS_FOF: lifetime = UDP_RELAY_LIFETIME_FOF; break; case UDP_RELAY_CLASS_FRIENDS: lifetime = UDP_RELAY_LIFETIME_FRIENDS; break; } if (now - rit->second.mStartTS > lifetime) { std::cerr << "UdpRelayReceiver::checkRelays() "; std::cerr << "Dropping Relay due to Passing Lifetime Limit: " << lifetime; std::cerr << " for class: " << rit->second.mRelayClass; std::cerr << " Relay: " << rit->first; std::cerr << std::endl; eraseList.push_back(rit->first); } } } std::list::iterator it; for(it = eraseList.begin(); it != eraseList.end(); it++) { removeUdpRelay_relayLocked(&(*it)); } return 1; } int UdpRelayReceiver::removeUdpRelay(UdpRelayAddrSet *addrSet) { RsStackMutex stack(relayMtx); /********** LOCK MUTEX *********/ return removeUdpRelay_relayLocked(addrSet); } int UdpRelayReceiver::addUdpRelay(UdpRelayAddrSet *addrSet, int relayClass, uint32_t &bandwidth) { RsStackMutex stack(relayMtx); /********** LOCK MUTEX *********/ /* check for duplicate */ std::map::iterator rit = mRelays.find(*addrSet); int ok = (rit == mRelays.end()); if (!ok) { //#ifdef DEBUG_UDP_RELAY std::cerr << "UdpRelayReceiver::addUdpRelay() ERROR Peer already exists!" << std::endl; //#endif return 0; } /* will install if there is space! */ if (installRelayClass_relayLocked(relayClass)) { //#ifdef DEBUG_UDP_RELAY std::cerr << "UdpRelayReceiver::addUdpRelay() adding Relay" << std::endl; //#endif /* create UdpRelay */ UdpRelayProxy udpRelay(addrSet, relayClass); UdpRelayAddrSet alt = addrSet->flippedSet(); UdpRelayProxy altUdpRelay(&alt, relayClass); /* must install two (A, B) & (B, A) */ mRelays[*addrSet] = udpRelay; mRelays[alt] = altUdpRelay; /* grab bandwidth from one set */ bandwidth = altUdpRelay.mBandwidthLimit; return 1; } //#ifdef DEBUG_UDP_RELAY std::cerr << "UdpRelayReceiver::addUdpRelay() WARNING Too many Relays!" << std::endl; //#endif return 0; } int UdpRelayReceiver::removeUdpRelay_relayLocked(UdpRelayAddrSet *addrSet) { /* find in Relay list */ std::map::iterator rit = mRelays.find(*addrSet); if (rit == mRelays.end()) { /* ERROR */ std::cerr << "UdpRelayReceiver::removeUdpRelay()"; std::cerr << "ERROR Finding Relay: " << *addrSet; std::cerr << std::endl; } else { /* lets drop the count here too */ removeRelayClass_relayLocked(rit->second.mRelayClass); mRelays.erase(rit); } /* rotate around and delete matching set */ UdpRelayAddrSet alt = addrSet->flippedSet(); rit = mRelays.find(alt); if (rit == mRelays.end()) { std::cerr << "UdpRelayReceiver::removeUdpRelay()"; std::cerr << "ERROR Finding Alt Relay: " << alt; std::cerr << std::endl; /* ERROR */ } else { mRelays.erase(rit); } return 1; } /* Need some stats, to work out how many relays we are supporting */ int UdpRelayReceiver::installRelayClass_relayLocked(int classIdx) { /* check for total number of Relays */ if (mClassCount[UDP_RELAY_CLASS_ALL] >= mClassLimit[UDP_RELAY_CLASS_ALL]) { std::cerr << "UdpRelayReceiver::installRelayClass() WARNING Too many Relays already"; std::cerr << std::endl; return 0; } /* check the idx too */ if ((classIdx < 0) || (classIdx >= UDP_RELAY_NUM_CLASS)) { std::cerr << "UdpRelayReceiver::installRelayClass() ERROR class Idx invalid"; std::cerr << std::endl; return 0; } /* now check the specifics of the class */ if (mClassCount[classIdx] >= mClassLimit[classIdx]) { std::cerr << "UdpRelayReceiver::installRelayClass() WARNING Relay Class Limit Exceeded"; std::cerr << std::endl; return 0; } std::cerr << "UdpRelayReceiver::installRelayClass() Relay Class Ok, Count incremented"; std::cerr << std::endl; /* if we get here we can add one */ mClassCount[UDP_RELAY_CLASS_ALL]++; mClassCount[classIdx]++; return 1; } int UdpRelayReceiver::removeRelayClass_relayLocked(int classIdx) { /* check for total number of Relays */ if (mClassCount[UDP_RELAY_CLASS_ALL] < 1) { std::cerr << "UdpRelayReceiver::removeRelayClass() ERROR no relays installed"; std::cerr << std::endl; return 0; } /* check the idx too */ if ((classIdx < 0) || (classIdx >= UDP_RELAY_NUM_CLASS)) { std::cerr << "UdpRelayReceiver::removeRelayClass() ERROR class Idx invalid"; std::cerr << std::endl; return 0; } /* now check the specifics of the class */ if (mClassCount[classIdx] < 1) { std::cerr << "UdpRelayReceiver::removeRelayClass() ERROR no relay of class installed"; std::cerr << std::endl; return 0; } std::cerr << "UdpRelayReceiver::removeRelayClass() Ok, Count decremented"; std::cerr << std::endl; /* if we get here we can add one */ mClassCount[UDP_RELAY_CLASS_ALL]--; mClassCount[classIdx]--; return 1; } int UdpRelayReceiver::setRelayTotal(int count) { RsStackMutex stack(relayMtx); /********** LOCK MUTEX *********/ mClassLimit[UDP_RELAY_CLASS_ALL] = count; mClassLimit[UDP_RELAY_CLASS_GENERAL] = (int) (UDP_RELAY_FRAC_GENERAL * count); mClassLimit[UDP_RELAY_CLASS_FOF] = (int) (UDP_RELAY_FRAC_FOF * count); mClassLimit[UDP_RELAY_CLASS_FRIENDS] = (int) (UDP_RELAY_FRAC_FRIENDS * count); return count; } int UdpRelayReceiver::setRelayClassMax(int classIdx, int count) { RsStackMutex stack(relayMtx); /********** LOCK MUTEX *********/ /* check the idx */ if ((classIdx < 0) || (classIdx >= UDP_RELAY_NUM_CLASS)) { std::cerr << "UdpRelayReceiver::setRelayMaximum() ERROR class Idx invalid"; std::cerr << std::endl; return 0; } mClassLimit[classIdx] = count; return 1; } int UdpRelayReceiver::getRelayClassMax(int classIdx) { RsStackMutex stack(relayMtx); /********** LOCK MUTEX *********/ /* check the idx */ if ((classIdx < 0) || (classIdx >= UDP_RELAY_NUM_CLASS)) { std::cerr << "UdpRelayReceiver::getRelayMaximum() ERROR class Idx invalid"; std::cerr << std::endl; return 0; } return mClassLimit[classIdx]; } int UdpRelayReceiver::getRelayCount(int classIdx) { RsStackMutex stack(relayMtx); /********** LOCK MUTEX *********/ /* check the idx */ if ((classIdx < 0) || (classIdx >= UDP_RELAY_NUM_CLASS)) { std::cerr << "UdpRelayReceiver::getRelayCount() ERROR class Idx invalid"; std::cerr << std::endl; return 0; } return mClassCount[classIdx]; } int UdpRelayReceiver::RelayStatus(std::ostream &out) { RsStackMutex stack(relayMtx); /********** LOCK MUTEX *********/ /* iterate through the Relays */ out << "UdpRelayReceiver::RelayStatus()"; out << std::endl; std::map::iterator rit; for(rit = mRelays.begin(); rit != mRelays.end(); rit++) { out << "Relay for: " << rit->first; out << std::endl; out << "\tClass: " << rit->second.mRelayClass; out << "\tBandwidth: " << rit->second.mBandwidth; out << "\tDataSize: " << rit->second.mDataSize; out << "\tLastBandwidthTS: " << rit->second.mLastBandwidthTS; } return 1; } int UdpRelayReceiver::status(std::ostream &out) { out << "UdpRelayReceiver::status()" << std::endl; out << "UdpRelayReceiver::Relayed Connections:" << std::endl; RelayStatus(out); RsStackMutex stack(relayMtx); /********** LOCK MUTEX *********/ out << "UdpRelayReceiver::Connections:" << std::endl; std::map::iterator pit; for(pit = mStreams.begin(); pit != mStreams.end(); pit++) { out << "\t" << pit->first << " : " << pit->second; out << std::endl; } return 1; } #define UDP_RELAY_HEADER_SIZE 16 /* higher level interface */ int UdpRelayReceiver::recvPkt(void *data, int size, struct sockaddr_in &from) { /* remove unused parameter warnings */ (void) from; /* print packet information */ #ifdef DEBUG_UDP_RELAY std::cerr << "UdpRelayReceiver::recvPkt(" << size << ") from: " << from; std::cerr << std::endl; displayUdpRelayPacketHeader(data, size); #endif if (!isUdpRelayPacket(data, size)) { #ifdef DEBUG_UDP_RELAY std::cerr << "UdpRelayReceiver::recvPkt() is Not RELAY Pkt"; std::cerr << std::endl; #endif return 0; } /* decide if we are the relay, or the endpoint */ UdpRelayAddrSet addrSet; if (!extractUdpRelayAddrSet(data, size, addrSet)) { /* fails most basic test, drop */ return 0; } { RsStackMutex stack(relayMtx); /********** LOCK MUTEX *********/ /* lookup relay first (double entries) */ std::map::iterator rit = mRelays.find(addrSet); if (rit != mRelays.end()) { /* we are the relay */ #ifdef DEBUG_UDP_RELAY std::cerr << "UdpRelayReceiver::recvPkt() We are the Relay. Passing onto: "; std::cerr << rit->first.mDestAddr; std::cerr << std::endl; #endif /* do accounting */ rit->second.mLastTS = time(NULL); rit->second.mDataSize += size; mPublisher->sendPkt(data, size, rit->first.mDestAddr, STD_RELAY_TTL); return 1; } } /* otherwise we are likely to be the endpoint, * use the peers Address from the header */ { RsStackMutex stack(udppeerMtx); /********** LOCK MUTEX *********/ std::map::iterator pit = mPeers.find(addrSet.mSrcAddr); if (pit != mPeers.end()) { /* we are the end-point */ #ifdef DEBUG_UDP_RELAY std::cerr << "UdpRelayReceiver::recvPkt() Sending to UdpPeer: "; std::cerr << pit->first; std::cerr << std::endl; #endif /* remove the header */ void *pktdata = (void *) (((uint8_t *) data) + UDP_RELAY_HEADER_SIZE); int pktsize = size - UDP_RELAY_HEADER_SIZE; if (pktsize > 0) { (pit->second)->recvPkt(pktdata, pktsize); } else { /* packet undersized */ #ifdef DEBUG_UDP_RELAY std::cerr << "UdpRelayReceiver::recvPkt() ERROR Packet Undersized"; std::cerr << std::endl; #endif } return 1; } /* done */ } /* unknown */ #ifdef DEBUG_UDP_RELAY std::cerr << "UdpRelayReceiver::recvPkt() Peer Unknown!"; std::cerr << std::endl; #endif return 0; } /* the address here must be the end point!, * it cannot be proxy, as we could be using the same proxy for multiple connections. */ int UdpRelayReceiver::sendPkt(const void *data, int size, const struct sockaddr_in &to, int /*ttl*/) { RsStackMutex stack(relayMtx); /********** LOCK MUTEX *********/ /* work out who the proxy is */ std::map::iterator it; it = mStreams.find(to); if (it == mStreams.end()) { //#ifdef DEBUG_UDP_RELAY std::cerr << "UdpRelayReceiver::sendPkt() Peer Unknown!"; std::cerr << std::endl; //#endif return 0; } /* add a header to packet */ int finalPktSize = createRelayUdpPacket(data, size, mTmpSendPkt, MAX_RELAY_UDP_PACKET_SIZE, &(it->second)); /* send the packet on */ return mPublisher->sendPkt(mTmpSendPkt, finalPktSize, it->second.mProxyAddr, STD_RELAY_TTL); } /***** RELAY PACKET FORMAT **************************** * * * [ 0 | 1 | 2 | 3 ] * * [ 'R' 'L' 'Y' Version ] * [ IP Address 1 ] * [ Port 1 ][ IP Address 2 .... * ... IP Address 2][ Port 2 ] * [.... TUNNELLED DATA .... * * * ... ] * * 16 Bytes: 4 ID, 6 IP:Port 1, 6 IP:Port 2 */ #define UDP_IDENTITY_STRING_V1 "RLY1" #define UDP_IDENTITY_SIZE_V1 4 int isUdpRelayPacket(const void *data, const int size) { if (size < UDP_RELAY_HEADER_SIZE) return 0; return (0 == strncmp((char *) data, UDP_IDENTITY_STRING_V1, UDP_IDENTITY_SIZE_V1)); } #ifdef DEBUG_UDP_RELAY int displayUdpRelayPacketHeader(const void *data, const int size) { int dsize = UDP_RELAY_HEADER_SIZE + 16; if (size < dsize) { dsize = size; } std::ostringstream out; for(int i = 0; i < dsize; i++) { if ((i > 0) && (i % 16 == 0)) { out << std::endl; } out << std::setw(2) << std::setfill('0') << std::hex << (uint32_t) ((uint8_t*) data)[i]; } std::cerr << "displayUdpRelayPacketHeader()" << std::endl; std::cerr << out.str(); std::cerr << std::endl; } #endif int extractUdpRelayAddrSet(const void *data, const int size, UdpRelayAddrSet &addrSet) { if (size < UDP_RELAY_HEADER_SIZE) { std::cerr << "createRelayUdpPacket() ERROR invalid size"; std::cerr << std::endl; return 0; } uint8_t *header = (uint8_t *) data; sockaddr_clear(&(addrSet.mSrcAddr)); sockaddr_clear(&(addrSet.mDestAddr)); /* as IP:Port are already in network byte order, we can just write them to the dataspace */ uint32_t ipaddr; uint16_t port; memcpy(&ipaddr, &(header[4]), 4); memcpy(&port, &(header[8]), 2); addrSet.mSrcAddr.sin_addr.s_addr = ipaddr; addrSet.mSrcAddr.sin_port = port; memcpy(&ipaddr, &(header[10]), 4); memcpy(&port, &(header[14]), 2); addrSet.mDestAddr.sin_addr.s_addr = ipaddr; addrSet.mDestAddr.sin_port = port; return 1; } int createRelayUdpPacket(const void *data, const int size, void *newpkt, int newsize, UdpRelayEnd *ure) { int pktsize = size + UDP_RELAY_HEADER_SIZE; if (newsize < pktsize) { std::cerr << "createRelayUdpPacket() ERROR invalid size"; std::cerr << std::endl; return 0; } uint8_t *header = (uint8_t *) newpkt; memcpy(header, UDP_IDENTITY_STRING_V1, UDP_IDENTITY_SIZE_V1); /* as IP:Port are already in network byte order, we can just write them to the dataspace */ uint32_t ipaddr = ure->mLocalAddr.sin_addr.s_addr; uint16_t port = ure->mLocalAddr.sin_port; memcpy(&(header[4]), &ipaddr, 4); memcpy(&(header[8]), &port, 2); ipaddr = ure->mRemoteAddr.sin_addr.s_addr; port = ure->mRemoteAddr.sin_port; memcpy(&(header[10]), &ipaddr, 4); memcpy(&(header[14]), &port, 2); memcpy(&(header[16]), data, size); return pktsize; } /******* Small Container Class Helper Functions ****/ UdpRelayAddrSet::UdpRelayAddrSet() { sockaddr_clear(&mSrcAddr); sockaddr_clear(&mDestAddr); } UdpRelayAddrSet::UdpRelayAddrSet(const sockaddr_in *ownAddr, const sockaddr_in *destAddr) { mSrcAddr = *ownAddr; mDestAddr = *destAddr; } UdpRelayAddrSet UdpRelayAddrSet::flippedSet() { UdpRelayAddrSet flipped(&mDestAddr, &mSrcAddr); return flipped; } int operator<(const UdpRelayAddrSet &a, const UdpRelayAddrSet &b) { if (a.mSrcAddr < b.mSrcAddr) { return 1; } if (a.mSrcAddr == b.mSrcAddr) { if (a.mDestAddr < b.mDestAddr) { return 1; } } return 0; } UdpRelayProxy::UdpRelayProxy() { mBandwidth = 0; mDataSize = 0; mLastBandwidthTS = 0; mLastTS = time(NULL); // Must be set here, otherwise Proxy Timesout before anything can happen! mRelayClass = 0; mStartTS = time(NULL); mBandwidthLimit = 0; } UdpRelayProxy::UdpRelayProxy(UdpRelayAddrSet *addrSet, int relayClass) { mAddrs = *addrSet; mRelayClass = relayClass; mBandwidth = 0; mDataSize = 0; mLastBandwidthTS = 0; mLastTS = time(NULL); mStartTS = time(NULL); switch(relayClass) { default: case UDP_RELAY_CLASS_GENERAL: mBandwidthLimit = RELAY_MAX_BANDWIDTH; break; case UDP_RELAY_CLASS_FOF: mBandwidthLimit = RELAY_MAX_BANDWIDTH; break; case UDP_RELAY_CLASS_FRIENDS: mBandwidthLimit = RELAY_MAX_BANDWIDTH; break; } } UdpRelayEnd::UdpRelayEnd() { sockaddr_clear(&mLocalAddr); sockaddr_clear(&mProxyAddr); sockaddr_clear(&mRemoteAddr); } UdpRelayEnd::UdpRelayEnd(UdpRelayAddrSet *endPoints, const struct sockaddr_in *proxyaddr) { mLocalAddr = endPoints->mSrcAddr; mRemoteAddr = endPoints->mDestAddr; mProxyAddr = *proxyaddr; } std::ostream &operator<<(std::ostream &out, const UdpRelayAddrSet &uras) { out << "<" << uras.mSrcAddr << "," << uras.mDestAddr << ">"; return out; } std::ostream &operator<<(std::ostream &out, const UdpRelayProxy &urp) { time_t now = time(NULL); out << "UdpRelayProxy for " << urp.mAddrs; out << std::endl; out << "\tRelayClass: " << urp.mRelayClass; out << std::endl; out << "\tBandwidth: " << urp.mBandwidth; out << std::endl; out << "\tDataSize: " << urp.mDataSize; out << std::endl; out << "\tLastBandwidthTS: " << now - urp.mLastBandwidthTS << " secs ago"; out << std::endl; out << "\tLastTS: " << now - urp.mLastTS << " secs ago"; out << std::endl; return out; } std::ostream &operator<<(std::ostream &out, const UdpRelayEnd &ure) { out << "UdpRelayEnd: <" << ure.mLocalAddr << " => " << ure.mProxyAddr << " <= "; out << ure.mRemoteAddr << ">"; return out; }