Appendix C. Backend writers' guide

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. Doing so requires access to parts of the PDNS source. Contact to get the details. PowerDNS BV also writes custom backends for a fee, for which you can also contact us.

C.1. Simple read-only non-slave backends

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

	class DNSBackend
	{
	public:

	virtual bool lookup(const QType &qtype, const string &qdomain, DNSPacket *pkt_p=0, int zoneId=-1)=0; 
	virtual bool list(int domain_id)=0; 
	virtual bool get(DNSResourceRecord &r)=0;
	virtual bool getSOA(const string &name, SOAData &soadata);
	};
	
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 methods will never be called simultaneously.

A normal lookup starts like this:

        YourBackend yb;
	if(!yb.lookup(QType::CNAME,"www.powerdns.com"))
	  throw exception("Backend unable to perform lookup");
	
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.

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

	if(!yb.lookup(QType::CNAME,"www.powerdns.com"))
	  throw exception("Backend unable to perform lookup");
	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 chose to implement it differently. 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 13) are enabled, a backend can receive wildcard lookups. These have a % as the first character of the qdomain in lookup.

C.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(int id) {
    return false; // we don't support AXFR
  }
    
  bool 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
    }
    return true;
  }

  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 Loader
{
public:
  Loader()
  {
    BackendMakers().report(new RandomFactory);
    
    L<<Logger::Info<<" [RandomBackend] This is the randombackend ("__DATE__", "__TIME__") reporting"<<endl;
  }  
};

static Loader loader;
	
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 the RandomBackend is actually in most PDNS releases. By default it lives on random.example.com, but you can change that by setting random-hostname.

C.1.2. Interface definition

Classes:

Methods:

bool 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 may be interested in calling its getRemote() method, or the remote struct sockaddr_in attribute.

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 succesfully, as indicated by returning true, answers should be made available over the get() method.

Should return false if the backend does not consider itself authoritative for this zone, or if it is already known that data is not available. This might happen if PDNS is slave for a domain, but the zone has not yet been pulled. Should throw an AhuException if an error occured accessing the database. Returning true indicates that data is available.

It is legal to return true here, and have the first call to get() return false. This is interpreted just as lookup returned false in the first place.

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 occured 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.

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.

C.1.4. Declaring and reading configuration details

It is highly likely that a backend needs configuration details. On launch, these parameters need to be declared with PDNS so it knows it should accept them in the configuration file and on the commandline. Furthermore, they will be listed in the output of --help.

Declaring arguments is done by implementing the member function declareArguments() in the factory class of your backend. PDNS will call this method after launching the backend.

In the declareArguments() method, the function declare() is available. The exact definitions:

A sample implementation:

	    void declareArguments(const string &suffix)
	    {
 	      declare(suffix,"dbname","Pdns backend database name to connect to","powerdns");
	      declare(suffix,"user","Pdns backend user to connect as","powerdns");
	      declare(suffix,"host","Pdns backend host to connect to","");
	      declare(suffix,"password","Pdns backend password to connect with","");
	    }
	  

After the arguments have been declared, they can be accessed from your backend using the mustDo(), getArg() and getArgAsNum() methods. The are defined as follows in the DNSBackend class:

void setArgPrefix(const string &prefix)

Must be called before any of the other accessing functions are used. Typical usage is 'setArgPrefix("mybackend"+suffix)' in the constructor of a backend.

bool mustDo(const string &key)

Returns true if the variable key is set to anything but 'no'.

const string& getArg(const string &key)

Returns the exact value of a parameter.

int getArgAsNum(const string &key)

Returns the numerical value of a parameter. Uses atoi() internally

Sample usage from the BindBackend, using the bind-example-zones and bind-config parameters.

  if(mustDo("example-zones")) {
    insert(0,"www.example.com","A","1.2.3.4");
    /* ... */
  }
  

  if(!getArg("config").empty()) {
    BindParser BP;
    
    BP.parse(getArg("config"));
  }