hare

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

resolve.ha (3097B)


      1 // License: MPL-2.0
      2 // (c) 2021 Drew DeVault <sir@cmpwn.com>
      3 // (c) 2021-2022 Eyal Sawady <ecs@d2evs.net>
      4 use crypto::random;
      5 use net::dns;
      6 use net::ip;
      7 use strconv;
      8 use strings;
      9 use unix::hosts;
     10 
     11 // Performs DNS resolution on a given address string for a given service,
     12 // including /etc/hosts lookup and SRV resolution, and returns a list of
     13 // candidate IP addresses and the appropriate port, or an error.
     14 //
     15 // The caller must free the [[net::ip::addr]] slice.
     16 export fn resolve(
     17 	proto: str,
     18 	addr: str,
     19 	service: str,
     20 ) (([]ip::addr, u16) | error) = {
     21 	let port = match (strings::index(addr, ':')) {
     22 	case void =>
     23 		yield match (strconv::stou16(service)) {
     24 		case let u: u16 =>
     25 			yield u;
     26 		case =>
     27 			yield 0u16;
     28 		};
     29 	case let i: size =>
     30 		const sub = strings::sub(addr, i + 1, strings::end);
     31 		addr = strings::sub(addr, 0, i);
     32 		yield match (strconv::stou16(sub)) {
     33 		case let u: u16 =>
     34 			yield u;
     35 		case =>
     36 			return invalid_address;
     37 		};
     38 	};
     39 	if (service == "unknown" && port == 0) {
     40 		return unknown_service;
     41 	};
     42 
     43 	let addrs = resolve_addr(addr)?;
     44 	if (port == 0) match (lookup_service(proto, service)) {
     45 	case let p: u16 =>
     46 		port = p;
     47 	case void => void;
     48 	};
     49 
     50 	// TODO:
     51 	// - Consult /etc/services
     52 	// - Fetch the SRV record
     53 
     54 	if (port == 0) {
     55 		return unknown_service;
     56 	};
     57 	if (len(addrs) == 0) {
     58 		return dns::name_error;
     59 	};
     60 
     61 	return (addrs, port);
     62 };
     63 
     64 fn resolve_addr(addr: str) ([]ip::addr | error) = {
     65 	match (ip::parse(addr)) {
     66 	case let addr: ip::addr =>
     67 		return alloc([addr]);
     68 	case ip::invalid => void;
     69 	};
     70 
     71 	const addrs = hosts::lookup(addr);
     72 	if (len(addrs) != 0) {
     73 		return addrs;
     74 	};
     75 
     76 	const domain = dns::parse_domain(addr);
     77 	defer free(domain);
     78 
     79 	let rand: []u8 = [0, 0];
     80 	random::buffer(rand);
     81 	let id = *(&rand[0]: *u16);
     82 
     83 	const query6 = dns::message {
     84 		header = dns::header {
     85 			id = id,
     86 			op = dns::op {
     87 				qr = dns::qr::QUERY,
     88 				opcode = dns::opcode::QUERY,
     89 				rd = true,
     90 				...
     91 			},
     92 			qdcount = 1,
     93 			...
     94 		},
     95 		questions = [
     96 			dns::question {
     97 				qname = domain,
     98 				qtype = dns::qtype::AAAA,
     99 				qclass = dns::qclass::IN,
    100 			},
    101 		],
    102 		...
    103 	};
    104 	const query4 = dns::message {
    105 		header = dns::header {
    106 			id = id + 1,
    107 			op = dns::op {
    108 				qr = dns::qr::QUERY,
    109 				opcode = dns::opcode::QUERY,
    110 				rd = true,
    111 				...
    112 			},
    113 			qdcount = 1,
    114 			...
    115 		},
    116 		questions = [
    117 			dns::question {
    118 				qname = domain,
    119 				qtype = dns::qtype::A,
    120 				qclass = dns::qclass::IN,
    121 			},
    122 		],
    123 		...
    124 	};
    125 
    126 	const resp6 = dns::query(&query6)?;
    127 	defer dns::message_free(resp6);
    128 	const resp4 = dns::query(&query4)?;
    129 	defer dns::message_free(resp4);
    130 
    131 	let addrs: []ip::addr = [];
    132 	collect_answers(&addrs, &resp6.answers);
    133 	collect_answers(&addrs, &resp4.answers);
    134 	return addrs;
    135 };
    136 
    137 fn collect_answers(addrs: *[]ip::addr, answers: *[]dns::rrecord) void = {
    138 	for (let i = 0z; i < len(answers); i += 1) {
    139 		match (answers[i].rdata) {
    140 		// XXX: harec bug prevents us from casting directly to ip::addr
    141 		case let addr: dns::aaaa =>
    142 			append(addrs, addr: ip::addr6: ip::addr);
    143 		case let addr: dns::a =>
    144 			append(addrs, addr: ip::addr4: ip::addr);
    145 		case => void;
    146 		};
    147 	};
    148 };