hare

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

resolve.ha (3753B)


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