File

spec/net_resolvers_service_spec.lua @ 12471:a3b12eeedd4b

mod_smacks: Improve activation of smacks on outgoing s2s Using a timer was a hack to get around that stream features are not available at the right time and sendq stanzas were stored as strings so could not be counted properly. The later has now been fixed and the former is fixed by recording the relevant stream feature on the session so that the correct version of XEP-0198 can be activated once the connection has been authenticated and is ready to start.
author Kim Alvefur <zash@zash.se>
date Sun, 24 Apr 2022 16:17:32 +0200
parent 12401:c029ddcad258
line wrap: on
line source

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);