diff --git a/base/deprecated.jl b/base/deprecated.jl index 77036b0e421c9..b396984c36690 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -1815,6 +1815,15 @@ end @deprecate get_creds!(cache::CachedCredentials, credid, default) get!(cache, credid, default) end +@noinline function getaddrinfo(callback::Function, host::AbstractString) + depwarn("getaddrinfo with a callback function is deprecated, wrap code in @async instead for deferred execution", :getaddrinfo) + @async begin + r = getaddrinfo(host) + callback(r) + end + nothing +end + # END 0.7 deprecations # BEGIN 1.0 deprecations diff --git a/base/exports.jl b/base/exports.jl index b15e1709383a0..5391623ad9b05 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -1019,6 +1019,8 @@ export fdio, flush, getaddrinfo, + getalladdrinfo, + getnameinfo, gethostname, getipaddr, getpeername, diff --git a/base/libuv.jl b/base/libuv.jl index 34aa99ba20c2e..29c0a044cdf17 100644 --- a/base/libuv.jl +++ b/base/libuv.jl @@ -92,6 +92,7 @@ function reinit_stdio() global uv_jl_connectcb = cfunction(uv_connectcb, Void, Tuple{Ptr{Void}, Cint}) global uv_jl_writecb_task = cfunction(uv_writecb_task, Void, Tuple{Ptr{Void}, Cint}) global uv_jl_getaddrinfocb = cfunction(uv_getaddrinfocb, Void, Tuple{Ptr{Void}, Cint, Ptr{Void}}) + global uv_jl_getnameinfocb = cfunction(uv_getnameinfocb, Void, Tuple{Ptr{Void}, Cint, Cstring, Cstring}) global uv_jl_recvcb = cfunction(uv_recvcb, Void, Tuple{Ptr{Void}, Cssize_t, Ptr{Void}, Ptr{Void}, Cuint}) global uv_jl_sendcb = cfunction(uv_sendcb, Void, Tuple{Ptr{Void}, Cint}) global uv_jl_return_spawn = cfunction(uv_return_spawn, Void, Tuple{Ptr{Void}, Int64, Int32}) diff --git a/base/socket.jl b/base/socket.jl index 2c31975829014..fc7b622797d18 100644 --- a/base/socket.jl +++ b/base/socket.jl @@ -583,7 +583,7 @@ end ## struct DNSError <: Exception - host::AbstractString + host::String code::Int32 end @@ -592,81 +592,193 @@ function show(io::IO, err::DNSError) " (", uverrorname(err.code), ")") end -callback_dict = ObjectIdDict() - function uv_getaddrinfocb(req::Ptr{Void}, status::Cint, addrinfo::Ptr{Void}) - data = ccall(:jl_uv_getaddrinfo_data, Ptr{Void}, (Ptr{Void},), req) - data == C_NULL && return - cb = unsafe_pointer_to_objref(data)::Function - pop!(callback_dict,cb) # using pop forces an error if cb not in callback_dict - if status != 0 || addrinfo == C_NULL - invokelatest(cb, UVError("uv_getaddrinfocb received an unexpected status code", status)) - else - freeaddrinfo = addrinfo - while addrinfo != C_NULL - sockaddr = ccall(:jl_sockaddr_from_addrinfo, Ptr{Void}, (Ptr{Void},), addrinfo) - if ccall(:jl_sockaddr_is_ip4, Int32, (Ptr{Void},), sockaddr) == 1 - invokelatest(cb, IPv4(ntoh(ccall(:jl_sockaddr_host4, UInt32, (Ptr{Void},), sockaddr)))) - break - #elseif ccall(:jl_sockaddr_is_ip6, Int32, (Ptr{Void},), sockaddr) == 1 - # host = Vector{UInt128}(1) - # scope_id = ccall(:jl_sockaddr_host6, UInt32, (Ptr{Void}, Ptr{UInt128}), sockaddr, host) - # invokelatest(cb, IPv6(ntoh(host[1]))) - # break + data = uv_req_data(req) + if data != C_NULL + t = unsafe_pointer_to_objref(data)::Task + uv_req_set_data(req, C_NULL) + if status != 0 || addrinfo == C_NULL + schedule(t, UVError("getaddrinfocb", status)) + else + freeaddrinfo = addrinfo + addrs = IPAddr[] + while addrinfo != C_NULL + sockaddr = ccall(:jl_sockaddr_from_addrinfo, Ptr{Void}, (Ptr{Void},), addrinfo) + if ccall(:jl_sockaddr_is_ip4, Int32, (Ptr{Void},), sockaddr) == 1 + ip4addr = ccall(:jl_sockaddr_host4, UInt32, (Ptr{Void},), sockaddr) + push!(addrs, IPv4(ntoh(ip4addr))) + elseif ccall(:jl_sockaddr_is_ip6, Int32, (Ptr{Void},), sockaddr) == 1 + ip6addr = Ref{UInt128}() + scope_id = ccall(:jl_sockaddr_host6, UInt32, (Ptr{Void}, Ptr{UInt128}), sockaddr, ip6addr) + push!(addrs, IPv6(ntoh(ip6addr[]))) + end + addrinfo = ccall(:jl_next_from_addrinfo, Ptr{Void}, (Ptr{Void},), addrinfo) end - addrinfo = ccall(:jl_next_from_addrinfo, Ptr{Void}, (Ptr{Void},), addrinfo) + ccall(:uv_freeaddrinfo, Void, (Ptr{Void},), freeaddrinfo) + schedule(t, addrs) end - ccall(:uv_freeaddrinfo, Void, (Ptr{Void},), freeaddrinfo) + else + # no owner for this req, safe to just free it + Libc.free(req) end - Libc.free(req) nothing end -function getaddrinfo(cb::Function, host::String) +""" + getalladdrinfo(host::AbstractString) -> Vector{IPAddr} + +Gets all of the IP addresses of the `host`. +Uses the operating system's underlying getaddrinfo implementation, which may do a DNS lookup. +""" +function getalladdrinfo(host::String) isascii(host) || error("non-ASCII hostname: $host") - callback_dict[cb] = cb - status = ccall(:jl_getaddrinfo, Int32, (Ptr{Void}, Cstring, Ptr{UInt8}, Any, Ptr{Void}), - eventloop(), host, C_NULL, cb, uv_jl_getaddrinfocb::Ptr{Void}) - if status == UV_EINVAL - throw(ArgumentError("Invalid uv_getaddrinfo() agument")) - elseif status in [UV_ENOMEM, UV_ENOBUFS] - throw(OutOfMemoryError()) - elseif status < 0 - throw(UVError("uv_getaddrinfo returned an unexpected error code", status)) + req = Libc.malloc(_sizeof_uv_getaddrinfo) + uv_req_set_data(req, C_NULL) # in case we get interrupted before arriving at the wait call + status = ccall(:jl_getaddrinfo, Int32, (Ptr{Void}, Ptr{Void}, Cstring, Ptr{Void}, Ptr{Void}), + eventloop(), req, host, #=service=#C_NULL, uv_jl_getaddrinfocb::Ptr{Void}) + if status < 0 + Libc.free(req) + if status == UV_EINVAL + throw(ArgumentError("Invalid getaddrinfo argument")) + elseif status == UV_ENOMEM || status == UV_ENOBUFS + throw(OutOfMemoryError()) + end + uv_error("getaddrinfo", status) + end + ct = current_task() + preserve_handle(ct) + r = try + uv_req_set_data(req, ct) + wait() + finally + if uv_req_data(req) != C_NULL + # req is still alive, + # so make sure we don't get spurious notifications later + uv_req_set_data(req, C_NULL) + ccall(:uv_cancel, Int32, (Ptr{Void},), req) # try to let libuv know we don't care anymore + else + # done with req + Libc.free(req) + end + unpreserve_handle(ct) end - return nothing + if isa(r, UVError) + code = r.code + if code in (UV_EAI_ADDRFAMILY, UV_EAI_AGAIN, UV_EAI_BADFLAGS, + UV_EAI_BADHINTS, UV_EAI_CANCELED, UV_EAI_FAIL, + UV_EAI_FAMILY, UV_EAI_NODATA, UV_EAI_NONAME, + UV_EAI_OVERFLOW, UV_EAI_PROTOCOL, UV_EAI_SERVICE, + UV_EAI_SOCKTYPE) + throw(DNSError(host, code)) + elseif code == UV_EAI_MEMORY + throw(OutOfMemoryError()) + else + throw(UVError("getaddrinfo", code)) + end + end + return r::Vector{IPAddr} end -getaddrinfo(cb::Function, host::AbstractString) = getaddrinfo(cb, String(host)) +getalladdrinfo(host::AbstractString) = getalladdrinfo(String(host)) """ - getaddrinfo(host::AbstractString) -> IPAddr + getalladdrinfo(host::AbstractString, IPAddr=IPv4) -> IPAddr -Gets the IP address of the `host` (may have to do a DNS lookup) +Gets the first IP address of the `host` of the specified IPAddr type. +Uses the operating system's underlying getaddrinfo implementation, which may do a DNS lookup. """ -function getaddrinfo(host::String) - c = Condition() - getaddrinfo(host) do IP - notify(c,IP) +function getaddrinfo(host::String, T::Type{<:IPAddr}) + addrs = getalladdrinfo(host) + for addr in addrs + if addr isa T + return addr + end + end + throw(DNSError(host, UV_EAI_NONAME)) +end +getaddrinfo(host::AbstractString, T::Type{<:IPAddr}) = getaddrinfo(String(host), T) +getaddrinfo(host::AbstractString) = getaddrinfo(String(host), IPv4) + +function uv_getnameinfocb(req::Ptr{Void}, status::Cint, hostname::Cstring, service::Cstring) + data = uv_req_data(req) + if data != C_NULL + t = unsafe_pointer_to_objref(data)::Task + uv_req_set_data(req, C_NULL) + if status != 0 + schedule(t, UVError("getnameinfocb", status)) + else + schedule(t, unsafe_string(hostname)) + end + else + # no owner for this req, safe to just free it + Libc.free(req) + end + nothing +end + +""" + getnameinfo(host::IPAddr) -> String + +Performs a reverse-lookup for IP address to return a hostname and service +using the operating system's underlying getnameinfo implementation. +""" +function getnameinfo(address::Union{IPv4, IPv6}) + req = Libc.malloc(_sizeof_uv_getnameinfo) + uv_req_set_data(req, C_NULL) # in case we get interrupted before arriving at the wait call + ev = eventloop() + port = hton(UInt16(0)) + flags = 0 + uvcb = uv_jl_getnameinfocb::Ptr{Void} + status = UV_EINVAL + if address isa IPv4 + status = ccall(:jl_getnameinfo, Int32, (Ptr{Void}, Ptr{Void}, UInt32, UInt16, Cint, Ptr{Void}), + ev, req, hton(address.host), port, flags, uvcb) + elseif address isa IPv6 + status = ccall(:jl_getnameinfo6, Int32, (Ptr{Void}, Ptr{Void}, Ref{UInt128}, UInt16, Cint, Ptr{Void}), + ev, req, hton(address.host), port, flags, uvcb) + end + if status < 0 + Libc.free(req) + if status == UV_EINVAL + throw(ArgumentError("Invalid getnameinfo argument")) + elseif status == UV_ENOMEM || status == UV_ENOBUFS + throw(OutOfMemoryError()) + end + uv_error("getnameinfo", status) + end + ct = current_task() + preserve_handle(ct) + r = try + uv_req_set_data(req, ct) + wait() + finally + if uv_req_data(req) != C_NULL + # req is still alive, + # so make sure we don't get spurious notifications later + uv_req_set_data(req, C_NULL) + ccall(:uv_cancel, Int32, (Ptr{Void},), req) # try to let libuv know we don't care anymore + else + # done with req + Libc.free(req) + end + unpreserve_handle(ct) end - r = wait(c) if isa(r, UVError) - r = r::UVError code = r.code if code in (UV_EAI_ADDRFAMILY, UV_EAI_AGAIN, UV_EAI_BADFLAGS, UV_EAI_BADHINTS, UV_EAI_CANCELED, UV_EAI_FAIL, UV_EAI_FAMILY, UV_EAI_NODATA, UV_EAI_NONAME, UV_EAI_OVERFLOW, UV_EAI_PROTOCOL, UV_EAI_SERVICE, UV_EAI_SOCKTYPE) - throw(DNSError(host, code)) + throw(DNSError(repr(address), code)) elseif code == UV_EAI_MEMORY throw(OutOfMemoryError()) else - throw(SystemError("uv_getaddrinfocb", -code)) + throw(UVError("getnameinfo", code)) end end - return r::IPAddr + return r::String end -getaddrinfo(host::AbstractString) = getaddrinfo(String(host)) + const _sizeof_uv_interface_address = ccall(:jl_uv_sizeof_interface_address,Int32,()) diff --git a/doc/src/stdlib/io-network.md b/doc/src/stdlib/io-network.md index d7b6a1973ea63..f56b014d2a439 100644 --- a/doc/src/stdlib/io-network.md +++ b/doc/src/stdlib/io-network.md @@ -159,6 +159,8 @@ Base.connect(::AbstractString) Base.listen(::Any) Base.listen(::AbstractString) Base.getaddrinfo +Base.getalladdrinfo +Base.getnameinfo Base.getsockname Base.getpeername Base.IPv4 diff --git a/src/jl_uv.c b/src/jl_uv.c index 640c4afd9baad..20a1372c44e68 100644 --- a/src/jl_uv.c +++ b/src/jl_uv.c @@ -118,7 +118,6 @@ JL_DLLEXPORT size_t jl_uv_buf_len(const uv_buf_t *buf) { return buf->len; } JL_DLLEXPORT void jl_uv_buf_set_base(uv_buf_t *buf, char *b) { buf->base = b; } JL_DLLEXPORT void jl_uv_buf_set_len(uv_buf_t *buf, size_t n) { buf->len = n; } JL_DLLEXPORT void *jl_uv_connect_handle(uv_connect_t *connect) { return connect->handle; } -JL_DLLEXPORT void *jl_uv_getaddrinfo_data(uv_getaddrinfo_t *req) { return req->data; } JL_DLLEXPORT uv_file jl_uv_file_handle(jl_uv_file_t *f) { return f->file; } JL_DLLEXPORT void *jl_uv_req_data(uv_req_t *req) { return req->data; } JL_DLLEXPORT void jl_uv_req_set_data(uv_req_t *req, void *data) { req->data = data; } @@ -712,23 +711,46 @@ JL_DLLEXPORT struct sockaddr_in *jl_uv_interface_address_sockaddr(uv_interface_a return &ifa->address.address4; } -JL_DLLEXPORT int jl_getaddrinfo(uv_loop_t *loop, const char *host, - const char *service, jl_function_t *cb, - uv_getaddrinfo_cb uvcb) +JL_DLLEXPORT int jl_getaddrinfo(uv_loop_t *loop, uv_getaddrinfo_t *req, + const char *host, const char *service, uv_getaddrinfo_cb uvcb) { - uv_getaddrinfo_t *req = (uv_getaddrinfo_t*)malloc(sizeof(uv_getaddrinfo_t)); struct addrinfo hints; - - memset (&hints, 0, sizeof (hints)); + memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags |= AI_CANONNAME; - req->data = cb; + req->data = NULL; + return uv_getaddrinfo(loop, req, uvcb, host, service, &hints); +} - return uv_getaddrinfo(loop,req,uvcb,host,service,&hints); +JL_DLLEXPORT int jl_getnameinfo(uv_loop_t *loop, uv_getnameinfo_t *req, + uint32_t host, uint16_t port, int flags, uv_getnameinfo_cb uvcb) +{ + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = host; + addr.sin_port = port; + + req->data = NULL; + return uv_getnameinfo(loop, req, uvcb, (struct sockaddr*)&addr, flags); } +JL_DLLEXPORT int jl_getnameinfo6(uv_loop_t *loop, uv_getnameinfo_t *req, + void *host, uint16_t port, int flags, uv_getnameinfo_cb uvcb) +{ + struct sockaddr_in6 addr; + memset(&addr, 0, sizeof(addr)); + addr.sin6_family = AF_INET6; + memcpy(&addr.sin6_addr, host, 16); + addr.sin6_port = port; + + req->data = NULL; + return uv_getnameinfo(loop, req, uvcb, (struct sockaddr*)&addr, flags); +} + + JL_DLLEXPORT struct sockaddr *jl_sockaddr_from_addrinfo(struct addrinfo *addrinfo) { return addrinfo->ai_addr; diff --git a/test/socket.jl b/test/socket.jl index a950672584104..b2228223d6d74 100644 --- a/test/socket.jl +++ b/test/socket.jl @@ -132,22 +132,51 @@ mktempdir() do tmpdir wait(tsk) end +# test some unroutable IP addresses (RFC 5737) +@test getnameinfo(ip"192.0.2.1") == "192.0.2.1" +@test getnameinfo(ip"198.51.100.1") == "198.51.100.1" +@test getnameinfo(ip"203.0.113.1") == "203.0.113.1" +@test getnameinfo(ip"0.1.1.1") == "0.1.1.1" +@test getnameinfo(ip"::ffff:0.1.1.1") == "::ffff:0.1.1.1" +@test getnameinfo(ip"::ffff:192.0.2.1") == "::ffff:192.0.2.1" +@test getnameinfo(ip"2001:db8::1") == "2001:db8::1" + +# test some valid IP addresses +@test !isempty(getnameinfo(ip"::")::String) +@test !isempty(getnameinfo(ip"0.0.0.0")::String) +@test !isempty(getnameinfo(ip"10.1.0.0")::String) +@test !isempty(getnameinfo(ip"10.1.0.255")::String) +@test !isempty(getnameinfo(ip"10.1.255.1")::String) +@test !isempty(getnameinfo(ip"255.255.255.255")::String) +@test !isempty(getnameinfo(ip"255.255.255.0")::String) +@test !isempty(getnameinfo(ip"192.168.0.1")::String) +@test !isempty(getnameinfo(ip"::1")::String) + +let localhost = getnameinfo(ip"127.0.0.1")::String + @test !isempty(localhost) && localhost != "127.0.0.1" + @test !isempty(getalladdrinfo(localhost)::Vector{IPAddr}) + @test getaddrinfo(localhost, IPv4)::IPv4 != ip"0.0.0.0" + @test try + getaddrinfo(localhost, IPv6)::IPv6 != ip"::" + catch ex + isa(ex, Base.DNSError) && ex.code == Base.UV_EAI_NONAME && ex.host == localhost + end +end @test_throws Base.DNSError getaddrinfo(".invalid") @test_throws ArgumentError getaddrinfo("localhost\0") # issue #10994 -@test_throws Base.UVError connect("localhost", 21452) +@test_throws Base.UVError("connect", Base.UV_ECONNREFUSED) connect(ip"127.0.0.1", 21452) # test invalid port -@test_throws ArgumentError connect(ip"127.0.0.1",-1) +@test_throws ArgumentError connect(ip"127.0.0.1", -1) @test_throws ArgumentError connect(ip"127.0.0.1", typemax(UInt16)+1) @test_throws ArgumentError connect(ip"0:0:0:0:0:ffff:127.0.0.1", -1) @test_throws ArgumentError connect(ip"0:0:0:0:0:ffff:127.0.0.1", typemax(UInt16)+1) -let - p, server = listenany(defaultport) +let (p, server) = listenany(defaultport) r = Channel(1) tsk = @async begin put!(r, :start) - @test_throws Base.UVError accept(server) + @test_throws Base.UVError("accept", Base.UV_ECONNABORTED) accept(server) end @test fetch(r) === :start close(server) @@ -159,8 +188,8 @@ let randport, server = listenany(defaultport) @async connect("localhost", randport) s1 = accept(server) - @test_throws ErrorException accept(server,s1) - @test_throws Base.UVError listen(randport) + @test_throws ErrorException("client TCPSocket is not in initialization state") accept(server, s1) + @test_throws Base.UVError("listen", Base.UV_EADDRINUSE) listen(randport) port2, server2 = listenany(randport) @test randport != port2 close(server) @@ -204,6 +233,7 @@ let close(a) close(b) end + if !Sys.iswindows() || Sys.windows_version() >= Sys.WINDOWS_VISTA_VER a = UDPSocket() b = UDPSocket() @@ -218,6 +248,8 @@ if !Sys.iswindows() || Sys.windows_version() >= Sys.WINDOWS_VISTA_VER end send(b, ip"::1", randport, "Hello World") wait(tsk) + send(b, ip"::1", randport, "Hello World") + wait(tsk) end for (addr, porthint) in [