Class: DNSSD::Service

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/dnssd/service.rb,
ext/dnssd/service.c

Overview

A DNSSD::Service may be used for one DNS-SD call at a time. The service is automatically stopped after calling. A single service can not be reused multiple times.

DNSSD::Service provides the raw DNS-SD functions via the _ variants.

Direct Known Subclasses

Register

Defined Under Namespace

Classes: Register

Constant Summary collapse

IPv4 =

IPv4 protocol for #getaddrinfo

ULONG2NUM(kDNSServiceProtocol_IPv4)
IPv6 =

IPv6 protocol for #getaddrinfo

ULONG2NUM(kDNSServiceProtocol_IPv6)
MAX_DOMAIN_NAME =

Maximum length for a domain name

ULONG2NUM(kDNSServiceMaxDomainName)
MAX_SERVICE_NAME =

Maximum length for a service name

ULONG2NUM(kDNSServiceMaxServiceName)
DaemonVersion =

DaemonVersion property value

rb_str_new2(kDNSServiceProperty_DaemonVersion)
TCP =

TCP protocol for creating NAT port mappings

ULONG2NUM(kDNSServiceProtocol_TCP)
UDP =

UDP protocol for creating NAT port mappings

ULONG2NUM(kDNSServiceProtocol_UDP)

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeService

Creates a new DNSSD::Service



23
24
25
26
27
28
# File 'lib/dnssd/service.rb', line 23

def initialize
  @replies  = []
  @continue = true
  @thread   = nil
  @lock     = Mutex.new
end

Class Method Details

.browse(type, domain = nil, flags = 0, interface = DNSSD::InterfaceAny) ⇒ Object

Browse for services.

For each service found a DNSSD::Reply object is yielded.

service = DNSSD::Service.new
timeout 6 do
  service.browse '_http._tcp' do |r|
    puts "Found HTTP service: #{r.name}"
  end
rescue Timeout::Error
end


62
63
64
65
66
67
# File 'lib/dnssd/service.rb', line 62

def self.browse type, domain = nil, flags = 0, interface = DNSSD::InterfaceAny
  check_domain domain
  interface = DNSSD.interface_index interface unless Integer === interface

  _browse flags.to_i, interface, type, domain
end

.check_domain(domain) ⇒ Object

Raises an ArgumentError if domain is too long including NULL terminator and trailing ‘.’

Raises:

  • (ArgumentError)


108
109
110
111
112
# File 'lib/dnssd/service.rb', line 108

def self.check_domain(domain)
  return unless domain
  raise ArgumentError, 'domain name string is too long' if
    domain.length >= MAX_DOMAIN_NAME - 1
end

.enumerate_domains(flags = DNSSD::Flags::BrowseDomains, interface = DNSSD::InterfaceAny, &block) ⇒ Object

Enumerate domains available for browsing and registration.

For each domain found a DNSSD::Reply object is passed to block with #domain set to the enumerated domain.

service = DNSSD::Service.enumerate_domains

service.each do |r|
  p r.domain
  break unless r.flags.more_coming?
end


128
129
130
131
132
133
# File 'lib/dnssd/service.rb', line 128

def self.enumerate_domains(flags = DNSSD::Flags::BrowseDomains,
                      interface = DNSSD::InterfaceAny, &block)
  interface = DNSSD.interface_index interface unless Integer === interface

  _enumerate_domains flags.to_i, interface
end

.DNSSD::Service.fullname(name, type, domain) ⇒ Object

Concatenate a three-part domain name like DNSSD::Reply#fullname into a properly-escaped full domain name.

Any dots or slashes in the name must NOT be escaped.

name may be nil (to construct a PTR record name, e.g. “_ftp._tcp.apple.com”).

The type is the service type followed by the protocol, separated by a dot (e.g. “_ftp._tcp”).

The domain is the domain name, e.g. “apple.com”. Any literal dots or backslashes must be escaped.

Raises ArgumentError if the full service name cannot be constructed from the arguments.



106
107
108
109
110
111
112
113
114
115
# File 'ext/dnssd/service.c', line 106

static VALUE
dnssd_service_s_fullname(VALUE klass, VALUE _name, VALUE _type, VALUE _domain) {
  char * name, * type, * domain;

  dnssd_utf8_cstr(_name, name);
  dnssd_utf8_cstr(_type, type);
  dnssd_utf8_cstr(_domain, domain);

  return create_fullname(name, type, domain);
}

.get_property(property) ⇒ Object

Binding for DNSServiceGetProperty. The only property currently supported in DNSSD is DaemonVersion



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'ext/dnssd/service.c', line 125

static VALUE
dnssd_service_s_get_property(VALUE klass, VALUE _property) {
  char * property;
  uint32_t result = 0;
  uint32_t size = sizeof(result);
  DNSServiceErrorType e;

  dnssd_utf8_cstr(_property, property);

  e = DNSServiceGetProperty(property, (void *)&result, &size);

  dnssd_check_error_code(e);

  /* as of this writing only a uint32_t will be returned */
  return ULONG2NUM(result);
}

.getaddrinfo(host, protocol = 0, flags = 0, interface = DNSSD::InterfaceAny, &block) ⇒ Object

Retrieve address information for host on protocol

addresses = []
service.getaddrinfo reply.target do |addrinfo|
  addresses << addrinfo.address
  break unless addrinfo.flags.more_coming?
end

When using DNSSD on top of the Avahi compatibilty shim you’ll need to setup your /etc/nsswitch.conf correctly. See avahi.org/wiki/AvahiAndUnicastDotLocal for details



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/dnssd/service.rb', line 148

def self.getaddrinfo(host, protocol = 0, flags = 0,
                interface = DNSSD::InterfaceAny, &block)
  interface = DNSSD.interface_index interface unless Integer === interface

  if respond_to? :_getaddrinfo, true then
    _getaddrinfo flags.to_i, interface, protocol, host
  else
    family = case protocol
             when IPv4 then Socket::AF_INET
             when IPv6 then Socket::AF_INET6
             else protocol
             end

    addrinfo = Socket.getaddrinfo host, nil, family

    list = addrinfo.map do |_, _, a_host, ip, _|
      sockaddr = Socket.pack_sockaddr_in 0, ip
      DNSSD::Reply::AddrInfo.new(self, 0, 0, a_host, sockaddr, 0)
    end
    def list.stop; end
    list
  end
end

.query_record(fullname, record_type, record_class = DNSSD::Record::IN, flags = 0, interface = DNSSD::InterfaceAny) ⇒ Object

Retrieves an arbitrary DNS record

fullname is the full name of the resource record. record_type is the type of the resource record (see DNSSD::Resource).

flags may be either DNSSD::Flags::ForceMulticast or DNSSD::Flags::LongLivedQuery

service.query_record "hostname._afpovertcp._tcp.local",
                     DNSService::Record::SRV do |record|
  p record
end


186
187
188
189
190
191
# File 'lib/dnssd/service.rb', line 186

def self.query_record(fullname, record_type, record_class = DNSSD::Record::IN,
                 flags = 0, interface = DNSSD::InterfaceAny)
  interface = DNSSD.interface_index interface unless Integer === interface

  _query_record flags.to_i, interface, fullname, record_type, record_class
end

.register(name, type, domain, port, host = nil, text_record = nil, flags = 0, interface = DNSSD::InterfaceAny) ⇒ Object

Register a service. A DNSSD::Reply object is passed to the optional block when the registration completes.

service.register "My Files", "_http._tcp", nil, 8080 do |r|
  puts "successfully registered: #{r.inspect}"
end


201
202
203
204
205
206
207
208
# File 'lib/dnssd/service.rb', line 201

def self.register(name, type, domain, port, host = nil, text_record = nil,
             flags = 0, interface = DNSSD::InterfaceAny)
  check_domain domain
  interface = DNSSD.interface_index interface unless Integer === interface
  text_record = text_record.encode if text_record

  _register flags.to_i, interface, name, type, domain, host, port, text_record
end

.resolve(name, type = name.type, domain = name.domain, flags = 0, interface = DNSSD::InterfaceAny) ⇒ Object

Resolve a service discovered via #browse.

name may be either the name of the service found or a DNSSD::Reply from DNSSD::Service#browse. When name is a DNSSD::Reply, type and domain are automatically filled in, otherwise the service type and domain must be supplied.

The service is resolved to a target host name, port number, and text record, all contained in the DNSSD::Reply object passed to the required block.

service.resolve "foo bar", "_http._tcp", "local" do |r|
  p r
end


226
227
228
229
230
231
232
233
# File 'lib/dnssd/service.rb', line 226

def self.resolve(name, type = name.type, domain = name.domain, flags = 0,
            interface = DNSSD::InterfaceAny)
  name = name.name if DNSSD::Reply === name
  check_domain domain
  interface = DNSSD.interface_index interface unless Integer === interface

  _resolve flags.to_i, interface, name, type, domain
end

Instance Method Details

#async_each(timeout = :never) ⇒ Object



93
94
95
96
97
98
# File 'lib/dnssd/service.rb', line 93

def async_each timeout = :never
  @lock.synchronize do
    raise DNSSD::Error, 'already stopped' unless @continue
    @thread = Thread.new { each(timeout) { |r| yield r } }
  end
end

#each(timeout = :never) ⇒ Object

Raises:



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/dnssd/service.rb', line 69

def each timeout = :never
  raise DNSSD::Error, 'already stopped' unless @continue

  return enum_for __method__, timeout unless block_given?

  io = IO.new ref_sock_fd
  rd = [io]

  start_at = clock_time

  while @continue
    break unless timeout == :never || clock_time - start_at < timeout

    if IO.select rd, nil, nil, 1
      begin
        process_result
      rescue DNSSD::UnknownError
      end
      @replies.each { |r| yield r }
      @replies.clear
    end
  end
end

#push(record) ⇒ Object



100
101
102
# File 'lib/dnssd/service.rb', line 100

def push record
  @replies << record
end

#started?Boolean

Returns true if the service has been started.

Returns:

  • (Boolean)


238
239
240
# File 'lib/dnssd/service.rb', line 238

def started?
  @continue
end

#stopObject

Raises:



242
243
244
245
246
247
248
# File 'lib/dnssd/service.rb', line 242

def stop
  raise DNSSD::Error, 'service is already stopped' unless started?
  @continue = false
  @thread.join if @thread
  _stop
  self
end