dial.ha (3368B)
1 // SPDX-License-Identifier: MPL-2.0 2 // (c) Hare authors <https://harelang.org> 3 4 use errors; 5 use fmt; 6 use net; 7 use net::ip; 8 use net::uri; 9 10 // Dials a remote address, establishing a connection and returning the resulting 11 // [[net::socket]]. The proto parameter should be the transport protocol (e.g. 12 // "tcp"), the address parameter should be the remote address, and the service 13 // should be the name of the service, or the default port to use. 14 // 15 // The interpretation of the address and service parameters is dependent on the 16 // protocol in use. For IP-based protocols (such as TCP or UDP), the address 17 // parameter may be either an IPv4 or IPv6 address, or a name, and may include a 18 // port separated by a colon (':'). If an IPv6 address and a port are both 19 // desired, use brackets ('[' and ']') to separate the address from the port 20 // (e.g. "[::1]:80"). If the port is not specified, it is inferred from the 21 // service parameter. If a name is used instead of an IP address, a DNS lookup 22 // is performed, consulting the local /etc/hosts file or equivalent, if 23 // possible. 24 // 25 // The service parameter can be a service name (e.g. "submission") or a default 26 // port to use, if one is not specified by address. If a service name is used, 27 // an internal list of services is consulted (see [[registersvc]]), and if not 28 // known to Hare, the system service list (e.g. /etc/services) will be 29 // consulted. If the connection port cannot be established, [[errors::invalid]] 30 // is returned. The special service name "unknown" will always consult the 31 // address parameter for a desired port, and will return [[errors::invalid]] if 32 // one is not provided there. 33 // 34 // If the address parameter includes a name, but not a port, an SRV lookup will 35 // be performed alongside the A or AAAA record lookup for that name. If the name 36 // server provides an SRV record for the given service, it will be utilized in 37 // lieu of the service database. 38 export fn dial( 39 proto: str, 40 address: str, 41 service: str, 42 ) (net::socket | error) = { 43 for (let i = 0z; i < len(default_protocols); i += 1) { 44 const p = default_protocols[i]; 45 if (p.name == proto) { 46 return p.dial(address, service); 47 }; 48 }; 49 for (let i = 0z; i < len(protocols); i += 1) { 50 const p = protocols[i]; 51 if (p.name == proto) { 52 return p.dial(address, service); 53 }; 54 }; 55 return net::unknownproto: net::error; 56 }; 57 58 def HOST_MAX: size = 255; 59 60 // Performs a [[dial]] operation for a given URI, taking the service name from 61 // the URI scheme and forming an address from the URI host and port. 62 export fn dial_uri(proto: str, uri: *uri::uri) (net::socket | error) = { 63 // XXX: Should the code to convert a URI to e.g. "[::1]:80" be 64 // generalized for end-user use? 65 if (uri.host is str && len(uri.host as str) > HOST_MAX) { 66 return invalid_address; 67 }; 68 static let addr: [HOST_MAX + len("[]:65535")]u8 = [0...]; 69 70 const colon = if (uri.port != 0) ":" else ""; 71 const port: fmt::formattable = if (uri.port != 0) uri.port else ""; 72 73 let addr = match (uri.host) { 74 case let host: str => 75 yield fmt::bsprintf(addr, "{}{}{}", host, colon, port); 76 case let ip: ip::addr4 => 77 const host = ip::string(ip); 78 yield fmt::bsprintf(addr, "{}{}{}", host, colon, port); 79 case let ip: ip::addr6 => 80 const host = ip::string(ip); 81 yield fmt::bsprintf(addr, "[{}]{}{}", host, colon, port); 82 }; 83 84 return dial(proto, addr, uri.scheme); 85 };