Lindenii Project Forge
Login

hare-ev

Temporary fork of hare-ev for... reasons
Commit info
ID
554723c96f63e1f109a87cfb2e9d79a8ba2804a2
Author
Drew DeVault <sir@cmpwn.com>
Author date
Thu, 11 Jul 2024 13:16:23 +0200
Committer
Drew DeVault <sir@cmpwn.com>
Committer date
Thu, 11 Jul 2024 13:16:23 +0200
Actions
ev::dial: attempt resolved IPs in order

To handle cases where, for instance, an AAAA record is returned but IPv6
is not available.

Signed-off-by: Drew DeVault <sir@cmpwn.com>
// 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 net;
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);
};

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 = {
	// TODO: Select IPs from a round-robin, or re-attempt on other IPs?
	// TODO: Detect supported networks? i.e. v4/v6
	const req = match (ev::connect_tcp(state.loop,
			&dial_tcp_connectcb,
			state.ip[0], state.port,
			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);
};

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 = {
	match (ev::connect_udp(state.loop,
			state.ip[0], state.port)) {
	case let err: (net::error | errors::error) =>
		dial_udp_complete(state, err);
		return;
	case let sock: *ev::file =>
		dial_udp_complete(state, sock);
		return;
	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);
};