Just An Application

October 31, 2013

Service Discovery In Android And iOS: Part Two – If You Want Something Done Properly … DNS Basics

1.0 Overview

Over simplifying wildly, the Domain Name System (DNS) defines a global tree-structured name space.

Each interior node and leaf node in the tree identifies a resource and can have zero or more associated resource records.

Each node in the tree has a label.

The name of a node is written as a dot separated list of the series of labels that result from traversing from that node to the root of the tree, with the root of the tree on the right

The label of the root node is the empty string so all node names end with a dot (.).

Each resource record has a class and a type.

A given DNS server maintains a database of all the resource records associated with a particular sub-tree of the entire name space.

Clients of the server can issue queries against the server’s database.

A query to a DNS server is a request for a set of resource records associated with a given node.

The query specifies a node within the DNS name space and the required record class and type.

If the specified node has a associated records of the given class and type they are returned in the response to the query.

In certain circumstances a DNS server may obtain an answer to a query it receives by performing a recursive query, that is, by itself querying another DNS name server.

Queries can be sent using either UDP or TCP. In both cases a DNS server listens on port 53.

2.0 Record Types

There are a large number of defined DNS record types.

For the purposes of service discovery we are only interested in three types

  • PTR
  • SRV
  • TXT

but we may see two further types

  • A
  • AAAA

2.1 The A Record

An A record contains an IPv4 address.

2.2 The AAAA Record

An AAAA record contains an IPv6 address.

2.3 The PTR Record

A PTR record contains the name of a node in the DNS name space.

2.4 The SRV Record

A SRV record contains information about a server.

2.5 The TXT Record

A TXT record contains arbitrary text.

3.0 DNS Messages

Queries to a DNS server and responses from it use a single message format.

3.1 The DNS Message Format

dns_message

3.1.1 Questions

A sequence of zero or more Questions.

If the message is a query than this section contains the question expressing the query.

If the message is a response than this section contains the question sent in the query to which this is the response.

3.1.2 Answers

A sequence of zero or more resource records.

If the message is a non-error response then this section contains the resource record(s) which match the query to which this is the response.

3.1.3 Authority

A sequence of zero or more resource records.

If the message is an error response then this section may contain resource record(s) identifying DNS servers which can be queried instead.

3.1.4 Additional

A sequence of zero or more Resource records.

If the message is a non-error response then this section may contain resource records, which do not match the query, but are related to it, e.g., other resource records for the same node.

3.2 The Header Format

header

3.2.1 The Message Id

If the message is a query then this is the id of the query allocated by the entity performing the query.

If the message is a response then this is the id of the query to which this is the response.

3.2.2 QR

A single bit field

0 if the message contains a query.

1 if the message contains a response.

3.2.3 Op Code

A four bit field

0 for a standard query

3.2.4 AA

A single bit field

Only valid in a response.

If set then the DNS server is the authority for the node specified in the question

3.2.5 TC

A single bit field

If set then this message was truncated.

3.2.6 RD

A single bit field

If set in a query then the server is requested to perform a recursive query if necessary.

If set in the query it will also bet set in the response.

3.2.7 RA

A single bit field

Only valid in a response.

If set then the server is capable of performing recursive queries.

3.2.7 Z

A three bit field

Not used. Reserved.

3.2.8 Response Code

A four bit field.

Only valid in a response.

0 if no error occurred.

3.2.9 Question Count

A 16-bit unsigned integer.

The number of questions in the questions section of this message.

3.2.10 Answer Count

A 16-bit unsigned integer.

The number of resource records in the answers section of this message.

3.2.11 Authority Count

A 16-bit unsigned integer.

The number of resource records in the authority section of this message.

3.2.12 Additional Count

A 16-bit unsigned integer.

The number of resource records in the additional section of this message.

3.3 The Question Format

question

3.3.1 Name

An N byte field.

The name of the node whose resource records are being requested.

3.3.2 Type

A 16-bit field.

The type of the requested resource record(s).

3.3.3 Class

A 16-bit field.

The class of the requested resource record(s).

3.4 The Resource Record Format

rr

3.4.1 Name

An N byte field.

The encoded name of the node to which this resource record applies.

3.4.2 Type

A 16-bit field.

The type of this resource record.

3.4.3 Class

A 16-bit field.

The class of this resource record.

3.4.4 TTL

A 32-bit unsigned integer.

The number of seconds after which this record is no longer valid.

3.4.5 Record Data Length

A 16-bit unsigned integer field.

The length in bytes of the data that follows.

3.4.6 Record Data

An N byte field.

Contains the type specific record data. (see below)

3.5 Classes

Although there are a number of DNS classes defined the class of a resource record is usually IN (Internet) which has the value 1.

3.6 Types

Resource record types are specified using 16-bit integers.

For the resource record types we are interested or which we might see the values are as follows

Record Type Value
A 1
AAAA 28
PTR 12
SRV 33
TXT 16

3.7 Node Names

3.7.1 Basic Encoding

In the basic encosing a node name is encoded as a sequence of labels.

Each label is encoded as a byte field specifying the length of the label followed by the bytes encoding the label.

Since the root label is the empty string an node name encoded in this way is always terminated by a zero byte.

3.7.1 Compressed Encoding

Alternatively all of a node name or the suffix of a node name can be encoded as a reference to all or the suffix of a previous node name in the message.

If the top two bits of the byte encoding the label length are set then the other 6-bits of the byte and the 8-bits of following byte specify the offset in the message of the ‘rest’ of the node name.

4.0 Specific Record Data Formats

4.1 A Record Data Format

A 32-bit IPv4 address in network byte order.

4.2 AAAA Record Data Format

A 128-bit IPV6 address in network byte order.

4.1 PTR Record Data Format

An encoded node name.

4.3 SRV Record Data Format

srv_data_format

4.3.1 Priority

A 16-bit unsigned int in network byte order.

Intended to be used by a client to choose between servers.

The client should attempt to use the server with the lowest priority.

4.3.2 Weight

A 16-bit unsigned int network byte order.

Intended to be used by a client to choose between servers of the same priority.

The client should attempt to use the server with the highest weight.

4.3.2 Port

A 16-bit unsigned int in network byte order.

The port on which the server is listening.

4.3.3 Target

An N byte field.

The encoded name of the node which represents the host on which the server is running.

4.4 TXT Record Data Format

The format of the data of a TXT record is context specific.


Copyright (c) 2013 By Simon Lewis. All Rights Reserved.

Unauthorized use and/or duplication of this material without express and written permission from this blog’s author and owner Simon Lewis is strictly prohibited.

Excerpts and links may be used, provided that full and clear credit is given to Simon Lewis and justanapplication.wordpress.com with appropriate and specific direction to the original content.

Advertisements

October 29, 2013

Service Discovery in Android And iOS – Part One: The android.net.nsd.NsdManager Class Considered Not Very Useful At All

Suppose I was writing an Android program and for some strange reason I wanted to automatically find any printers on the local network that support the Internet Printing Protocol (IPP).

Quite why I would want to do this is anyone’s guess. Perhaps I want to do something bizarre like support printing from
my program.

Anyway, in theory I am in luck because I can use an instance of the android.net.NsdManager class which supports
mDNS based service discovery.

1.0 Getting An NsdManager Instance

An instance of android.net.nsd.NsdManager can be obtained by a call to the Context.getSystemService
method with the argumentcContext.NSD_SERVICE.

So in an Application’s main Activity we can do something like this

    NsdManager manager = (NsdManager)getSystemService(NSD_SERVICE);

2.0 Starting Service Discovery

The process of service discovery is started by invoking the NsdManager.discoverServices method which is defined like this

    public void discoverServices(String serviceType, int protocolType, NsdManager.DiscoveryListener listener)

The methods defined by the DiscoveryListener instance passed as the listener argument are invoked asynchronously.

3.0 android.net.nsd.NsdManager.DiscoveryListener

The DiscoveryListener interface defines two methods to report the outcome of starting the process of service discovery

    public void onDiscoveryStarted(String serviceType)

    public void onStartDiscoveryFailed(String serviceType, int errorCode)

two methods to report the outcome of stopping the process of service discovery

    public void onDiscoveryStopped(String serviceType)

    public void onStopDiscoveryFailed(String serviceType, int errorCode)

a method to report that a service has been discovered

    public void onServiceFound(NsdServiceInfo serviceInfo)

and a method to report that a previously discovered service has disappeared.

    public void onServiceLost (NsdServiceInfo serviceInfo)

4.0 Resolving A Service

If the onServiceFound method of the DiscoveryListener instance passed to the call to discoverServices is invoked then a service has been discovered.

We can then call the NsdManager.resolveService which is declared like this

    public void resolveService(NsdServiceInfo serviceInfo, NsdManager.ResolveListener listener)

The serviceInfo argument is NsdServiceInfo instance passed to onServiceFound.

The methods defined by the ResolveListener instance passed as the listener argument are invoked asynchronously.

5.0 android.net.nsd.NsdManager.ResolveListener

The ResolveListener interface declares a method to report that a service has been resolved

    public void onServiceResolved(NsdServiceInfo serviceInfo)

and a method to report that it was not possible to resolve a service

    public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode)

6.0 The FindServicesNSD Class

Here is the source code for the FindServicesNSD class which encapsulates the use of the NsdManager service
discovery methods

    // FindServicesNSD.java
    
    // Copyright (c) 2013 By Simon Lewis. All Rights Reserved.
    
    package xper.villiars;
    
    import android.net.nsd.NsdManager;
    import android.net.nsd.NsdManager.DiscoveryListener;
    import android.net.nsd.NsdManager.ResolveListener;
    import android.net.nsd.NsdServiceInfo;
    import android.util.Log;
    
    final class FindServicesNSD
                implements
                    DiscoveryListener,
                    ResolveListener
    {
        // DiscoveryListener
	
        @Override
        public void onDiscoveryStarted(String theServiceType)
        {
            Log.d(TAG, "onDiscoveryStarted");
        }
    
        @Override
        public void onStartDiscoveryFailed(String theServiceType, int theErrorCode)
        {
            Log.d(TAG, "onStartDiscoveryFailed(" + theServiceType + ", " + theErrorCode);
        }
	
        @Override
        public void onDiscoveryStopped(String serviceType)
        {
            Log.d(TAG, "onDiscoveryStopped");
        }
	
        @Override
        public void onStopDiscoveryFailed(String theServiceType, int theErrorCode)
        {
            Log.d(TAG, "onStartDiscoveryFailed(" + theServiceType + ", " + theErrorCode);
        }
	
        @Override
        public void onServiceFound(NsdServiceInfo theServiceInfo)
        {
            Log.d(TAG, "onServiceFound(" + theServiceInfo + ")");
            Log.d(TAG, "name == " + theServiceInfo.getServiceName());
            Log.d(TAG, "type == " + theServiceInfo.getServiceType());
            serviceFound(theServiceInfo);
        }
    
        @Override
        public void onServiceLost(NsdServiceInfo theServiceInfo)
        {
            Log.d(TAG, "onServiceLost(" + theServiceInfo + ")");
        }
    
        // Resolve Listener
    
        @Override
        public void onServiceResolved(NsdServiceInfo theServiceInfo)
        {
            Log.d(TAG, "onServiceResolved(" + theServiceInfo + ")");
            Log.d(TAG, "name == " + theServiceInfo.getServiceName());
            Log.d(TAG, "type == " + theServiceInfo.getServiceType());
            Log.d(TAG, "host == " + theServiceInfo.getHost());
            Log.d(TAG, "port == " + theServiceInfo.getPort());
	    }
	
        @Override
        public void onResolveFailed(NsdServiceInfo theServiceInfo, int theErrorCode)
        {
            Log.d(TAG, "onResolveFailed(" + theServiceInfo + ", " + theErrorCode);
        }
    
        //
	
        FindServicesNSD(NsdManager theManager, String theServiceType)
        {
            manager     = theManager;
            serviceType = theServiceType;
        }
	
        void run()
        {
            manager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, this);
        }
	
        private void serviceFound(NsdServiceInfo theServiceInfo)
        {
            manager.resolveService(theServiceInfo, this);
        }
	
        //
	
        private NsdManager   manager;
        private String       serviceType;
	
        //
	
        private static final String TAG = "FindServicesNSD";
    }

7.0 Example

Running an instance of FindServicesNSD created like this

    new FindServicesNSD((NsdManager)getSystemService(NSD_SERVICE), "_ipp._tcp")

on a device on a network with an IPP capable printer present results in the following


    D/FindServicesNSD( 2459): onDiscoveryStarted
    D/FindServicesNSD( 2459): onServiceFound(name:\
        Canon MG6200 seriestype: _ipp._tcp.host: nullport: 0txtRecord: null)
    D/FindServicesNSD( 2459): name == Canon MG6200 series
    D/FindServicesNSD( 2459): type == _ipp._tcp.
    D/FindServicesNSD( 2459): onServiceResolved(name:\
        Canon32MG620032seriestype: ._ipp._tcphost: /10.0.1.5port: 631txtRecord: null)
    D/FindServicesNSD( 2459): name == Canon32MG620032series
    D/FindServicesNSD( 2459): type == ._ipp._tcp
    D/FindServicesNSD( 2459): host == /10.0.1.5
    D/FindServicesNSD( 2459): port == 631

This is output from adb logcat run through grep FindSevicesNSD then slightly edited for clarity.

8.0 android.net.nsd.NsdServiceInfo: Where’s The Rest Of It ?

Given the instance of NsdServiceInfo passed to the onServiceResolved method it is possible to determine, the name and type of the service, the host on which it is running and the port it is listening on.

There is however additional per service information available via the mDNS service discovery mechanism which is not made accessible.[1]

While it may be possible to use a service without the missing information, this is not guaranteed to be the case

Using a service supporting IPP is one example where at least one piece of the missing per-service information is necessary.

9.0 Conclusion

Although the NsdManager class is documented as supporting mDNS based service discovery the deficiency in the implementation means that it ends up being not very useful at all.

Notes

  1. They could have made it accessible, its just that they had it and then they threw it away !


Copyright (c) 2013 By Simon Lewis. All Rights Reserved.

Unauthorized use and/or duplication of this material without express and written permission from this blog’s author and owner Simon Lewis is strictly prohibited.

Excerpts and links may be used, provided that full and clear credit is given to Simon Lewis and justanapplication.wordpress.com with appropriate and specific direction to the original content.

Blog at WordPress.com.

%d bloggers like this: