dnssd 1.2 → 1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. data.tar.gz.sig +0 -0
  2. data/.autotest +14 -0
  3. data/History.txt +22 -3
  4. data/Manifest.txt +15 -0
  5. data/README.txt +27 -3
  6. data/Rakefile +5 -4
  7. data/ext/dnssd/dnssd.c +4 -0
  8. data/ext/dnssd/dnssd.h +13 -18
  9. data/ext/dnssd/errors.c +9 -3
  10. data/ext/dnssd/extconf.rb +51 -44
  11. data/ext/dnssd/flags.c +68 -14
  12. data/ext/dnssd/record.c +218 -0
  13. data/ext/dnssd/service.c +341 -121
  14. data/lib/dnssd.rb +46 -11
  15. data/lib/dnssd/record.rb +97 -0
  16. data/lib/dnssd/reply.rb +39 -92
  17. data/lib/dnssd/reply/addr_info.rb +47 -0
  18. data/lib/dnssd/reply/browse.rb +52 -0
  19. data/lib/dnssd/reply/domain.rb +22 -0
  20. data/lib/dnssd/reply/query_record.rb +183 -0
  21. data/lib/dnssd/reply/register.rb +37 -0
  22. data/lib/dnssd/reply/resolve.rb +105 -0
  23. data/lib/dnssd/service.rb +123 -16
  24. data/lib/dnssd/text_record.rb +28 -19
  25. data/sample/browse.rb +24 -6
  26. data/sample/enumerate_domains.rb +7 -1
  27. data/sample/getaddrinfo.rb +28 -0
  28. data/sample/growl.rb +2 -0
  29. data/sample/query_record.rb +15 -0
  30. data/sample/register.rb +19 -20
  31. data/sample/resolve.rb +31 -7
  32. data/sample/resolve_ichat.rb +5 -6
  33. data/sample/server.rb +2 -0
  34. data/sample/socket.rb +4 -0
  35. data/test/test_dnssd.rb +6 -4
  36. data/test/test_dnssd_flags.rb +1 -15
  37. data/test/test_dnssd_record.rb +92 -0
  38. data/test/test_dnssd_reply.rb +13 -79
  39. data/test/test_dnssd_reply_browse.rb +28 -0
  40. data/test/test_dnssd_reply_query_record.rb +92 -0
  41. data/test/test_dnssd_reply_resolve.rb +47 -0
  42. data/test/test_dnssd_service.rb +12 -0
  43. data/test/test_dnssd_text_record.rb +3 -3
  44. metadata +32 -11
  45. metadata.gz.sig +0 -0
@@ -0,0 +1,22 @@
1
+ ##
2
+ # Returned by DNSSD::Service#enumerate_domains
3
+
4
+ class DNSSD::Reply::Domain < DNSSD::Reply
5
+
6
+ ##
7
+ # A domain for registration or browsing
8
+
9
+ attr_reader :domain
10
+
11
+ ##
12
+ # Creates a new Browse, called internally by
13
+ # DNSSD::Service#enumerate_domains
14
+
15
+ def initialize(service, flags, interface, domain)
16
+ super service, flags, interface
17
+
18
+ @domain = domain
19
+ end
20
+
21
+ end
22
+
@@ -0,0 +1,183 @@
1
+ require 'ipaddr'
2
+
3
+ ##
4
+ # Created by DNSSD::Service#query_record
5
+
6
+ class DNSSD::Reply::QueryRecord < DNSSD::Reply
7
+
8
+ ##
9
+ # A domain for registration or browsing
10
+
11
+ attr_reader :domain
12
+
13
+ ##
14
+ # The service name
15
+
16
+ attr_reader :name
17
+
18
+ ##
19
+ # DNS Record data
20
+
21
+ attr_reader :record
22
+
23
+ ##
24
+ # DNS Record class (only IN is supported)
25
+
26
+ attr_reader :record_class
27
+
28
+ ##
29
+ # DNS Record type
30
+
31
+ attr_reader :record_type
32
+
33
+ ##
34
+ # Time-to-live for this record. See #expired?
35
+
36
+ attr_reader :ttl
37
+
38
+ ##
39
+ # The service type
40
+
41
+ attr_reader :type
42
+
43
+ ##
44
+ # Creates a new QueryRecord, called internally by
45
+ # DNSSD::Service#query_record
46
+
47
+ def initialize(service, flags, interface, fullname, record_type,
48
+ record_class, record, ttl)
49
+ super service, flags, interface
50
+
51
+ set_fullname fullname
52
+
53
+ @record_type = record_type
54
+ @record_class = record_class
55
+ @record = record
56
+
57
+ @created = Time.now
58
+ @ttl = ttl
59
+ end
60
+
61
+ ##
62
+ # Converts a RFC 1035 character-string into a ruby String
63
+
64
+ def character_string_to_string(character_string)
65
+ length = character_string.slice 0
66
+ length = length.ord unless Numeric === length
67
+ string = character_string.slice 1, length
68
+
69
+ if string.length != length then
70
+ raise TypeError,
71
+ "invalid character string, expected #{length} got #{string.length} in #{@record.inspect}"
72
+ end
73
+
74
+ string
75
+ end
76
+
77
+ ##
78
+ # Converts a RFC 1035 domain-name into a ruby String
79
+
80
+ def domain_name_to_string(domain_name)
81
+ return '.' if domain_name == "\0"
82
+
83
+ domain_name = domain_name.dup
84
+ string = []
85
+
86
+ until domain_name.empty? do
87
+ string << character_string_to_string(domain_name)
88
+ domain_name.slice! 0, string.last.length + 1
89
+ end
90
+
91
+ string << nil unless string.last.empty?
92
+
93
+ string.join('.')
94
+ end
95
+
96
+ ##
97
+ # Has this QueryRecord passed its TTL?
98
+
99
+ def expired?
100
+ Time.now > @created + ttl
101
+ end
102
+
103
+ def inspect # :nodoc:
104
+ "#<%s:0x%x %s %s %s %p interface: %s flags: %p>" % [
105
+ self.class, object_id,
106
+ fullname, record_class_name, record_type_name, record,
107
+ interface_name, @flags
108
+ ]
109
+ end
110
+
111
+ ##
112
+ # Name of this record's record_class
113
+
114
+ def record_class_name
115
+ return "unknown #{@record_class}" unless @record_class == DNSSD::Record::IN
116
+ 'IN' # Only IN is supported
117
+ end
118
+
119
+ ##
120
+ # Decodes output for #record, returning the raw record if it can't be
121
+ # decoded. Handles:
122
+ #
123
+ # A AAAA CNAME MX NS PTR SOA SRV TXT
124
+
125
+ def record_data
126
+ return @record unless @record_class == DNSSD::Record::IN
127
+
128
+ case @record_type
129
+ when DNSSD::Record::A,
130
+ DNSSD::Record::AAAA then
131
+ IPAddr.new_ntoh @record
132
+ when DNSSD::Record::CNAME,
133
+ DNSSD::Record::NS,
134
+ DNSSD::Record::PTR then
135
+ domain_name_to_string @record
136
+ when DNSSD::Record::MX then
137
+ mx = @record.unpack 'nZ*'
138
+ mx[-1] = domain_name_to_string mx.last
139
+ mx
140
+ when DNSSD::Record::SOA then
141
+ soa = @record.unpack 'Z*Z*NNNNN'
142
+ soa[0] = domain_name_to_string soa[0]
143
+ soa[1] = domain_name_to_string soa[1]
144
+ soa
145
+ when DNSSD::Record::SRV then
146
+ srv = @record.unpack 'nnnZ*'
147
+ srv[-1] = domain_name_to_string srv.last
148
+ srv
149
+ when DNSSD::Record::TXT then
150
+ record = @record.dup
151
+ txt = []
152
+
153
+ until record.empty? do
154
+ txt << character_string_to_string(record)
155
+ record.slice! 0, txt.last.length + 1
156
+ end
157
+
158
+ txt
159
+ else
160
+ @record
161
+ end
162
+ end
163
+
164
+ ##
165
+ # Name of this record's record_type
166
+
167
+ def record_type_name
168
+ return "unknown #{@record_type} for record class (#{@record_class})" unless
169
+ @record_class == DNSSD::Record::IN
170
+ DNSSD::Record::VALUE_TO_NAME[@record_type]
171
+ end
172
+
173
+ ##
174
+ # Outputs this record in a BIND-like DNS format
175
+
176
+ def to_s
177
+ "%s %d %s %s %p" % [
178
+ fullname, ttl, record_class_name, record_type_name, record_data
179
+ ]
180
+ end
181
+
182
+ end
183
+
@@ -0,0 +1,37 @@
1
+ ##
2
+ # Returned by DNSSD::Service#register
3
+
4
+ class DNSSD::Reply::Register < DNSSD::Reply
5
+
6
+ ##
7
+ # A domain for registration or browsing
8
+
9
+ attr_reader :domain
10
+
11
+ ##
12
+ # The service name
13
+
14
+ attr_reader :name
15
+
16
+ ##
17
+ # The service type
18
+
19
+ attr_reader :type
20
+
21
+ ##
22
+ # Creates a Register, called internally by DNSSD::Service#register
23
+
24
+ def initialize(service, flags, name, type, domain)
25
+ super service, flags, nil
26
+
27
+ set_names name, type, domain
28
+ end
29
+
30
+ def inspect # :nodoc:
31
+ "#<%s:0x%x %p flags: %p>" % [
32
+ self.class, object_id, fullname, @flags
33
+ ]
34
+ end
35
+
36
+ end
37
+
@@ -0,0 +1,105 @@
1
+ ##
2
+ # Created by DNSSD::Service#resolve
3
+
4
+ class DNSSD::Reply::Resolve < DNSSD::Reply
5
+
6
+ ##
7
+ # A domain for registration or browsing
8
+
9
+ attr_reader :domain
10
+
11
+ ##
12
+ # The service name
13
+
14
+ attr_reader :name
15
+
16
+ ##
17
+ # The port for this service
18
+
19
+ attr_reader :port
20
+
21
+ ##
22
+ # The hostname of the host provide the service
23
+
24
+ attr_reader :target
25
+
26
+ ##
27
+ # The service's primary text record
28
+
29
+ attr_reader :text_record
30
+
31
+ ##
32
+ # The service type
33
+
34
+ attr_reader :type
35
+
36
+ ##
37
+ # Creates a new Resolve, called internally by DNSSD::Service#resolve
38
+
39
+ def initialize(service, flags, interface, fullname, target, port,
40
+ text_record)
41
+ super service, flags, interface
42
+
43
+ set_fullname fullname
44
+
45
+ @target = target
46
+ @port = port
47
+ @text_record = DNSSD::TextRecord.new text_record
48
+ end
49
+
50
+ ##
51
+ # Connects to this Reply. If #target and #port are missing, DNSSD.resolve
52
+ # is automatically called.
53
+ #
54
+ # +family+ can be used to select a particular address family (IPv6 vs IPv4).
55
+ #
56
+ # +addrinfo_flags+ are passed to DNSSD::Service#getaddrinfo as flags.
57
+
58
+ def connect(family = Socket::AF_UNSPEC, addrinfo_flags = 0)
59
+ addrinfo_protocol = case family
60
+ when Socket::AF_INET then DNSSD::Service::IPv4
61
+ when Socket::AF_INET6 then DNSSD::Service::IPv6
62
+ when Socket::AF_UNSPEC then 0
63
+ else raise ArgumentError, "invalid family #{family}"
64
+ end
65
+
66
+ addresses = []
67
+
68
+ service = DNSSD::Service.new
69
+
70
+ begin
71
+ service.getaddrinfo target, addrinfo_protocol, addrinfo_flags,
72
+ @interface do |addrinfo|
73
+ address = addrinfo.address
74
+
75
+ begin
76
+ socket = nil
77
+
78
+ case protocol
79
+ when 'tcp' then
80
+ socket = TCPSocket.new address, port
81
+ when 'udp' then
82
+ socket = UDPSocket.new
83
+ socket.connect address, port
84
+ end
85
+
86
+ return socket
87
+ rescue
88
+ next if addrinfo.flags.more_coming?
89
+ raise
90
+ end
91
+ end
92
+ ensure
93
+ service.stop
94
+ end
95
+ end
96
+
97
+ def inspect # :nodoc:
98
+ "#<%s:0x%x %s at %s:%d text_record: %p interface: %s flags: %p>" % [
99
+ self.class, object_id,
100
+ fullname, @target, @port, @text_record, interface_name, @flags
101
+ ]
102
+ end
103
+
104
+ end
105
+
@@ -8,6 +8,11 @@ require 'thread'
8
8
 
9
9
  class DNSSD::Service
10
10
 
11
+ # :stopdoc:
12
+ IPv4 = 1 unless const_defined? :IPv4
13
+ IPv6 = 2 unless const_defined? :IPv6
14
+ # :startdoc:
15
+
11
16
  ##
12
17
  # Creates a new DNSSD::Service
13
18
 
@@ -15,6 +20,22 @@ class DNSSD::Service
15
20
  @replies = []
16
21
  @continue = true
17
22
  @thread = nil
23
+ @type = nil
24
+ end
25
+
26
+ ##
27
+ # Adds an extra DNS record of +type+ containing +data+. +ttl+ is in
28
+ # seconds, use 0 for the default value. +flags+ are currently ignored.
29
+ #
30
+ # Must be called on a service only after #register.
31
+ #
32
+ # Returns the added DNSSD::Record
33
+
34
+ def add_record(type, data, ttl = 0, flags = 0)
35
+ raise TypeError, 'must be called after register' unless @type == :register
36
+ @records ||= []
37
+
38
+ _add_record flags.to_i, type, data, ttl
18
39
  end
19
40
 
20
41
  ##
@@ -37,7 +58,9 @@ class DNSSD::Service
37
58
 
38
59
  raise DNSSD::Error, 'service in progress' if started?
39
60
 
40
- _browse type, domain, flags.to_i, interface
61
+ @type = :browse
62
+
63
+ _browse flags.to_i, interface, type, domain
41
64
 
42
65
  process(&block)
43
66
  end
@@ -60,11 +83,9 @@ class DNSSD::Service
60
83
  #
61
84
  # available_domains = []
62
85
  #
63
- # timeout(2) do
64
- # DNSSD.enumerate_domains! do |r|
65
- # available_domains << r.domain
66
- # end
67
- # rescue TimeoutError
86
+ # service.enumerate_domains! do |r|
87
+ # available_domains << r.domain
88
+ # break unless r.flags.more_coming?
68
89
  # end
69
90
  #
70
91
  # p available_domains
@@ -77,15 +98,61 @@ class DNSSD::Service
77
98
 
78
99
  _enumerate_domains flags.to_i, interface
79
100
 
101
+ @type = :enumerate_domains
102
+
80
103
  process(&block)
81
104
  end
82
-
105
+
106
+ ##
107
+ # Retrieve address information for +host+ on +protocol+
108
+ #
109
+ # addresses = []
110
+ # service.getaddrinfo reply.target do |addrinfo|
111
+ # addresses << addrinfo.address
112
+ # break unless addrinfo.flags.more_coming?
113
+ # end
114
+ #
115
+ # When using DNSSD on top of the Avahi compatibilty shim you'll need to
116
+ # setup your /etc/nsswitch.conf correctly. See
117
+ # http://avahi.org/wiki/AvahiAndUnicastDotLocal for details
118
+
119
+ def getaddrinfo(host, protocol = nil, flags = 0,
120
+ interface = DNSSD::InterfaceAny, &block)
121
+ interface = DNSSD.interface_index interface unless Integer === interface
122
+
123
+ if respond_to? :_getaddrinfo then
124
+ raise DNSSD::Error, 'service in progress' if started?
125
+
126
+ _getaddrinfo flags.to_i, interface, protocol, host
127
+
128
+ @type = :getaddrinfo
129
+
130
+ process(&block)
131
+ else
132
+ family = case protocol
133
+ when IPv4 then Socket::AF_INET
134
+ when IPv6 then Socket::AF_INET6
135
+ else protocol
136
+ end
137
+
138
+ addrinfo = Socket.getaddrinfo host, nil, family
139
+
140
+ addrinfo.each do |_, _, host, ip, _|
141
+ sockaddr = Socket.pack_sockaddr_in 0, ip
142
+ @replies << DNSSD::Reply::AddrInfo.new(self, 0, 0, host, sockaddr, 0)
143
+ end
144
+ end
145
+ end
146
+
83
147
  def inspect # :nodoc:
84
148
  stopped = stopped? ? 'stopped' : 'running'
85
149
  "#<%s:0x%x %s>" % [self.class, object_id, stopped]
86
150
  end
87
151
 
88
- def process
152
+ ##
153
+ # Yields results from the mDNS daemon, blocking until data is available.
154
+
155
+ def process # :yields: DNSSD::Result
89
156
  @thread = Thread.current
90
157
 
91
158
  while @continue do
@@ -95,14 +162,45 @@ class DNSSD::Service
95
162
 
96
163
  @thread = nil
97
164
 
165
+ self
166
+ rescue DNSSD::ServiceNotRunningError
167
+ # raised when we jump out of DNSServiceProcess() while it's waiting for a
168
+ # response
98
169
  self
99
170
  end
100
171
 
172
+ ##
173
+ # Retrieves an arbitrary DNS record
174
+ #
175
+ # +fullname+ is the full name of the resource record. +record_type+ is the
176
+ # type of the resource record (see DNSSD::Resource).
177
+ #
178
+ # +flags+ may be either DNSSD::Flags::ForceMulticast or
179
+ # DNSSD::Flags::LongLivedQuery
180
+ #
181
+ # service.query_record "hostname._afpovertcp._tcp.local",
182
+ # DNSService::Record::SRV do |record|
183
+ # p record
184
+ # end
185
+
186
+ def query_record(fullname, record_type, record_class = DNSSD::Record::IN,
187
+ flags = 0, interface = DNSSD::InterfaceAny, &block)
188
+ interface = DNSSD.interface_index interface unless Integer === interface
189
+
190
+ raise DNSSD::Error, 'service in progress' if started?
191
+
192
+ _query_record flags.to_i, interface, fullname, record_type, record_class
193
+
194
+ @type = :query_record
195
+
196
+ process(&block)
197
+ end
198
+
101
199
  ##
102
200
  # Register a service. A DNSSD::Reply object is passed to the optional block
103
201
  # when the registration completes.
104
202
  #
105
- # DNSSD.register! "My Files", "_http._tcp", nil, 8080 do |r|
203
+ # service.register "My Files", "_http._tcp", nil, 8080 do |r|
106
204
  # puts "successfully registered: #{r.inspect}"
107
205
  # end
108
206
 
@@ -110,14 +208,16 @@ class DNSSD::Service
110
208
  flags = 0, interface = DNSSD::InterfaceAny, &block)
111
209
  check_domain domain
112
210
  interface = DNSSD.interface_index interface unless Integer === interface
211
+ text_record = text_record.encode if text_record
113
212
 
114
213
  raise DNSSD::Error, 'service in progress' if started?
115
214
 
116
- _register name, type, domain, host, port, text_record, flags.to_i, interface
215
+ _register flags.to_i, interface, name, type, domain, host, port,
216
+ text_record, &block
117
217
 
118
- block = proc { } unless block
218
+ @type = :register
119
219
 
120
- process(&block)
220
+ process(&block) if block
121
221
  end
122
222
 
123
223
  ##
@@ -135,11 +235,9 @@ class DNSSD::Service
135
235
  # The returned service can be used to control when to stop resolving the
136
236
  # service (see DNSSD::Service#stop).
137
237
  #
138
- # s = DNSSD.resolve "foo bar", "_http._tcp", "local" do |r|
238
+ # service.resolve "foo bar", "_http._tcp", "local" do |r|
139
239
  # p r
140
240
  # end
141
- # sleep 2
142
- # s.stop
143
241
 
144
242
  def resolve(name, type = name.type, domain = name.domain, flags = 0,
145
243
  interface = DNSSD::InterfaceAny, &block)
@@ -149,10 +247,19 @@ class DNSSD::Service
149
247
 
150
248
  raise DNSSD::Error, 'service in progress' if started?
151
249
 
152
- _resolve name, type, domain, flags.to_i, interface
250
+ _resolve flags.to_i, interface, name, type, domain
251
+
252
+ @type = :resolve
153
253
 
154
254
  process(&block)
155
255
  end
156
256
 
257
+ ##
258
+ # Returns true if the service has been started.
259
+
260
+ def started?
261
+ not stopped?
262
+ end
263
+
157
264
  end
158
265