/* * libretroshare/src/dht: opendht.cc * * Interface with OpenDHT for RetroShare. * * Copyright 2007-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 "dht/opendht.h" #include "dht/opendhtstr.h" #include "dht/b64.h" #include #include #include #include #include "util/rsnet.h" #include "util/rsprint.h" const std::string openDHT_Client = "Retroshare V0.4"; const std::string openDHT_Agent = "RS-HTTP-V0.4"; #define MAX_DHT_PEER_FAILS 2 /* then discard */ #define MAX_DHT_TOTAL_FAILS 10 /* in a row -> think we're not connected! */ #define MAX_DHT_ATTEMPTS 10 /* attempts per search/publish */ #define MIN_DHT_SERVERS 5 /**** * #define OPENDHT_DEBUG 1 ****/ bool OpenDHTClient::checkServerFile(std::string filename) { #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::checkServerFile(" << filename << ")" << std::endl; #endif /* open the file */ std::ifstream file(filename.c_str()); if (file.fail()) { #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::checkServerFile() Open Failed" << std::endl; #endif return false; } /* get the first line */ std::string line; getline(file, line); char day[16], month[16]; int date; char day2[16], month2[16]; int date2; if (3 != sscanf(line.c_str(), "%15s %15s %d", day, month, &date)) { #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::checkServerFile() failed file TS parse"; std::cerr << std::endl; #endif return false; } #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::checkServerFile() file TS month: "; std::cerr << month << " date: " << date << std::endl; #endif /* store current timestamp */ struct tm result; time_t now = time(NULL); char nowstr[1023]; asctime_r(gmtime_r(&now, &result), nowstr); if (3 != sscanf(nowstr, "%15s %15s %d", day2, month2, &date2)) { #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::checkServerFile() failed now TS parse"; std::cerr << std::endl; #endif return false; } #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::checkServerFile() current TS month: "; std::cerr << month2 << " date: " << date2 << std::endl; #endif /* if month is different */ if (0 != strcmp(month, month2)) { #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::checkServerFile() different MONTHS fail"; std::cerr << std::endl; #endif return false; } /* if month is different */ int delta = abs(date-date2); if (delta > 2) { #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::checkServerFile() fail - large DATE diff: " << delta; std::cerr << std::endl; #endif return false; } #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::checkServerFile() file is up-to-date!"; std::cerr << std::endl; #endif return true; } bool OpenDHTClient::loadServers(std::string filename) { /* open the file */ std::ifstream file(filename.c_str()); return loadServers(file); } bool OpenDHTClient::loadServers(std::istream &instr) { std::string line; char number[1024]; char ipaddr[1024]; char dnsname[1024]; dhtMutex.lock(); /**** LOCK ****/ mServers.clear(); dhtMutex.unlock(); /**** UNLOCK ****/ /* chew first line */ instr.ignore(1024, '\n'); while((!instr.eof()) && (!instr.fail())) { line = ""; getline(instr, line); if (3 == sscanf(line.c_str(), "%1023s %1023s %1023s", number, ipaddr, dnsname)) { dhtServer srv; srv.host = dnsname; srv.port = 5851; srv.failed = 0; srv.ts = 0; srv.addr.sin_addr.s_addr = 0; srv.addr.sin_port = 0; #ifdef OPENDHT_DEBUG std::cerr << "Read Server: " << dnsname << std::endl; #endif dhtMutex.lock(); /**** LOCK ****/ mServers[dnsname] = srv; dhtMutex.unlock(); /**** UNLOCK ****/ } else { #ifdef OPENDHT_DEBUG std::cerr << "Failed to Read Server" << std::endl; #endif } dhtMutex.lock(); /**** LOCK ****/ mDHTFailCount = 0; dhtMutex.unlock(); /**** UNLOCK ****/ } dhtMutex.lock(); /**** LOCK ****/ uint32_t count = mServers.size(); dhtMutex.unlock(); /**** UNLOCK ****/ return (count >= MIN_DHT_SERVERS); } /******* refresh Servers from WebPage ******/ bool OpenDHTClient::loadServersFromWeb(std::string storename) { #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::loadServersFromWeb()" << std::endl; #endif std::string response; if (!openDHT_getDHTList(response)) { #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::loadServersFromWeb() Web GET failed" << std::endl; #endif return false; } std::string::size_type i; if (std::string::npos == (i = response.find("\r\n\r\n"))) { #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::loadServersFromWeb() Failed to Find Content" << std::endl; #endif return false; } /* now step past 4 chars */ i += 4; std::string content(response, i, response.length() - i); #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::loadServersFromWeb() Content:" << std::endl; std::cerr << content << std::endl; std::cerr << "<== OpenDHTClient::loadServersFromWeb() Content" << std::endl; #endif std::istringstream iss(content); if (loadServers(iss)) { #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::loadServersFromWeb() Saving WebData to: "; std::cerr << storename << std::endl; #endif /* save the data to file - replacing old data */ std::ofstream ofstr(storename.c_str()); ofstr << content; ofstr.close(); return true; } return false; } bool OpenDHTClient::getServer(std::string &host, uint16_t &port, struct sockaddr_in &addr) { /* randomly choose one */ dhtMutex.lock(); /**** LOCK ****/ #ifndef WINDOWS_SYS #else /* WINDOWS don't randomise properly so we'll do it ourselves... */ uint32_t randomize = timeGetTime(); srand(randomize); #endif uint32_t len = mServers.size(); uint32_t rnd = len * (rand() / (RAND_MAX + 1.0)); if (len < 1) { #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::getServer() No Servers available!" << std::endl; #endif dhtMutex.unlock(); /**** UNLOCK ****/ return false; } std::map::const_iterator it; uint32_t i = 0; for(it = mServers.begin(); (it != mServers.end()) && (i < rnd); it++, i++); if (it == mServers.end()) { #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::getServer() Error getting Server!" << std::endl; #endif dhtMutex.unlock(); /**** UNLOCK ****/ return false; } host = (it->second).host; port = (it->second).port; time_t now = time(NULL); if (now - (it->second).ts < 3600) { addr = (it->second).addr; } else { addr.sin_addr.s_addr = 0; } dhtMutex.unlock(); /**** UNLOCK ****/ return true; } bool OpenDHTClient::setServerIp(std::string host, struct sockaddr_in addr) { dhtMutex.lock(); /**** LOCK ****/ std::map::iterator it; it = mServers.find(host); if (it == mServers.end()) { dhtMutex.unlock(); /**** UNLOCK ****/ #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::setServerIp() Error finding Server!" << std::endl; #endif return false; } (it -> second).addr = addr; (it -> second).ts = time(NULL); (it -> second).failed = 0; mDHTFailCount = 0; dhtMutex.unlock(); /**** UNLOCK ****/ return true; } void OpenDHTClient::setServerFailed(std::string host) { dhtMutex.lock(); /**** LOCK ****/ std::map::iterator it; it = mServers.find(host); if (it == mServers.end()) { #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::setServerFailed() Error finding Server!" << std::endl; #endif dhtMutex.unlock(); /**** UNLOCK ****/ return; } mDHTFailCount++; if (mDHTFailCount > MAX_DHT_TOTAL_FAILS) /* might be not connected to Internet */ { #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::setServerFailed() Probably not connected!" << std::endl; #endif dhtMutex.unlock(); /**** UNLOCK ****/ return; } /* up the fail count on this one */ (it -> second).failed++; if ((it -> second).failed > MAX_DHT_PEER_FAILS) { #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::setServerFailed() fail count high -> removing: "; std::cerr << host << " from list" << std::endl; #endif /* remove from list */ mServers.erase(it); } dhtMutex.unlock(); /**** UNLOCK ****/ return; } bool OpenDHTClient::dhtActive() { dhtMutex.lock(); /**** LOCK ****/ bool ok = (mDHTFailCount <= MAX_DHT_TOTAL_FAILS) && (mServers.size() > MIN_DHT_SERVERS); dhtMutex.unlock(); /**** UNLOCK ****/ return ok; } bool OpenDHTClient::publishKey(std::string key, std::string value, uint32_t ttl) { /* create request */ #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::openDHT_publishKey() key: 0x" << RsUtil::BinToHex(key) << " value: 0x" << RsUtil::BinToHex(value); std::cerr << std::endl; #endif std::string putmsg = createOpenDHT_put(key, value, ttl, openDHT_Client); std::string response; for(uint16_t i = 0; (!openDHT_sendMessage(putmsg, response)); i++) { if (i > MAX_DHT_ATTEMPTS) { #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::openDHT_publishKey() Failed -> Giving Up"; std::cerr << std::endl; #endif return false; } #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::openDHT_publishKey() Failed -> reattempting"; std::cerr << std::endl; #endif } /* check response */ return true; } bool OpenDHTClient::searchKey(std::string key, std::list &values) { #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::openDHT_searchKey() key: 0x" << RsUtil::BinToHex(key); std::cerr << std::endl; #endif /* create request */ std::string getmsg = createOpenDHT_get(key, 1024, openDHT_Client); std::string response; for(uint16_t i = 0; (!openDHT_sendMessage(getmsg, response)); i++) { if (i > MAX_DHT_ATTEMPTS) { #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::openDHT_searchKey() Failed -> Giving Up"; std::cerr << std::endl; #endif return false; } #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::openDHT_searchKey() Failed -> reattempting"; std::cerr << std::endl; #endif } #ifdef OPENDHT_DEBUG std::cerr << "Parsing expression :$$$$$$$$$-" << response << "-$$$$$$$$" << std::endl ; #endif /* search through the response for ... */ std::string::size_type start = 0; std::string::size_type loc = 0; std::string::size_type end = 0; while(1) { loc = response.find("", start); if (loc == std::string::npos) return true; /* finished */ #ifdef OPENDHT_DEBUG std::cerr << "found loc=" << loc << std::endl ; #endif loc += 8; /* shift to end of */ end = response.find("", loc); if (end == std::string::npos) return true; /* finished */ #ifdef OPENDHT_DEBUG std::cerr << "found end=" << end << std::endl ; #endif std::string value = response.substr(loc, end-loc); #ifdef OPENDHT_DEBUG std::cerr << "found value=" << value << std::endl ; #endif /* clear out whitespace */ for(std::string::size_type i = 0; i < value.length();) { if (isspace(value[i])) { value.erase(i,1); #ifdef OPENDHT_DEBUG std::cerr << "Cleanup Result:" << value << ":END:" << std::endl; #endif } else { i++; } } if (value.length() > 0) { std::string result = convertFromBase64(value); values.push_back(result); #ifdef OPENDHT_DEBUG std::cerr << "openDHT_searchKey() Value:" << value << ":END:" << std::endl; std::cerr << "openDHT_searchKey() Result: 0x" << RsUtil::BinToHex(result) << ":END:" << std::endl; #endif } /* the answer should be between loc and end */ start = end + 9; } /* parse response */ return true; } bool OpenDHTClient::openDHT_sendMessage(std::string msg, std::string &response) { struct sockaddr_in addr; std::string host; uint16_t port; if (!getServer(host, port, addr)) { #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::openDHT_sendMessage() Failed to get Server"; std::cerr << std::endl; #endif return false; } if (addr.sin_addr.s_addr == 0) { /* lookup the address */ addr.sin_port = htons(port); if (LookupDNSAddr(host, addr) && (addr.sin_addr.s_addr != 0)) { /* update the IP addr if necessary */ setServerIp(host, addr); } else { /* no address */ #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::openDHT_sendMessage()"; std::cerr << " ERROR: No Address"; std::cerr << std::endl; #endif setServerFailed(host); return false; } } #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::openDHT_sendMessage()"; std::cerr << " Connecting to:" << host << ":" << port; std::cerr << " (" << inet_ntoa(addr.sin_addr) << ":" << ntohs(addr.sin_port) << ")"; std::cerr << std::endl; #endif /* create request */ std::string putheader = createHttpHeader(host, port, openDHT_Agent, msg.length()); /* open a socket */ int sockfd = unix_socket(PF_INET, SOCK_STREAM, 0); /* connect */ int err = unix_connect(sockfd, (struct sockaddr *) &addr, sizeof(addr)); if (err) { unix_close(sockfd); #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::openDHT_sendMessage()"; std::cerr << " ERROR: Failed to Connect"; std::cerr << std::endl; #endif setServerFailed(host); return false; } #ifdef OPENDHT_DEBUG std::cerr << "HTTP message *******************" << std::endl; std::cerr << putheader; std::cerr << msg; std::cerr << std::endl; std::cerr << "HTTP message *******************" << std::endl; #endif /* send data */ int sendsize = strlen(putheader.c_str()); int size = send(sockfd, putheader.c_str(), sendsize, 0); if (sendsize != size) { unix_close(sockfd); #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::openDHT_sendMessage()"; std::cerr << " ERROR: Failed to Send(1)"; std::cerr << std::endl; #endif setServerFailed(host); return false; } #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::openDHT_sendMessage()"; std::cerr << " Send(1):" << size; std::cerr << std::endl; #endif sendsize = strlen(msg.c_str()); size = send(sockfd, msg.c_str(), sendsize, 0); if (sendsize != size) { unix_close(sockfd); #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::openDHT_sendMessage()"; std::cerr << " ERROR: Failed to Send(2)"; std::cerr << std::endl; #endif setServerFailed(host); return false; } #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::openDHT_sendMessage()"; std::cerr << " Send(2):" << size; std::cerr << std::endl; #endif /* now wait for the response */ /********************************** WINDOWS/UNIX SPECIFIC PART ******************/ #ifndef WINDOWS_SYS sleep(1); #else Sleep(1000); #endif /********************************** WINDOWS/UNIX SPECIFIC PART ******************/ int recvsize = 51200; /* 50kb */ char *inbuf = (char *) malloc(recvsize); uint32_t idx = 0; while(0 < (size = recv(sockfd, &(inbuf[idx]), recvsize - idx, 0))) { #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::openDHT_sendMessage()"; std::cerr << " Recvd Chunk:" << size; std::cerr << std::endl; #endif idx += size; } #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::openDHT_sendMessage()"; std::cerr << " Recvd Msg:" << idx; #endif response = std::string(inbuf, idx); free(inbuf); /* print it out */ #ifdef OPENDHT_DEBUG std::cerr << "HTTP What We Sent ***************" << std::endl; std::cerr << putheader; std::cerr << msg; std::cerr << std::endl; std::cerr << "HTTP response *******************" << std::endl; std::cerr << response; std::cerr << std::endl; std::cerr << "HTTP response *******************" << std::endl; #endif unix_close(sockfd); return true; } bool OpenDHTClient::openDHT_getDHTList(std::string &response) { struct sockaddr_in addr; std::string host = "www.opendht.org"; uint16_t port = 80; sockaddr_clear(&addr); /* lookup the address */ addr.sin_family = AF_INET; addr.sin_port = htons(port); if (!LookupDNSAddr(host, addr)) { /* no address */ #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::openDHT_getDHTList()"; std::cerr << " ERROR: No Address"; std::cerr << std::endl; #endif return false; } #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::openDHT_getDHTList()"; std::cerr << " Connecting to:" << host << ":" << port; std::cerr << " (" << inet_ntoa(addr.sin_addr) << ":" << ntohs(addr.sin_port) << ")"; std::cerr << std::endl; #endif /* create request */ std::string putheader = createHttpHeaderGET(host, port, "servers.txt", openDHT_Agent, 0); /* open a socket */ int sockfd = unix_socket(PF_INET, SOCK_STREAM, 0); /* connect */ int err = unix_connect(sockfd, (struct sockaddr *) &addr, sizeof(addr)); if (err) { unix_close(sockfd); #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::openDHT_getDHTList()"; std::cerr << " ERROR: Failed to Connect"; std::cerr << std::endl; #endif return false; } #ifdef OPENDHT_DEBUG std::cerr << "HTTP message *******************" << std::endl; std::cerr << putheader; std::cerr << std::endl; std::cerr << "HTTP message *******************" << std::endl; #endif /* send data */ int sendsize = strlen(putheader.c_str()); int size = send(sockfd, putheader.c_str(), sendsize, 0); if (sendsize != size) { unix_close(sockfd); #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::openDHT_getDHTList()"; std::cerr << " ERROR: Failed to Send(1)"; std::cerr << std::endl; #endif return false; } #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::openDHT_getDHTList()"; std::cerr << " Send(1):" << size; std::cerr << std::endl; #endif /* now wait for the response */ /********************************** WINDOWS/UNIX SPECIFIC PART ******************/ #ifndef WINDOWS_SYS sleep(1); #else Sleep(1000); #endif /********************************** WINDOWS/UNIX SPECIFIC PART ******************/ int recvsize = 51200; /* 50kb */ char *inbuf = (char *) malloc(recvsize); uint32_t idx = 0; while(0 < (size = recv(sockfd, &(inbuf[idx]), recvsize - idx, 0))) { #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::openDHT_getDHTList()"; std::cerr << " Recvd Chunk:" << size; std::cerr << std::endl; #endif idx += size; } #ifdef OPENDHT_DEBUG std::cerr << "OpenDHTClient::openDHT_getDHTList()"; std::cerr << " Recvd Msg:" << idx; #endif response = std::string(inbuf, idx); free(inbuf); /* print it out */ #ifdef OPENDHT_DEBUG std::cerr << "HTTP response *******************" << std::endl; std::cerr << response; std::cerr << std::endl; std::cerr << "HTTP response *******************" << std::endl; #endif unix_close(sockfd); return true; }