<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN""http://www.w3.org/TR/html4/loose.dtd"> <HTML ><HEAD ><TITLE >Backend writers' guide</TITLE ><META NAME="GENERATOR" CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK REL="HOME" TITLE="PowerDNS manual" HREF="index.html"><LINK REL="PREVIOUS" TITLE="How PDNS translates DNS queries into backend queries" HREF="dns-to-query.html"><LINK REL="NEXT" TITLE="Reporting errors" HREF="backend-error-reporting.html"></HEAD ><BODY CLASS="APPENDIX" BGCOLOR="#FFFFFF" TEXT="#000000" LINK="#0000FF" VLINK="#840084" ALINK="#0000FF" ><DIV CLASS="NAVHEADER" ><TABLE SUMMARY="Header navigation table" WIDTH="100%" BORDER="0" CELLPADDING="0" CELLSPACING="0" ><TR ><TH COLSPAN="3" ALIGN="center" >PowerDNS manual</TH ></TR ><TR ><TD WIDTH="10%" ALIGN="left" VALIGN="bottom" ><A HREF="dns-to-query.html" ACCESSKEY="P" >Prev</A ></TD ><TD WIDTH="80%" ALIGN="center" VALIGN="bottom" ></TD ><TD WIDTH="10%" ALIGN="right" VALIGN="bottom" ><A HREF="backend-error-reporting.html" ACCESSKEY="N" >Next</A ></TD ></TR ></TABLE ><HR ALIGN="LEFT" WIDTH="100%"></DIV ><DIV CLASS="APPENDIX" ><H1 ><A NAME="BACKEND-WRITERS-GUIDE" ></A >Appendix C. Backend writers' guide</H1 ><P > 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 <A HREF="http://wiki.powerdns.com" TARGET="_top" >the wiki</A >. </P ><P > 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. </P ><P > <DIV CLASS="WARNING" ><P ></P ><TABLE CLASS="WARNING" WIDTH="100%" BORDER="0" ><TR ><TD WIDTH="25" ALIGN="CENTER" VALIGN="TOP" ><IMG SRC="../images/warning.gif" HSPACE="5" ALT="Warning"></TD ><TD ALIGN="LEFT" VALIGN="TOP" ><P > 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. </P ></TD ></TR ></TABLE ></DIV > </P ><P > <DIV CLASS="WARNING" ><P ></P ><TABLE CLASS="WARNING" WIDTH="100%" BORDER="0" ><TR ><TD WIDTH="25" ALIGN="CENTER" VALIGN="TOP" ><IMG SRC="../images/warning.gif" HSPACE="5" ALT="Warning"></TD ><TD ALIGN="LEFT" VALIGN="TOP" ><P > 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. </P ></TD ></TR ></TABLE ></DIV > </P ><DIV CLASS="SECT1" ><H1 CLASS="SECT1" ><A NAME="SIMPLE-BACKENDS" >C.1. Simple read-only native backends</A ></H1 ><P > 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: <PRE CLASS="PROGRAMLISTING" > 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); }; </PRE > Note that the first three methods must be implemented. <CODE CLASS="FUNCTION" >getSOA()</CODE > has a useful default implementation. </P ><P > 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. </P ><P > Some examples, a more formal specification is down below. A normal lookup starts like this: <PRE CLASS="PROGRAMLISTING" > YourBackend yb; yb.lookup(QType::CNAME,"www.powerdns.com"); </PRE > 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. </P ><P > PDNS will then call the <CODE CLASS="FUNCTION" >get()</CODE > method to get <B CLASS="COMMAND" >DNSResourceRecord</B >s back. The following code illustrates a typical query: <PRE CLASS="PROGRAMLISTING" > yb.lookup(QType::CNAME,"www.powerdns.com"); DNSResourceRecord rr; while(yb.get(rr)) cout<<"Found cname pointing to '"+rr.content+"'"<<endl; } </PRE > </P ><P > 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 <CODE CLASS="FUNCTION" >getSOA()</CODE > 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 <CODE CLASS="FUNCTION" >getSOA()</CODE >. </P ><P > 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 <CODE CLASS="FUNCTION" >list()</CODE > method is used. Conveniently, the domain_id is also available in the <B CLASS="COMMAND" >SOAData</B > structure. </P ><P > The following lists the contents of a zone called "powerdns.com". <PRE CLASS="PROGRAMLISTING" > 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; </PRE > </P ><P > Please note that when so called 'fancy records' (see <A HREF="fancy-records.html" >Chapter 14</A >) are enabled, a backend can receive wildcard lookups. These have a % as the first character of the qdomain in lookup. </P ><DIV CLASS="SECT2" ><H2 CLASS="SECT2" ><A NAME="AEN5636" >C.1.1. A sample minimal backend</A ></H2 ><P > This backend only knows about the host "random.powerdns.com", and furthermore, only about its A record: <PRE CLASS="PROGRAMLISTING" >/* FIRST PART */ class RandomBackend : public DNSBackend { public: bool list(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; </PRE > 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. </P ><P > 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. </P ><P > 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. </P ><P > 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 <B CLASS="COMMAND" >random-hostname</B >. </P ><P > NOTE: this simple backend neglects to handle case properly! </P ></DIV ><DIV CLASS="SECT2" ><H2 CLASS="SECT2" ><A NAME="AEN5645" >C.1.2. Interface definition</A ></H2 ><P > Classes: <DIV CLASS="TABLE" ><A NAME="AEN5648" ></A ><P ><B >Table C-1. DNSResourceRecord class</B ></P ><TABLE BORDER="1" CLASS="CALSTABLE" ><COL><COL><TBODY ><TR ><TD >QType qtype</TD ><TD >QType of this record</TD ></TR ><TR ><TD >string qname</TD ><TD >name of this record</TD ></TR ><TR ><TD >string content</TD ><TD >ASCII representation of right hand side</TD ></TR ><TR ><TD >u_int16_t priority</TD ><TD >priority of an MX record.</TD ></TR ><TR ><TD >u_int32_t ttl</TD ><TD >Time To Live of this record</TD ></TR ><TR ><TD >int domain_id</TD ><TD >ID of the domain this record belongs to</TD ></TR ><TR ><TD >time_t last_modified</TD ><TD >If unzero, last time_t this record was changed</TD ></TR ></TBODY ></TABLE ></DIV > </P ><P > <DIV CLASS="TABLE" ><A NAME="AEN5674" ></A ><P ><B >Table C-2. SOAData struct</B ></P ><TABLE BORDER="1" CLASS="CALSTABLE" ><COL><COL><TBODY ><TR ><TD >string nameserver</TD ><TD >Name of the master nameserver of this zone</TD ></TR ><TR ><TD >string hostmaster</TD ><TD >Hostmaster of this domain. May contain an @</TD ></TR ><TR ><TD >u_int32_t serial</TD ><TD >Serial number of this zone</TD ></TR ><TR ><TD >u_int32_t refresh</TD ><TD >How often this zone should be refreshed</TD ></TR ><TR ><TD >u_int32_t retry</TD ><TD >How often a failed zone pull should be retried.</TD ></TR ><TR ><TD >u_int32_t expire</TD ><TD >If zone pulls failed for this long, retire records</TD ></TR ><TR ><TD >u_int32_t default_ttl</TD ><TD >Difficult</TD ></TR ><TR ><TD >int domain_id</TD ><TD >The ID of the domain within this backend. Must be filled!</TD ></TR ><TR ><TD >DNSBackend *db</TD ><TD >Pointer to the backend that feels authoritative for a domain and can act as a slave</TD ></TR ></TBODY ></TABLE ></DIV > </P ><P > Methods: <P ></P ><DIV CLASS="VARIABLELIST" ><DL ><DT >void lookup(const QType &qtype, const string &qdomain, DNSPacket *pkt=0, int zoneId=-1)</DT ><DD ><P > 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 <CODE CLASS="FUNCTION" >getCode()</CODE > method and into a string with the <CODE CLASS="FUNCTION" >getCode()</CODE >. </P ><P > 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 <CODE CLASS="FUNCTION" >getRemote(DNSPacket *)</CODE > method. Alternatively, <CODE CLASS="FUNCTION" >bool getRemote(struct sockaddr *sa, socklen_t *len)</CODE > is available. </P ><P > Note that <B CLASS="COMMAND" >qdomain</B > 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 <CODE CLASS="FUNCTION" >get()</CODE >! </P ><P > 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. </P ><P > If initiated succesfully, as indicated by returning <B CLASS="COMMAND" >true</B >, answers should be made available over the <CODE CLASS="FUNCTION" >get()</CODE > method. </P ><P > Should throw an AhuException if an error occured accessing the database. Returning otherwise indicates that the query was started succesfully. 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. </P ><P > It is legal to return here, and have the first call to <CODE CLASS="FUNCTION" >get()</CODE > return false. This is interpreted as 'no data' </P ></DD ><DT >bool list(int domain_id)</DT ><DD ><P > Initiates a list of the indicated domain. Records should then be made available via the <CODE CLASS="FUNCTION" >get()</CODE > method. Need not include the SOA record. If it is, PDNS will not get confused. </P ><P > 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. </P ></DD ><DT >bool get(DNSResourceRecord &rr)</DT ><DD ><P > Request a DNSResourceRecord from a query started by <CODE CLASS="FUNCTION" >get()</CODE > of <CODE CLASS="FUNCTION" >list()</CODE >. If this functions returns <B CLASS="COMMAND" >true</B >, <B CLASS="COMMAND" >rr</B > has been filled with data. When it returns false, no more data is available, and <B CLASS="COMMAND" >rr</B > 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. </P ><P > The qname field of the DNSResourceRecord should be filled out with the exact <CODE CLASS="FUNCTION" >qdomain</CODE > passed to lookup, preserving its case. So if a query for 'CaSe.yourdomain.com' comes in and your database contains dat afor 'case.yourdomain.com', the qname field of rr should contin 'CaSe.yourdomain.com'! </P ><P > Should throw an AhuException in case a database error occurred. </P ></DD ><DT >bool getSOA(const string &name, SOAData &soadata)</DT ><DD ><P > If the backend considers itself authoritative over domain <CODE CLASS="FUNCTION" >name</CODE >, this method should fill out the passed <B CLASS="COMMAND" >SOAData</B > 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. </P ></DD ></DL ></DIV > </P ></DIV ></DIV ></DIV ><DIV CLASS="NAVFOOTER" ><HR ALIGN="LEFT" WIDTH="100%"><TABLE SUMMARY="Footer navigation table" WIDTH="100%" BORDER="0" CELLPADDING="0" CELLSPACING="0" ><TR ><TD WIDTH="33%" ALIGN="left" VALIGN="top" ><A HREF="dns-to-query.html" ACCESSKEY="P" >Prev</A ></TD ><TD WIDTH="34%" ALIGN="center" VALIGN="top" ><A HREF="index.html" ACCESSKEY="H" >Home</A ></TD ><TD WIDTH="33%" ALIGN="right" VALIGN="top" ><A HREF="backend-error-reporting.html" ACCESSKEY="N" >Next</A ></TD ></TR ><TR ><TD WIDTH="33%" ALIGN="left" VALIGN="top" >How PDNS translates DNS queries into backend queries</TD ><TD WIDTH="34%" ALIGN="center" VALIGN="top" > </TD ><TD WIDTH="33%" ALIGN="right" VALIGN="top" >Reporting errors</TD ></TR ></TABLE ></DIV ></BODY ></HTML >