#include <unordered_set>
#include "sstuff.hh"
#include <thread>
+#include <mutex>
+#include "minicurl.hh"
#if !defined(HAVE_LUA)
class IsUpOracle
{
+private:
+ typedef std::unordered_map<string,string> opts_t;
+ struct CheckDesc
+ {
+ ComboAddress rem;
+ string url;
+ opts_t opts;
+ bool operator<(const CheckDesc& rhs) const
+ {
+ return std::make_tuple(rem, url) <
+ std::make_tuple(rhs.rem, rhs.url);
+ }
+ };
public:
bool isUp(const ComboAddress& remote);
-
-
+ bool isUp(const ComboAddress& remote, const std::string& url, opts_t opts=opts_t());
+
private:
- void checkThread(const ComboAddress& rem) {
- d_statuses[rem].second=false;
- for(;;) {
+ void checkURLThread(ComboAddress rem, std::string url, opts_t opts);
+ void checkTCPThread(const ComboAddress& rem) {
+ CheckDesc cd{rem};
+ setDown(cd);
+ for(bool first=true;;first=false) {
try {
Socket s(rem.sin4.sin_family, SOCK_STREAM);
s.setNonBlocking();
s.connect(rem, 1);
- if(!d_statuses[rem].second)
+ if(!d_statuses[{rem,string()}].second)
cout<<"Declaring "<<rem.toStringWithPort()<<" UP!"<<endl;
- d_statuses[rem].second=true;
+ setUp(cd);
}
catch(NetworkError& ne) {
- if(d_statuses[rem].second)
+ if(d_statuses[{rem,string()}].second || first)
cout<<"Failed to connect to "<<rem.toStringWithPort()<<", setting DOWN"<<endl;
- d_statuses[rem].second=false;
+ setDown(cd);
}
sleep(1);
}
}
- map<ComboAddress, pair<std::thread*, bool>> d_statuses;
+
+
+ map<CheckDesc, pair<std::thread*, bool>> d_statuses;
+
+ std::mutex d_mutex;
+
+ void setStatus(const CheckDesc& cd, bool status)
+ {
+ std::lock_guard<std::mutex> l(d_mutex);
+ d_statuses[cd].second=status;
+ }
+
+ void setDown(const ComboAddress& rem, const std::string& url=std::string(), opts_t opts=opts_t())
+ {
+ CheckDesc cd{rem, url, opts};
+ setStatus(cd, false);
+ }
+
+ void setUp(const ComboAddress& rem, const std::string& url=std::string(), opts_t opts=opts_t())
+ {
+ CheckDesc cd{rem, url, opts};
+ setStatus(cd, true);
+ }
+
+ void setDown(const CheckDesc& cd)
+ {
+ setStatus(cd, false);
+ }
+
+ void setUp(const CheckDesc& cd)
+ {
+ setStatus(cd, true);
+ }
+
+ bool upStatus(const ComboAddress& rem, const std::string& url=std::string(), opts_t opts=opts_t())
+ {
+ CheckDesc cd{rem, url, opts};
+ std::lock_guard<std::mutex> l(d_mutex);
+ return d_statuses[cd].second;
+ }
+
};
bool IsUpOracle::isUp(const ComboAddress& remote)
{
- auto iter = d_statuses.find(remote);
+ std::lock_guard<std::mutex> l(d_mutex);
+ CheckDesc cd{remote};
+ auto iter = d_statuses.find(cd);
if(iter == d_statuses.end()) {
cout<<"First ever query for "<<remote.toStringWithPort()<<", launching checker"<<endl;
- std::thread* checker = new std::thread(&IsUpOracle::checkThread, this, remote);
- d_statuses[remote]={checker, false};
+ std::thread* checker = new std::thread(&IsUpOracle::checkTCPThread, this, remote);
+ d_statuses[cd]={checker, false};
return false;
}
return iter->second.second;
}
+bool IsUpOracle::isUp(const ComboAddress& remote, const std::string& url, std::unordered_map<string,string> opts)
+{
+ CheckDesc cd{remote, url, opts};
+ std::lock_guard<std::mutex> l(d_mutex);
+ auto iter = d_statuses.find(cd);
+ if(iter == d_statuses.end()) {
+ cout<<"First ever query for "<<remote.toString()<<" and url "<<url<<", launching checker"<<endl;
+ std::thread* checker = new std::thread(&IsUpOracle::checkURLThread, this, remote, url, opts);
+ d_statuses[cd]={checker, false};
+ return false;
+ }
+ return iter->second.second;
+}
+
+void IsUpOracle::checkURLThread(ComboAddress rem, std::string url, opts_t opts)
+{
+ setDown(rem, url, opts);
+ for(bool first=true;;first=false) {
+ try {
+ MiniCurl mc;
+ cout<<"Checking URL "<<url<<" at "<<rem.toString()<<endl;
+ string content=mc.getURL(url, &rem);
+ if(opts.count("stringmatch") && content.find(opts["stringmatch"]) == string::npos) {
+ cout<<"URL "<<url<<" is up at "<<rem.toString()<<", but could not find stringmatch "<<opts["stringmatch"]<<" in page content, setting DOWN"<<endl;
+ setDown(rem, url, opts);
+ continue;
+ }
+ if(!upStatus(rem,url))
+ cout<<"Declaring "<<rem.toString()<<" UP for URL "<<url<<"!"<<endl;
+ setUp(rem, url);
+ }
+ catch(std::exception& ne) {
+ if(upStatus(rem,url,opts) || first)
+ cout<<"Failed to connect to "<<rem.toString()<<" for URL "<<url<<", setting DOWN, error: "<<ne.what()<<endl;
+ setDown(rem,url);
+ }
+ sleep(5);
+ }
+}
+
+
IsUpOracle g_up;
std::vector<shared_ptr<DNSRecordContent>> luaSynth(const std::string& code, const DNSName& query, const ComboAddress& who, uint16_t qtype)
for(const auto& c : candidates)
cout<<c.toString()<<" ";
cout<<endl;
+ vector<string> ret;
+ if(candidates.empty()) {
+ cout<<"Everything is down. Returning all of them"<<endl;
+ for(const auto& i : ips)
+ ret.push_back(i.second);
+ }
+ else
+ ret.push_back(candidates[random() % candidates.size()].toString());
+ return ret;
+ });
+
+
+ lua.writeFunction("ifurlup", [](const std::string& url, const vector<pair<int, string> >& ips, boost::optional<std::unordered_map<string,string>> options) {
+
+ vector<ComboAddress> candidates;
+ for(const auto& i : ips) {
+ ComboAddress rem(i.second, 80);
+ std::unordered_map<string,string> opts;
+ if(options)
+ opts = *options;
+ if(g_up.isUp(rem, url, opts))
+ candidates.push_back(rem);
+ }
+ cout<<"Have "<<candidates.size()<<" candidate IP addresses: ";
+ for(const auto& c : candidates)
+ cout<<c.toString()<<" ";
+ cout<<endl;
+
+ vector<string> ret;
if(candidates.empty()) {
- cout<<"Picking a random one, everything is down. Should return all of them"<<endl;
- return ips[random() % ips.size()].second;
+ cout<<"Everything is down, returning all IP addresses"<<endl;
+ for(const auto& i : ips)
+ ret.push_back(i.second);
}
- return candidates[random() % candidates.size()].toString();
+ else
+ ret.push_back(candidates[random() % candidates.size()].toString());
+ return ret;
});
+
lua.writeFunction("pickRandom", [](const vector<pair<int, string> >& ips) {
return ips[random()%ips.size()].second;
});
- string content=lua.executeCode<string>(strip);
- if(qtype==QType::TXT)
- content = '"'+content+'"';
-
- ret.push_back(std::shared_ptr<DNSRecordContent>(DNSRecordContent::mastermake(qtype, 1, content )));
+ auto content=lua.executeCode<boost::variant<string, vector<pair<int, string> > > >(strip);
+
+ vector<string> contents;
+ if(auto str = boost::get<string>(&content))
+ contents.push_back(*str);
+ else
+ for(const auto& c : boost::get<vector<pair<int,string>>>(content))
+ contents.push_back(c.second);
+
+ for(const auto& content: contents) {
+ if(qtype==QType::TXT)
+ ret.push_back(std::shared_ptr<DNSRecordContent>(DNSRecordContent::mastermake(qtype, 1, '"'+content+'"' )));
+ else
+ ret.push_back(std::shared_ptr<DNSRecordContent>(DNSRecordContent::mastermake(qtype, 1, content )));
+ }
return ret;
}
--- /dev/null
+#include "minicurl.hh"
+#include <curl/curl.h>
+#include <stdexcept>
+
+MiniCurl::MiniCurl()
+{
+ d_curl = curl_easy_init();
+}
+
+MiniCurl::~MiniCurl()
+{
+ curl_easy_cleanup(d_curl);
+}
+
+size_t MiniCurl::write_callback(char *ptr, size_t size, size_t nmemb, void *userdata)
+{
+ MiniCurl* us = (MiniCurl*)userdata;
+ us->d_data.append(ptr, size*nmemb);
+ return size*nmemb;
+}
+
+static string extractHostFromURL(const std::string& url)
+{
+ auto pos = url.find("://");
+ if(pos == string::npos)
+ throw runtime_error("Can't find host part of '"+url+"'");
+ pos += 3;
+ auto endpos = url.find('/', pos);
+ if(endpos == string::npos)
+ return url.substr(pos);
+
+ return url.substr(pos, endpos-pos);
+}
+
+void MiniCurl::setupURL(const std::string& str, const ComboAddress* rem)
+{
+ if(rem) {
+ struct curl_slist *hostlist = NULL;
+
+ // url = http://hostname.enzo/url
+
+ string host4=extractHostFromURL(str);
+ cout<<"Host name: '"<<host4<<"'"<<endl;
+ string hcode=(host4+":80:"+rem->toString());
+ //cout<<"Setting hardcoded IP: "<<hcode<<endl;
+ hostlist = curl_slist_append(NULL, hcode.c_str());
+ hcode=(host4+":443:"+rem->toString());
+ // cout<<"Setting hardcoded IP: "<<hcode<<endl;;
+ hostlist = curl_slist_append(hostlist, hcode.c_str());
+
+ curl_easy_setopt(d_curl, CURLOPT_RESOLVE, hostlist);
+ }
+ curl_easy_setopt(d_curl, CURLOPT_FOLLOWLOCATION, true);
+ /* only allow HTTP, TFTP and SFTP */
+ curl_easy_setopt(d_curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
+ curl_easy_setopt(d_curl, CURLOPT_SSL_VERIFYPEER, false);
+ curl_easy_setopt(d_curl, CURLOPT_SSL_VERIFYHOST, false);
+ curl_easy_setopt(d_curl, CURLOPT_FAILONERROR, true);
+ curl_easy_setopt(d_curl, CURLOPT_URL, str.c_str());
+ curl_easy_setopt(d_curl, CURLOPT_WRITEFUNCTION, write_callback);
+ curl_easy_setopt(d_curl, CURLOPT_WRITEDATA, this);
+
+
+ d_data.clear();
+}
+std::string MiniCurl::getURL(const std::string& str, const ComboAddress* rem)
+{
+ setupURL(str, rem);
+ auto res = curl_easy_perform(d_curl);
+ long http_code = 0;
+ curl_easy_getinfo(d_curl, CURLINFO_RESPONSE_CODE, &http_code);
+
+ if(res != CURLE_OK || http_code != 200) {
+ throw std::runtime_error("Unable to retrieve URL ("+std::to_string(http_code)+"): "+string(curl_easy_strerror(res)));
+ }
+ std::string ret=d_data;
+ d_data.clear();
+ return ret;
+}
+
+std::string MiniCurl::postURL(const std::string& str, const std::string& postdata)
+{
+ setupURL(str);
+ curl_easy_setopt(d_curl, CURLOPT_POSTFIELDS, postdata.c_str());
+
+ auto res = curl_easy_perform(d_curl);
+ if(res != CURLE_OK)
+ throw std::runtime_error("Unable to post URL");
+
+ std::string ret=d_data;
+
+ d_data.clear();
+ return ret;
+}