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:
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,
+ };
+ };
};