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
@@ -1,6 +1,3 @@
1
- require 'dnssd/dnssd'
2
- require 'socket'
3
-
4
1
  ##
5
2
  # DNSSD is a wrapper for the DNS Service Discovery library.
6
3
  #
@@ -16,7 +13,7 @@ module DNSSD
16
13
  ##
17
14
  # The version of DNSSD you're using.
18
15
 
19
- VERSION = '1.2'
16
+ VERSION = '1.3'
20
17
 
21
18
  ##
22
19
  # Registers +socket+ with DNSSD as +name+. If +service+ is omitted it is
@@ -120,9 +117,14 @@ module DNSSD
120
117
  interface = DNSSD::InterfaceAny, &block)
121
118
  service = DNSSD::Service.new
122
119
 
123
- Thread.start do
124
- run(service, :register, name, type, domain, port, nil, text_record,
125
- flags, interface, &block)
120
+ if block_given? then
121
+ Thread.start do
122
+ run(service, :register, name, type, domain, port, nil, text_record,
123
+ flags, interface, &block)
124
+ end
125
+ else
126
+ service.register name, type, domain, port, nil, text_record, flags,
127
+ interface
126
128
  end
127
129
 
128
130
  service
@@ -135,8 +137,15 @@ module DNSSD
135
137
  interface = DNSSD::InterfaceAny, &block)
136
138
  service = DNSSD::Service.new
137
139
 
138
- run(service, :register, name, type, domain, port, nil, text_record, flags,
139
- interface, &block)
140
+ if block_given? then
141
+ run(service, :register, name, type, domain, port, nil, text_record, flags,
142
+ interface, &block)
143
+ else
144
+ service.register name, type, domain, port, nil, text_record, flags,
145
+ interface
146
+ end
147
+
148
+ service
140
149
  end
141
150
 
142
151
  ##
@@ -175,8 +184,34 @@ module DNSSD
175
184
 
176
185
  end
177
186
 
178
- require 'dnssd/flags'
187
+ require 'socket'
188
+
179
189
  require 'dnssd/reply'
180
- require 'dnssd/service'
190
+ require 'dnssd/reply/addr_info'
191
+ require 'dnssd/reply/browse'
192
+ require 'dnssd/reply/domain'
193
+ require 'dnssd/reply/query_record'
194
+ require 'dnssd/reply/register'
195
+ require 'dnssd/reply/resolve'
181
196
  require 'dnssd/text_record'
182
197
 
198
+ # Suppress avahi compatibilty warning
199
+ # http://0pointer.de/avahi-compat?s=libdns_sd&e=ruby
200
+ ENV['AVAHI_COMPAT_NOWARN'] = '1'
201
+
202
+ # The C extension uses above-defined classes
203
+ require 'dnssd/dnssd'
204
+
205
+ module DNSSD
206
+ # :stopdoc:
207
+ class ServiceNotRunningError < UnknownError; end unless
208
+ const_defined? :ServiceNotRunningError
209
+
210
+ InterfaceUnicast = 4294967294 unless const_defined? :InterfaceUnicast # -2
211
+ # :startdoc:
212
+ end
213
+
214
+ require 'dnssd/flags'
215
+ require 'dnssd/service'
216
+ require 'dnssd/record'
217
+
@@ -0,0 +1,97 @@
1
+ require 'ipaddr'
2
+
3
+ ##
4
+ # Created when adding a DNS record using DNSSD::Service#add_record. Provides
5
+ # convenience methods for creating the DNS record.
6
+ #
7
+ # See also {RFC 1035}[http://www.rfc-editor.org/rfc/rfc1035.txt]
8
+
9
+ class DNSSD::Record
10
+
11
+ value_to_name = constants.map do |name|
12
+ next if name.intern == :IN
13
+ [const_get(name), name.to_s]
14
+ end.compact.flatten
15
+
16
+ ##
17
+ # Maps record constant values to the constant name
18
+
19
+ VALUE_TO_NAME = Hash[*value_to_name]
20
+
21
+ ##
22
+ # Turns +string+ into an RFC-1035 character-string
23
+
24
+ def self.string_to_character_string(string)
25
+ length = string.length
26
+ raise ArgumentError, "#{string.inspect} is too long (255 bytes max)" if
27
+ length > 255
28
+ "#{length.chr}#{string}"
29
+ end
30
+
31
+ ##
32
+ # Turns +string+ into an RFC-1035 domain-name
33
+
34
+ def self.string_to_domain_name(string)
35
+ string.split('.').map do |part|
36
+ string_to_character_string part
37
+ end.join('') << "\0"
38
+ end
39
+
40
+ ##
41
+ # Encodes resource +args+ into +type+. Handles:
42
+ #
43
+ # A AAAA CNAME MX NS PTR SOA SRV TXT
44
+
45
+ def self.to_data(type, *args)
46
+ raise ArgumentError, "unknown type #{type}" unless VALUE_TO_NAME.key? type
47
+
48
+ data = case type
49
+ when A then
50
+ addr = args.shift
51
+ addr = IPAddr.new addr unless IPAddr === addr
52
+ raise ArgumentError, "#{addr} is not IPv4" unless addr.ipv4?
53
+ addr.hton
54
+ when AAAA then
55
+ addr = args.shift
56
+ addr = IPAddr.new addr unless IPAddr === addr
57
+ raise ArgumentError, "#{addr} is not IPv6" unless addr.ipv6?
58
+ addr.hton
59
+ when CNAME, NS, PTR then
60
+ string_to_domain_name args.shift
61
+ when MX then
62
+ [args.shift, string_to_domain_name(args.shift)].pack 'na*'
63
+ when SOA then
64
+ [
65
+ string_to_domain_name(args.shift),
66
+ string_to_domain_name(args.shift),
67
+ args.shift, args.shift, args.shift, args.shift, args.shift
68
+ ].pack 'a*a*NNNNN'
69
+ when SRV then
70
+ [
71
+ args.shift, args.shift, args.shift,
72
+ string_to_domain_name(args.shift)
73
+ ].pack 'nnna*'
74
+ when TXT then
75
+ data = args.map do |string|
76
+ string_to_character_string string
77
+ end.join ''
78
+
79
+ raise ArgumentError,
80
+ "TXT record too long (#{data.length} bytes)" if
81
+ data.length > 65535
82
+
83
+ args.clear
84
+
85
+ data
86
+ else
87
+ raise ArgumentError, "unhandled record type #{VALUE_TO_NAME[type]}"
88
+ end
89
+
90
+ raise ArgumentError, "Too many arguments for #{VALUE_TO_NAME[type]}" unless
91
+ args.empty?
92
+
93
+ data
94
+ end
95
+
96
+ end
97
+
@@ -4,131 +4,78 @@
4
4
  class DNSSD::Reply
5
5
 
6
6
  ##
7
- # The service domain
8
-
9
- attr_reader :domain
10
-
11
- ##
12
- # Flags describing the reply, see DNSSD::Flags
7
+ # Flags for this reply, see DNSSD::Flags
13
8
 
14
9
  attr_reader :flags
15
10
 
16
11
  ##
17
- # The interface on which the service is available
12
+ # The interface name for this reply
18
13
 
19
14
  attr_reader :interface
20
15
 
21
16
  ##
22
- # The service name
23
-
24
- attr_reader :name
25
-
26
- ##
27
- # The port for this service
28
-
29
- attr_reader :port
30
-
31
- ##
32
- # The DNSSD::Service associated with the reply
17
+ # The DNSSD::Service that created this reply
33
18
 
34
19
  attr_reader :service
35
20
 
36
21
  ##
37
- # The hostname of the host provide the service
38
-
39
- attr_reader :target
40
-
41
- ##
42
- # The service's primary text record
43
-
44
- attr_reader :text_record
45
-
46
- ##
47
- # The service type
48
-
49
- attr_reader :type
50
-
51
- ##
52
- # Creates a DNSSD::Reply from +service+ and +flags+
22
+ # Creates a new reply attached to +service+ with +flags+ on interface index
23
+ # +interface+
53
24
 
54
- def self.from_service(service, flags)
55
- reply = new
56
- reply.instance_variable_set :@service, service
57
- reply.instance_variable_set :@flags, DNSSD::Flags.new(flags)
58
- reply
59
- end
60
-
61
- ##
62
- # Connects to this Reply. If +target+ and +port+ are missing, DNSSD.resolve
63
- # is automatically called. +family+ can be used to select a particular
64
- # address family.
65
-
66
- def connect(family = Socket::AF_UNSPEC)
67
- unless target and port then
68
- value = nil
69
-
70
- DNSSD.resolve! self do |reply|
71
- value = reply
72
- break
73
- end
74
-
75
- return value.connect
76
- end
77
-
78
- socktype = case protocol
79
- when 'tcp' then Socket::SOCK_STREAM
80
- when 'udp' then Socket::SOCK_DGRAM
81
- else raise ArgumentError, "invalid protocol #{protocol}"
82
- end
83
-
84
- addresses = Socket.getaddrinfo target, port, family, socktype
85
-
86
- socket = nil
87
-
88
- addresses.each do |address|
89
- begin
90
- case protocol
91
- when 'tcp' then
92
- socket = TCPSocket.new address[3], port
93
- when 'udp' then
94
- socket = UDPSocket.new
95
- socket.connect address[3], port rescue next
96
- end
97
-
98
- return socket
99
- rescue
100
- next
101
- end
102
- end
103
-
104
- raise DNSSD::Error, "unable to connect to #{target}:#{port}" unless socket
25
+ def initialize(service, flags, interface)
26
+ @service = service
27
+ @flags = DNSSD::Flags.new flags
28
+ @interface = if interface then
29
+ interface > 0 ? DNSSD.interface_name(interface) : interface
30
+ end
105
31
  end
106
32
 
107
33
  ##
108
34
  # The full service domain name, see DNSS::Service#fullname
109
35
 
110
36
  def fullname
111
- DNSSD::Service.fullname @name.gsub("\032", ' '), @type, @domain
37
+ fullname = DNSSD::Service.fullname @name.gsub("\032", ' '), @type, @domain
38
+ fullname << '.' unless fullname =~ /\.$/
39
+ fullname
112
40
  end
113
41
 
114
42
  def inspect # :nodoc:
115
- "#<%s:0x%x %p type: %s domain: %s interface: %s flags: %s>" % [
116
- self.class, object_id, @name, @type, @domain, @interface, @flags
43
+ "#<%s:0x%x interface: %s flags: %p>" % [
44
+ self.class, object_id, interface_name, @flags
117
45
  ]
118
46
  end
119
47
 
48
+ ##
49
+ # Expands the name of the interface including constants
50
+
51
+ def interface_name
52
+ case @interface
53
+ when nil then 'nil'
54
+ when DNSSD::InterfaceAny then 'any'
55
+ when DNSSD::InterfaceLocalOnly then 'local'
56
+ when DNSSD::InterfaceUnicast then 'unicast'
57
+ else @interface
58
+ end
59
+ end
60
+
120
61
  ##
121
62
  # Protocol of this service
122
63
 
123
64
  def protocol
124
- type.split('.').last.sub '_', ''
65
+ raise TypeError, 'no type on this reply' unless
66
+ instance_variable_defined? :@type
67
+
68
+ @type.split('.').last.sub '_', ''
125
69
  end
126
70
 
127
71
  ##
128
72
  # Service name as in Socket.getservbyname
129
73
 
130
74
  def service_name
131
- type.split('.').first.sub '_', ''
75
+ raise TypeError, 'no type on this reply' unless
76
+ instance_variable_defined? :@type
77
+
78
+ @type.split('.').first.sub '_', ''
132
79
  end
133
80
 
134
81
  ##
@@ -141,8 +88,8 @@ class DNSSD::Reply
141
88
  end
142
89
 
143
90
  @name = fullname[0]
144
- @type = fullname[1, 2].join '.'
145
- @domain = fullname.last + '.'
91
+ @type = fullname[1, 2].join '.'
92
+ @domain = fullname[3..-1].map { |part| part.sub '.', '\\.' }.join('.') + '.'
146
93
  end
147
94
 
148
95
  ##
@@ -0,0 +1,47 @@
1
+ ##
2
+ # Created by DNSSD::Service#getaddrinfo
3
+
4
+ class DNSSD::Reply::AddrInfo < DNSSD::Reply
5
+
6
+ ##
7
+ # IP address of host
8
+
9
+ attr_reader :address
10
+
11
+ ##
12
+ # Host name
13
+
14
+ attr_reader :hostname
15
+
16
+ ##
17
+ # Port name
18
+
19
+ attr_reader :port
20
+
21
+ ##
22
+ # Time to live see #expired?
23
+
24
+ attr_reader :ttl
25
+
26
+ ##
27
+ # Creates a new AddrInfo, called internally by DNSSD::Service#getaddrinfo
28
+
29
+ def initialize(service, flags, interface, hostname, sockaddr, ttl)
30
+ super service, flags, interface
31
+
32
+ @hostname = hostname
33
+ @port, @address = Socket.unpack_sockaddr_in sockaddr
34
+
35
+ @created = Time.now
36
+ @ttl = ttl
37
+ end
38
+
39
+ ##
40
+ # Has this AddrInfo passed its TTL?
41
+
42
+ def expired?
43
+ Time.now > @created + ttl
44
+ end
45
+
46
+ end
47
+
@@ -0,0 +1,52 @@
1
+ ##
2
+ # Returned by DNSSD::Service#browse
3
+
4
+ class DNSSD::Reply::Browse < 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 new Browse, called internally by DNSSD::Service#browse
23
+
24
+ def initialize(service, flags, interface, name, type, domain)
25
+ super service, flags, interface
26
+
27
+ set_names name, type, domain
28
+ end
29
+
30
+ ##
31
+ # Resolves this service's target using DNSSD::Reply::Resolve#connect which
32
+ # connects, returning a TCP or UDP socket.
33
+
34
+ def connect(family = Socket::AF_UNSPEC, addrinfo_flags = 0)
35
+ value = nil
36
+
37
+ DNSSD.resolve! self do |reply|
38
+ value = reply
39
+ break
40
+ end
41
+
42
+ value.connect family, addrinfo_flags
43
+ end
44
+
45
+ def inspect # :nodoc:
46
+ "#<%s:0x%x %p interface: %s flags: %p>" % [
47
+ self.class, object_id, fullname, interface_name, @flags
48
+ ]
49
+ end
50
+
51
+ end
52
+