#include "iputils.hh"
#include "logger.hh"
#include "ixfrdist-stats.hh"
+#include "ixfrdist-web.hh"
#include <yaml-cpp/yaml.h>
/* BEGIN Needed because of deeper dependencies */
}
}
};
+
+template<>
+struct convert<Netmask> {
+ static Node encode(const Netmask& rhs) {
+ return Node(rhs.toString());
+ }
+ static bool decode(const Node& node, Netmask& rhs) {
+ if (!node.IsScalar()) {
+ return false;
+ }
+ try {
+ rhs = Netmask(node.as<string>());
+ return true;
+ } catch(const runtime_error &e) {
+ return false;
+ } catch (const PDNSException &e) {
+ return false;
+ }
+ }
+};
} // namespace YAML
struct ixfrdiff_t {
shared_ptr<SOARecordContent> newSOA;
vector<DNSRecord> removals;
vector<DNSRecord> additions;
+ uint32_t oldSOATTL;
+ uint32_t newSOATTL;
};
struct ixfrinfo_t {
shared_ptr<SOARecordContent> soa; // The SOA of the latest AXFR
records_t latestAXFR; // The most recent AXFR
vector<std::shared_ptr<ixfrdiff_t>> ixfrDiffs;
+ uint32_t soaTTL;
};
// Why a struct? This way we can add more options to a domain in the future
static ixfrdistStats g_stats;
+// g_stats is static, so local to this file. But the webserver needs this info
+string doGetStats() {
+ return g_stats.getStats();
+}
+
static void handleSignal(int signum) {
g_log<<Logger::Notice<<"Got "<<strsignal(signum)<<" signal";
if (g_exiting) {
}
}
-static shared_ptr<SOARecordContent> getSOAFromRecords(const records_t& records) {
+static void getSOAFromRecords(const records_t& records, shared_ptr<SOARecordContent>& soa, uint32_t& soaTTL) {
for (const auto& dnsrecord : records) {
if (dnsrecord.d_type == QType::SOA) {
- auto soa = getRR<SOARecordContent>(dnsrecord);
+ soa = getRR<SOARecordContent>(dnsrecord);
if (soa == nullptr) {
throw PDNSException("Unable to determine SOARecordContent from old records");
}
- return soa;
+ soaTTL = dnsrecord.d_ttl;
+ return;
}
}
throw PDNSException("No SOA in supplied records");
}
-static void makeIXFRDiff(const records_t& from, const records_t& to, std::shared_ptr<ixfrdiff_t>& diff, const shared_ptr<SOARecordContent>& fromSOA = nullptr, const shared_ptr<SOARecordContent>& toSOA = nullptr) {
+static void makeIXFRDiff(const records_t& from, const records_t& to, std::shared_ptr<ixfrdiff_t>& diff, const shared_ptr<SOARecordContent>& fromSOA = nullptr, uint32_t fromSOATTL=0, const shared_ptr<SOARecordContent>& toSOA = nullptr, uint32_t toSOATTL = 0) {
set_difference(from.cbegin(), from.cend(), to.cbegin(), to.cend(), back_inserter(diff->removals), from.value_comp());
set_difference(to.cbegin(), to.cend(), from.cbegin(), from.cend(), back_inserter(diff->additions), from.value_comp());
diff->oldSOA = fromSOA;
+ diff->oldSOATTL = fromSOATTL;
if (fromSOA == nullptr) {
- diff->oldSOA = getSOAFromRecords(from);
+ getSOAFromRecords(from, diff->oldSOA, diff->oldSOATTL);
}
diff->newSOA = toSOA;
+ diff->newSOATTL = toSOATTL;
if (toSOA == nullptr) {
- diff->newSOA = getSOAFromRecords(to);
+ getSOAFromRecords(to, diff->newSOA, diff->newSOATTL);
}
}
std::lock_guard<std::mutex> guard(g_soas_mutex);
g_soas[domain] = newInfo;
g_stats.setSOASerial(domain, newInfo->soa->d_st.serial);
+ // FIXME: also report zone size?
}
void updateThread(const string& workdir, const uint16_t& keep, const uint16_t& axfrTimeout, const uint16_t& soaRetry) {
g_log<<Logger::Info<<"Trying to initially load domain "<<domain<<" from disk"<<endl;
auto serial = getSerialsFromDir(dir);
shared_ptr<SOARecordContent> soa;
+ uint32_t soaTTL;
{
string fname = workdir + "/" + domain.toString() + "/" + std::to_string(serial);
- loadSOAFromDisk(domain, fname, soa);
+ loadSOAFromDisk(domain, fname, soa, soaTTL);
records_t records;
if (soa != nullptr) {
loadZoneFromDisk(records, fname, domain);
auto zoneInfo = std::make_shared<ixfrinfo_t>();
zoneInfo->latestAXFR = std::move(records);
zoneInfo->soa = soa;
+ zoneInfo->soaTTL = soaTTL;
updateCurrentZoneInfo(domain, zoneInfo);
}
if (soa != nullptr) {
// Attempt to create it, if _that_ fails, there is no hope
if (mkdir(dir.c_str(), 0777) == -1 && errno != EEXIST) {
g_log<<Logger::Error<<"Could not create '"<<dir<<"': "<<strerror(errno)<<endl;
- exit(EXIT_FAILURE);
+ _exit(EXIT_FAILURE);
}
}
}
g_log<<Logger::Notice<<"Update Thread started"<<endl;
while (true) {
- cout<<g_stats.getStats()<<endl;
if (g_exiting) {
g_log<<Logger::Notice<<"UpdateThread stopped"<<endl;
break;
shared_ptr<SOARecordContent> sr;
try {
zoneLastCheck = now;
+ g_stats.incrementSOAChecks(domain);
auto newSerial = getSerialFromMaster(master, domain, sr); // TODO TSIG
if(current_soa != nullptr) {
g_log<<Logger::Info<<"Got SOA Serial for "<<domain<<" from "<<master.toStringWithPort()<<": "<< newSerial<<", had Serial: "<<current_soa->d_st.serial;
}
} catch (runtime_error &e) {
g_log<<Logger::Warning<<"Unable to get SOA serial update for '"<<domain<<"' from master "<<master.toStringWithPort()<<": "<<e.what()<<endl;
+ g_stats.incrementSOAChecksFailed(domain);
continue;
}
// Now get the full zone!
// The *new* SOA
shared_ptr<SOARecordContent> soa;
+ uint32_t soaTTL = 0;
records_t records;
try {
AXFRRetriever axfr(master, domain, tt, &local);
nrecords++;
if (dr.d_type == QType::SOA) {
soa = getRR<SOARecordContent>(dr);
+ soaTTL = dr.d_ttl;
}
}
axfr_now = time(nullptr);
if (axfr_now - t_start > axfrTimeout) {
+ g_stats.incrementAXFRFailures(domain);
throw PDNSException("Total AXFR time exceeded!");
}
}
if (soa == nullptr) {
+ g_stats.incrementAXFRFailures(domain);
g_log<<Logger::Warning<<"No SOA was found in the AXFR of "<<domain<<endl;
continue;
}
g_log<<Logger::Notice<<"Retrieved all zone data for "<<domain<<". Received "<<nrecords<<" records."<<endl;
} catch (PDNSException &e) {
+ g_stats.incrementAXFRFailures(domain);
g_log<<Logger::Warning<<"Could not retrieve AXFR for '"<<domain<<"': "<<e.reason<<endl;
continue;
} catch (runtime_error &e) {
+ g_stats.incrementAXFRFailures(domain);
g_log<<Logger::Warning<<"Could not retrieve AXFR for zone '"<<domain<<"': "<<e.what()<<endl;
continue;
}
auto diff = std::make_shared<ixfrdiff_t>();
zoneInfo->ixfrDiffs = oldZoneInfo->ixfrDiffs;
g_log<<Logger::Debug<<"Calculating diff for "<<domain<<endl;
- makeIXFRDiff(oldZoneInfo->latestAXFR, records, diff, oldZoneInfo->soa, soa);
+ makeIXFRDiff(oldZoneInfo->latestAXFR, records, diff, oldZoneInfo->soa, oldZoneInfo->soaTTL, soa, soaTTL);
g_log<<Logger::Debug<<"Calculated diff for "<<domain<<", we had "<<diff->removals.size()<<" removals and "<<diff->additions.size()<<" additions"<<endl;
zoneInfo->ixfrDiffs.push_back(std::move(diff));
}
g_log<<Logger::Debug<<"Zone "<<domain<<" previously contained "<<(oldZoneInfo ? oldZoneInfo->latestAXFR.size() : 0)<<" entries, "<<records.size()<<" now"<<endl;
zoneInfo->latestAXFR = std::move(records);
zoneInfo->soa = soa;
+ zoneInfo->soaTTL = soaTTL;
updateCurrentZoneInfo(domain, zoneInfo);
} catch (PDNSException &e) {
+ g_stats.incrementAXFRFailures(domain);
g_log<<Logger::Warning<<"Could not save zone '"<<domain<<"' to disk: "<<e.reason<<endl;
} catch (runtime_error &e) {
+ g_stats.incrementAXFRFailures(domain);
g_log<<Logger::Warning<<"Could not save zone '"<<domain<<"' to disk: "<<e.what()<<endl;
}
pw.getHeader()->rd = mdp.d_header.rd;
pw.getHeader()->qr = 1;
- pw.startRecord(mdp.d_qname, QType::SOA);
+ pw.startRecord(mdp.d_qname, QType::SOA, zoneInfo->soaTTL);
zoneInfo->soa->toPacket(pw);
pw.commit();
return true;
}
-static vector<uint8_t> getSOAPacket(const MOADNSParser& mdp, const shared_ptr<SOARecordContent>& soa) {
+static vector<uint8_t> getSOAPacket(const MOADNSParser& mdp, const shared_ptr<SOARecordContent>& soa, uint32_t soaTTL) {
vector<uint8_t> packet;
DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
pw.getHeader()->id = mdp.d_header.id;
pw.getHeader()->qr = 1;
// Add the first SOA
- pw.startRecord(mdp.d_qname, QType::SOA);
+ pw.startRecord(mdp.d_qname, QType::SOA, soaTTL);
soa->toPacket(pw);
pw.commit();
return packet;
A newer one may arise in the meantime, but this one will stay valid
until we release it.
*/
+
+ g_stats.incrementAXFRinQueries(mdp.d_qname);
+
auto zoneInfo = getCurrentZoneInfo(mdp.d_qname);
if (zoneInfo == nullptr) {
return false;
}
shared_ptr<SOARecordContent> soa = zoneInfo->soa;
+ uint32_t soaTTL = zoneInfo->soaTTL;
const records_t& records = zoneInfo->latestAXFR;
// Initial SOA
- const auto soaPacket = getSOAPacket(mdp, soa);
+ const auto soaPacket = getSOAPacket(mdp, soa, soaTTL);
if (!sendPacketOverTCP(fd, soaPacket)) {
return false;
}
A newer one may arise in the meantime, but this one will stay valid
until we release it.
*/
+
+ g_stats.incrementIXFRinQueries(mdp.d_qname);
+
auto zoneInfo = getCurrentZoneInfo(mdp.d_qname);
if (zoneInfo == nullptr) {
return false;
}
if (toSend.empty()) {
+ // FIXME: incrementIXFRFallbacks
g_log<<Logger::Warning<<"No IXFR available from serial "<<clientSOA->d_st.serial<<" for zone "<<mdp.d_qname<<", attempting to send AXFR"<<endl;
return handleAXFR(fd, mdp);
}
* SOA new_serial
*/
- const auto newSOAPacket = getSOAPacket(mdp, diff->newSOA);
- const auto oldSOAPacket = getSOAPacket(mdp, diff->oldSOA);
+ const auto newSOAPacket = getSOAPacket(mdp, diff->newSOA, diff->newSOATTL);
+ const auto oldSOAPacket = getSOAPacket(mdp, diff->oldSOA, diff->oldSOATTL);
if (!sendPacketOverTCP(fd, newSOAPacket)) {
return false;
* Let's not complicate this with IXFR over UDP (and looking if we need to truncate etc).
* Just send the current SOA and let the client try over TCP
*/
+ g_stats.incrementSOAinQueries(mdp.d_qname); // FIXME: this also counts IXFR queries (but the response is the same as to a SOA query)
makeSOAPacket(mdp, packet);
} else {
makeRefusedPacket(mdp, packet);
config["compress"] = false;
}
+ if (config["webserver-address"]) {
+ try {
+ config["webserver-address"].as<ComboAddress>();
+ }
+ catch (const runtime_error &e) {
+ g_log<<Logger::Error<<"Unable to read 'webserver-address' value: "<<e.what()<<endl;
+ retval = false;
+ }
+ }
+
+ if (config["webserver-acl"]) {
+ try {
+ config["webserver-acl"].as<vector<Netmask>>();
+ }
+ catch (const runtime_error &e) {
+ g_log<<Logger::Error<<"Unable to read 'webserver-acl' value: "<<e.what()<<endl;
+ retval = false;
+ }
+ }
+
return retval;
}
}
}
+ if (config["webserver-address"]) {
+ NetmaskGroup wsACL;
+ wsACL.addMask("127.0.0.0/8");
+ wsACL.addMask("::1/128");
+
+ if (config["webserver-acl"]) {
+ wsACL.clear();
+ for (const auto &acl : config["webserver-acl"].as<vector<Netmask>>()) {
+ wsACL.addMask(acl);
+ }
+ }
+
+ // Launch the webserver!
+ std::thread(&IXFRDistWebServer::go, IXFRDistWebServer(config["webserver-address"].as<ComboAddress>(), wsACL)).detach();
+ }
+
int newuid = 0;
if (config["uid"]) {