interoperability, and strive to turn this functionalitity into a broadly
supported standard.
+To enable this feature, either set 'global-lua-record' in the configuration,
+or set the 'ENABLE-LUA-RECORD' per-zone metadata item to 1.
+
+In addition, to benefit from the geographical features, make sure the PowerDNS
+launch statement includes the ``geoip`` backend.
+
Examples
--------
the requestor and the listed IP addresses. It will return with one of the closest
addresses.
+``closest`` and ifportup can be combined as follows::
+
+ www IN LUA A ("ifportup(443, {'192.0.2.1', '192.0.2.2', '198.51.100.1'}"
+ ", {selector='closest'}) ")
+
+This will pick from the viable IP addresses the one deemed closest to the user.
Record format
-------------
IP address of requesting resolver
-``ecs-who``
+``ecswho``
~~~~~~~~~~~
The EDNS Client Subnet, should one have been set on the query. Unset
otherwise.
-``best-who``
+``bestwho``
~~~~~~~~~~~~
In absence of ECS, this is set to the IP address of requesting resolver.
Otherwise set to the network part of the EDNS Client Subnet supplied by the
Functions available
-------------------
-``ifportup(portnum, {'ip1', 'ip2'})``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+``ifportup(portnum, {'ip1', 'ip2'}, options)``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Simplistic test to see if an IP address listens on a certain port. Note that
both IPv4 and IPv6 addresses can be tested, but that it is an error to list
IPv4 addresses on an AAAA record, or IPv6 addresses on an A record.
no IP address is available, will return a random element of the set of
addresses suppplied for testing.
+Various options can be set in the ``options`` parameter:
+
+ - ``selector``: used to pick the IP address from list of viable candidates. Choices include 'closest', 'random', 'hashed'.
+ - ``source``: Source IP address to check from
+
+
``ifurlup(url, {{'ip1', 'ip2'}, {ip3}, options)``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
More sophisticated test that attempts an actual http(s) connection to
Various options can be set in the ``options`` parameter:
- - ``interval``: number of seconds to wait between checks
+ - ``selector``: used to pick the IP address from list of viable candidates. Choices include 'closest', 'random', 'hashed'.
+ - ``source``: Source IP address to check from
- ``stringmatch``: check ``url`` for this string, only declare 'up' if
found
This function also works for CNAME or TXT records.
-``whashed({{weight, 'ip1'}, {weight, 'ip2'}})``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+``whashed({{weight, 'ip1'}, ..})``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Based on the hash of ``bestwho``, returns an IP address from the list
supplied, as weighted by the various ``weight`` parameters.
Performs no uptime checking.
-``wrandom({{weight, 'ip1'}, {weight, 'ip2'}})``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+``wrandom({{weight, 'ip1'}, ..})``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Returns a random IP address from the list supplied, as weighted by the
various ``weight`` parameters. Performs no uptime checking.
-``country('NL')``
-~~~~~~~~~~~~~~~~~
-Returns true if the ``best-who`` IP address of the client is within the
+``asnum(num)`` or ``asnum({num1,num2..})``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Returns true if the ``bestwho`` IP address is determined to be from
+any of the listed AS numbers.
+
+``country('NL')`` or ``country({'NL',..})
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Returns true if the ``bestwho`` IP address of the client is within the
two letter ISO country code passed, as described in :doc:`backends/geoip`.
-``continent('EU')``
-~~~~~~~~~~~~~~~~~~~
-Returns true if the ``best-who`` IP address of the client is within the
+``continent('EU')`` or ``continent({'EU',..})
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Returns true if the ``bestwho`` IP address of the client is within the
continent passed, as described in :doc:`backends/geoip`.
Details & Security
------------------
LUA records are synthesized on query. They can also be transferred via AXFR
-to other PowerDNS servers. LUA records themselves can not be queried
+to other PowerDNS servers.
+
+LUA records themselves can not be queried
however, as this would allow third parties to see load balancing internals
they do not need to see.
the signing key must be available on the server creating answers based on
LUA records.
+Note that to protect operators, support for the LUA record must be enabled
+explicitly, either globally (``global-lua-record``) or per zone
+(``ENABLE-LUA-RECORD``=1).
s_rc++;
}
-string getGeoForLua(const std::string& ip, GeoIPBackend::GeoIPQueryAttribute qa)
+string getGeoForLua(const std::string& ip, int qaint)
{
+ GeoIPBackend::GeoIPQueryAttribute qa((GeoIPBackend::GeoIPQueryAttribute)qaint);
try {
GeoIPBackend gib;
GeoIPLookup gl;
s_domains.clear();
std::swap(s_domains, tmp_domains);
- extern std::function<std::string(const std::string& ip, GeoIPBackend::GeoIPQueryAttribute)> g_getGeo;
+ extern std::function<std::string(const std::string& ip, int)> g_getGeo;
g_getGeo = getGeoForLua;
}
bool g_anyToTcp;
bool g_8bitDNS;
+bool g_doGlobalLuaRecord;
typedef Distributor<DNSPacket,DNSPacket,PacketHandler> DNSDistributor;
ArgvMap theArg;
::arg().setSwitch("expand-alias", "Expand ALIAS records")="no";
::arg().setSwitch("outgoing-axfr-expand-alias", "Expand ALIAS records during outgoing AXFR")="no";
::arg().setSwitch("8bit-dns", "Allow 8bit dns queries")="no";
+ ::arg().setSwitch("global-lua-record", "Process LUA record for all zones")="no";
::arg().setSwitch("axfr-lower-serial", "Also AXFR a zone from a master with a lower serial")="no";
::arg().set("lua-axfr-script", "Script to be used to edit incoming AXFRs")="";
g_anyToTcp = ::arg().mustDo("any-to-tcp");
g_8bitDNS = ::arg().mustDo("8bit-dns");
+ g_doGlobalLuaRecord = ::arg().mustDo("global-lua-record");
DNSPacket::s_udpTruncationThreshold = std::max(512, ::arg().asNum("udp-truncation-threshold"));
DNSPacket::s_doEDNSSubnetProcessing = ::arg().mustDo("edns-subnet-processing");
void* carbonDumpThread(void*);
extern bool g_anyToTcp;
extern bool g_8bitDNS;
+extern bool g_doGlobalLuaRecord;
#endif // COMMON_STARTUP_HH
#include "dnspacket.hh"
#include "dns.hh"
+// this has to be somewhere central, and not in a file that requires Lua
+// this is so the geoipbackend can set this pointer if loaded for lua-record.cc
+std::function<std::string(const std::string&, int)> g_getGeo;
+
bool DNSBackend::getAuth(const DNSName &target, SOAData *sd)
{
return this->getSOA(target, *sd);
#include "minicurl.hh"
#include "ueberbackend.hh"
#include <boost/format.hpp>
-// this is only for the ENUM
-#include "../../modules/geoipbackend/geoipbackend.hh"
+
+#include "../modules/geoipbackend/geoipbackend.hh" // only for the enum
/* to do:
- global allow-lua-record setting
- zone metadata setting
- fix compilation/linking with/without geoipbackend
- use weak symbol?
+ block AXFR unless TSIG, or override
+
+ zone metadata setting to enable
+
unify ifupurl/ifupport
add attribute for query source
add attribute for certificate chedk
add list of current monitors
expire them too?
-
*/
class IsUpOracle
opts_t opts;
bool operator<(const CheckDesc& rhs) const
{
- return std::make_tuple(rem, url) <
- std::make_tuple(rhs.rem, rhs.url);
+ std::map<string,string> oopts, rhsoopts;
+ for(const auto& m : opts)
+ oopts[m.first]=m.second;
+ for(const auto& m : rhs.opts)
+ rhsoopts[m.first]=m.second;
+
+ return std::make_tuple(rem, url, oopts) <
+ std::make_tuple(rhs.rem, rhs.url, rhsoopts);
}
};
public:
- bool isUp(const ComboAddress& remote);
+ bool isUp(const ComboAddress& remote, opts_t opts);
bool isUp(const ComboAddress& remote, const std::string& url, opts_t opts=opts_t());
+ bool isUp(const CheckDesc& cd);
private:
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(!isUp(rem))
- L<<Logger::Warning<<"Lua record monitoring declaring TCP/IP "<<rem.toStringWithPort()<<" UP!"<<endl;
- setUp(cd);
- }
- catch(NetworkError& ne) {
- if(isUp(rem) || first)
- L<<Logger::Warning<<"Lua record monitoring declaring TCP/IP "<<rem.toStringWithPort()<<" DOWN!"<<endl;
- setDown(cd);
- }
- sleep(1);
- }
- }
-
+ void checkTCPThread(ComboAddress rem, opts_t opts);
struct Checker
{
};
-bool IsUpOracle::isUp(const ComboAddress& remote)
+bool IsUpOracle::isUp(const CheckDesc& cd)
{
std::lock_guard<std::mutex> l(d_mutex);
- CheckDesc cd{remote};
auto iter = d_statuses.find(cd);
if(iter == d_statuses.end()) {
- L<<Logger::Warning<<"Launching TCP/IP status checker for "<<remote.toStringWithPort()<<endl;
- std::thread* checker = new std::thread(&IsUpOracle::checkTCPThread, this, remote);
+// L<<Logger::Warning<<"Launching TCP/IP status checker for "<<remote.toStringWithPort()<<endl;
+ std::thread* checker = new std::thread(&IsUpOracle::checkTCPThread, this, cd.rem, cd.opts);
d_statuses[cd]=Checker{checker, false};
return false;
}
return iter->second.status;
+
+}
+
+bool IsUpOracle::isUp(const ComboAddress& remote, opts_t opts)
+{
+ CheckDesc cd{remote, "", opts};
+ return isUp(cd);
}
bool IsUpOracle::isUp(const ComboAddress& remote, const std::string& url, std::unordered_map<string,string> opts)
return iter->second.status;
}
+void IsUpOracle::checkTCPThread(ComboAddress rem, opts_t opts)
+{
+ CheckDesc cd{rem, "", opts};
+ setDown(cd);
+ for(bool first=true;;first=false) {
+ try {
+ Socket s(rem.sin4.sin_family, SOCK_STREAM);
+ s.setNonBlocking();
+ ComboAddress src;
+ if(opts.count("source")) {
+ src=ComboAddress(opts["source"]);
+ s.bind(src);
+ }
+ s.connect(rem, 1);
+ if(!isUp(cd)) {
+ L<<Logger::Warning<<"Lua record monitoring declaring TCP/IP "<<rem.toStringWithPort()<<" ";
+ if(opts.count("source"))
+ L<<"(source "<<src.toString()<<") ";
+ L<<"UP!"<<endl;
+ }
+ setUp(cd);
+ }
+ catch(NetworkError& ne) {
+ if(isUp(rem, opts) || first)
+ L<<Logger::Warning<<"Lua record monitoring declaring TCP/IP "<<rem.toStringWithPort()<<" DOWN: "<<ne.what()<<endl;
+ setDown(cd);
+ }
+ sleep(1);
+ }
+}
+
+
void IsUpOracle::checkURLThread(ComboAddress rem, std::string url, opts_t opts)
{
setDown(rem, url, opts);
try {
MiniCurl mc;
// cout<<"Checking URL "<<url<<" at "<<rem.toString()<<endl;
- string content=mc.getURL(url, &rem);
+
+ string content;
+ if(opts.count("source")) {
+ ComboAddress src(opts["source"]);
+ content=mc.getURL(url, &rem, &src);
+ }
+ else
+ 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);
}
if(!upStatus(rem,url))
L<<Logger::Warning<<"LUA record monitoring declaring "<<rem.toString()<<" UP for URL "<<url<<"!"<<endl;
- setUp(rem, url);
+ setUp(rem, url,opts);
}
catch(std::exception& ne) {
if(upStatus(rem,url,opts) || first)
L<<Logger::Warning<<"LUA record monitoring declaring "<<rem.toString()<<" DOWN for URL "<<url<<", error: "<<ne.what()<<endl;
- setDown(rem,url);
+ setDown(rem,url,opts);
}
loop:;
sleep(5);
}
-std::function<std::string(const std::string&, GeoIPBackend::GeoIPQueryAttribute)> g_getGeo;
-
std::string getGeo(const std::string& ip, GeoIPBackend::GeoIPQueryAttribute qa)
{
static bool initialized;
+ extern std::function<std::string(const std::string& ip, int)> g_getGeo;
if(!g_getGeo) {
if(!initialized) {
L<<Logger::Error<<"LUA Record attempted to use GeoIPBackend functionality, but backend not launched"<<endl;
return "unknown";
}
else
- return g_getGeo(ip, qa);
+ return g_getGeo(ip, (int)qa);
}
static ComboAddress pickrandom(vector<ComboAddress>& ips)
lua.writeFunction("ifportup", [&bestwho](int port, const vector<pair<int, string> >& ips, const boost::optional<std::unordered_map<string,string>> options) {
vector<ComboAddress> candidates;
+ std::unordered_map<string, string> opts;
+ if(options)
+ opts = *options;
+
for(const auto& i : ips) {
ComboAddress rem(i.second, port);
- if(g_up.isUp(rem))
+ if(g_up.isUp(rem, opts))
candidates.push_back(rem);
}
vector<string> ret;
auto lr = getRR<LUARecordContent>(dr.dr);
lua.executeCode(lr->getCode());
}
- }catch(std::exception& e) { cerr<<"Oops: "<<e.what()<<endl; }
+ }
+ catch(std::exception& e) {
+ L<<Logger::Error<<"Failed to load include record for LUArecord "<<(DNSName(record)+zone)<<": "<<e.what()<<endl;
+ }
});
return url.substr(pos, endpos-pos);
}
-void MiniCurl::setupURL(const std::string& str, const ComboAddress* rem)
+void MiniCurl::setupURL(const std::string& str, const ComboAddress* rem, const ComboAddress* src)
{
if(rem) {
struct curl_slist *hostlist = NULL;
curl_easy_setopt(d_curl, CURLOPT_RESOLVE, hostlist);
}
+ if(src) {
+ curl_easy_setopt(d_curl, CURLOPT_INTERFACE, src->toString());
+ }
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);
d_data.clear();
}
-std::string MiniCurl::getURL(const std::string& str, const ComboAddress* rem)
+std::string MiniCurl::getURL(const std::string& str, const ComboAddress* rem, const ComboAddress* src)
{
- setupURL(str, rem);
+ setupURL(str, rem, src);
auto res = curl_easy_perform(d_curl);
long http_code = 0;
curl_easy_getinfo(d_curl, CURLINFO_RESPONSE_CODE, &http_code);
MiniCurl();
~MiniCurl();
MiniCurl& operator=(const MiniCurl&) = delete;
- std::string getURL(const std::string& str, const ComboAddress* rem=0);
+ std::string getURL(const std::string& str, const ComboAddress* rem=0, const ComboAddress* src=0);
std::string postURL(const std::string& str, const std::string& postdata);
private:
CURL *d_curl;
static size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata);
std::string d_data;
- void setupURL(const std::string& str, const ComboAddress* rem=0);
+ void setupURL(const std::string& str, const ComboAddress* rem=0, const ComboAddress* src=0);
};
DNSName subdomain(target);
bool haveSomething=false;
+ bool doLua=g_doGlobalLuaRecord;
+ if(!doLua) {
+ string val;
+ d_dk.getFromMeta(sd.qname, "ENABLE-LUA-RECORD", val);
+ doLua = (val=="1");
+ }
+
+
wildcard=subdomain;
while( subdomain.chopOff() && !haveSomething ) {
if (subdomain.empty()) {
}
while(B.get(rr)) {
if(rr.dr.d_type == QType::LUA) {
+ if(!doLua) {
+ DLOG(L<<"Have a wildcard LUA match, but not doing LUA record for this zone"<<endl);
+ continue;
+ }
+
DLOG(L<<"Have a wildcard LUA match"<<endl);
+
auto rec=getRR<LUARecordContent>(rr.dr);
if(rec->d_type == QType::CNAME || rec->d_type == p->qtype.getCode()) {
// noCache=true;
DNSPacket *r=0;
bool noCache=false;
+ bool doLua=g_doGlobalLuaRecord;
if(p->d.qr) { // QR bit from dns packet (thanks RA from N)
if(d_logDNSDetails)
rrset.clear();
haveAlias.trimToLabels(0);
weDone = weRedirected = weHaveUnauth = false;
+
+
+ if(!doLua) {
+ string val;
+ d_dk.getFromMeta(sd.qname, "ENABLE-LUA-RECORD", val);
+ doLua = (val=="1");
+ }
while(B.get(rr)) {
if(rr.dr.d_type == QType::LUA) {
+ if(!doLua)
+ continue;
auto rec=getRR<LUARecordContent>(rr.dr);
if(rec->d_type == QType::CNAME || rec->d_type == p->qtype.getCode()) {
noCache=true;