PageRenderTime 42ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/dnssd/service.rb

http://github.com/tenderlove/dnssd
Ruby | 261 lines | 123 code | 45 blank | 93 comment | 10 complexity | 9bbbf1d9b01957d3121fb96302548996 MD5 | raw file
  1. require 'thread'
  2. ##
  3. # A DNSSD::Service may be used for one DNS-SD call at a time. The service is
  4. # automatically stopped after calling. A single service can not be reused
  5. # multiple times.
  6. #
  7. # DNSSD::Service provides the raw DNS-SD functions via the +_+ variants.
  8. class DNSSD::Service
  9. include Enumerable
  10. # :stopdoc:
  11. IPv4 = 1 unless const_defined? :IPv4
  12. IPv6 = 2 unless const_defined? :IPv6
  13. # :startdoc:
  14. class << self; private :new; end
  15. ##
  16. # Creates a new DNSSD::Service
  17. def initialize
  18. @replies = []
  19. @continue = true
  20. @thread = nil
  21. @lock = Mutex.new
  22. end
  23. class Register < ::DNSSD::Service
  24. def initialize
  25. super
  26. @records = []
  27. end
  28. ##
  29. # Adds an extra DNS record of +type+ containing +data+. +ttl+ is in
  30. # seconds, use 0 for the default value. +flags+ are currently ignored.
  31. #
  32. # Must be called on a service only after #register.
  33. #
  34. # Returns the added DNSSD::Record
  35. def add_record type, data, ttl = 0, flags = 0
  36. @records << _add_record(flags.to_i, type, data, ttl)
  37. end
  38. end
  39. ##
  40. # Browse for services.
  41. #
  42. # For each service found a DNSSD::Reply object is yielded.
  43. #
  44. # service = DNSSD::Service.new
  45. # timeout 6 do
  46. # service.browse '_http._tcp' do |r|
  47. # puts "Found HTTP service: #{r.name}"
  48. # end
  49. # rescue Timeout::Error
  50. # end
  51. def self.browse type, domain = nil, flags = 0, interface = DNSSD::InterfaceAny
  52. check_domain domain
  53. interface = DNSSD.interface_index interface unless Integer === interface
  54. _browse flags.to_i, interface, type, domain
  55. end
  56. def each timeout = :never
  57. raise DNSSD::Error, 'already stopped' unless @continue
  58. return enum_for __method__, timeout unless block_given?
  59. io = IO.new ref_sock_fd
  60. rd = [io]
  61. start_at = clock_time
  62. while @continue
  63. break unless timeout == :never || clock_time - start_at < timeout
  64. if IO.select rd, nil, nil, 1
  65. begin
  66. process_result
  67. rescue DNSSD::UnknownError
  68. end
  69. @replies.each { |r| yield r }
  70. @replies.clear
  71. end
  72. end
  73. end
  74. def async_each timeout = :never
  75. @lock.synchronize do
  76. raise DNSSD::Error, 'already stopped' unless @continue
  77. @thread = Thread.new { each(timeout) { |r| yield r } }
  78. end
  79. end
  80. def push record
  81. @replies << record
  82. end
  83. ##
  84. # Raises an ArgumentError if +domain+ is too long including NULL terminator
  85. # and trailing '.'
  86. def self.check_domain(domain)
  87. return unless domain
  88. raise ArgumentError, 'domain name string is too long' if
  89. domain.length >= MAX_DOMAIN_NAME - 1
  90. end
  91. ##
  92. # Enumerate domains available for browsing and registration.
  93. #
  94. # For each domain found a DNSSD::Reply object is passed to block with
  95. # #domain set to the enumerated domain.
  96. #
  97. # service = DNSSD::Service.enumerate_domains
  98. #
  99. # service.each do |r|
  100. # p r.domain
  101. # break unless r.flags.more_coming?
  102. # end
  103. def self.enumerate_domains(flags = DNSSD::Flags::BrowseDomains,
  104. interface = DNSSD::InterfaceAny, &block)
  105. interface = DNSSD.interface_index interface unless Integer === interface
  106. _enumerate_domains flags.to_i, interface
  107. end
  108. ##
  109. # Retrieve address information for +host+ on +protocol+
  110. #
  111. # addresses = []
  112. # service.getaddrinfo reply.target do |addrinfo|
  113. # addresses << addrinfo.address
  114. # break unless addrinfo.flags.more_coming?
  115. # end
  116. #
  117. # When using DNSSD on top of the Avahi compatibilty shim you'll need to
  118. # setup your /etc/nsswitch.conf correctly. See
  119. # http://avahi.org/wiki/AvahiAndUnicastDotLocal for details
  120. def self.getaddrinfo(host, protocol = 0, flags = 0,
  121. interface = DNSSD::InterfaceAny, &block)
  122. interface = DNSSD.interface_index interface unless Integer === interface
  123. if respond_to? :_getaddrinfo, true then
  124. _getaddrinfo flags.to_i, interface, protocol, host
  125. else
  126. family = case protocol
  127. when IPv4 then Socket::AF_INET
  128. when IPv6 then Socket::AF_INET6
  129. else protocol
  130. end
  131. addrinfo = Socket.getaddrinfo host, nil, family
  132. list = addrinfo.map do |_, _, a_host, ip, _|
  133. sockaddr = Socket.pack_sockaddr_in 0, ip
  134. DNSSD::Reply::AddrInfo.new(self, 0, 0, a_host, sockaddr, 0)
  135. end
  136. def list.stop; end
  137. list
  138. end
  139. end
  140. ##
  141. # Retrieves an arbitrary DNS record
  142. #
  143. # +fullname+ is the full name of the resource record. +record_type+ is the
  144. # type of the resource record (see DNSSD::Resource).
  145. #
  146. # +flags+ may be either DNSSD::Flags::ForceMulticast or
  147. # DNSSD::Flags::LongLivedQuery
  148. #
  149. # service.query_record "hostname._afpovertcp._tcp.local",
  150. # DNSService::Record::SRV do |record|
  151. # p record
  152. # end
  153. def self.query_record(fullname, record_type, record_class = DNSSD::Record::IN,
  154. flags = 0, interface = DNSSD::InterfaceAny)
  155. interface = DNSSD.interface_index interface unless Integer === interface
  156. _query_record flags.to_i, interface, fullname, record_type, record_class
  157. end
  158. ##
  159. # Register a service. A DNSSD::Reply object is passed to the optional block
  160. # when the registration completes.
  161. #
  162. # service.register "My Files", "_http._tcp", nil, 8080 do |r|
  163. # puts "successfully registered: #{r.inspect}"
  164. # end
  165. def self.register(name, type, domain, port, host = nil, text_record = nil,
  166. flags = 0, interface = DNSSD::InterfaceAny)
  167. check_domain domain
  168. interface = DNSSD.interface_index interface unless Integer === interface
  169. text_record = text_record.encode if text_record
  170. _register flags.to_i, interface, name, type, domain, host, port, text_record
  171. end
  172. ##
  173. # Resolve a service discovered via #browse.
  174. #
  175. # +name+ may be either the name of the service found or a DNSSD::Reply from
  176. # DNSSD::Service#browse. When +name+ is a DNSSD::Reply, +type+ and +domain+
  177. # are automatically filled in, otherwise the service type and domain must be
  178. # supplied.
  179. #
  180. # The service is resolved to a target host name, port number, and text
  181. # record, all contained in the DNSSD::Reply object passed to the required
  182. # block.
  183. #
  184. # service.resolve "foo bar", "_http._tcp", "local" do |r|
  185. # p r
  186. # end
  187. def self.resolve(name, type = name.type, domain = name.domain, flags = 0,
  188. interface = DNSSD::InterfaceAny)
  189. name = name.name if DNSSD::Reply === name
  190. check_domain domain
  191. interface = DNSSD.interface_index interface unless Integer === interface
  192. _resolve flags.to_i, interface, name, type, domain
  193. end
  194. ##
  195. # Returns true if the service has been started.
  196. def started?
  197. @continue
  198. end
  199. def stop
  200. raise DNSSD::Error, 'service is already stopped' unless started?
  201. @continue = false
  202. @thread.join if @thread
  203. _stop
  204. self
  205. end
  206. private
  207. if defined? Process::CLOCK_MONOTONIC
  208. def clock_time
  209. Process.clock_gettime Process::CLOCK_MONOTONIC
  210. end
  211. else
  212. def clock_time
  213. Time.now
  214. end
  215. end
  216. end