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 };