Lindenii Project Forge
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); };