dnssd 1.2 → 1.3

Sign up to get free protection for your applications and to get access to all the features.
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