From 1915931e6bdf11bfee9f4cb4478233c5f5e42801 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Tue, 5 Sep 2017 17:29:53 -0400 Subject: [PATCH] implement getnameinfo This function is the complement of getaddinfo. --- base/exports.jl | 1 + base/libuv.jl | 1 + base/socket.jl | 82 +++++++++++++++++++++ doc/src/stdlib/io-network.md | 1 + src/jl_uv.c | 27 +++++++ test/socket.jl | 138 ++++++++++++++++++++--------------- 6 files changed, 191 insertions(+), 59 deletions(-) diff --git a/base/exports.jl b/base/exports.jl index 80ac4269281922..7a24cb973c37cc 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -1020,6 +1020,7 @@ export flush, getaddrinfo, getalladdrinfo, + getnameinfo, gethostname, getipaddr, getpeername, diff --git a/base/libuv.jl b/base/libuv.jl index 237e3d22883d07..0ea9316d96eb49 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 d53b0a549ee659..c0a059060e5cff 100644 --- a/base/socket.jl +++ b/base/socket.jl @@ -698,6 +698,88 @@ 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 + 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(repr(address), code)) + elseif code == UV_EAI_MEMORY + throw(OutOfMemoryError()) + else + throw(UVError("getnameinfo", code)) + end + end + return r::String +end + + 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 ba9ca63785ff5b..e6052fb373548a 100644 --- a/doc/src/stdlib/io-network.md +++ b/doc/src/stdlib/io-network.md @@ -161,6 +161,7 @@ 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 a5c9baf0afcce4..20a1372c44e686 100644 --- a/src/jl_uv.c +++ b/src/jl_uv.c @@ -724,6 +724,33 @@ JL_DLLEXPORT int jl_getaddrinfo(uv_loop_t *loop, uv_getaddrinfo_t *req, 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 ab6f72458d5358..2649fb365a1b8b 100644 --- a/test/socket.jl +++ b/test/socket.jl @@ -109,42 +109,61 @@ mktempdir() do tmpdir wait(tsk) end -@test !isempty(getalladdrinfo("localhost")::Vector{IPAddr}) -@test getaddrinfo("localhost", IPv4) === ip"127.0.0.1" -@test getaddrinfo("localhost", IPv6) === ip"::1" +# test some invalid IP addresses +@test getnameinfo(ip"10.1.0.0") == "10.1.0.0" +@test getnameinfo(ip"10.1.0.255") == "10.1.0.255" +@test getnameinfo(ip"10.1.255.1") == "10.1.255.1" +@test getnameinfo(ip"0.1.1.1") == "0.1.1.1" + +# 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 some valid IP addresses +@test !isempty(getnameinfo(ip"::")::String) +@test !isempty(getnameinfo(ip"0.0.0.0")::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 getaddrinfo(localhost, IPv6)::IPv6 != ip"::" +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) -p, server = listenany(defaultport) -r = Channel(1) -tsk = @async begin - put!(r, :start) - @test_throws Base.UVError accept(server) +let (p, server) = listenany(defaultport) + r = Channel(1) + tsk = @async begin + put!(r, :start) + @test_throws Base.UVError("accept", Base.UV_ECONNABORTED) accept(server) + end + @test fetch(r) === :start + close(server) + wait(tsk) +end + +let (port, server) = listenany(defaultport) + @async connect("localhost", port) + s1 = accept(server) + @test_throws ErrorException accept(server, s1) + @test_throws Base.UVError listen(port) + port2, server2 = listenany(port) + @test port != port2 + close(server) + close(server2) end -@test fetch(r) === :start -close(server) -wait(tsk) - -port, server = listenany(defaultport) -@async connect("localhost",port) -s1 = accept(server) -@test_throws ErrorException accept(server,s1) -@test_throws Base.UVError listen(port) -port2, server2 = listenany(port) -@test port != port2 -close(server) -close(server2) @test_throws Base.DNSError connect(".invalid",80) -begin +let port = defaultport a = UDPSocket() b = UDPSocket() bind(a, ip"127.0.0.1", port) @@ -174,57 +193,58 @@ begin send(b, ip"127.0.0.1", port, "Hello World") wait(tsk) - @test_throws MethodError bind(UDPSocket(),port) + @test_throws MethodError bind(UDPSocket(), port) close(a) close(b) end -if !Sys.iswindows() || Sys.windows_version() >= Sys.WINDOWS_VISTA_VER - a = UDPSocket() - b = UDPSocket() - bind(a, ip"::1", UInt16(port)) - bind(b, ip"::1", UInt16(port+1)) - tsk = @async begin - @test begin - (addr, data) = recvfrom(a) - addr == ip"::1" && String(data) == "Hello World" +if !Sys.iswindows() || Sys.windows_version() >= Sys.WINDOWS_VISTA_VER + let port = defaultport + a = UDPSocket() + b = UDPSocket() + bind(a, ip"::1", UInt16(port)) + bind(b, ip"::1", UInt16(port + 1)) + + tsk = @async begin + @test begin + (addr, data) = recvfrom(a) + addr == ip"::1" && String(data) == "Hello World" + end end + send(b, ip"::1", port, "Hello World") + wait(tsk) end - send(b, ip"::1", port, "Hello World") - wait(tsk) end -begin - for (addr, porthint) in [(IPv4("127.0.0.1"), UInt16(11011)), - (IPv6("::1"), UInt16(11012)), (getipaddr(), UInt16(11013))] - port, listen_sock = listenany(addr, porthint) - gsn_addr, gsn_port = getsockname(listen_sock) +for (addr, porthint) in [(IPv4("127.0.0.1"), UInt16(11011)), + (IPv6("::1"), UInt16(11012)), (getipaddr(), UInt16(11013))] + port, listen_sock = listenany(addr, porthint) + gsn_addr, gsn_port = getsockname(listen_sock) - @test addr == gsn_addr - @test port == gsn_port + @test addr == gsn_addr + @test port == gsn_port - @test_throws MethodError getpeername(listen_sock) + @test_throws MethodError getpeername(listen_sock) - # connect to it - client_sock = connect(addr, port) - server_sock = accept(listen_sock) + # connect to it + client_sock = connect(addr, port) + server_sock = accept(listen_sock) - self_client_addr, self_client_port = getsockname(client_sock) - peer_client_addr, peer_client_port = getpeername(client_sock) - self_srvr_addr, self_srvr_port = getsockname(server_sock) - peer_srvr_addr, peer_srvr_port = getpeername(server_sock) + self_client_addr, self_client_port = getsockname(client_sock) + peer_client_addr, peer_client_port = getpeername(client_sock) + self_srvr_addr, self_srvr_port = getsockname(server_sock) + peer_srvr_addr, peer_srvr_port = getpeername(server_sock) - @test self_client_addr == peer_client_addr == self_srvr_addr == peer_srvr_addr + @test self_client_addr == peer_client_addr == self_srvr_addr == peer_srvr_addr - @test peer_client_port == self_srvr_port - @test peer_srvr_port == self_client_port - @test self_srvr_port != self_client_port + @test peer_client_port == self_srvr_port + @test peer_srvr_port == self_client_port + @test self_srvr_port != self_client_port - close(listen_sock) - close(client_sock) - close(server_sock) - end + close(listen_sock) + close(client_sock) + close(server_sock) end # Local-machine broadcast