hare

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

resolve.ha (3732B)


      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 id = 0u16;
    110 	random::buffer(&id: *[size(u16)]u8);
    111 
    112 	const query6 = dns::message {
    113 		header = dns::header {
    114 			id = id,
    115 			op = dns::op {
    116 				qr = dns::qr::QUERY,
    117 				opcode = dns::opcode::QUERY,
    118 				rd = true,
    119 				...
    120 			},
    121 			qdcount = 1,
    122 			...
    123 		},
    124 		questions = [
    125 			dns::question {
    126 				qname = domain,
    127 				qtype = dns::qtype::AAAA,
    128 				qclass = dns::qclass::IN,
    129 			},
    130 		],
    131 		...
    132 	};
    133 	const query4 = dns::message {
    134 		header = dns::header {
    135 			id = id + 1,
    136 			op = dns::op {
    137 				qr = dns::qr::QUERY,
    138 				opcode = dns::opcode::QUERY,
    139 				rd = true,
    140 				...
    141 			},
    142 			qdcount = 1,
    143 			...
    144 		},
    145 		questions = [
    146 			dns::question {
    147 				qname = domain,
    148 				qtype = dns::qtype::A,
    149 				qclass = dns::qclass::IN,
    150 			},
    151 		],
    152 		...
    153 	};
    154 
    155 	const resp6 = dns::query(&query6)?;
    156 	defer dns::message_free(resp6);
    157 	const resp4 = dns::query(&query4)?;
    158 	defer dns::message_free(resp4);
    159 
    160 	let addrs: []ip::addr = [];
    161 	collect_answers(&addrs, &resp6.answers);
    162 	collect_answers(&addrs, &resp4.answers);
    163 	return addrs;
    164 };
    165 
    166 fn collect_answers(addrs: *[]ip::addr, answers: *[]dns::rrecord) void = {
    167 	for (let i = 0z; i < len(answers); i += 1) {
    168 		match (answers[i].rdata) {
    169 		case let addr: dns::aaaa =>
    170 			append(addrs, addr: ip::addr)!;
    171 		case let addr: dns::a =>
    172 			append(addrs, addr: ip::addr)!;
    173 		case => void;
    174 		};
    175 	};
    176 };