//! Types/fns concerning Internet Protocol (IP), versions 4 & 6 import vec; import uint; import iotask = uv::iotask::iotask; import interact = uv::iotask::interact; import comm::methods; import sockaddr_in = uv::ll::sockaddr_in; import sockaddr_in6 = uv::ll::sockaddr_in6; import addrinfo = uv::ll::addrinfo; import uv_getaddrinfo_t = uv::ll::uv_getaddrinfo_t; import uv_ip4_addr = uv::ll::ip4_addr; import uv_ip4_name = uv::ll::ip4_name; import uv_ip6_addr = uv::ll::ip6_addr; import uv_ip6_name = uv::ll::ip6_name; import uv_getaddrinfo = uv::ll::getaddrinfo; import uv_freeaddrinfo = uv::ll::freeaddrinfo; import create_uv_getaddrinfo_t = uv::ll::getaddrinfo_t; import set_data_for_req = uv::ll::set_data_for_req; import get_data_for_req = uv::ll::get_data_for_req; import ll = uv::ll; export ip_addr, parse_addr_err; export format_addr; export v4, v6; export get_addr; /// An IP address enum ip_addr { /// An IPv4 address ipv4(sockaddr_in), ipv6(sockaddr_in6) } /// Human-friendly feedback on why a parse_addr attempt failed type parse_addr_err = { err_msg: str }; /** * Convert a `ip_addr` to a str * * # Arguments * * * ip - a `std::net::ip::ip_addr` */ fn format_addr(ip: ip_addr) -> str { alt ip { ipv4(addr) { unsafe { let result = uv_ip4_name(&addr); if result == "" { fail "failed to convert inner sockaddr_in address to str" } result } } ipv6(addr) { unsafe { let result = uv_ip6_name(&addr); if result == "" { fail "failed to convert inner sockaddr_in address to str" } result } } } } /// Represents errors returned from `net::ip::get_addr()` enum ip_get_addr_err { get_addr_unknown_error } /** * Attempts name resolution on the provided `node` string * * # Arguments * * * `node` - a string representing some host address * * `iotask` - a `uv::iotask` used to interact with the underlying event loop * * # Returns * * A `result<[ip_addr]/~, ip_get_addr_err>` instance that will contain * a vector of `ip_addr` results, in the case of success, or an error * object in the case of failure */ fn get_addr(++node: str, iotask: iotask) -> result::result<[ip_addr]/~, ip_get_addr_err> unsafe { do comm::listen |output_ch| { do str::unpack_slice(node) |node_ptr, len| { log(debug, #fmt("slice len %?", len)); let handle = create_uv_getaddrinfo_t(); let handle_ptr = ptr::addr_of(handle); let handle_data: get_addr_data = { output_ch: output_ch }; let handle_data_ptr = ptr::addr_of(handle_data); do interact(iotask) |loop_ptr| { let result = uv_getaddrinfo( loop_ptr, handle_ptr, get_addr_cb, node_ptr, ptr::null(), ptr::null()); alt result { 0i32 { set_data_for_req(handle_ptr, handle_data_ptr); } _ { output_ch.send(result::err(get_addr_unknown_error)); } } }; output_ch.recv() } } } mod v4 { /** * Convert a str to `ip_addr` * * # Failure * * Fails if the string is not a valid IPv4 address * * # Arguments * * * ip - a string of the format `x.x.x.x` * * # Returns * * * an `ip_addr` of the `ipv4` variant */ fn parse_addr(ip: str) -> ip_addr { alt try_parse_addr(ip) { result::ok(addr) { copy(addr) } result::err(err_data) { fail err_data.err_msg } } } // the simple, old style numberic representation of // ipv4 type ipv4_rep = { a: u8, b: u8, c: u8, d:u8 }; impl x for ipv4_rep { // this is pretty dastardly, i know unsafe fn as_u32() -> u32 { *((ptr::addr_of(self)) as *u32) } } fn parse_to_ipv4_rep(ip: str) -> result::result { let parts = vec::map(str::split_char(ip, '.'), |s| { alt uint::from_str(s) { some(n) if n <= 255u { n } _ { 256u } } }); if vec::len(parts) != 4u { result::err(#fmt("'%s' doesn't have 4 parts", ip)) } else if vec::contains(parts, 256u) { result::err(#fmt("invalid octal in addr '%s'", ip)) } else { result::ok({a: parts[0] as u8, b: parts[1] as u8, c: parts[2] as u8, d: parts[3] as u8}) } } fn try_parse_addr(ip: str) -> result::result { unsafe { let INADDR_NONE = ll::get_INADDR_NONE(); let ip_rep_result = parse_to_ipv4_rep(ip); if result::is_err(ip_rep_result) { let err_str = result::get_err(ip_rep_result); ret result::err({err_msg: err_str}) } // ipv4_rep.as_u32 is unsafe :/ let input_is_inaddr_none = result::get(ip_rep_result).as_u32() == INADDR_NONE; let new_addr = uv_ip4_addr(ip, 22); let reformatted_name = uv_ip4_name(&new_addr); log(debug, #fmt("try_parse_addr: input ip: %s reparsed ip: %s", ip, reformatted_name)); let ref_ip_rep_result = parse_to_ipv4_rep(reformatted_name); if result::is_err(ref_ip_rep_result) { let err_str = result::get_err(ref_ip_rep_result); ret result::err({err_msg: err_str}) } if result::get(ref_ip_rep_result).as_u32() == INADDR_NONE && !input_is_inaddr_none { ret result::err( {err_msg: "uv_ip4_name produced invalid result."}) } else { result::ok(ipv4(copy(new_addr))) } } } } mod v6 { /** * Convert a str to `ip_addr` * * # Failure * * Fails if the string is not a valid IPv6 address * * # Arguments * * * ip - an ipv6 string. See RFC2460 for spec. * * # Returns * * * an `ip_addr` of the `ipv6` variant */ fn parse_addr(ip: str) -> ip_addr { alt try_parse_addr(ip) { result::ok(addr) { copy(addr) } result::err(err_data) { fail err_data.err_msg } } } fn try_parse_addr(ip: str) -> result::result { unsafe { // need to figure out how to establish a parse failure.. let new_addr = uv_ip6_addr(ip, 22); let reparsed_name = uv_ip6_name(&new_addr); log(debug, #fmt("v6::try_parse_addr ip: '%s' reparsed '%s'", ip, reparsed_name)); // '::' appears to be uv_ip6_name() returns for bogus // parses.. if ip != "::" && reparsed_name == "::" { result::err({err_msg:#fmt("failed to parse '%s'", ip)}) } else { result::ok(ipv6(new_addr)) } } } } type get_addr_data = { output_ch: comm::chan> }; extern fn get_addr_cb(handle: *uv_getaddrinfo_t, status: libc::c_int, res: *addrinfo) unsafe { log(debug, "in get_addr_cb"); let handle_data = get_data_for_req(handle) as *get_addr_data; if status == 0i32 { if res != (ptr::null::()) { let mut out_vec = []/~; log(debug, #fmt("initial addrinfo: %?", res)); let mut curr_addr = res; loop { let new_ip_addr = if ll::is_ipv4_addrinfo(curr_addr) { ipv4(copy(( *ll::addrinfo_as_sockaddr_in(curr_addr)))) } else if ll::is_ipv6_addrinfo(curr_addr) { ipv6(copy(( *ll::addrinfo_as_sockaddr_in6(curr_addr)))) } else { log(debug, "curr_addr is not of family AF_INET or "+ "AF_INET6. Error."); (*handle_data).output_ch.send( result::err(get_addr_unknown_error)); break; }; out_vec += [new_ip_addr]/~; let next_addr = ll::get_next_addrinfo(curr_addr); if next_addr == ptr::null::() as *addrinfo { log(debug, "null next_addr encountered. no mas"); break; } else { curr_addr = next_addr; log(debug, #fmt("next_addr addrinfo: %?", curr_addr)); } } log(debug, #fmt("successful process addrinfo result, len: %?", vec::len(out_vec))); (*handle_data).output_ch.send(result::ok(out_vec)); } else { log(debug, "addrinfo pointer is NULL"); (*handle_data).output_ch.send( result::err(get_addr_unknown_error)); } } else { log(debug, "status != 0 error in get_addr_cb"); (*handle_data).output_ch.send( result::err(get_addr_unknown_error)); } if res != (ptr::null::()) { uv_freeaddrinfo(res); } log(debug, "leaving get_addr_cb"); } #[cfg(test)] mod test { #[test] fn test_ip_ipv4_parse_and_format_ip() { let localhost_str = "127.0.0.1"; assert (format_addr(v4::parse_addr(localhost_str)) == localhost_str) } #[test] fn test_ip_ipv6_parse_and_format_ip() { let localhost_str = "::1"; let format_result = format_addr(v6::parse_addr(localhost_str)); log(debug, #fmt("results: expected: '%s' actual: '%s'", localhost_str, format_result)); assert format_result == localhost_str; } #[test] fn test_ip_ipv4_bad_parse() { alt v4::try_parse_addr("b4df00d") { result::err(err_info) { log(debug, #fmt("got error as expected %?", err_info)); assert true; } result::ok(addr) { fail #fmt("Expected failure, but got addr %?", addr); } } } #[test] #[ignore(target_os="win32")] fn test_ip_ipv6_bad_parse() { alt v6::try_parse_addr("::,~2234k;") { result::err(err_info) { log(debug, #fmt("got error as expected %?", err_info)); assert true; } result::ok(addr) { fail #fmt("Expected failure, but got addr %?", addr); } } } #[test] #[ignore(reason = "valgrind says it's leaky")] fn test_ip_get_addr() { let localhost_name = "localhost"; let iotask = uv::global_loop::get(); let ga_result = get_addr(localhost_name, iotask); if result::is_err(ga_result) { fail "got err result from net::ip::get_addr();" } // note really sure how to realiably test/assert // this.. mostly just wanting to see it work, atm. let results = result::unwrap(ga_result); log(debug, #fmt("test_get_addr: Number of results for %s: %?", localhost_name, vec::len(results))); for vec::each(results) |r| { let ipv_prefix = alt r { ipv4(_) { "IPv4" } ipv6(_) { "IPv6" } }; log(debug, #fmt("test_get_addr: result %s: '%s'", ipv_prefix, format_addr(r))); } // at least one result.. this is going to vary from system // to system, based on stuff like the contents of /etc/hosts assert vec::len(results) > 0; } #[test] #[ignore(reason = "valgrind says it's leaky")] fn test_ip_get_addr_bad_input() { let localhost_name = "sjkl234m,./sdf"; let iotask = uv::global_loop::get(); let ga_result = get_addr(localhost_name, iotask); assert result::is_err(ga_result); } }