hare

The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

commit 662836c2173638c78e7593e95271250eca8fb5df
parent dacf8fdbe8243ba42cd4e315d15e1b4293b087e2
Author: Drew DeVault <sir@cmpwn.com>
Date:   Wed, 23 Jun 2021 17:58:56 -0400

net::dial: initial version of dial::resolve

Signed-off-by: Drew DeVault <sir@cmpwn.com>

Diffstat:
Mnet/dial/dial.ha | 4++--
Mnet/dial/registry.ha | 86+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mnet/dial/resolve.ha | 127+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
3 files changed, 176 insertions(+), 41 deletions(-)

diff --git a/net/dial/dial.ha b/net/dial/dial.ha @@ -35,13 +35,13 @@ export fn dial( service: str, ) (*io::stream | net::error) = { for (let i = 0z; i < len(default_protocols); i += 1) { - let p = default_protocols[i]; + const p = default_protocols[i]; if (p.name == proto) { return p.dial(address, service); }; }; for (let i = 0z; i < len(protocols); i += 1) { - let p = protocols[i]; + const p = protocols[i]; if (p.name == proto) { return p.dial(address, service); }; diff --git a/net/dial/registry.ha b/net/dial/registry.ha @@ -10,40 +10,36 @@ type protocol = struct { }; type service = struct { + proto: str, name: str, alias: []str, port: u16, }; -let default_protocols: []protocol = [ +let default_protocols: [_]protocol = [ protocol { name = "tcp", dial = &dial_tcp }, protocol { name = "udp", dial = &dial_udp }, ]; -let default_tcp: []service = [ - service { name = "ssh", alias = [], port = 22 }, - service { name = "smtp", alias = ["mail"], port = 25 }, - service { name = "domain", alias = ["dns"], port = 53 }, - service { name = "http", alias = ["www"], port = 80 }, - service { name = "imap2", alias = ["imap"], port = 143 }, - service { name = "https", alias = [], port = 443 }, - service { name = "submission", alias = [], port = 587 }, - service { name = "imaps", alias = [], port = 993 }, -]; - -let default_udp: []service = [ - service { name = "domain", alias = ["dns"], port = 53 }, - service { name = "ntp", alias = [], port = 123 }, +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 tcp_services: []service = []; -let udp_services: []service = []; +let services: []service = []; @fini fn fini() void = { free(protocols); - free(tcp_services); - free(udp_services); + free(services); }; // Registers a new transport-level protocol (e.g. TCP) with the dialer. The name @@ -60,27 +56,43 @@ export fn registerproto(name: str, dial: *dialer) void = { // port for TCP and UDP connections. The name and alias list should be // statically allocated. export fn registersvc( + proto: str, name: str, alias: []str, - tcp: (u16 | void), - udp: (u16 | void), + port: u16, ) void = { - assert(!(tcp is void) || !(udp is void), - "Expected registersvc to receive TCP or UDP port, or both"); - match (tcp) { - void => void, - port: u16 => append(tcp_services, service { - name = name, - alias = alias, - port = port, - }), + 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; }; - match (udp) { - void => void, - port: u16 => append(udp_services, service { - name = name, - alias = alias, - port = port, - }), + for (let j = 0z; j < len(candidate.alias); j += 1) { + if (candidate.alias[j] == service) { + return true; + }; }; + return false; }; diff --git a/net/dial/resolve.ha b/net/dial/resolve.ha @@ -1,9 +1,132 @@ +use crypto::random; use net::dns; use net::ip; +use strconv; +use strings; +use unix::hosts; // 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. -export fn resolve(addr: str, service: str) (([]ip::addr, u16) | dns::error) = { - abort(); // TODO +// +// The caller must free the [[ip::addr]] slice. +export fn resolve( + proto: str, + addr: str, + service: str, +) (([]ip::addr, u16) | dns::error) = { + let port = match (strings::index(addr, ':')) { + void => match (strconv::stou16(service)) { + u: u16 => u, + * => 0u16, + }, + i: size => { + const sub = strings::sub(addr, i, strings::end); + addr = strings::sub(addr, 0, i); + // TODO: Return error on invalid port + strconv::stou16(sub)!; + }, + }; + + let addrs = resolve_addr(addr)?; + if (port == 0) match (lookup_service(proto, service)) { + p: u16 => port = p, + void => void, + }; + + // TODO: + // - Consult /etc/services + // - Fetch the SRV record + + assert(port != 0); // TODO: Return an error + + if (len(addrs) == 0) { + return dns::name_error; + }; + + return (addrs, port); +}; + +fn resolve_addr(addr: str) ([]ip::addr | dns::error) = { + match (ip::parse(addr)) { + addr: ip::addr => alloc([addr]), + ip::invalid => void, + }; + + const addrs = hosts::lookup(addr); + if (len(addrs) != 0) { + return addrs; + }; + + 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, + }, + ], + ... + }; + + const resp6 = dns::query(&query6)?; + defer dns::message_free(resp6); + const resp4 = dns::query(&query4)?; + defer dns::message_free(resp4); + + let addrs: []ip::addr = []; + collect_answers(&addrs, &resp6.answers); + collect_answers(&addrs, &resp4.answers); + return addrs; +}; + +fn collect_answers(addrs: *[]ip::addr, answers: *[]dns::rrecord) void = { + for (let i = 0z; i < len(*answers); i += 1) { + match (answers[i].rdata) { + // XXX: harec bug prevents us from casting directly to + // ip::addr + addr: dns::aaaa => append(*addrs, addr: ip::addr6: ip::addr), + addr: dns::a => append(*addrs, addr: ip::addr4: ip::addr), + * => void, + }; + }; };