PageRenderTime 39ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/eventless/resolver/cares.rb

http://github.com/davidbalbert/eventless
Ruby | 146 lines | 94 code | 27 blank | 25 comment | 15 complexity | d7f2f54489ebfdd7906ab85997ba1205 MD5 | raw file
  1. raise "Cares resolver is not ready yet, sorry"
  2. require 'socket'
  3. require 'cares'
  4. require 'ipaddress'
  5. require 'eventless/sockaddr'
  6. module Eventless
  7. class Loop
  8. SocketHandler = Struct.new(:read_watcher, :write_watcher)
  9. def setup_resolver
  10. @resolver_sockets = {}
  11. resolver = Cares.new do |socket, read, write|
  12. handler = @resolver_sockets[socket.fileno]
  13. if read or write
  14. if handler.nil?
  15. # new socket
  16. attach(@resolver_timer) unless @resolver_timer.attached?
  17. handler = create_socket_watchers(socket)
  18. @resolver_sockets[socket.fileno] = handler
  19. end
  20. if read
  21. attach(handler.read_watcher)
  22. else
  23. detach(handler.read_watcher) if handler.read_watcher.attached?
  24. end
  25. if write
  26. attach(handler.write_watcher)
  27. else
  28. detach(handler.write_watcher) if handler.write_watcher.attached?
  29. end
  30. else # socket just got closed
  31. detach(handler.read_watcher) if handler.read_watcher.attached?
  32. detach(handler.write_watcher) if handler.write_watcher.attached?
  33. @resolver_sockets.delete(socket.fileno)
  34. if @resolver_sockets.size == 0
  35. detach(@resolver_timer)
  36. end
  37. end
  38. end
  39. @resolver_timer = timer(1, true) do
  40. resolver.process_fd(Cares::ARES_SOCKET_BAD, Cares::ARES_SOCKET_BAD)
  41. end
  42. @resolver = resolver
  43. end
  44. ########################################################################
  45. private
  46. # TODO: these SocketErrors are thrown in the context of the eventloop,
  47. # which means that one dns error will probably kill the entire system! This
  48. # is very bad! I'm not sure the best way to handle this one. I think I may
  49. # have to add error callbacks to ruby-cares
  50. def create_socket_watchers(socket)
  51. handler = SocketHandler.new
  52. resolver = @resolver
  53. handler.read_watcher = io(:read, socket) do
  54. begin
  55. resolver.process_fd(socket, Cares::ARES_SOCKET_BAD)
  56. rescue Cares::CaresError => e
  57. raise SocketError, e.message
  58. end
  59. end
  60. handler.write_watcher = io(:write, socket) do
  61. begin
  62. resolver.process_fd(Cares::ARES_SOCKET_BAD, socket)
  63. rescue Cares::CaresError => e
  64. raise SocketError, e.message
  65. end
  66. end
  67. handler
  68. end
  69. end
  70. class IPSocket < BasicSocket
  71. def self.getaddress(hostname)
  72. # return if we're already a valid ip address
  73. begin
  74. IPAddress.parse hostname
  75. return hostname
  76. rescue
  77. end
  78. # NOTE: Force c-ares to behave like the system resolver, namely if there
  79. # are any dots in the hostname, don't do a lookup with search domains.
  80. #
  81. # We're assuming ndots == 1 (see resolver(5) on OS X). The OS X system
  82. # resolver makes a AAAA and A query at the same time when the address
  83. # family is AF_UNSPEC. C-ares makes a AAAA query, if that fails, then a
  84. # AAAA query with the search domain, and then finally an A query. Here we
  85. # force c-ares to skip the search domain by appending a trailing "."
  86. #
  87. # Not yet tested on Linux.
  88. hostname << "." if hostname.include? "." and hostname[-1] != "."
  89. fiber = Fiber.current
  90. addr = nil
  91. Eventless.resolver.gethostbyname(hostname, Socket::AF_UNSPEC) do |name, aliases, faimly, *addrs|
  92. addr = addrs[0]
  93. # NOTE: if c-ares resolves hostname from /etc/hosts,
  94. # Cares#gethostbyname will call this block _before_ returning. In that
  95. # case, we haven't transfered to the event loop, so we can just return
  96. # addr.
  97. return addr if fiber == Fiber.current
  98. # XXX: I thought calling fiber.transfer(addrs[0]) would make
  99. # Eventless.loop.transfer return addrs[0], but it doesn't. I'm not sure
  100. # why. If anyone knows how to fix it, let me know. I think closing around
  101. # addr is a bit hacky.
  102. fiber.transfer
  103. end
  104. Eventless.loop.transfer
  105. addr
  106. end
  107. end
  108. class Socket < BasicSocket
  109. class << self
  110. def pack_sockaddr_in(port, host)
  111. debug_puts "Sockaddr.pack_sockaddr_in"
  112. ip = IPAddress.parse(IPSocket.getaddress(host))
  113. family = ip.ipv6? ? Socket::AF_INET6 : Socket::AF_INET
  114. Eventless::Sockaddr.pack_sockaddr_in(port, ip.to_s, family)
  115. end
  116. alias_method :sockaddr_in, :pack_sockaddr_in
  117. end
  118. end
  119. end