Lindenii Project Forge
Login

hare-ev

Temporary fork of hare-ev for... reasons
Commit info
ID
9d2c11ebf759e16e6becaf1d4ba440875c600b70
Author
Armin Preiml <apreiml@strohwolke.at>
Author date
Thu, 23 Jan 2025 15:54:31 +0100
Committer
Drew DeVault <sir@cmpwn.com>
Committer date
Thu, 23 Jan 2025 16:11:39 +0100
Actions
ev::dial: nomen changes

Signed-off-by: Armin Preiml <apreiml@strohwolke.at>
// License: MPL-2.0
// (c) 2021-2023 Drew DeVault <sir@cmpwn.com>
// (c) 2021 Bor Grošelj Simić <bor.groseljsimic@telemach.net>
// (c) 2021 Ember Sawady <ecs@d2evs.net>
//
// Provides default dialers for tcp and udp
use errors;
use ev;
use math::random;
use net::ip;
use net::tcp;
use net::udp;
use net;
use time;

type tcp_dialer = struct {
	loop: *ev::loop,
	cb: *dialcb,
	user: nullable *opaque,
	req: ev::req,
	ip: []ip::addr,
	n: uint,
	port: u16,
};

fn dial_tcp(
	loop: *ev::loop,
	addr: str,
	service: str,
	cb: *dialcb,
	user: nullable *opaque,
) (ev::req | error) = {
	let state = alloc(tcp_dialer {
		loop = loop,
		cb = cb,
		user = user,
		...
	});
	})?;

	const req = resolve(loop, "tcp", addr,
		service, &dial_tcp_resolvecb, state)?;
	state.req = req;
	return ev::mkreq(&dial_tcp_cancel, state);
	match (resolve(loop, "tcp", addr,
		service, &dial_tcp_resolvecb, state)) {
	case let req: ev::req =>
		state.req = req;
		return ev::mkreq(&dial_tcp_cancel, state);
	case let e: error =>
		free(state);
		return e;
	};
};

fn dial_tcp_resolvecb(
	user: nullable *opaque,
	r: (([]ip::addr, u16) | error),
) void = {
	let state = user: *tcp_dialer;
	state.req = ev::req { ... };
	const (ip, port) = match (r) {
	case let r: ([]ip::addr, u16) =>
		yield r;
	case let err: error =>
		dial_tcp_complete(state, err);
		return;
	};

	state.ip = ip;
	state.port = port;
	dial_tcp_connect(state);
};

fn dial_tcp_connect(state: *tcp_dialer) void = {
	const req = match (ev::connect_tcp(state.loop,
			&dial_tcp_connectcb,
			state.ip[state.n], state.port,
			state)) {
	case let err: (net::error | errors::error) =>
		dial_tcp_complete(state, err);
		return;
	case let req: ev::req =>
		yield req;
	};
	state.req = req;
};

fn dial_tcp_connectcb(
	r: (*ev::file | net::error),
	user: nullable *opaque,
) void = {
	let state = user: *tcp_dialer;
	match (r) {
	case let sock: *ev::file =>
		ev::setuser(sock, null);
		dial_tcp_complete(state, sock);
		return;
	case let err: net::error =>
		if (err is errors::netunreachable) {
			state.n += 1;
			if (state.n < len(state.ip)) {
				dial_tcp_connect(state);
				return;
			};
		};
		dial_tcp_complete(state, err);
	};
};

fn dial_tcp_cancel(req: *ev::req) void = {
	let state = req: *tcp_dialer;
	ev::cancel(&state.req);
	free(state.ip);
	free(state);
};

fn dial_tcp_complete(state: *tcp_dialer, r: (*ev::file | error)) void = {
	const cb = state.cb;
	const user = state.user;
	free(state.ip);
	free(state);
	cb(user, r);
};

type udp_dialer = struct {
	loop: *ev::loop,
	cb: *dialcb,
	user: nullable *opaque,
	req: ev::req,
	ip: []ip::addr,
	n: uint,
	port: u16,
};

fn dial_udp(
	loop: *ev::loop,
	addr: str,
	service: str,
	cb: *dialcb,
	user: nullable *opaque,
) (ev::req | error) = {
	let state = alloc(udp_dialer {
		loop = loop,
		cb = cb,
		user = user,
		...
	});
	})?;

	const req = resolve(loop, "udp", addr,
		service, &dial_udp_resolvecb, state)?;
	state.req = req;
	return ev::mkreq(&dial_udp_cancel, state);
	match (resolve(loop, "udp", addr,
		service, &dial_udp_resolvecb, state)) {
	case let req: ev::req =>
		state.req = req;
		return ev::mkreq(&dial_udp_cancel, state);
	case let e: error =>
		free(state);
		return e;
	};
};

fn dial_udp_resolvecb(
	user: nullable *opaque,
	r: (([]ip::addr, u16) | error),
) void = {
	let state = user: *udp_dialer;
	state.req = ev::req { ... };
	const (ip, port) = match (r) {
	case let r: ([]ip::addr, u16) =>
		yield r;
	case let err: error =>
		dial_udp_complete(state, err);
		return;
	};

	state.ip = ip;
	state.port = port;
	dial_udp_connect(state);
};

fn dial_udp_connect(state: *udp_dialer) void = {
	for (true) {
		match (ev::connect_udp(state.loop, state.ip[state.n], state.port)) {
		case let err: net::error =>
			if (err is errors::netunreachable) {
				state.n += 1;
				if (state.n < len(state.ip)) {
					continue;
				};
			};
			dial_udp_complete(state, err);
			break;
		case let err: errors::error =>
			dial_udp_complete(state, err);
			if (err is errors::netunreachable) {
				state.n += 1;
				if (state.n < len(state.ip)) {
					continue;
				};
			};
			break;
		case let sock: *ev::file =>
			dial_udp_complete(state, sock);
			break;
		};
	};
};

fn dial_udp_cancel(req: *ev::req) void = {
	let state = req: *udp_dialer;
	ev::cancel(&state.req);
	free(state.ip);
	free(state);
};

fn dial_udp_complete(state: *udp_dialer, r: (*ev::file | error)) void = {
	const cb = state.cb;
	const user = state.user;
	free(state.ip);
	free(state);
	cb(user, r);
};
// License: MPL-2.0
// (c) 2021-2023 Drew DeVault <sir@cmpwn.com>
// (c) 2021 Ember Sawady <ecs@d2evs.net>
use errors;
use ev;
use net;
use net::dns;
use unix::hosts;

// Returned if the address parameter was invalid, for example if it specifies an
// invalid port number.
export type invalid_address = !void;

// Returned if the service parameter does not name a service known to the
// system.
export type unknown_service = !void;

// Errors which can occur from dial.
export type error = !(invalid_address | unknown_service
export type error = !(nomem | invalid_address | unknown_service
	| net::error | dns::error | hosts::error | errors::error);

// Converts an [[error]] to a human-readable string.
export fn strerror(err: error) const str = {
	match (err) {
	case invalid_address =>
		return "Attempted to dial an invalid address";
	case unknown_service =>
		return "Unknown service";
	case let err: net::error =>
		return net::strerror(err);
	case let err: dns::error =>
		return dns::strerror(err);
	case let err: hosts::error =>
		return hosts::strerror(err);
	case let err: errors::error =>
		return errors::strerror(err);
	};
};

// A dialer is a function which implements dial for a specific protocol.
export type dialer = fn(
	loop: *ev::loop,
	addr: str,
	service: str,
	cb: *dialcb,
	user: nullable *opaque,
) (ev::req | error);

type protocol = struct {
	name: str,
	dial: *dialer,
};

type service = struct {
	proto: str,
	name: str,
	alias: []str,
	port: u16,
};

let default_protocols: [_]protocol = [
	protocol { name = "tcp", dial = &dial_tcp },
	protocol { name = "udp", dial = &dial_udp },
];

let default_services: [_]service = [
	service { proto = "tcp", name = "ssh", alias = [], port = 22 },
	service { proto = "tcp", name = "smtp", alias = ["mail"], port = 25 },
	service { proto = "tcp", name = "domain", alias = ["dns"], port = 53 },
	service { proto = "tcp", name = "http", alias = ["www"], port = 80 },
	service { proto = "tcp", name = "imap2", alias = ["imap"], port = 143 },
	service { proto = "tcp", name = "https", alias = [], port = 443 },
	service { proto = "tcp", name = "submission", alias = [], port = 587 },
	service { proto = "tcp", name = "imaps", alias = [], port = 993 },
	service { proto = "udp", name = "domain", alias = ["dns"], port = 53 },
	service { proto = "udp", name = "ntp", alias = [], port = 123 },
];

let protocols: []protocol = [];
let services: []service = [];

@fini fn fini() void = {
	free(protocols);
	free(services);
};

// Registers a new transport-level protocol (e.g. TCP) with the dialer. The name
// should be statically allocated.
export fn registerproto(name: str, dial: *dialer) void = {
export fn registerproto(name: str, dial: *dialer) (void | nomem) = {
	append(protocols, protocol {
		name = name,
		dial = dial,
	});
	})?;
};

// Registers a new application-level service (e.g. SSH) with the dialer. Note
// that the purpose of services is simply to establish the default outgoing
// port for TCP and UDP connections. The name and alias list should be
// statically allocated.
export fn registersvc(
	proto: str,
	name: str,
	alias: []str,
	port: u16,
) void = {
) (void | nomem) = {
	append(services, service {
		proto = proto,
		name = name,
		alias = alias,
		port = port,
	});
	})?;
};

fn lookup_service(proto: str, service: str) (u16 | void) = {
	for (let i = 0z; i < len(default_services); i += 1) {
		const serv = &default_services[i];
		if (service_match(serv, proto, service)) {
			return serv.port;
		};
	};

	for (let i = 0z; i < len(services); i += 1) {
		const serv = &services[i];
		if (service_match(serv, proto, service)) {
			return serv.port;
		};
	};
};

fn service_match(candidate: *service, proto: str, service: str) bool = {
	if (candidate.name == service) {
		return true;
	};
	for (let j = 0z; j < len(candidate.alias); j += 1) {
		if (candidate.alias[j] == service) {
			return true;
		};
	};
	return false;
};
use crypto::random;
use errors;
use ev;
use edns = ev::dns;
use net;
use net::ip;
use net::dial;
use net::dns;
use unix::hosts;

// Callback from a [[resolve]] operation.
export type resolvecb = fn(
	user: nullable *opaque,
	r: (([]ip::addr, u16) | error),
) void;

type resolve_state = struct {
	user: nullable *opaque,
	cb: *resolvecb,
	r4: ev::req,
	r6: ev::req,
	nq: uint,
	ip: []ip::addr,
	port: u16,
};

// Performs DNS resolution on a given address string for a given service,
// including /etc/hosts lookup and SRV resolution, and returns a list of
// candidate IP addresses and the appropriate port, or an error, to the
// callback.
//
// The caller must free the [[net::ip::addr]] slice.
export fn resolve(
	loop: *ev::loop,
	proto: str,
	addr: str,
	service: str,
	cb: *resolvecb,
	user: nullable *opaque = null
) (ev::req | error) = {
	// TODO: Reduce duplication with net::dial
	let state = alloc(resolve_state {
		cb = cb,
		user = user,
		...
	});
	})?;

	const (addr, port) = match (dial::splitaddr(addr, service)) {
	case let svc: (str, u16) =>
		yield svc;
	case dial::invalid_address =>
		resolve_finish(state, invalid_address);
		return ev::req { ... };
	};

	if (service == "unknown" && port == 0) {
		resolve_finish(state, unknown_service);
		return ev::req { ... };
	};

	if (port == 0) {
		match (lookup_service(proto, service)) {
		case let p: u16 =>
			port = p;
		case void => yield;
		};
	};

	// TODO:
	// - Consult /etc/services
	// - Fetch the SRV record

	if (port == 0) {
		resolve_finish(state, unknown_service);
		return ev::req { ... };
	};

	match (ip::parse(addr)) {
	case let addr: ip::addr =>
		const addrs = alloc([addr]);
		resolve_finish(state, (addrs, port));
		return ev::req { ... };
		let addrs: []ip::addr = [];
		match (append(addrs, addr)) {
		case void =>
			resolve_finish(state, (addrs, port));
			return ev::req { ... };
		case nomem =>
			resolve_finish(state, nomem);
			return nomem;
		};
	case ip::invalid => yield;
	};

	const addrs = hosts::lookup(addr)?;
	const addrs = match (hosts::lookup(addr)) {
	case let addrs: []ip::addr =>
		yield addrs;
	case let e: hosts::error =>
		resolve_finish(state, nomem);
		return e;
	};
	if (len(addrs) != 0) {
		resolve_finish(state, (addrs, port));
		return ev::req { ... };
	};

	state.port = port;
	return resolve_dns(state, loop, addr);
};

fn resolve_dns(
	state: *resolve_state,
	loop: *ev::loop,
	addr: str,
) (ev::req | error) = {
	const domain = dns::parse_domain(addr);
	defer free(domain);

	let rand: []u8 = [0, 0];
	random::buffer(rand);
	let id = *(&rand[0]: *u16);

	const query6 = dns::message {
		header = dns::header {
			id = id,
			op = dns::op {
				qr = dns::qr::QUERY,
				opcode = dns::opcode::QUERY,
				rd = true,
				...
			},
			qdcount = 1,
			...
		},
		questions = [
			dns::question {
				qname = domain,
				qtype = dns::qtype::AAAA,
				qclass = dns::qclass::IN,
			},
		],
		...
	};
	const query4 = dns::message {
		header = dns::header {
			id = id + 1,
			op = dns::op {
				qr = dns::qr::QUERY,
				opcode = dns::opcode::QUERY,
				rd = true,
				...
			},
			qdcount = 1,
			...
		},
		questions = [
			dns::question {
				qname = domain,
				qtype = dns::qtype::A,
				qclass = dns::qclass::IN,
			},
		],
		...
	};

	let ok = false;
	state.r6 = edns::query(loop, &query6, &query_cb_v6, state)?;
	defer if(!ok) ev::cancel(&state.r6);
	state.r4 = edns::query(loop, &query4, &query_cb_v4, state)?;
	ok = true;
	return ev::mkreq(&resolve_cancel, state);
};

fn resolve_finish(st: *resolve_state, r: (([]ip::addr, u16) | error)) void = {
	const user = st.user;
	const cb = st.cb;
	if (r is error) {
		free(st.ip);
	};
	free(st);
	cb(user, r);
};

fn resolve_cancel(req: *ev::req) void = {
	const state = req.user: *resolve_state;
	ev::cancel(&state.r4);
	ev::cancel(&state.r6);
	free(state.ip);
	free(state);
};

fn query_cb_v4(user: nullable *opaque, r: (*dns::message | dns::error)) void = {
fn query_cb_v4(user: nullable *opaque, r: (*dns::message | dns::error | nomem)) void = {
	let state = user: *resolve_state;
	state.r4 = ev::req { ... };

	match (r) {
	case let err: dns::error =>
		ev::cancel(&state.r6);
		resolve_finish(state, err);
		return;
	case nomem =>
		ev::cancel(&state.r6);
		resolve_finish(state, nomem);
		return;
	case let msg: *dns::message =>
		collect_answers(&state.ip, &msg.answers);
		match (collect_answers(&state.ip, &msg.answers)) {
		case void => void;
		case nomem =>
			ev::cancel(&state.r6);
			resolve_finish(state, nomem);
			return;
		};
		state.nq += 1;
	};

	if (state.nq < 2) {
		return;
	};
	resolve_finish(state, (state.ip, state.port));
};

fn query_cb_v6(user: nullable *opaque, r: (*dns::message | dns::error)) void = {
fn query_cb_v6(user: nullable *opaque, r: (*dns::message | dns::error | nomem)) void = {
	let state = user: *resolve_state;
	state.r6 = ev::req { ... };

	match (r) {
	case let err: dns::error =>
		ev::cancel(&state.r4);
		resolve_finish(state, err);
		return;
	case nomem =>
		ev::cancel(&state.r4);
		resolve_finish(state, nomem);
		return;
	case let msg: *dns::message =>
		collect_answers(&state.ip, &msg.answers);
		match (collect_answers(&state.ip, &msg.answers)) {
		case void => void;
		case nomem =>
			ev::cancel(&state.r4);
			resolve_finish(state, nomem);
			return;
		};
		state.nq += 1;
	};

	if (state.nq < 2) {
		return;
	};
	resolve_finish(state, (state.ip, state.port));
};

fn collect_answers(addrs: *[]ip::addr, answers: *[]dns::rrecord) void = {
fn collect_answers(addrs: *[]ip::addr, answers: *[]dns::rrecord) (void | nomem) = {
	for (let answer &.. answers) {
		match (answer.rdata) {
		case let addr: dns::aaaa =>
			append(addrs, addr: ip::addr);
			append(addrs, addr: ip::addr)?;
		case let addr: dns::a =>
			append(addrs, addr: ip::addr);
			append(addrs, addr: ip::addr)?;
		case => void;
		};
	};
};