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