/test/socket/test_unix.rb
Ruby | 715 lines | 667 code | 44 blank | 4 comment | 55 complexity | 374a58c8a67af417f308f3875513b615 MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause, AGPL-3.0
- # frozen_string_literal: true
- begin
- require "socket"
- rescue LoadError
- end
- require "test/unit"
- require "tempfile"
- require "timeout"
- require "tmpdir"
- require "io/nonblock"
- class TestSocket_UNIXSocket < Test::Unit::TestCase
- def test_fd_passing
- r1, w = IO.pipe
- s1, s2 = UNIXSocket.pair
- begin
- s1.send_io(nil)
- rescue NotImplementedError
- assert_raise(NotImplementedError) { s2.recv_io }
- rescue TypeError
- s1.send_io(r1)
- r2 = s2.recv_io
- assert_equal(r1.stat.ino, r2.stat.ino)
- assert_not_equal(r1.fileno, r2.fileno)
- assert(r2.close_on_exec?)
- w.syswrite "a"
- assert_equal("a", r2.sysread(10))
- ensure
- s1.close
- s2.close
- w.close
- r1.close
- r2.close if r2 && !r2.closed?
- end
- end
- def test_fd_passing_class_mode
- UNIXSocket.pair do |s1, s2|
- s1.send_io(s1.fileno)
- r = s2.recv_io(nil)
- assert_kind_of Integer, r, 'recv_io with klass=nil returns integer FD'
- assert_not_equal s1.fileno, r
- r = IO.for_fd(r)
- assert_equal s1.stat.ino, r.stat.ino
- r.close
- s1.send_io(s1)
- # klass = UNIXSocket FIXME: [ruby-core:71860] [Bug #11778]
- klass = IO
- r = s2.recv_io(klass, 'r+')
- assert_instance_of klass, r, 'recv_io with proper klass'
- assert_not_equal s1.fileno, r.fileno
- r.close
- end
- end
- def test_fd_passing_n
- io_ary = []
- return if !defined?(Socket::SCM_RIGHTS)
- io_ary.concat IO.pipe
- io_ary.concat IO.pipe
- io_ary.concat IO.pipe
- send_io_ary = []
- io_ary.each {|io|
- send_io_ary << io
- UNIXSocket.pair {|s1, s2|
- begin
- ret = s1.sendmsg("\0", 0, nil, [Socket::SOL_SOCKET, Socket::SCM_RIGHTS,
- send_io_ary.map {|io2| io2.fileno }.pack("i!*")])
- rescue NotImplementedError
- return
- end
- assert_equal(1, ret)
- ret = s2.recvmsg(:scm_rights=>true)
- _, _, _, *ctls = ret
- recv_io_ary = []
- begin
- ctls.each {|ctl|
- next if ctl.level != Socket::SOL_SOCKET || ctl.type != Socket::SCM_RIGHTS
- recv_io_ary.concat ctl.unix_rights
- }
- assert_equal(send_io_ary.length, recv_io_ary.length)
- send_io_ary.length.times {|i|
- assert_not_equal(send_io_ary[i].fileno, recv_io_ary[i].fileno)
- assert(File.identical?(send_io_ary[i], recv_io_ary[i]))
- assert(recv_io_ary[i].close_on_exec?)
- }
- ensure
- recv_io_ary.each {|io2| io2.close if !io2.closed? }
- end
- }
- }
- ensure
- io_ary.each {|io| io.close if !io.closed? }
- end
- def test_fd_passing_n2
- io_ary = []
- return if !defined?(Socket::SCM_RIGHTS)
- return if !defined?(Socket::AncillaryData)
- io_ary.concat IO.pipe
- io_ary.concat IO.pipe
- io_ary.concat IO.pipe
- send_io_ary = []
- io_ary.each {|io|
- send_io_ary << io
- UNIXSocket.pair {|s1, s2|
- begin
- ancdata = Socket::AncillaryData.unix_rights(*send_io_ary)
- ret = s1.sendmsg("\0", 0, nil, ancdata)
- rescue NotImplementedError
- return
- end
- assert_equal(1, ret)
- ret = s2.recvmsg(:scm_rights=>true)
- _, _, _, *ctls = ret
- recv_io_ary = []
- begin
- ctls.each {|ctl|
- next if ctl.level != Socket::SOL_SOCKET || ctl.type != Socket::SCM_RIGHTS
- recv_io_ary.concat ctl.unix_rights
- }
- assert_equal(send_io_ary.length, recv_io_ary.length)
- send_io_ary.length.times {|i|
- assert_not_equal(send_io_ary[i].fileno, recv_io_ary[i].fileno)
- assert(File.identical?(send_io_ary[i], recv_io_ary[i]))
- assert(recv_io_ary[i].close_on_exec?)
- }
- ensure
- recv_io_ary.each {|io2| io2.close if !io2.closed? }
- end
- }
- }
- ensure
- io_ary.each {|io| io.close if !io.closed? }
- end
- def test_fd_passing_race_condition
- r1, w = IO.pipe
- s1, s2 = UNIXSocket.pair
- s1.nonblock = s2.nonblock = true
- lock = Thread::Mutex.new
- nr = 0
- x = 2
- y = 1000
- begin
- s1.send_io(nil)
- rescue NotImplementedError
- assert_raise(NotImplementedError) { s2.recv_io }
- rescue TypeError
- thrs = x.times.map do
- Thread.new do
- y.times do
- s2.recv_io.close
- lock.synchronize { nr += 1 }
- end
- true
- end
- end
- (x * y).times { s1.send_io r1 }
- assert_equal([true]*x, thrs.map { |t| t.value })
- assert_equal x * y, nr
- ensure
- s1.close
- s2.close
- w.close
- r1.close
- end
- end
- def test_sendmsg
- return if !defined?(Socket::SCM_RIGHTS)
- IO.pipe {|r1, w|
- UNIXSocket.pair {|s1, s2|
- begin
- ret = s1.sendmsg("\0", 0, nil, [Socket::SOL_SOCKET, Socket::SCM_RIGHTS, [r1.fileno].pack("i!")])
- rescue NotImplementedError
- return
- end
- assert_equal(1, ret)
- r2 = s2.recv_io
- begin
- assert(File.identical?(r1, r2))
- assert(r2.close_on_exec?)
- ensure
- r2.close
- end
- }
- }
- end
- def test_sendmsg_ancillarydata_int
- return if !defined?(Socket::SCM_RIGHTS)
- return if !defined?(Socket::AncillaryData)
- IO.pipe {|r1, w|
- UNIXSocket.pair {|s1, s2|
- begin
- ad = Socket::AncillaryData.int(:UNIX, :SOCKET, :RIGHTS, r1.fileno)
- ret = s1.sendmsg("\0", 0, nil, ad)
- rescue NotImplementedError
- return
- end
- assert_equal(1, ret)
- r2 = s2.recv_io
- begin
- assert(File.identical?(r1, r2))
- ensure
- r2.close
- end
- }
- }
- end
- def test_sendmsg_ancillarydata_unix_rights
- return if !defined?(Socket::SCM_RIGHTS)
- return if !defined?(Socket::AncillaryData)
- IO.pipe {|r1, w|
- UNIXSocket.pair {|s1, s2|
- begin
- ad = Socket::AncillaryData.unix_rights(r1)
- ret = s1.sendmsg("\0", 0, nil, ad)
- rescue NotImplementedError
- return
- end
- assert_equal(1, ret)
- r2 = s2.recv_io
- begin
- assert(File.identical?(r1, r2))
- ensure
- r2.close
- end
- }
- }
- end
- def test_recvmsg
- return if !defined?(Socket::SCM_RIGHTS)
- return if !defined?(Socket::AncillaryData)
- IO.pipe {|r1, w|
- UNIXSocket.pair {|s1, s2|
- s1.send_io(r1)
- ret = s2.recvmsg(:scm_rights=>true)
- data, srcaddr, flags, *ctls = ret
- assert_equal("\0", data)
- if flags == nil
- # struct msghdr is 4.3BSD style (msg_accrights field).
- assert_instance_of(Array, ctls)
- assert_equal(0, ctls.length)
- else
- # struct msghdr is POSIX/4.4BSD style (msg_control field).
- assert_equal(0, flags & (Socket::MSG_TRUNC|Socket::MSG_CTRUNC))
- assert_instance_of(Addrinfo, srcaddr)
- assert_instance_of(Array, ctls)
- assert_equal(1, ctls.length)
- ctl = ctls[0]
- assert_instance_of(Socket::AncillaryData, ctl)
- assert_equal(Socket::SOL_SOCKET, ctl.level)
- assert_equal(Socket::SCM_RIGHTS, ctl.type)
- assert_instance_of(String, ctl.data)
- ios = ctl.unix_rights
- assert_equal(1, ios.length)
- r2 = ios[0]
- begin
- assert(File.identical?(r1, r2))
- assert(r2.close_on_exec?)
- ensure
- r2.close
- end
- end
- }
- }
- end
- def bound_unix_socket(klass)
- tmpfile = Tempfile.new("s")
- path = tmpfile.path
- tmpfile.close(true)
- io = klass.new(path)
- yield io, path
- ensure
- io.close
- File.unlink path if path && File.socket?(path)
- end
- def test_open_nul_byte
- tmpfile = Tempfile.new("s")
- path = tmpfile.path
- tmpfile.close(true)
- assert_raise(ArgumentError) {UNIXServer.open(path+"\0")}
- assert_raise(ArgumentError) {UNIXSocket.open(path+"\0")}
- ensure
- File.unlink path if path && File.socket?(path)
- end
- def test_addr
- bound_unix_socket(UNIXServer) {|serv, path|
- UNIXSocket.open(path) {|c|
- s = serv.accept
- begin
- assert_equal(["AF_UNIX", path], c.peeraddr)
- assert_equal(["AF_UNIX", ""], c.addr)
- assert_equal(["AF_UNIX", ""], s.peeraddr)
- assert_equal(["AF_UNIX", path], s.addr)
- assert_equal(path, s.path)
- assert_equal("", c.path)
- ensure
- s.close
- end
- }
- }
- end
- def test_cloexec
- bound_unix_socket(UNIXServer) {|serv, path|
- UNIXSocket.open(path) {|c|
- s = serv.accept
- begin
- assert(serv.close_on_exec?)
- assert(c.close_on_exec?)
- assert(s.close_on_exec?)
- ensure
- s.close
- end
- }
- }
- end
- def test_noname_path
- s1, s2 = UNIXSocket.pair
- assert_equal("", s1.path)
- assert_equal("", s2.path)
- ensure
- s1.close
- s2.close
- end
- def test_noname_addr
- s1, s2 = UNIXSocket.pair
- assert_equal(["AF_UNIX", ""], s1.addr)
- assert_equal(["AF_UNIX", ""], s2.addr)
- ensure
- s1.close
- s2.close
- end
- def test_noname_peeraddr
- s1, s2 = UNIXSocket.pair
- assert_equal(["AF_UNIX", ""], s1.peeraddr)
- assert_equal(["AF_UNIX", ""], s2.peeraddr)
- ensure
- s1.close
- s2.close
- end
- def test_noname_unpack_sockaddr_un
- s1, s2 = UNIXSocket.pair
- n = nil
- assert_equal("", Socket.unpack_sockaddr_un(n)) if (n = s1.getsockname) != ""
- assert_equal("", Socket.unpack_sockaddr_un(n)) if (n = s1.getsockname) != ""
- assert_equal("", Socket.unpack_sockaddr_un(n)) if (n = s2.getsockname) != ""
- assert_equal("", Socket.unpack_sockaddr_un(n)) if (n = s1.getpeername) != ""
- assert_equal("", Socket.unpack_sockaddr_un(n)) if (n = s2.getpeername) != ""
- ensure
- s1.close
- s2.close
- end
- def test_noname_recvfrom
- s1, s2 = UNIXSocket.pair
- s2.write("a")
- assert_equal(["a", ["AF_UNIX", ""]], s1.recvfrom(10))
- ensure
- s1.close
- s2.close
- end
- def test_noname_recv_nonblock
- s1, s2 = UNIXSocket.pair
- s2.write("a")
- IO.select [s1]
- assert_equal("a", s1.recv_nonblock(10))
- ensure
- s1.close
- s2.close
- end
- def test_too_long_path
- assert_raise(ArgumentError) { Socket.sockaddr_un("a" * 3000) }
- assert_raise(ArgumentError) { UNIXServer.new("a" * 3000) }
- end
- def test_abstract_namespace
- return if /linux/ !~ RUBY_PLATFORM
- addr = Socket.pack_sockaddr_un("\0foo")
- assert_match(/\0foo\z/, addr)
- assert_equal("\0foo", Socket.unpack_sockaddr_un(addr))
- end
- def test_dgram_pair
- s1, s2 = UNIXSocket.pair(Socket::SOCK_DGRAM)
- begin
- s1.recv_nonblock(10)
- fail
- rescue => e
- assert(IO::EAGAINWaitReadable === e)
- assert(IO::WaitReadable === e)
- end
- s2.send("", 0)
- s2.send("haha", 0)
- s2.send("", 0)
- s2.send("", 0)
- assert_equal("", s1.recv(10))
- assert_equal("haha", s1.recv(10))
- assert_equal("", s1.recv(10))
- assert_equal("", s1.recv(10))
- assert_raise(IO::EAGAINWaitReadable) { s1.recv_nonblock(10) }
- buf = "".dup
- s2.send("BBBBBB", 0)
- IO.select([s1])
- rv = s1.recv(100, 0, buf)
- assert_equal buf.object_id, rv.object_id
- assert_equal "BBBBBB", rv
- ensure
- s1.close if s1
- s2.close if s2
- end
- def test_dgram_pair_sendrecvmsg_errno_set
- s1, s2 = to_close = UNIXSocket.pair(Socket::SOCK_DGRAM)
- pipe = IO.pipe
- to_close.concat(pipe)
- set_errno = lambda do
- begin
- pipe[0].read_nonblock(1)
- fail
- rescue => e
- assert(IO::EAGAINWaitReadable === e)
- end
- end
- Timeout.timeout(10) do
- set_errno.call
- assert_equal(2, s1.sendmsg("HI"))
- set_errno.call
- assert_equal("HI", s2.recvmsg[0])
- end
- ensure
- to_close.each(&:close) if to_close
- end
- def test_epipe # [ruby-dev:34619]
- UNIXSocket.pair {|s1, s2|
- s1.shutdown(Socket::SHUT_WR)
- assert_raise(Errno::EPIPE) { s1.write "a" }
- assert_equal(nil, s2.read(1))
- s2.write "a"
- assert_equal("a", s1.read(1))
- }
- end
- def test_socket_pair_with_block
- pair = nil
- ret = Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM, 0) {|s1, s2|
- pair = [s1, s2]
- :return_value
- }
- assert_equal(:return_value, ret)
- assert_kind_of(Socket, pair[0])
- assert_kind_of(Socket, pair[1])
- end
- def test_unix_socket_pair_with_block
- pair = nil
- UNIXSocket.pair {|s1, s2|
- pair = [s1, s2]
- }
- assert_kind_of(UNIXSocket, pair[0])
- assert_kind_of(UNIXSocket, pair[1])
- end
- def test_unix_socket_pair_close_on_exec
- UNIXSocket.pair {|s1, s2|
- assert(s1.close_on_exec?)
- assert(s2.close_on_exec?)
- }
- end
- def test_initialize
- Dir.mktmpdir {|d|
- Socket.open(Socket::AF_UNIX, Socket::SOCK_STREAM, 0) {|s|
- s.bind(Socket.pack_sockaddr_un("#{d}/s1"))
- addr = s.getsockname
- assert_nothing_raised { Socket.unpack_sockaddr_un(addr) }
- assert_raise(ArgumentError) { Socket.unpack_sockaddr_in(addr) }
- }
- Socket.open("AF_UNIX", "SOCK_STREAM", 0) {|s|
- s.bind(Socket.pack_sockaddr_un("#{d}/s2"))
- addr = s.getsockname
- assert_nothing_raised { Socket.unpack_sockaddr_un(addr) }
- assert_raise(ArgumentError) { Socket.unpack_sockaddr_in(addr) }
- }
- }
- end
- def test_unix_server_socket
- Dir.mktmpdir {|d|
- path = "#{d}/sock"
- s0 = nil
- Socket.unix_server_socket(path) {|s|
- assert_equal(path, s.local_address.unix_path)
- assert(File.socket?(path))
- s0 = s
- }
- assert(s0.closed?)
- assert_raise(Errno::ENOENT) { File.stat path }
- }
- end
- def test_getcred_ucred
- return if /linux/ !~ RUBY_PLATFORM
- Dir.mktmpdir {|d|
- sockpath = "#{d}/sock"
- Socket.unix_server_socket(sockpath) {|serv|
- Socket.unix(sockpath) {|c|
- s, = serv.accept
- begin
- cred = s.getsockopt(:SOCKET, :PEERCRED)
- inspect = cred.inspect
- assert_match(/ pid=#{$$} /, inspect)
- assert_match(/ euid=#{Process.euid} /, inspect)
- assert_match(/ egid=#{Process.egid} /, inspect)
- assert_match(/ \(ucred\)/, inspect)
- ensure
- s.close
- end
- }
- }
- }
- end
- def test_getcred_xucred
- return if /freebsd|darwin/ !~ RUBY_PLATFORM
- Dir.mktmpdir do |d|
- sockpath = "#{d}/sock"
- serv = Socket.unix_server_socket(sockpath)
- u = Socket.unix(sockpath)
- s, = serv.accept
- cred = s.getsockopt(0, Socket::LOCAL_PEERCRED)
- inspect = cred.inspect
- assert_match(/ euid=#{Process.euid} /, inspect)
- assert_match(/ \(xucred\)/, inspect)
- ensure
- s&.close
- u&.close
- serv&.close
- end
- end
- def test_sendcred_ucred
- return if /linux/ !~ RUBY_PLATFORM
- Dir.mktmpdir {|d|
- sockpath = "#{d}/sock"
- Socket.unix_server_socket(sockpath) {|serv|
- Socket.unix(sockpath) {|c|
- s, = serv.accept
- begin
- s.setsockopt(:SOCKET, :PASSCRED, 1)
- c.print "a"
- msg, _, _, cred = s.recvmsg
- inspect = cred.inspect
- assert_equal("a", msg)
- assert_match(/ pid=#{$$} /, inspect)
- assert_match(/ uid=#{Process.uid} /, inspect)
- assert_match(/ gid=#{Process.gid} /, inspect)
- assert_match(/ \(ucred\)/, inspect)
- ensure
- s.close
- end
- }
- }
- }
- end
- def test_sendcred_sockcred
- return if /netbsd|freebsd/ !~ RUBY_PLATFORM
- Dir.mktmpdir {|d|
- sockpath = "#{d}/sock"
- serv = Socket.unix_server_socket(sockpath)
- c = Socket.unix(sockpath)
- s, = serv.accept
- s.setsockopt(0, Socket::LOCAL_CREDS, 1)
- c.print "a"
- msg, _, _, cred = s.recvmsg
- assert_equal("a", msg)
- inspect = cred.inspect
- assert_match(/ uid=#{Process.uid} /, inspect)
- assert_match(/ euid=#{Process.euid} /, inspect)
- assert_match(/ gid=#{Process.gid} /, inspect)
- assert_match(/ egid=#{Process.egid} /, inspect)
- assert_match(/ \(sockcred\)/, inspect)
- }
- end
- def test_sendcred_cmsgcred
- return if /freebsd/ !~ RUBY_PLATFORM
- Dir.mktmpdir {|d|
- sockpath = "#{d}/sock"
- serv = Socket.unix_server_socket(sockpath)
- c = Socket.unix(sockpath)
- s, = serv.accept
- c.sendmsg("a", 0, nil, [:SOCKET, Socket::SCM_CREDS, ""])
- msg, _, _, cred = s.recvmsg
- assert_equal("a", msg)
- inspect = cred.inspect
- assert_match(/ pid=#{$$} /, inspect)
- assert_match(/ uid=#{Process.uid} /, inspect)
- assert_match(/ euid=#{Process.euid} /, inspect)
- assert_match(/ gid=#{Process.gid} /, inspect)
- assert_match(/ \(cmsgcred\)/, inspect)
- }
- end
- def test_getpeereid
- Dir.mktmpdir {|d|
- path = "#{d}/sock"
- Socket.unix_server_socket(path) {|serv|
- Socket.unix(path) {|c|
- s, = serv.accept
- begin
- assert_equal([Process.euid, Process.egid], c.getpeereid)
- assert_equal([Process.euid, Process.egid], s.getpeereid)
- rescue NotImplementedError
- ensure
- s.close
- end
- }
- }
- }
- end
- def test_abstract_unix_server
- return if /linux/ !~ RUBY_PLATFORM
- name = "\0ruby-test_unix"
- s0 = nil
- UNIXServer.open(name) {|s|
- assert_equal(name, s.local_address.unix_path)
- s0 = s
- UNIXSocket.open(name) {|c|
- sock = s.accept
- begin
- assert_equal(name, c.remote_address.unix_path)
- ensure
- sock.close
- end
- }
- }
- assert(s0.closed?)
- end
- def test_abstract_unix_socket_econnrefused
- return if /linux/ !~ RUBY_PLATFORM
- name = "\0ruby-test_unix"
- assert_raise(Errno::ECONNREFUSED) do
- UNIXSocket.open(name) {}
- end
- end
- def test_abstract_unix_server_socket
- return if /linux/ !~ RUBY_PLATFORM
- name = "\0ruby-test_unix"
- s0 = nil
- Socket.unix_server_socket(name) {|s|
- assert_equal(name, s.local_address.unix_path)
- s0 = s
- Socket.unix(name) {|c|
- sock, = s.accept
- begin
- assert_equal(name, c.remote_address.unix_path)
- ensure
- sock.close
- end
- }
- }
- assert(s0.closed?)
- end
- def test_autobind
- return if /linux/ !~ RUBY_PLATFORM
- s0 = nil
- Socket.unix_server_socket("") {|s|
- name = s.local_address.unix_path
- assert_match(/\A\0[0-9a-f]{5}\z/, name)
- s0 = s
- Socket.unix(name) {|c|
- sock, = s.accept
- begin
- assert_equal(name, c.remote_address.unix_path)
- ensure
- sock.close
- end
- }
- }
- assert(s0.closed?)
- end
- def test_accept_nonblock
- bound_unix_socket(UNIXServer) {|serv, path|
- assert_raise(IO::WaitReadable) { serv.accept_nonblock }
- assert_raise(IO::WaitReadable) { serv.accept_nonblock(exception: true) }
- assert_equal :wait_readable, serv.accept_nonblock(exception: false)
- }
- end
- end if defined?(UNIXSocket) && /cygwin/ !~ RUBY_PLATFORM