The backends above are 'natively capable' in that they contain all data relevant for a domain and do not pull in data from other nameservers. To enable storage of information, a backend must be able to do more.
Before diving into the details of the implementation some theory is in order. Slave domains are pulled from the master. PDNS needs to know for which domains it is to be a slave, and for each slave domain, what the IP address of the master is.
A slave zone is pulled from a master, after which it is 'fresh', but this is only temporary. In the SOA record of a zone there is a field which specifies the 'refresh' interval. After that interval has elapsed, the slave nameserver needs to check at the master ff the serial number there is higher than what is stored in the backend locally.
If this is the case, PDNS dubs the domain 'stale', and schedules a transfer of data from the remote. This transfer remains scheduled until the serial numbers remote and locally are identical again.
This theory is implemented by the getUnfreshSlaveInfos
method, which is called on all backends periodically.
This method fills a vector of SlaveDomains with domains that are unfresh and possibly stale.
PDNS then retrieves the SOA of those domains remotely and locally and creates a list of stale domains. For each of these domains, PDNS starts a zone transfer to resynchronise. Because zone transfers can fail, it is important that the interface to the backend allows for transaction semantics because a zone might otherwise be left in a halfway updated situation.
The following excerpt from the DNSBackend shows the relevant functions:
class DNSBackend { public: /* ... */ virtual bool getDomainInfo(const string &domain, DomainInfo &di); virtual bool isMaster(const string &name, const string &ip); virtual bool startTransaction(const string &qname, int id); virtual bool commitTransaction(); virtual bool abortTransaction(); virtual bool feedRecord(const DNSResourceRecord &rr); virtual void getUnfreshSlaveInfos(vector<DomainInfo>* domains); virtual void setFresh(int id); /* ... */ }
The mentioned DomainInfo struct looks like this:
Table C.3. DomainInfo struct
uint32_t id | ID of this zone within this backend |
string master | IP address of the master of this domain, if any |
uint32_t serial | Serial number of this zone |
uint32_t notified_serial | Last serial number of this zone that slaves have seen |
time_t last_check | Last time this zone was checked over at the master for changes |
enum {Master,Slave,Native} kind | Type of zone |
DNSBackend *backend | Pointer to the backend that feels authoritative for a domain and can act as a slave |
These functions all have a default implementation that returns false - which explains that these methods can be omitted in simple backends. Furthermore, unlike with simple backends, a slave capable backend must make sure that the 'DNSBackend *db' field of the SOAData record is filled out correctly - it is used to determine which backend will house this zone.
If a backend considers itself a slave for the domain name and if the IP address in ip is indeed a master, it should return true. False otherwise. This is a first line of checks to guard against reloading a domain unnecessarily.
When called, the backend should examine its list of slave domains and add any unfresh ones to the domains vector.
This is like getUnfreshSlaveInfos, but for a specific domain. If the backend considers itself authoritative for the named
zone, di
should be filled out, and 'true' be returned. Otherwise return false.
When called, the backend should start a transaction that can be committed or rolled back atomically later on. In SQL terms, this function should BEGIN a transaction and DELETE all records.
Insert this record.
Make the changes effective. In SQL terms, execute COMMIT.
Abort changes. In SQL terms, execute ABORT.
Indicate that a domain has either been updated or refreshed without the need for a retransfer. This causes
the domain to vanish from the vector modified by getUnfreshSlaveInfos()
.
PDNS will always call startTransaction()
before making calls to feedRecord()
.
Although it is likely that abortTransaction()
will be called in case of problems, backends should also
be prepared to abort from their destructor.
The actual code in PDNS is currently (1.99.9):
Resolver resolver; resolver.axfr(remote,domain.c_str()); db->startTransaction(domain, domain_id); L<<Logger::Error<<"AXFR started for '"<<domain<<"'"<<endl; Resolver::res_t recs; while(resolver.axfrChunk(recs)) { for(Resolver::res_t::const_iterator i=recs.begin();i!=recs.end();++i) { db->feedRecord(*i); } } db->commitTransaction(); db->setFresh(domain_id); L<<Logger::Error<<"AXFR done for '"<<domain<<"'"<<endl;
A backend that wants to act as a 'superslave' for a master should implement the following method:
class DNSBackend { virtual bool superMasterBackend(const string &ip, const string &domain, const vector<DNSResourceRecord>&nsset, string *account, DNSBackend **db) };
This function gets called with the IP address of the potential supermaster, the domain it is sending a notification for and the set of NS records for this domain at that IP address.
Using the supplied data, the backend needs to determine if this is a bonafide 'supernotification' which should be honoured. If it decides that it should, the supplied pointer to 'account' needs to be filled with the configured name of the supermaster (if accounting is desired), and the db needs to be filled with a pointer to your backend.
Supermaster/superslave is a complicated concept, if this is all unclear see Section 2.1, “Supermaster automatic provisioning of slaves”.