Appendix C. Backend writers' guide

Table of Contents

1. Simple read-only native backends
1.1. A sample minimal backend
1.2. Interface definition
2. Reporting errors
3. Declaring and reading configuration details
4. Read/write slave-capable backends
4.1. Supermaster/Superslave capability
5. Read/write master-capable backends

PDNS backends are implemented via a simple yet powerful C++ interface. If your needs are not met by the PipeBackend, you may want to write your own. Before doing any PowerDNS development, please visit the wiki.

A backend contains zero DNS logic. It need not look for CNAMEs, it need not return NS records unless explicitly asked for, etcetera. All DNS logic is contained within PDNS itself - backends should simply return records matching the description asked for.

[Warning]Warning

However, please note that your backend can get queries in aNy CAsE! If your database is case sensitive, like most are (with the notable exception of MySQL), you must make sure that you do find answers which differ only in case.

[Warning]Warning

PowerDNS may instantiate multiple instances of your backend, or destroy existing copies and instantiate new ones. Backend code should therefore be thread-safe with respect to its static data. Additionally, it is wise if instantiation is a fast operation, with the possible exception of the first construction.

1. Simple read-only native backends

Implementing a backend consists of inheriting from the DNSBackend class. For read-only backends, which do not support slave operation, only the following methods are relevant:

	class DNSBackend
	{
	public:

	virtual void lookup(const QType &qtype, const string &qdomain, DNSPacket *pkt_p=0, int zoneId=-1)=0;
	virtual bool list(const string &target, int domain_id)=0;
	virtual bool get(DNSResourceRecord &r)=0;
	virtual bool getSOA(const string &name, SOAData &soadata, DNSPacket *p=0);
	};
	

Note that the first three methods must be implemented. getSOA() has a useful default implementation.

The semantics are simple. Each instance of your class only handles one (1) query at a time. There is no need for locking as PDNS guarantees that your backend will never be called reentrantly.

Some examples, a more formal specification is down below. A normal lookup starts like this:

        YourBackend yb;
	yb.lookup(QType::CNAME,"www.powerdns.com");
	

Your class should now do everything to start this query. Perform as much preparation as possible - handling errors at this stage is better for PDNS than doing so later on. A real error should be reported by throwing an exception.

PDNS will then call the get() method to get DNSResourceRecords back. The following code illustrates a typical query:

	yb.lookup(QType::CNAME,"www.powerdns.com");

	DNSResourceRecord rr;
	while(yb.get(rr))
   	  cout<<"Found cname pointing to '"+rr.content+"'"<<endl;
	}
	

Each zone starts with a Start of Authority (SOA) record. This record is special so many backends will choose to implement it specially. The default getSOA() method performs a regular lookup on your backend to figure out the SOA, so if you have no special treatment for SOA records, where is no need to implement your own getSOA().

Besides direct queries, PDNS also needs to be able to list a zone, to do zone transfers for example. Each zone has an id which should be unique within the backend. To list all records belonging to a zone id, the list() method is used. Conveniently, the domain_id is also available in the SOAData structure.

The following lists the contents of a zone called "powerdns.com".

	SOAData sd;
	if(!yb.getSOA("powerdns.com",sd))  // are we authoritative over powerdns.com?
	  return RCode::NotAuth;           // no

	yb.list(sd.domain_id); 
	while(yb.get(rr))
   	  cout<<rr.qname<<"\t IN "<<rr.qtype.getName()<<"\t"<<rr.content<<endl;
	

Please note that when so called 'fancy records' (see Chapter 19, Fancy records for seamless email and URL integration) are enabled, a backend can receive wildcard lookups. These have a % as the first character of the qdomain in lookup.

1.1. A sample minimal backend

This backend only knows about the host "random.powerdns.com", and furthermore, only about its A record:

/* FIRST PART */
class RandomBackend : public DNSBackend
{
public:
  bool list(const string &target, int id)
  {
    return false; // we don't support AXFR
  }
    
  void lookup(const QType &type, const string &qdomain, DNSPacket *p, int zoneId)
  {
    if(type.getCode()!=QType::A || qdomain!="random.powerdns.com")  // we only know about random.powerdns.com A
      d_answer="";                                                  // no answer
    else {
      ostringstream os;
      os<<random()%256<<"."<<random()%256<<"."<<random()%256<<"."<<random()%256;
      d_answer=os.str();                                           // our random ip address
    }
  }

  bool get(DNSResourceRecord &rr)
  {
    if(!d_answer.empty()) {
      rr.qname="random.powerdns.com";                               // fill in details
      rr.qtype=QType::A;                                            // A record
      rr.ttl=86400;                                                 // 1 day
      rr.content=d_answer;

      d_answer="";                                                  // this was the last answer
      
      return true;
    }
    return false;                                                   // no more data
  }
  
private:
  string d_answer;
};

/* SECOND PART */

class RandomFactory : public BackendFactory
{
public:
  RandomFactory() : BackendFactory("random") {}

  DNSBackend *make(const string &suffix)
  {
    return new RandomBackend();
  }
};

/* THIRD PART */

class RandomLoader
{
public:
  RandomLoader()
  {
    BackendMakers().report(new RandomFactory);
    
    L<<Logger::Info<<" [RandomBackend] This is the randombackend ("__DATE__", "__TIME__") reporting"<<endl;
  }  
};

static RandomLoader randomloader;
	

This simple backend can be used as an 'overlay'. In other words, it only knows about a single record, another loaded backend would have to know about the SOA and NS records and such. But nothing prevents us from loading it without another backend.

The first part of the code contains the actual logic and should be pretty straightforward. The second part is a boilerplate 'factory' class which PDNS calls to create randombackend instances. Note that a 'suffix' parameter is passed. Real life backends also declare parameters for the configuration file; these get the 'suffix' appended to them. Note that the "random" in the constructor denotes the name by which the backend will be known.

The third part registers the RandomFactory with PDNS. This is a simple C++ trick which makes sure that this function is called on execution of the binary or when loading the dynamic module.

Please note that a RandomBackend is actually in most PDNS releases. By default it lives on random.example.com, but you can change that by setting random-hostname.

NOTE: this simple backend neglects to handle case properly!

1.2. Interface definition

Classes:

Table C.1. DNSResourceRecord class

QType qtypeQType of this record
string qnamename of this record
string contentASCII representation of right hand side
uint16_t prioritypriority of an MX record.
uint32_t ttlTime To Live of this record
int domain_idID of the domain this record belongs to
time_t last_modifiedIf unzero, last time_t this record was changed
bool authUsed for DNSSEC operations. See Chapter 12, Serving authoritative DNSSEC data and more specifically Section 3, “Migration”. It is also useful to check out the rectifyZone() in pdnssec.cc


Table C.2. SOAData struct

string nameserverName of the master nameserver of this zone
string hostmasterHostmaster of this domain. May contain an @
u_int32_t serialSerial number of this zone
u_int32_t refreshHow often this zone should be refreshed
u_int32_t retryHow often a failed zone pull should be retried.
u_int32_t expireIf zone pulls failed for this long, retire records
u_int32_t default_ttlDifficult
int domain_idThe ID of the domain within this backend. Must be filled!
DNSBackend *dbPointer to the backend that feels authoritative for a domain and can act as a slave


Methods:

void lookup(const QType &qtype, const string &qdomain, DNSPacket *pkt=0, int zoneId=-1)

This function is used to initiate a straight lookup for a record of name 'qdomain' and type 'qtype'. A QType can be converted into an integer by invoking its getCode() method and into a string with the getCode().

The original question may or may not be passed in the pointer p. If it is, you can retrieve (from 1.99.11 onwards) information about who asked the question with the getRemote(DNSPacket *) method. Alternatively, bool getRemote(struct sockaddr *sa, socklen_t *len) is available.

Note that qdomain can be of any case and that your backend should make sure it is in effect case insensitive. Furthermore, the case of the original question should be retained in answers returned by get()!

Finally, the domain_id might also be passed indicating that only answers from the indicated zone need apply. This can both be used as a restriction or as a possible speedup, hinting your backend where the answer might be found.

If initiated successfully, as indicated by returning true, answers should be made available over the get() method.

Should throw an AhuException if an error occurred accessing the database. Returning otherwise indicates that the query was started successfully. If it is known that no data is available, no exception should be thrown! An exception indicates that the backend considers itself broken - not that no answers are available for a question.

It is legal to return here, and have the first call to get() return false. This is interpreted as 'no data'.

bool list(int domain_id)

Initiates a list of the indicated domain. Records should then be made available via the get() method. Need not include the SOA record. If it is, PDNS will not get confused.

Should return false if the backend does not consider itself authoritative for this zone. Should throw an AhuException if an error occurred accessing the database. Returning true indicates that data is or should be available.

bool get(DNSResourceRecord &rr)

Request a DNSResourceRecord from a query started by get() of list(). If this functions returns true, rr has been filled with data. When it returns false, no more data is available, and rr does not contain new data. A backend should make sure that it either fills out all fields of the DNSResourceRecord or resets them to their default values.

The qname field of the DNSResourceRecord should be filled out with the exact qdomain passed to lookup, preserving its case. So if a query for 'CaSe.yourdomain.com' comes in and your database contains data for 'case.yourdomain.com', the qname field of rr should contain 'CaSe.yourdomain.com'!

Should throw an AhuException in case a database error occurred.

bool getSOA(const string &name, SOAData &soadata)

If the backend considers itself authoritative over domain name, this method should fill out the passed SOAData structure and return a positive number. If the backend is functioning correctly, but does not consider itself authoritative, it should return 0. In case of errors, an AhuException should be thrown.