Lindenii Project Forge
Login

hare-ev

Temporary fork of hare-ev for... reasons

Warning: Due to various recent migrations, viewing non-HEAD refs may be broken.

/ev/dial/resolve.ha (raw)

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

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 | nomem) = {
	// 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 =>
		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 = 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 | nomem) = {
	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 | nomem)) (void | nomem) = {
	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 =>
		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 | nomem)) (void | nomem) = {
	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 =>
		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 | nomem) = {
	for (let answer &.. answers) {
		match (answer.rdata) {
		case let addr: dns::aaaa =>
			append(addrs, addr: ip::addr)?;
		case let addr: dns::a =>
			append(addrs, addr: ip::addr)?;
		case => void;
		};
	};
};