aboutsummaryrefslogtreecommitdiffstats
path: root/spec/net_resolvers_service_spec.lua
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 /spec/net_resolvers_service_spec.lua
parentd1c2e34e610477b901998f7230631f6c99166f9b (diff)
downloadprosody-d26811f5e570192b2dd5fb66d3d789b66362508c.tar.gz
prosody-d26811f5e570192b2dd5fb66d3d789b66362508c.zip
net.resolvers.service: Honour record 'weight' when picking SRV targets
#NotHappyEyeballs
Diffstat (limited to 'spec/net_resolvers_service_spec.lua')
-rw-r--r--spec/net_resolvers_service_spec.lua241
1 files changed, 241 insertions, 0 deletions
diff --git a/spec/net_resolvers_service_spec.lua b/spec/net_resolvers_service_spec.lua
new file mode 100644
index 00000000..53ce4754
--- /dev/null
+++ b/spec/net_resolvers_service_spec.lua
@@ -0,0 +1,241 @@
+local set = require "util.set";
+
+insulate("net.resolvers.service", function ()
+ local adns = {
+ resolver = function ()
+ return {
+ lookup = function (_, cb, qname, qtype, qclass)
+ if qname == "_xmpp-server._tcp.example.com"
+ and (qtype or "SRV") == "SRV"
+ and (qclass or "IN") == "IN" then
+ cb({
+ { -- 60+35+60
+ srv = { target = "xmpp0-a.example.com", port = 5228, priority = 0, weight = 60 };
+ };
+ {
+ srv = { target = "xmpp0-b.example.com", port = 5216, priority = 0, weight = 35 };
+ };
+ {
+ srv = { target = "xmpp0-c.example.com", port = 5200, priority = 0, weight = 0 };
+ };
+ {
+ srv = { target = "xmpp0-d.example.com", port = 5256, priority = 0, weight = 120 };
+ };
+
+ {
+ srv = { target = "xmpp1-a.example.com", port = 5273, priority = 1, weight = 30 };
+ };
+ {
+ srv = { target = "xmpp1-b.example.com", port = 5274, priority = 1, weight = 30 };
+ };
+
+ {
+ srv = { target = "xmpp2.example.com", port = 5275, priority = 2, weight = 0 };
+ };
+ });
+ elseif qname == "_xmpp-server._tcp.single.example.com"
+ and (qtype or "SRV") == "SRV"
+ and (qclass or "IN") == "IN" then
+ cb({
+ {
+ srv = { target = "xmpp0-a.example.com", port = 5269, priority = 0, weight = 0 };
+ };
+ });
+ elseif qname == "_xmpp-server._tcp.half.example.com"
+ and (qtype or "SRV") == "SRV"
+ and (qclass or "IN") == "IN" then
+ cb({
+ {
+ srv = { target = "xmpp0-a.example.com", port = 5269, priority = 0, weight = 0 };
+ };
+ {
+ srv = { target = "xmpp0-b.example.com", port = 5270, priority = 0, weight = 1 };
+ };
+ });
+ elseif qtype == "A" then
+ local l = qname:match("%-(%a)%.example.com$") or "1";
+ local d = ("%d"):format(l:byte())
+ cb({
+ {
+ a = "127.0.0."..d;
+ };
+ });
+ elseif qtype == "AAAA" then
+ local l = qname:match("%-(%a)%.example.com$") or "1";
+ local d = ("%04d"):format(l:byte())
+ cb({
+ {
+ aaaa = "fdeb:9619:649e:c7d9::"..d;
+ };
+ });
+ else
+ cb(nil);
+ end
+ end;
+ };
+ end;
+ };
+ package.loaded["net.adns"] = mock(adns);
+ local resolver = require "net.resolvers.service";
+ math.randomseed(os.time());
+ it("works for 99% of deployments", function ()
+ -- Most deployments only have a single SRV record, let's make
+ -- sure that works okay
+
+ local expected_targets = set.new({
+ -- xmpp0-a
+ "tcp4 127.0.0.97 5269";
+ "tcp6 fdeb:9619:649e:c7d9::0097 5269";
+ });
+ local received_targets = set.new({});
+
+ local r = resolver.new("single.example.com", "xmpp-server");
+ local done = false;
+ local function handle_target(...)
+ if ... == nil then
+ done = true;
+ -- No more targets
+ return;
+ end
+ received_targets:add(table.concat({ ... }, " ", 1, 3));
+ end
+ r:next(handle_target);
+ while not done do
+ r:next(handle_target);
+ end
+
+ -- We should have received all expected targets, and no unexpected
+ -- ones:
+ assert.truthy(set.xor(received_targets, expected_targets):empty());
+ end);
+
+ it("supports A/AAAA fallback", function ()
+ -- Many deployments don't have any SRV records, so we should
+ -- fall back to A/AAAA records instead when that is the case
+
+ local expected_targets = set.new({
+ -- xmpp0-a
+ "tcp4 127.0.0.97 5269";
+ "tcp6 fdeb:9619:649e:c7d9::0097 5269";
+ });
+ local received_targets = set.new({});
+
+ local r = resolver.new("xmpp0-a.example.com", "xmpp-server", "tcp", { default_port = 5269 });
+ local done = false;
+ local function handle_target(...)
+ if ... == nil then
+ done = true;
+ -- No more targets
+ return;
+ end
+ received_targets:add(table.concat({ ... }, " ", 1, 3));
+ end
+ r:next(handle_target);
+ while not done do
+ r:next(handle_target);
+ end
+
+ -- We should have received all expected targets, and no unexpected
+ -- ones:
+ assert.truthy(set.xor(received_targets, expected_targets):empty());
+ end);
+
+
+ it("works", function ()
+ local expected_targets = set.new({
+ -- xmpp0-a
+ "tcp4 127.0.0.97 5228";
+ "tcp6 fdeb:9619:649e:c7d9::0097 5228";
+ "tcp4 127.0.0.97 5273";
+ "tcp6 fdeb:9619:649e:c7d9::0097 5273";
+
+ -- xmpp0-b
+ "tcp4 127.0.0.98 5274";
+ "tcp6 fdeb:9619:649e:c7d9::0098 5274";
+ "tcp4 127.0.0.98 5216";
+ "tcp6 fdeb:9619:649e:c7d9::0098 5216";
+
+ -- xmpp0-c
+ "tcp4 127.0.0.99 5200";
+ "tcp6 fdeb:9619:649e:c7d9::0099 5200";
+
+ -- xmpp0-d
+ "tcp4 127.0.0.100 5256";
+ "tcp6 fdeb:9619:649e:c7d9::0100 5256";
+
+ -- xmpp2
+ "tcp4 127.0.0.49 5275";
+ "tcp6 fdeb:9619:649e:c7d9::0049 5275";
+
+ });
+ local received_targets = set.new({});
+
+ local r = resolver.new("example.com", "xmpp-server");
+ local done = false;
+ local function handle_target(...)
+ if ... == nil then
+ done = true;
+ -- No more targets
+ return;
+ end
+ received_targets:add(table.concat({ ... }, " ", 1, 3));
+ end
+ r:next(handle_target);
+ while not done do
+ r:next(handle_target);
+ end
+
+ -- We should have received all expected targets, and no unexpected
+ -- ones:
+ assert.truthy(set.xor(received_targets, expected_targets):empty());
+ end);
+
+ it("balances across weights correctly #slow", function ()
+ -- This mimics many repeated connections to 'example.com' (mock
+ -- records defined above), and records the port number of the
+ -- first target. Therefore it (should) only return priority
+ -- 0 records, and the input data is constructed such that the
+ -- last two digits of the port number represent the percentage
+ -- of times that record should (on average) be picked first.
+
+ -- To prevent random test failures, we test across a handful
+ -- of fixed (randomly selected) seeds.
+ for _, seed in ipairs({ 8401877, 3943829, 7830992 }) do
+ math.randomseed(seed);
+
+ local results = {};
+ local function run()
+ local run_results = {};
+ local r = resolver.new("example.com", "xmpp-server");
+ local function record_target(...)
+ if ... == nil then
+ -- No more targets
+ return;
+ end
+ run_results = { ... };
+ end
+ r:next(record_target);
+ return run_results[3];
+ end
+
+ for _ = 1, 1000 do
+ local port = run();
+ results[port] = (results[port] or 0) + 1;
+ end
+
+ local ports = {};
+ for port in pairs(results) do
+ table.insert(ports, port);
+ end
+ table.sort(ports);
+ for _, port in ipairs(ports) do
+ --print("PORT", port, tostring((results[port]/1000) * 100).."% hits (expected "..tostring(port-5200).."%)");
+ local hit_pct = (results[port]/1000) * 100;
+ local expected_pct = port - 5200;
+ --print(hit_pct, expected_pct, math.abs(hit_pct - expected_pct));
+ assert.is_true(math.abs(hit_pct - expected_pct) < 5);
+ end
+ --print("---");
+ end
+ end);
+end);