File

net/resolvers/service.lua @ 12301:4f1fe6eb1ddb

mod_c2s,mod_s2s: Wait for sessions to close before proceeding with shutdown steps Ensures unavailable presence and other outgoing stanzas are sent. Waiting for c2s sessions to close first before proceeding to disable and close s2s ensures that unavailable presence can go out, even if it requires dialback to complete first.
author Kim Alvefur <zash@zash.se>
date Thu, 17 Feb 2022 03:49:47 +0100
parent 12129:7a68d5828f3b
child 12401:c029ddcad258
line wrap: on
line source

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

-- 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
				cb(nil);
				return;
			end
			local next_target = table.remove(self.targets, 1);
			self.resolver = basic.new(unpack(next_target, 1, 4));
		end
		self.resolver:next(function (...)
			if self.resolver then
				self.last_error = self.resolver.last_error;
			end
			if ... == nil then
				self.resolver = nil;
				self:next(cb);
			else
				cb(...);
			end
		end);
		return;
	end

	if not self.hostname then
		self.last_error = "hostname failed IDNA";
		cb(nil);
		return;
	end

	local targets = {};
	local function ready()
		self.targets = targets;
		self:next(cb);
	end

	-- Resolve DNS to target list
	local dns_resolver = adns.resolver();
	dns_resolver:lookup(function (answer, err)
		if not answer and not err then
			-- net.adns returns nil if there are zero records or nxdomain
			answer = {};
		end
		if answer then
			if self.extra and not answer.secure then
				self.extra.use_dane = false;
			elseif answer.bogus then
				self.last_error = "Validation error in SRV lookup";
				ready();
				return;
			end

			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 });
				else
					self.last_error = "zero SRV records found";
				end
				ready();
				return;
			end

			if #answer == 1 and answer[1].srv.target == "." then -- No service here
				self.last_error = "service explicitly unavailable";
				ready();
				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
		else
			self.last_error = err;
		end
		ready();
	end, "_" .. self.service .. "._" .. self.conn_type .. "." .. self.hostname, "SRV", "IN");
end

local function new(hostname, service, conn_type, extra)
	local is_ip = inet_pton(hostname);
	if not is_ip and hostname:sub(1,1) == '[' then
		is_ip = inet_pton(hostname:sub(2,-2));
	end
	if is_ip and extra and extra.default_port then
		return basic.new(hostname, extra.default_port, conn_type, extra);
	end

	return setmetatable({
		hostname = idna_to_ascii(hostname);
		service = service;
		conn_type = conn_type or "tcp";
		extra = extra;
	}, resolver_mt);
end

return {
	new = new;
};