aboutsummaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorMatthew Wild <mwild1@gmail.com>2022-03-17 18:20:26 +0000
committerMatthew Wild <mwild1@gmail.com>2022-03-17 18:20:26 +0000
commitd26811f5e570192b2dd5fb66d3d789b66362508c (patch)
treed135147707552230584a27f067a7d9512cc0a3d4 /net
parentd1c2e34e610477b901998f7230631f6c99166f9b (diff)
downloadprosody-d26811f5e570192b2dd5fb66d3d789b66362508c.tar.gz
prosody-d26811f5e570192b2dd5fb66d3d789b66362508c.zip
net.resolvers.service: Honour record 'weight' when picking SRV targets
#NotHappyEyeballs
Diffstat (limited to 'net')
-rw-r--r--net/resolvers/service.lua81
1 files changed, 68 insertions, 13 deletions
diff --git a/net/resolvers/service.lua b/net/resolvers/service.lua
index 3810cac8..a7ce76a3 100644
--- a/net/resolvers/service.lua
+++ b/net/resolvers/service.lua
@@ -2,23 +2,78 @@ local adns = require "net.adns";
local basic = require "net.resolvers.basic";
local inet_pton = require "util.net".pton;
local idna_to_ascii = require "util.encodings".idna.to_ascii;
-local unpack = table.unpack or unpack; -- luacheck: ignore 113
local methods = {};
local resolver_mt = { __index = methods };
+local function new_target_selector(rrset)
+ local rr_count = rrset and #rrset;
+ if not rr_count or rr_count == 0 then
+ rrset = nil;
+ else
+ table.sort(rrset, function (a, b) return a.srv.priority < b.srv.priority end);
+ end
+ local rrset_pos = 1;
+ local priority_bucket, bucket_total_weight, bucket_len, bucket_used;
+ return function ()
+ if not rrset then return; end
+
+ if not priority_bucket or bucket_used >= bucket_len then
+ if rrset_pos > rr_count then return; end -- Used up all records
+
+ -- Going to start on a new priority now. Gather up all the next
+ -- records with the same priority and add them to priority_bucket
+ priority_bucket, bucket_total_weight, bucket_len, bucket_used = {}, 0, 0, 0;
+ local current_priority;
+ repeat
+ local curr_record = rrset[rrset_pos].srv;
+ if not current_priority then
+ current_priority = curr_record.priority;
+ elseif current_priority ~= curr_record.priority then
+ break;
+ end
+ table.insert(priority_bucket, curr_record);
+ bucket_total_weight = bucket_total_weight + curr_record.weight;
+ bucket_len = bucket_len + 1;
+ rrset_pos = rrset_pos + 1;
+ until rrset_pos > rr_count;
+ end
+
+ bucket_used = bucket_used + 1;
+ local n, running_total = math.random(0, bucket_total_weight), 0;
+ local target_record;
+ for i = 1, bucket_len do
+ local candidate = priority_bucket[i];
+ if candidate then
+ running_total = running_total + candidate.weight;
+ if running_total >= n then
+ target_record = candidate;
+ bucket_total_weight = bucket_total_weight - candidate.weight;
+ priority_bucket[i] = nil;
+ break;
+ end
+ end
+ end
+ return target_record;
+ end;
+end
+
-- Find the next target to connect to, and
-- pass it to cb()
function methods:next(cb)
- if self.targets then
- if not self.resolver then
- if #self.targets == 0 then
+ if self.resolver or self._get_next_target then
+ if not self.resolver then -- Do we have a basic resolver currently?
+ -- We don't, so fetch a new SRV target, create a new basic resolver for it
+ local next_srv_target = self._get_next_target and self._get_next_target();
+ if not next_srv_target then
+ -- No more SRV targets left
cb(nil);
return;
end
- local next_target = table.remove(self.targets, 1);
- self.resolver = basic.new(unpack(next_target, 1, 4));
+ -- Create a new basic resolver for this SRV target
+ self.resolver = basic.new(next_srv_target.target, next_srv_target.port, self.conn_type, self.extra);
end
+ -- Look up the next (basic) target from the current target's resolver
self.resolver:next(function (...)
if self.resolver then
self.last_error = self.resolver.last_error;
@@ -31,6 +86,9 @@ function methods:next(cb)
end
end);
return;
+ elseif self.in_progress then
+ cb(nil);
+ return;
end
if not self.hostname then
@@ -39,9 +97,9 @@ function methods:next(cb)
return;
end
- local targets = {};
+ self.in_progress = true;
+
local function ready()
- self.targets = targets;
self:next(cb);
end
@@ -63,7 +121,7 @@ function methods:next(cb)
if #answer == 0 then
if self.extra and self.extra.default_port then
- table.insert(targets, { self.hostname, self.extra.default_port, self.conn_type, self.extra });
+ self.resolver = basic.new(self.hostname, self.extra.default_port, self.conn_type, self.extra);
else
self.last_error = "zero SRV records found";
end
@@ -77,10 +135,7 @@ function methods:next(cb)
return;
end
- table.sort(answer, function (a, b) return a.srv.priority < b.srv.priority end);
- for _, record in ipairs(answer) do
- table.insert(targets, { record.srv.target, record.srv.port, self.conn_type, self.extra });
- end
+ self._get_next_target = new_target_selector(answer);
else
self.last_error = err;
end