Domain Name Resolver Daemon (DND)

Overview

The EPOC32 differs architecturally from the usual Unix resolver library implementations:

  • Traditionally on Unix, the resolver library is linked with the application and from the view point of the kernel, the application is just using additional sockets to communicate with the name servers.

  • on EPOC32, the name name resolver for the internet is implemented by the TCP/IP protocol module, and is shared by all applications. Applications only establish a session to this service via RHostResolver class. EPOC32 has standardised the resolver interface, and each protocol can have its own implementation of the service (with TCPIP protocol, all protocols use the same instance of the name resolver).

The IPv6/Ipv4 stack does not contain the name resolution code. Instead, it provides a special socket API for a protocol named resolver, which is used by an external server application providing the actual implementation of the name resolution.
The stack maintains a communication path or session between user RHostResolver and the resolver instance within the name resolver implementation. This path is a chain of objects starting from the RHostResolver and ending into CDndResolver, which performs the real name resolution using the other modules of the name resolver application:
  RHostResolver (application -> Socket Server)
    CHostResolver (Socket Server -> TCPIP stack)
      CDndSession (TCPIP stack -> gateway socket)
        RSocket (gateway socket -> resolver application)
          CDndResolver (resolver instance)
          - use host files
          - use UDP sockets to communicate with DNS servers

The name resolver is a server application listening to the incoming requests from a special resolver socket. When an application requests a name resolution via some RHostResolver method (GetByName, GetByAddress or Query), the resolver gateway code in the stack (res.cpp) translates this into a message which is received by the DND listener. The message includes a session id, which is assigned by the resolver gateway. TDnsRequestBase defines the basic message format.

The name resolver receives the request and uses the session id in choosing the correct resolver instance to serve the request. If a session does not yet have a resolver, it will be allocated.

The resolver processes the request and returns the result as a reply message to the socket. The resolver gateway will then translate the result into RHostResolver completion.

Major components of the implementation

Application                .
===================== RHostResolver ==========
Socket Server              .
===================== CHostResolver ==========
TCPIP Stack                .
                       CDndSession
           resolver        .
            gateway   (session id)
============ socket ===========================
DND                   (session id)
                           .
     main                  .
       \                   .
       engine              .
       /  \                .
      /    \               .
   hosts    \              .
   file    listener        .
           /   |  \        .
          /    |    \      .
         /     |      \    .
   DNS  /     DNS       \  .
 servers   protocol    resolvers
 manager  /  . |  . .    . .
         /  .  |   . .  . .
        /  .  DNS   . .. .
  (LLMNR) .  cache   query
       . .          sessions
        .
        .
     UDP/TCP
==== socket(s) =================================
        .
        .
      remote
     DNS Server

Main component

The main contains the necessary glue and wrappings that are required to run an application under EPOC32. The main implements the user interface of the application. The standard DND is run as a daemon process and there is no real user interface. However, the architecture also allows one to write a main module with full user interface, if desired.

The MDemonMain class defines the services which are required by the resolver implementation (engine) from the main module.

Engine component

The engine contains usually the implementation of the application. This is intended to be independent of the user interface. The class MDemonEngine defines the API from engine to the main.

In the name resolver implementation, the engine module is left as an intermediate between the real implementation in the listener and the main module. The CDndEngine is passed as the control instance to almost every other component of the resolver implementation. Engine creates and owns some common resources, for example: timer service (MTimeoutManager), host files handler (RHostsFile) and the configuration parameters (TDndConfigParameters).

Listener component

The listener (CDndListener) is the real main program of the name resolver implementation. If opens the socket and listens to the incoming request messages.

Using the session id, the listener delegates the task of serving the session to an available resolver instance (CDndResolver).

Resolver component

The resolver (CDndResolver) serves one RHostResolver session at time. The same session may serve different RHostResolver's at different times. It receives the requests from the gateway socket via the listener. The resolver state diagram is roughly

     Start
       |----------------> Stop
     Receive a Query
       |----------------> Stop
     Return first result
       |
   +-->|
   |   | - - -> listener forced Stop
   |   |
   |   |----------------> Stop
   | Receive Next Query
   |   |----------------> Stop
   | Return next result
   +---|
     Stop

Because many applications are only interested about the first result, and because they often leave the RHostResolver session(s) open, the gateway may sometimes steal a session for a new RHostResolver, if all resolvers are already busy. The oldest resolver that has returned at least one answer is assigned to the new RHostResolver.

The "stealing action" action does not harm the application in any other way, except that if it now tries to call Next(), it will return an error. If such application issues a new query (GetByName, GetByAddress or Query), a new resolver is just assigned to serve it.

The resolver session is always stopped, after query has completed (no further answers are available).

The resolver performs its work by establishing a number of query sessions (MDnsSession) with the DNS protocol module. The progress of an active query session is reported through callbacks (MDnsResolver).

Host File component

The hosts file handler (RHostsFile) provides the service for looking an answer from the local hosts file (if any is present). Only the GetByName or GetByAddr queries can be satisfied from the hosts file. The "hosts" file has a very simple format which binds a name to an address, for example:

127.0.0.1 localhost

10.0.0.2 server
10.0.0.3 server
10.0.0.3 alternatename

fe80::dead:beef local-beef

::1 localhost6

ff02::1 allnodes6
ff02::2 allrouters6
ff02::3 allhosts6

DNS Protocol implementation

The DNS procotol is implemented by CDndDnsclient class (or by some derived class). The class exports MDnsSource API, which is used by the resolvers to start query sessions (MDnsSession), which send and receive the actual DNS protocol messages.

Link Local Multicast Name Resolution

The link local multicast name resolution uses the DNS protocol with link local multicast. The DNS protocol messages are sent to the link with multicast destinations and a node that owns the queried name, will respond as if it was a DNS server.

When LLMNR is compiled in, the DNS protocol is implemented by CDndLlmnrSender class, which is derived from the CDndDnsclient. This class extends the normal DNS with the link local multicast name resolution additions.

Server Manager component

The server manager (MDnsServerManager) maintains the list of currently known and avalable DNS servers. The content of the list may be constantly changing depending on which interfaces are active at any particular point of time. The issues are even more complicated by the fact that initially there are no interfaces up (or at least, there may be no known servers), and name resolver just has to wait until something becomes available.

DNS Cache

The cache component (CDndCache) stores cached answers from the DNS servers. When resolvers send questions to the protocol module, the cache is first checked for a matching valid answer. If found, the cached answer is returned to the resolver, and no actual queries need to be sent.

Scoped name resolution architecture

The answers to a query can be found from different sources at different scope levels. This implementation uses three levels:

  • the hosts file is a node local scope source.

  • the "real" DNS is a global scope source.

  • LLMNR is a link local scope source.

The above order is also the current default order of the sources: first, the hosts file is checked, then real DNS and finally, the link local name resolution is attempted. The first source to give an answer defines the whole answer. Answers from different sources are never mixed.

A scope level is assigned to all queries that are received. Currently a global scope is given to all GetByName and Query requests. The scope level of the GetByAddress is the scope level of queried address.

For a specific query, only sources that have same or smaller scope level are used. For example, only LLMNR is used, when GetByAddress to a link local address (169.254/16 or fe80::/10) is requested.

GetByName queries could also have other than global scope. For example, it could be agreed that a query for a name ending with ".local" would have a link local scope, and thus only LLMNR would be tried for it (the current implementation does not have such feature coded).