PageRenderTime 43ms CodeModel.GetById 11ms RepoModel.GetById 1ms app.codeStats 0ms

/test/socket/test_socket.rb

https://github.com/EarthJem/ruby
Ruby | 578 lines | 526 code | 38 blank | 14 comment | 43 complexity | 59d0e13a99c4fb505b74fbd915728eef MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause
  1. begin
  2. require "socket"
  3. require "tmpdir"
  4. require "fcntl"
  5. require "test/unit"
  6. rescue LoadError
  7. end
  8. class TestSocket < Test::Unit::TestCase
  9. def test_socket_new
  10. begin
  11. s = Socket.new(:INET, :STREAM)
  12. assert_kind_of(Socket, s)
  13. ensure
  14. s.close
  15. end
  16. end
  17. def test_socket_new_cloexec
  18. return unless defined? Fcntl::FD_CLOEXEC
  19. begin
  20. s = Socket.new(:INET, :STREAM)
  21. assert(s.close_on_exec?)
  22. ensure
  23. s.close
  24. end
  25. end
  26. def test_unpack_sockaddr
  27. sockaddr_in = Socket.sockaddr_in(80, "")
  28. assert_raise(ArgumentError) { Socket.unpack_sockaddr_un(sockaddr_in) }
  29. sockaddr_un = Socket.sockaddr_un("/testdir/s")
  30. assert_raise(ArgumentError) { Socket.unpack_sockaddr_in(sockaddr_un) }
  31. assert_raise(ArgumentError) { Socket.unpack_sockaddr_in("") }
  32. assert_raise(ArgumentError) { Socket.unpack_sockaddr_un("") }
  33. end if Socket.respond_to?(:sockaddr_un)
  34. def test_sysaccept
  35. serv = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
  36. serv.bind(Socket.sockaddr_in(0, "127.0.0.1"))
  37. serv.listen 5
  38. c = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
  39. c.connect(serv.getsockname)
  40. fd, peeraddr = serv.sysaccept
  41. assert_equal(c.getsockname, peeraddr.to_sockaddr)
  42. ensure
  43. serv.close if serv
  44. c.close if c
  45. IO.for_fd(fd).close if fd
  46. end
  47. def test_initialize
  48. Socket.open(Socket::AF_INET, Socket::SOCK_STREAM, 0) {|s|
  49. s.bind(Socket.sockaddr_in(0, "127.0.0.1"))
  50. addr = s.getsockname
  51. assert_nothing_raised { Socket.unpack_sockaddr_in(addr) }
  52. assert_raise(ArgumentError, NoMethodError) { Socket.unpack_sockaddr_un(addr) }
  53. }
  54. Socket.open("AF_INET", "SOCK_STREAM", 0) {|s|
  55. s.bind(Socket.sockaddr_in(0, "127.0.0.1"))
  56. addr = s.getsockname
  57. assert_nothing_raised { Socket.unpack_sockaddr_in(addr) }
  58. assert_raise(ArgumentError, NoMethodError) { Socket.unpack_sockaddr_un(addr) }
  59. }
  60. Socket.open(:AF_INET, :SOCK_STREAM, 0) {|s|
  61. s.bind(Socket.sockaddr_in(0, "127.0.0.1"))
  62. addr = s.getsockname
  63. assert_nothing_raised { Socket.unpack_sockaddr_in(addr) }
  64. assert_raise(ArgumentError, NoMethodError) { Socket.unpack_sockaddr_un(addr) }
  65. }
  66. end
  67. def test_bind
  68. Socket.open(Socket::AF_INET, Socket::SOCK_STREAM, 0) {|bound|
  69. bound.bind(Socket.sockaddr_in(0, "127.0.0.1"))
  70. addr = bound.getsockname
  71. port, = Socket.unpack_sockaddr_in(addr)
  72. Socket.open(Socket::AF_INET, Socket::SOCK_STREAM, 0) {|s|
  73. e = assert_raises(Errno::EADDRINUSE) do
  74. s.bind(Socket.sockaddr_in(port, "127.0.0.1"))
  75. end
  76. assert_match "bind(2) for 127.0.0.1:#{port}", e.message
  77. }
  78. }
  79. end
  80. def test_getaddrinfo
  81. # This should not send a DNS query because AF_UNIX.
  82. assert_raise(SocketError) { Socket.getaddrinfo("www.kame.net", 80, "AF_UNIX") }
  83. end
  84. def test_getaddrinfo_raises_no_errors_on_port_argument_of_0 # [ruby-core:29427]
  85. assert_nothing_raised('[ruby-core:29427]'){ Socket.getaddrinfo('localhost', 0, Socket::AF_INET, Socket::SOCK_STREAM, nil, Socket::AI_CANONNAME) }
  86. assert_nothing_raised('[ruby-core:29427]'){ Socket.getaddrinfo('localhost', '0', Socket::AF_INET, Socket::SOCK_STREAM, nil, Socket::AI_CANONNAME) }
  87. assert_nothing_raised('[ruby-core:29427]'){ Socket.getaddrinfo('localhost', '00', Socket::AF_INET, Socket::SOCK_STREAM, nil, Socket::AI_CANONNAME) }
  88. assert_raise(SocketError, '[ruby-core:29427]'){ Socket.getaddrinfo(nil, nil, Socket::AF_INET, Socket::SOCK_STREAM, nil, Socket::AI_CANONNAME) }
  89. assert_nothing_raised('[ruby-core:29427]'){ TCPServer.open('localhost', 0) {} }
  90. end
  91. def test_getnameinfo
  92. assert_raise(SocketError) { Socket.getnameinfo(["AF_UNIX", 80, "0.0.0.0"]) }
  93. end
  94. def test_ip_address_list
  95. begin
  96. list = Socket.ip_address_list
  97. rescue NotImplementedError
  98. return
  99. end
  100. list.each {|ai|
  101. assert_instance_of(Addrinfo, ai)
  102. assert(ai.ip?)
  103. }
  104. end
  105. def test_tcp
  106. TCPServer.open(0) {|serv|
  107. addr = serv.connect_address
  108. addr.connect {|s1|
  109. s2 = serv.accept
  110. begin
  111. assert_equal(s2.remote_address.ip_unpack, s1.local_address.ip_unpack)
  112. ensure
  113. s2.close
  114. end
  115. }
  116. }
  117. end
  118. def test_tcp_cloexec
  119. return unless defined? Fcntl::FD_CLOEXEC
  120. TCPServer.open(0) {|serv|
  121. addr = serv.connect_address
  122. addr.connect {|s1|
  123. s2 = serv.accept
  124. begin
  125. assert(s2.close_on_exec?)
  126. ensure
  127. s2.close
  128. end
  129. }
  130. }
  131. end
  132. def random_port
  133. # IANA suggests dynamic port for 49152 to 65535
  134. # http://www.iana.org/assignments/port-numbers
  135. 49152 + rand(65535-49152+1)
  136. end
  137. def errors_addrinuse
  138. [Errno::EADDRINUSE]
  139. end
  140. def test_tcp_server_sockets
  141. port = random_port
  142. begin
  143. sockets = Socket.tcp_server_sockets(port)
  144. rescue *errors_addrinuse
  145. return # not test failure
  146. end
  147. begin
  148. sockets.each {|s|
  149. assert_equal(port, s.local_address.ip_port)
  150. }
  151. ensure
  152. sockets.each {|s|
  153. s.close
  154. }
  155. end
  156. end
  157. def test_tcp_server_sockets_port0
  158. sockets = Socket.tcp_server_sockets(0)
  159. ports = sockets.map {|s| s.local_address.ip_port }
  160. the_port = ports.first
  161. ports.each {|port|
  162. assert_equal(the_port, port)
  163. }
  164. ensure
  165. if sockets
  166. sockets.each {|s|
  167. s.close
  168. }
  169. end
  170. end
  171. if defined? UNIXSocket
  172. def test_unix
  173. Dir.mktmpdir {|tmpdir|
  174. path = "#{tmpdir}/sock"
  175. UNIXServer.open(path) {|serv|
  176. Socket.unix(path) {|s1|
  177. s2 = serv.accept
  178. begin
  179. s2raddr = s2.remote_address
  180. s1laddr = s1.local_address
  181. assert(s2raddr.to_sockaddr.empty? ||
  182. s1laddr.to_sockaddr.empty? ||
  183. s2raddr.unix_path == s1laddr.unix_path)
  184. assert(s2.close_on_exec?)
  185. ensure
  186. s2.close
  187. end
  188. }
  189. }
  190. }
  191. end
  192. def test_unix_server_socket
  193. Dir.mktmpdir {|tmpdir|
  194. path = "#{tmpdir}/sock"
  195. 2.times {
  196. serv = Socket.unix_server_socket(path)
  197. begin
  198. assert_kind_of(Socket, serv)
  199. assert(File.socket?(path))
  200. assert_equal(path, serv.local_address.unix_path)
  201. ensure
  202. serv.close
  203. end
  204. }
  205. }
  206. end
  207. def test_accept_loop_with_unix
  208. Dir.mktmpdir {|tmpdir|
  209. tcp_servers = []
  210. clients = []
  211. accepted = []
  212. begin
  213. tcp_servers = Socket.tcp_server_sockets(0)
  214. unix_server = Socket.unix_server_socket("#{tmpdir}/sock")
  215. tcp_servers.each {|s|
  216. addr = s.connect_address
  217. assert_nothing_raised("connect to #{addr.inspect}") {
  218. clients << addr.connect
  219. }
  220. }
  221. addr = unix_server.connect_address
  222. assert_nothing_raised("connect to #{addr.inspect}") {
  223. clients << addr.connect
  224. }
  225. Socket.accept_loop(tcp_servers, unix_server) {|s|
  226. accepted << s
  227. break if clients.length == accepted.length
  228. }
  229. assert_equal(clients.length, accepted.length)
  230. ensure
  231. tcp_servers.each {|s| s.close if !s.closed? }
  232. unix_server.close if unix_server && !unix_server.closed?
  233. clients.each {|s| s.close if !s.closed? }
  234. accepted.each {|s| s.close if !s.closed? }
  235. end
  236. }
  237. end
  238. end
  239. def test_accept_loop
  240. servers = []
  241. begin
  242. servers = Socket.tcp_server_sockets(0)
  243. port = servers[0].local_address.ip_port
  244. Socket.tcp("localhost", port) {|s1|
  245. Socket.accept_loop(servers) {|s2, client_ai|
  246. begin
  247. assert_equal(s1.local_address.ip_unpack, client_ai.ip_unpack)
  248. ensure
  249. s2.close
  250. end
  251. break
  252. }
  253. }
  254. ensure
  255. servers.each {|s| s.close if !s.closed? }
  256. end
  257. end
  258. def test_accept_loop_multi_port
  259. servers = []
  260. begin
  261. servers = Socket.tcp_server_sockets(0)
  262. port = servers[0].local_address.ip_port
  263. servers2 = Socket.tcp_server_sockets(0)
  264. servers.concat servers2
  265. port2 = servers2[0].local_address.ip_port
  266. Socket.tcp("localhost", port) {|s1|
  267. Socket.accept_loop(servers) {|s2, client_ai|
  268. begin
  269. assert_equal(s1.local_address.ip_unpack, client_ai.ip_unpack)
  270. ensure
  271. s2.close
  272. end
  273. break
  274. }
  275. }
  276. Socket.tcp("localhost", port2) {|s1|
  277. Socket.accept_loop(servers) {|s2, client_ai|
  278. begin
  279. assert_equal(s1.local_address.ip_unpack, client_ai.ip_unpack)
  280. ensure
  281. s2.close
  282. end
  283. break
  284. }
  285. }
  286. ensure
  287. servers.each {|s| s.close if !s.closed? }
  288. end
  289. end
  290. def test_udp_server
  291. begin
  292. ip_addrs = Socket.ip_address_list
  293. rescue NotImplementedError
  294. skip "Socket.ip_address_list not implemented"
  295. end
  296. ifconfig = nil
  297. Socket.udp_server_sockets(0) {|sockets|
  298. famlies = {}
  299. sockets.each {|s| famlies[s.local_address.afamily] = s }
  300. nd6 = {}
  301. ip_addrs.reject! {|ai|
  302. s = famlies[ai.afamily]
  303. next true unless s
  304. case RUBY_PLATFORM
  305. when /linux/
  306. if ai.ip_address.include?('%') and
  307. (`uname -r`[/[0-9.]+/].split('.').map(&:to_i) <=> [2,6,18]) <= 0
  308. # Cent OS 5.6 (2.6.18-238.19.1.el5xen) doesn't correctly work
  309. # sendmsg with pktinfo for link-local ipv6 addresses
  310. next true
  311. end
  312. when /freebsd/
  313. if ifr_name = ai.ip_address[/%(.*)/, 1]
  314. # FreeBSD 9.0 with default setting (ipv6_activate_all_interfaces
  315. # is not YES) sets IFDISABLED to interfaces which don't have
  316. # global IPv6 address.
  317. # Link-local IPv6 addresses on those interfaces don't work.
  318. ulSIOCGIFINFO_IN6 = 3225971052
  319. ulND6_IFF_IFDISABLED = 8
  320. in6_ondireq = ifr_name
  321. s.ioctl(ulSIOCGIFINFO_IN6, in6_ondireq)
  322. flag = in6_ondireq.unpack('A16L6').last
  323. next true if flag & ulND6_IFF_IFDISABLED != 0
  324. nd6[ai] = flag
  325. end
  326. when /darwin/
  327. if !ai.ipv6?
  328. elsif ai.ipv6_unique_local? && /darwin1[01]\./ =~ RUBY_PLATFORM
  329. next true # iCloud addresses do not work, see Bug #6692
  330. elsif ifr_name = ai.ip_address[/%(.*)/, 1]
  331. # Mac OS X may sets IFDISABLED as FreeBSD does
  332. ulSIOCGIFFLAGS = 3223349521
  333. ulSIOCGIFINFO_IN6 = 3224398156
  334. ulSIOCGIFAFLAG_IN6 = 3240126793
  335. ulIFF_POINTOPOINT = 0x10
  336. ulND6_IFF_IFDISABLED = 8
  337. in6_ondireq = ifr_name
  338. s.ioctl(ulSIOCGIFINFO_IN6, in6_ondireq)
  339. flag = in6_ondireq.unpack('A16L6').last
  340. next true if (flag & ulND6_IFF_IFDISABLED) != 0
  341. nd6[ai] = flag
  342. in6_ifreq = [ifr_name,ai.to_sockaddr].pack('a16A*')
  343. s.ioctl(ulSIOCGIFFLAGS, in6_ifreq)
  344. next true if in6_ifreq.unpack('A16L1').last & ulIFF_POINTOPOINT != 0
  345. else
  346. ifconfig ||= `/sbin/ifconfig`
  347. next true if ifconfig.scan(/^(\w+):(.*(?:\n\t.*)*)/).find do|ifname, value|
  348. value.include?(ai.ip_address) && value.include?('POINTOPOINT')
  349. end
  350. end
  351. end
  352. false
  353. }
  354. skipped = false
  355. begin
  356. port = sockets.first.local_address.ip_port
  357. ping_p = false
  358. th = Thread.new {
  359. Socket.udp_server_loop_on(sockets) {|msg, msg_src|
  360. break if msg == "exit"
  361. rmsg = Marshal.dump([msg, msg_src.remote_address, msg_src.local_address])
  362. ping_p = true
  363. msg_src.reply rmsg
  364. }
  365. }
  366. ip_addrs.each {|ai|
  367. Addrinfo.udp(ai.ip_address, port).connect {|s|
  368. ping_p = false
  369. msg1 = "<<<#{ai.inspect}>>>"
  370. s.sendmsg msg1
  371. unless IO.select([s], nil, nil, 10)
  372. nd6options = nd6.key?(ai) ? "nd6=%x " % nd6[ai] : ''
  373. raise "no response from #{ai.inspect} #{nd6options}ping=#{ping_p}"
  374. end
  375. msg2, addr = s.recvmsg
  376. msg2, remote_address, local_address = Marshal.load(msg2)
  377. assert_equal(msg1, msg2)
  378. assert_equal(ai.ip_address, addr.ip_address)
  379. }
  380. }
  381. rescue NotImplementedError, Errno::ENOSYS
  382. skipped = true
  383. skip "need sendmsg and recvmsg: #{$!}"
  384. ensure
  385. if th
  386. if skipped
  387. Thread.kill th unless th.join(10)
  388. else
  389. Addrinfo.udp("127.0.0.1", port).connect {|s| s.sendmsg "exit" }
  390. unless th.join(10)
  391. Thread.kill th
  392. th.join(10)
  393. raise "thread killed"
  394. end
  395. end
  396. end
  397. end
  398. }
  399. end
  400. def test_linger
  401. opt = Socket::Option.linger(true, 0)
  402. assert_equal([true, 0], opt.linger)
  403. Addrinfo.tcp("127.0.0.1", 0).listen {|serv|
  404. serv.local_address.connect {|s1|
  405. s2, _ = serv.accept
  406. begin
  407. s1.setsockopt(opt)
  408. s1.close
  409. assert_raise(Errno::ECONNRESET) { s2.read }
  410. ensure
  411. s2.close
  412. end
  413. }
  414. }
  415. end
  416. def test_timestamp
  417. return if /linux|freebsd|netbsd|openbsd|solaris|darwin/ !~ RUBY_PLATFORM
  418. return if !defined?(Socket::AncillaryData)
  419. t1 = Time.now.strftime("%Y-%m-%d")
  420. stamp = nil
  421. Addrinfo.udp("127.0.0.1", 0).bind {|s1|
  422. Addrinfo.udp("127.0.0.1", 0).bind {|s2|
  423. s1.setsockopt(:SOCKET, :TIMESTAMP, true)
  424. s2.send "a", 0, s1.local_address
  425. msg, addr, rflags, stamp = s1.recvmsg
  426. assert_equal("a", msg)
  427. assert(stamp.cmsg_is?(:SOCKET, :TIMESTAMP))
  428. }
  429. }
  430. t2 = Time.now.strftime("%Y-%m-%d")
  431. pat = Regexp.union([t1, t2].uniq)
  432. assert_match(pat, stamp.inspect)
  433. t = stamp.timestamp
  434. assert_match(pat, t.strftime("%Y-%m-%d"))
  435. pat = /\.#{"%06d" % t.usec}/
  436. assert_match(pat, stamp.inspect)
  437. end
  438. def test_timestampns
  439. return if /linux/ !~ RUBY_PLATFORM || !defined?(Socket::SO_TIMESTAMPNS)
  440. t1 = Time.now.strftime("%Y-%m-%d")
  441. stamp = nil
  442. Addrinfo.udp("127.0.0.1", 0).bind {|s1|
  443. Addrinfo.udp("127.0.0.1", 0).bind {|s2|
  444. begin
  445. s1.setsockopt(:SOCKET, :TIMESTAMPNS, true)
  446. rescue Errno::ENOPROTOOPT
  447. # SO_TIMESTAMPNS is available since Linux 2.6.22
  448. return
  449. end
  450. s2.send "a", 0, s1.local_address
  451. msg, addr, rflags, stamp = s1.recvmsg
  452. assert_equal("a", msg)
  453. assert(stamp.cmsg_is?(:SOCKET, :TIMESTAMPNS))
  454. }
  455. }
  456. t2 = Time.now.strftime("%Y-%m-%d")
  457. pat = Regexp.union([t1, t2].uniq)
  458. assert_match(pat, stamp.inspect)
  459. t = stamp.timestamp
  460. assert_match(pat, t.strftime("%Y-%m-%d"))
  461. pat = /\.#{"%09d" % t.nsec}/
  462. assert_match(pat, stamp.inspect)
  463. end
  464. def test_bintime
  465. return if /freebsd/ !~ RUBY_PLATFORM
  466. t1 = Time.now.strftime("%Y-%m-%d")
  467. stamp = nil
  468. Addrinfo.udp("127.0.0.1", 0).bind {|s1|
  469. Addrinfo.udp("127.0.0.1", 0).bind {|s2|
  470. s1.setsockopt(:SOCKET, :BINTIME, true)
  471. s2.send "a", 0, s1.local_address
  472. msg, addr, rflags, stamp = s1.recvmsg
  473. assert_equal("a", msg)
  474. assert(stamp.cmsg_is?(:SOCKET, :BINTIME))
  475. }
  476. }
  477. t2 = Time.now.strftime("%Y-%m-%d")
  478. pat = Regexp.union([t1, t2].uniq)
  479. assert_match(pat, stamp.inspect)
  480. t = stamp.timestamp
  481. assert_match(pat, t.strftime("%Y-%m-%d"))
  482. assert_equal(stamp.data[-8,8].unpack("Q")[0], t.subsec * 2**64)
  483. end
  484. def test_closed_read
  485. require 'timeout'
  486. require 'socket'
  487. bug4390 = '[ruby-core:35203]'
  488. server = TCPServer.new("localhost", 0)
  489. serv_thread = Thread.new {server.accept}
  490. begin sleep(0.1) end until serv_thread.stop?
  491. sock = TCPSocket.new("localhost", server.addr[1])
  492. client_thread = Thread.new do
  493. sock.readline
  494. end
  495. begin sleep(0.1) end until client_thread.stop?
  496. Timeout.timeout(1) do
  497. sock.close
  498. sock = nil
  499. assert_raise(IOError, bug4390) {client_thread.join}
  500. end
  501. ensure
  502. server.close
  503. end
  504. def test_connect_timeout
  505. host = "127.0.0.1"
  506. server = TCPServer.new(host, 0)
  507. port = server.addr[1]
  508. serv_thread = Thread.new {server.accept}
  509. sock = Socket.tcp(host, port, :connect_timeout => 30)
  510. accepted = serv_thread.value
  511. assert_kind_of TCPSocket, accepted
  512. assert_equal sock, IO.select(nil, [ sock ])[1][0], "not writable"
  513. sock.close
  514. # some platforms may not timeout when the listener queue overflows,
  515. # but we know Linux does with the default listen backlog of SOMAXCONN for
  516. # TCPServer.
  517. assert_raises(Errno::ETIMEDOUT) do
  518. (Socket::SOMAXCONN*2).times do |i|
  519. sock = Socket.tcp(host, port, :connect_timeout => 0)
  520. assert_equal sock, IO.select(nil, [ sock ])[1][0],
  521. "not writable (#{i})"
  522. sock.close
  523. end
  524. end if RUBY_PLATFORM =~ /linux/
  525. ensure
  526. server.close
  527. accepted.close if accepted
  528. sock.close if sock && ! sock.closed?
  529. end
  530. def test_getifaddrs
  531. begin
  532. list = Socket.getifaddrs
  533. rescue NotImplementedError
  534. return
  535. end
  536. list.each {|ifaddr|
  537. assert_instance_of(Socket::Ifaddr, ifaddr)
  538. }
  539. end
  540. end if defined?(Socket)