net.connect: New API for outgoing connections, based on 'service resolvers'
authorMatthew Wild <mwild1@gmail.com>
Fri, 23 Feb 2018 15:53:45 +0000
changeset 8534 601681acea73
parent 8533 075df839c110
child 8535 17c754b81234
net.connect: New API for outgoing connections, based on 'service resolvers'
net/connect.lua
net/resolvers/basic.lua
net/resolvers/manual.lua
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/net/connect.lua	Fri Feb 23 15:53:45 2018 +0000
@@ -0,0 +1,78 @@
+local server = require "net.server";
+local log = require "util.logger".init("net.connect");
+local new_id = require "util.id".short;
+
+local pending_connection_methods = {};
+local pending_connection_mt = {
+	__name = "pending_connection";
+	__index = pending_connection_methods;
+	__tostring = function (p)
+		return "<pending connection "..p.id.." to "..tostring(p.target_resolver.hostname)..">";
+	end;
+};
+
+function pending_connection_methods:log(level, message, ...)
+	log(level, "[pending connection %s] "..message, self.id, ...);
+end
+
+-- pending_connections_map[conn] = pending_connection
+local pending_connections_map = {};
+
+local pending_connection_listeners = {};
+
+local function attempt_connection(p)
+	p:log("debug", "Checking for targets...");
+	if p.conn then
+		pending_connections_map[p.conn] = nil;
+		p.conn = nil;
+	end
+	p.target_resolver:next(function (conn_type, ip, port, extra)
+		p:log("debug", "Next target to try is %s:%d", ip, port);
+		local conn = assert(server.addclient(ip, port, pending_connection_listeners, p.options.pattern, p.options.sslctx, conn_type, extra));
+		p.conn = conn;
+		pending_connections_map[conn] = p;
+	end);
+end
+
+function pending_connection_listeners.onconnect(conn)
+	local p = pending_connections_map[conn];
+	if not p then
+		log("warn", "Successful connection, but unexpected! Closing.");
+		conn:close();
+		return;
+	end
+	pending_connections_map[conn] = nil;
+	p:log("debug", "Successfully connected");
+	if p.listeners.onattach then
+		p.listeners.onattach(conn, p.data);
+	end
+	conn:setlistener(p.listeners);
+	return p.listeners.onconnect(conn);
+end
+
+function pending_connection_listeners.ondisconnect(conn, reason)
+	local p = pending_connections_map[conn];
+	if not p then
+		log("warn", "Failed connection, but unexpected!");
+		return;
+	end
+	p:log("debug", "Connection attempt failed");
+	attempt_connection(p);
+end
+
+local function connect(target_resolver, listeners, options, data)
+	local p = setmetatable({
+		id = new_id();
+		target_resolver = target_resolver;
+		listeners = assert(listeners);
+		options = options or {};
+		cb = cb;
+	}, pending_connection_mt);
+
+	p:log("debug", "Starting connection process");
+	attempt_connection(p);
+end
+
+return {
+	connect = connect;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/net/resolvers/basic.lua	Fri Feb 23 15:53:45 2018 +0000
@@ -0,0 +1,60 @@
+local adns = require "net.adns";
+
+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 #self.targets == 0 then
+			cb(nil);
+			return;
+		end
+		local next_target = table.remove(self.targets, 1);
+		cb(unpack(next_target, 1, 4));
+		return;
+	end
+
+	local targets = {};
+	local n = 2;
+	local function ready()
+		n = n - 1;
+		if n > 0 then return; end
+		self.targets = targets;
+		self:next(cb);
+	end
+
+	-- Resolve DNS to target list
+	local dns_resolver = adns.resolver();
+	dns_resolver:lookup(function (answer)
+		if answer then
+			for _, record in ipairs(answer) do
+				table.insert(targets, { self.conn_type, record.a, self.port, self.extra });
+			end
+		end
+		ready();
+	end, self.hostname, "A", "IN");
+
+	dns_resolver:lookup(function (answer)
+		if answer then
+			for _, record in ipairs(answer) do
+				table.insert(targets, { self.conn_type.."6", record.aaaa, self.port, self.extra });
+			end
+		end
+		ready();
+	end, self.hostname, "AAAA", "IN");
+end
+
+local function new(hostname, port, conn_type, extra)
+	return setmetatable({
+		hostname = hostname;
+		port = port;
+		conn_type = conn_type or "tcp";
+		extra = extra;
+	}, resolver_mt);
+end
+
+return {
+	new = new;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/net/resolvers/manual.lua	Fri Feb 23 15:53:45 2018 +0000
@@ -0,0 +1,25 @@
+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 == 0 then
+		cb(nil);
+		return;
+	end
+	local next_target = table.remove(self.targets, 1);
+	cb(unpack(next_target, 1, 4));
+end
+
+local function new(targets, conn_type, extra)
+	return setmetatable({
+		conn_type = conn_type;
+		extra = extra;
+		targets = targets or {};
+	}, resolver_mt);
+end
+
+return {
+	new = new;
+};