Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

auth: luarecords, enhance ifportup() with lists of sets of addresses like ifurlup() #12669

Merged
merged 4 commits into from Apr 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/lua-records/index.rst
Expand Up @@ -49,6 +49,14 @@ addresses listen on port 443.
If either IP address stops listening, only the other address will be
returned. If all IP addresses are down, all candidates are returned.

You can also provide multiple sets of IP addresses to prioritize a set over the
rest. If an IP address from the first set is available, it will be returned. If
no addresses work in the first set, the second set is tried.

For example::

www IN LUA A "ifportup(443, {{'192.0.2.1', '192.0.2.2'}, {'192.0.3.1'}})"

Because DNS queries require rapid answers, server availability is not checked
synchronously. In the background, a process periodically determines if IP
addresses mentioned in availability rules are, in fact, available.
Expand Down
132 changes: 69 additions & 63 deletions pdns/lua-record.cc
Expand Up @@ -301,7 +301,6 @@ bool doCompare(const T& var, const std::string& res, const C& cmp)
}
}


static std::string getGeo(const std::string& ip, GeoIPInterface::GeoIPQueryAttribute qa)
{
static bool initialized;
Expand Down Expand Up @@ -548,18 +547,41 @@ static vector<string> convComboAddressListToString(const vector<ComboAddress>& i
return result;
}

static vector<ComboAddress> convComboAddressList(const iplist_t& items)
static vector<ComboAddress> convComboAddressList(const iplist_t& items, uint16_t port=0)
{
vector<ComboAddress> result;
result.reserve(items.size());

for(const auto& item : items) {
result.emplace_back(ComboAddress(item.second));
result.emplace_back(ComboAddress(item.second, port));
}

return result;
}

/**
* Reads and unify single or multiple sets of ips :
* - {'192.0.2.1', '192.0.2.2'}
* - {{'192.0.2.1', '192.0.2.2'}, {'198.51.100.1'}}
*/

static vector<vector<ComboAddress>> convMultiComboAddressList(const boost::variant<iplist_t, ipunitlist_t>& items, uint16_t port = 0)
{
vector<vector<ComboAddress>> candidates;

if(auto simple = boost::get<iplist_t>(&items)) {
vector<ComboAddress> unit = convComboAddressList(*simple, port);
candidates.push_back(unit);
} else {
auto units = boost::get<ipunitlist_t>(items);
for(const auto& u : units) {
vector<ComboAddress> unit = convComboAddressList(u.second, port);
candidates.push_back(unit);
}
}
return candidates;
}

static vector<string> convStringList(const iplist_t& items)
{
vector<string> result;
Expand Down Expand Up @@ -596,6 +618,37 @@ typedef struct AuthLuaRecordContext

static thread_local unique_ptr<lua_record_ctx_t> s_lua_record_ctx;

static vector<string> genericIfUp(const boost::variant<iplist_t, ipunitlist_t>& ips, boost::optional<opts_t> options, std::function<bool(const ComboAddress&, const opts_t&)> upcheckf, uint16_t port = 0) {
vector<vector<ComboAddress> > candidates;
opts_t opts;
if(options)
opts = *options;

candidates = convMultiComboAddressList(ips, port);

for(const auto& unit : candidates) {
vector<ComboAddress> available;
for(const auto& c : unit) {
if (upcheckf(c, opts)) {
available.push_back(c);
}
}
if(!available.empty()) {
vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
return convComboAddressListToString(res);
}
}

// All units down, apply backupSelector on all candidates
vector<ComboAddress> ret{};
for(const auto& unit : candidates) {
ret.insert(ret.end(), unit.begin(), unit.end());
}

vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, ret);
return convComboAddressListToString(res);
}

static void setupLuaRecords(LuaContext& lua)
{
lua.writeFunction("latlon", []() {
Expand Down Expand Up @@ -832,34 +885,18 @@ static void setupLuaRecords(LuaContext& lua)
*
* @example ifportup(443, { '1.2.3.4', '5.4.3.2' })"
*/
lua.writeFunction("ifportup", [](int port, const vector<pair<int, string> >& ips, const boost::optional<std::unordered_map<string,string>> options) {
vector<ComboAddress> candidates, unavailables;
opts_t opts;
vector<ComboAddress > conv;
std::string selector;

if(options)
opts = *options;
for(const auto& i : ips) {
ComboAddress rem(i.second, port);
if(g_up.isUp(rem, opts)) {
candidates.push_back(rem);
}
else {
unavailables.push_back(rem);
}
lua.writeFunction("ifportup", [](int port, const boost::variant<iplist_t, ipunitlist_t>& ips, const boost::optional<std::unordered_map<string,string>> options) {
if (port < 0) {
port = 0;
}
if(!candidates.empty()) {
// use regular selector
selector = getOptionValue(options, "selector", "random");
} else {
// All units are down, apply backupSelector on all candidates
candidates = std::move(unavailables);
selector = getOptionValue(options, "backupSelector", "random");
if (port > std::numeric_limits<uint16_t>::max()) {
port = std::numeric_limits<uint16_t>::max();
}

vector<ComboAddress> res = useSelector(selector, s_lua_record_ctx->bestwho, candidates);
return convComboAddressListToString(res);
auto checker = [](const ComboAddress& addr, const opts_t& opts) {
return g_up.isUp(addr, opts);
};
return genericIfUp(ips, options, checker, port);
});

lua.writeFunction("ifurlextup", [](const vector<pair<int, opts_t> >& ipurls, boost::optional<opts_t> options) {
Expand Down Expand Up @@ -898,42 +935,11 @@ static void setupLuaRecords(LuaContext& lua)
lua.writeFunction("ifurlup", [](const std::string& url,
const boost::variant<iplist_t, ipunitlist_t>& ips,
boost::optional<opts_t> options) {
vector<vector<ComboAddress> > candidates;
opts_t opts;
if(options)
opts = *options;
if(auto simple = boost::get<iplist_t>(&ips)) {
vector<ComboAddress> unit = convComboAddressList(*simple);
candidates.push_back(unit);
} else {
auto units = boost::get<ipunitlist_t>(ips);
for(const auto& u : units) {
vector<ComboAddress> unit = convComboAddressList(u.second);
candidates.push_back(unit);
}
}

for(const auto& unit : candidates) {
vector<ComboAddress> available;
for(const auto& c : unit) {
if(g_up.isUp(c, url, opts)) {
available.push_back(c);
}
}
if(!available.empty()) {
vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
return convComboAddressListToString(res);
}
}

// All units down, apply backupSelector on all candidates
vector<ComboAddress> ret{};
for(const auto& unit : candidates) {
ret.insert(ret.end(), unit.begin(), unit.end());
}

vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, ret);
return convComboAddressListToString(res);
auto checker = [&url](const ComboAddress& addr, const opts_t& opts) {
return g_up.isUp(addr, url, opts);
};
return genericIfUp(ips, options, checker);
});
/*
* Returns a random IP address from the supplied list
Expand Down
72 changes: 64 additions & 8 deletions regression-tests.auth-py/test_LuaRecords.py
Expand Up @@ -60,6 +60,7 @@ class TestLuaRecords(AuthTest):

all.ifportup 3600 IN LUA A "ifportup(8080, {{'{prefix}.101', '{prefix}.102'}})"
some.ifportup 3600 IN LUA A "ifportup(8080, {{'192.168.42.21', '{prefix}.102'}})"
multi.ifportup 3600 IN LUA A "ifportup(8080, {{ {{'192.168.42.23'}}, {{'192.168.42.21', '{prefix}.102'}}, {{'{prefix}.101'}} }})"
none.ifportup 3600 IN LUA A "ifportup(8080, {{'192.168.42.21', '192.168.21.42'}})"
all.noneup.ifportup 3600 IN LUA A "ifportup(8080, {{'192.168.42.21', '192.168.21.42'}}, {{ backupSelector='all' }})"

Expand All @@ -83,21 +84,21 @@ class TestLuaRecords(AuthTest):
config IN LUA LUA ("settings={{stringmatch='Programming in Lua'}} "
"EUWips={{'{prefix}.101','{prefix}.102'}} "
"EUEips={{'192.168.42.101','192.168.42.102'}} "
"NLips={{'{prefix}.111', '{prefix}.112'}} "
"USAips={{'{prefix}.103'}} ")
"NLips={{'{prefix}.111', '{prefix}.112'}} "
"USAips={{'{prefix}.103', '192.168.42.105'}} ")

usa IN LUA A ( ";include('config') "
"return ifurlup('http://www.lua.org:8080/', "
"{{USAips, EUEips}}, settings) ")
"USAips, settings) ")

usa-ext IN LUA A ( ";include('config') "
"return ifurlup('http://www.lua.org:8080/', "
"{{EUEips, USAips}}, settings) ")

mix.ifurlup IN LUA A ("ifurlup('http://www.other.org:8080/ping.json', "
"{{ '192.168.42.101', '{prefix}.101' }}, "
"{{ stringmatch='pong' }}) ")

eu-west IN LUA A ( ";include('config') "
"return ifurlup('http://www.lua.org:8080/', "
"{{EUWips, EUEips, USAips}}, settings) ")

ifurlextup IN LUA A "ifurlextup({{{{['192.168.0.1']='http://{prefix}.101:8080/404',['192.168.0.2']='http://{prefix}.102:8080/404'}}, {{['192.168.0.3']='http://{prefix}.101:8080/'}}}})"

nl IN LUA A ( ";include('config') "
Expand Down Expand Up @@ -327,6 +328,33 @@ def testIfportupWithSomeDown(self):
self.assertRcodeEqual(res, dns.rcode.NOERROR)
self.assertAnyRRsetInAnswer(res, expected)

def testIfportupWithSomeDownMultiset(self):
"""
Basic ifportup() test with some ports DOWN from multiple sets
"""
query = dns.message.make_query('multi.ifportup.example.org', 'A')
expected = [
dns.rrset.from_text('multi.ifportup.example.org.', 0, dns.rdataclass.IN, 'A',
'192.168.42.21'),
dns.rrset.from_text('multi.ifportup.example.org.', 0, dns.rdataclass.IN, 'A',
'192.168.42.23'),
dns.rrset.from_text('multi.ifportup.example.org.', 0, dns.rdataclass.IN, 'A',
'{prefix}.102'.format(prefix=self._PREFIX)),
dns.rrset.from_text('multi.ifportup.example.org.', 0, dns.rdataclass.IN, 'A',
'{prefix}.101'.format(prefix=self._PREFIX))
]

# we first expect any of the IPs as no check has been performed yet
res = self.sendUDPQuery(query)
self.assertRcodeEqual(res, dns.rcode.NOERROR)
self.assertAnyRRsetInAnswer(res, expected)

# An ip is up in 2 sets, but we expect only the one from the middle set
expected = [expected[2]]
res = self.sendUDPQuery(query)
self.assertRcodeEqual(res, dns.rcode.NOERROR)
self.assertAnyRRsetInAnswer(res, expected)

def testIfportupWithAllDown(self):
"""
Basic ifportup() test with all ports DOWN
Expand Down Expand Up @@ -371,7 +399,7 @@ def testIfurlup(self):
reachable = [
'{prefix}.103'.format(prefix=self._PREFIX)
]
unreachable = ['192.168.42.101', '192.168.42.102']
unreachable = ['192.168.42.105']
ips = reachable + unreachable
all_rrs = []
reachable_rrs = []
Expand All @@ -392,6 +420,34 @@ def testIfurlup(self):
self.assertRcodeEqual(res, dns.rcode.NOERROR)
self.assertAnyRRsetInAnswer(res, reachable_rrs)

def testIfurlupMultiSet(self):
"""
Basic ifurlup() test with mutiple sets
"""
reachable = [
'{prefix}.103'.format(prefix=self._PREFIX)
]
unreachable = ['192.168.42.101', '192.168.42.102', '192.168.42.105']
ips = reachable + unreachable
all_rrs = []
reachable_rrs = []
for ip in ips:
rr = dns.rrset.from_text('usa-ext.example.org.', 0, dns.rdataclass.IN, 'A', ip)
all_rrs.append(rr)
if ip in reachable:
reachable_rrs.append(rr)

query = dns.message.make_query('usa-ext.example.org', 'A')
res = self.sendUDPQuery(query)
self.assertRcodeEqual(res, dns.rcode.NOERROR)
self.assertAnyRRsetInAnswer(res, all_rrs)

# the timeout in the LUA health checker is 2 second, so we make sure to wait slightly longer here
time.sleep(3)
res = self.sendUDPQuery(query)
self.assertRcodeEqual(res, dns.rcode.NOERROR)
self.assertAnyRRsetInAnswer(res, reachable_rrs)

def testIfurlextup(self):
expected = [dns.rrset.from_text('ifurlextup.example.org.', 0, dns.rdataclass.IN, dns.rdatatype.A, '192.168.0.3')]

Expand Down