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