A.5. Generic MySQL and PgSQL backends

Table A-5. Generic PgSQL and MySQL backend capabilities

NativeYes - but PostgreSQL does not replicate
MasterYes
SlaveYes
SuperslaveYes
AutoserialNO
CaseAll lower
Module name < 2.9.3pgmysql
Module name > 2.9.2gmysql and gpgsql
Lauch namegmysql and gpgsql2 and gpgsql

PostgreSQL and MySQL backend with easily configurable SQL statements, allowing you to graft PDNS on any PostgreSQL or MySQL database of your choosing. Because all database schemas will be different, a generic backend is needed to cover all needs.

The template queries are expanded using the C function 'snprintf' which implies that substitutions are performed on the basis of %-place holders. To place a a % in a query which will not be substituted, use %%. Make sure to fill out the search key, often called 'name' in lower case!

There are in fact two backends, one for PostgreSQL and one for MySQL but they accept the same settings and use almost exactly the same database schema.

A.5.1. MySQL specifics

Warning

If using MySQL with 'slave' support enabled in PowerDNS you must run MySQL with a table engine that supports transactions.

In practice, great results are achieved with the 'InnoDB' tables. PowerDNS will silently function with non-transaction aware MySQLs but at one point this is going to harm your database, for example when an incoming zone transfer fails.

The default setup conforms to the following schema:

create table domains (
 id		 INT auto_increment,
 name		 VARCHAR(255) NOT NULL,
 master		 VARCHAR(128) DEFAULT NULL,
 last_check	 INT DEFAULT NULL,
 type		 VARCHAR(6) NOT NULL,
 notified_serial INT DEFAULT NULL, 
 account         VARCHAR(40) DEFAULT NULL,
 primary key (id)
)type=InnoDB;

CREATE UNIQUE INDEX name_index ON domains(name);

CREATE TABLE records (
  id              INT auto_increment,
  domain_id       INT DEFAULT NULL,
  name            VARCHAR(255) DEFAULT NULL,
  type            VARCHAR(6) DEFAULT NULL,
  content         VARCHAR(255) DEFAULT NULL,
  ttl             INT DEFAULT NULL,
  prio            INT DEFAULT NULL,
  change_date     INT DEFAULT NULL,
  primary key(id)
)type=InnoDB;

CREATE INDEX rec_name_index ON records(name);
CREATE INDEX nametype_index ON records(name,type);
CREATE INDEX domain_id ON records(domain_id);

create table supermasters (
  ip VARCHAR(25) NOT NULL, 
  nameserver VARCHAR(255) NOT NULL, 
  account VARCHAR(40) DEFAULT NULL
);

GRANT SELECT ON supermasters TO pdns;
GRANT ALL ON domains TO pdns;
GRANT ALL ON records TO pdns;
	  

Zone2sql with the --gmysql flag also assumes this layout is in place.

This schema contains all elements needed for master, slave and superslave operation. Depending on which features will be used, the 'GRANT' statements can be trimmed to make sure PDNS cannot subvert the contents of your database.

When using the InnoDB storage engine, we suggest adding the following lines to the 'create table records' command above:

CONSTRAINT `records_ibfk_1` FOREIGN KEY (`domain_id`) REFERENCES `domains`
(`id`) ON DELETE CASCADE

This automates deletion of records on deletion of a domain from the domains table.

A.5.2. PostgresSQL specifics

The default setup conforms to the following schema, which you should add to a PostgreSQL database.

create table domains (
 id		 SERIAL PRIMARY KEY,
 name		 VARCHAR(255) NOT NULL,
 master		 VARCHAR(128) DEFAULT NULL,
 last_check	 INT DEFAULT NULL,
 type		 VARCHAR(6) NOT NULL,
 notified_serial INT DEFAULT NULL, 
 account         VARCHAR(40) DEFAULT NULL
);
CREATE UNIQUE INDEX name_index ON domains(name);
  
CREATE TABLE records (
        id              SERIAL PRIMARY KEY,
        domain_id       INT DEFAULT NULL,
        name            VARCHAR(255) DEFAULT NULL,
        type            VARCHAR(6) DEFAULT NULL,
        content         VARCHAR(255) DEFAULT NULL,
        ttl             INT DEFAULT NULL,
        prio            INT DEFAULT NULL,
        change_date     INT DEFAULT NULL, 
        CONSTRAINT domain_exists 
        FOREIGN KEY(domain_id) REFERENCES domains(id)
        ON DELETE CASCADE
);

CREATE INDEX rec_name_index ON records(name);
CREATE INDEX nametype_index ON records(name,type);
CREATE INDEX domain_id ON records(domain_id);

create table supermasters (
	  ip VARCHAR(25) NOT NULL, 
	  nameserver VARCHAR(255) NOT NULL, 
	  account VARCHAR(40) DEFAULT NULL
);

GRANT SELECT ON supermasters TO pdns;
GRANT ALL ON domains TO pdns;
GRANT ALL ON domains_id_seq TO pdns;
GRANT ALL ON records TO pdns;
GRANT ALL ON records_id_seq TO pdns;
	

This schema contains all elements needed for master, slave and superslave operation. Depending on which features will be used, the 'GRANT' statements can be trimmed to make sure PDNS cannot subvert the contents of your database.

Zone2sql with the --gpgsql flag also assumes this layout is in place.

With PostgreSQL, you may have to run 'createdb powerdns' first and then connect to that database with 'psql powerdns', and feed it the schema above.

A.5.3. Oracle specifics

Generic Oracle support is only available since version 2.9.18. The default setup conforms to the following schema, which you should add to an Oracle database. You may need or want to add 'namespace' statements.


create table domains (
 id		 NUMBER,
 name		 VARCHAR(255) NOT NULL,
 master		 VARCHAR(128) DEFAULT NULL,
 last_check	 INT DEFAULT NULL,
 type		 VARCHAR(6) NOT NULL,
 notified_serial INT DEFAULT NULL, 
 account         VARCHAR(40) DEFAULT NULL,
 primary key (id)
);
create sequence DOMAINS_ID_SEQUENCE; 
create index DOMAINS$NAME on Domains (NAME);

 
CREATE TABLE records (
        id              number(11) not NULL,
        domain_id       INT DEFAULT NULL REFERENCES Domains(ID) ON DELETE CASCADE,
        name            VARCHAR(255) DEFAULT NULL,
        type            VARCHAR(6) DEFAULT NULL,
        content         VARCHAR(255) DEFAULT NULL,
        ttl             INT DEFAULT NULL,
        prio            INT DEFAULT NULL,
        change_date     INT DEFAULT NULL, 
	primary key (id)
);

create index RECORDS$NAME on RECORDS (NAME);
create sequence RECORDS_ID_SEQUENCE;

create table supermasters (
	  ip VARCHAR(25) NOT NULL, 
	  nameserver VARCHAR(255) NOT NULL, 
	  account VARCHAR(40) DEFAULT NULL
);

	

This schema contains all elements needed for master, slave and superslave operation. Depending on which features will be used, 'GRANT' statements can be trimmed to make sure PDNS cannot subvert the contents of your database.

Zone2sql with the --gpgsql flag also assumes this layout is in place.

Inserting records is a bit different compared to MySQL and PostgreSQL, you should use:

insert into domains (id,name,type) values (domains_id_sequence.nextval,'netherlabs.nl','NATIVE');
	    

Furthermore, use the goracle-tnsname setting to specify which TNSNAME the Generic Oracle Backend should be connectiong to. There are no goracle-dbname, goracle-host or goracle-port settings, their equivalent is in /etc/tnsnames.ora.

A.5.4. Basic functionality

4 queries are needed for regular lookups, 4 for 'fancy records' which are disabled by default and 1 is needed for zone transfers.

The 4+4 regular queries must return the following 6 fields, in this exact order:

content

This is the 'right hand side' of a DNS record. For an A record, this is the IP address for example.

ttl

TTL of this record, in seconds. Must be a real value, no checking is performed.

prio

For MX records, this should be the priority of the mail exchanger specified.

qtype

The ASCII representation of the qtype of this record. Examples are 'A', 'MX', 'SOA', 'AAAA'. Make sure that this field returns an exact answer - PDNS won't recognise 'A ' as 'A'. This can be achieved by using a VARCHAR instead of a CHAR.

domain_id

Each domain must have a unique domain_id. No two domains may share a domain_id, all records in a domain should have the same. A number.

name

Actual name of a record. Must not end in a '.' and be fully qualified - it is not relative to the name of the domain!

Please note that the names of the fields are not relevant, but the order is!

As said earlier, there are 8 SQL queries for regular lookups. To configure them, set 'gmysql-basic-query' or 'gpgsql-basic-query', depending on your choice of backend. If so called 'MBOXFW' fancy records are not used, four queries remain:

basic-query

Default: select content,ttl,prio,type,domain_id,name from records where type='%s' and name='%s' This is the most used query, needed for doing 1:1 lookups of qtype/name values. First %s is replaced by the ASCII representation of the qtype of the question, the second by the name.

id-query

Default: select content,ttl,prio,type,domain_id,name from records where type='%s' and name='%s' and domain_id=%d Used for doing lookups within a domain. First %s is replaced by the qtype, the %d which should appear after the %s by the numeric domain_id.

any-query

For doing ANY queries. Also used internally. Default: select content,ttl,prio,type,domain_id,name from records where name='%s' The %s is replaced by the qname of the question.

any-id-query

For doing ANY queries within a domain. Also used internally. Default: select content,ttl,prio,type,domain_id,name from records where name='%s' and domain_id=%d The %s is replaced by the name of the domain, the %d by the numerical domain id.

The last query is for listing the entire contents of a zone. This is needed when performing a zone transfer, but sometimes also internally:

list-query

To list an entire zone. Default: select content,ttl,prio,type,domain_id,name from records where domain_id=%d

A.5.5. Master/slave queries

Most installations will have zero need to change the following settings, but should the need arise, here they are:

master-zone-query

Called to determine the master of a zone. Default: select master from domains where name='%s' and type='SLAVE'

info-zone-query

Called to retrieve (nearly) all information for a domain: Default: select id,name,master,last_check,notified_serial,type from domains where name='%s'

info-all-slaves-query

Called to retrieve all slave domains Default: select id,name,master,last_check,type from domains where type='SLAVE'

supermaster-query

Called to determine if a certain host is a supermaster for a certain domain name. Default: select account from supermasters where ip='%s' and nameserver='%s'");

insert-slave-query

Called to add a domain as slave after a supermaster notification. Default: insert into domains (type,name,master,account) values('SLAVE','%s','%s','%s')

insert-record-query

Called during incoming AXFR. Default: insert into records (content,ttl,prio,type,domain_id,name) values ('%s',%d,%d,'%s',%d,'%s')

update-serial-query

Called to update the last notified serial of a master domain. Default: update domains set notified_serial=%d where id=%d

update-lastcheck-query

Called to update the last time a slave domain was checked for freshness. Default: update domains set notified_serial=%d where id=%d

info-all-master-query

Called to get data on all domains for which the server is master. Default: select id,name,master,last_check,notified_serial,type from domains where type='MASTER'

delete-zone-query

Called to delete all records of a zone. Used before an incoming AXFR. Default: delete from records where domain_id=%d

A.5.6. Fancy records

If PDNS is used with so called 'Fancy Records', the 'MBOXFW' record exists which specifies an email address forwarding instruction, wildcard queries are sometimes needed. This is not enabled by default. A wildcard query is an internal concept - it has no relation to *.domain-type lookups. You can safely leave these queries blank.

wildcard-query

Can be left blank. See above for an explanation. Default: select content,ttl,prio,type,domain_id,name from records where type='%s' and name like '%s'

wildcard-id-query

Can be left blank. See above for an explanation. Default: select content,ttl,prio,type,domain_id,name from records where type='%s' and name like '%s' and domain_id=%d Used for doing lookups within a domain.

wildcard-any-query

For doing wildcard ANY queries. Default: select content,ttl,prio,type,domain_id,name from records where name like '%s'

wildcard-any-id-query

For doing wildcard ANY queries within a domain. Default: select content,ttl,prio,type,domain_id,name from records where name like '%s' and domain_id=%d

A.5.7. Settings and specifying queries

The queries above are specified in pdns.conf. For example, the basic-query would appear as:

	    gpgsql-basic-query=select content,ttl,prio,type,domain_id,name from records where type='%s' and name='%s'
	  
When using the Generic PostgreSQL backend, they appear as above. When using the generic MySQL backend, change the "gpgsql-" prefix to "gmysql-".

Queries can span multiple lines, like this:

	    gpgsql-basic-query=select content,ttl,prio,type,domain_id,name from records \
	    where type='%s' and name='%s'
	  
Do not wrap statements in quotes as this will not work. Besides the query related settings, the following configuration options are available:

gpgsql-dbname

Database name to connect to

gpgsql-host

Database host to connect to. WARNING: When specified as a hostname a chicken/egg situation might arise where the database is needed to resolve the IP address of the database. It is best to supply an IP address of the database here.

gmysql-socket (only for MySQL!)

Filename where the MySQL connection socket resides. Often /tmp/mysql.sock or /var/run/mysqld/mysqld.sock.

gpgsql-password

Password to connect with

gpgsql-user

PgSQL user to connect as

A.5.8. Native operation

For native operation, either drop the FOREIGN KEY on the domain_id field, or (recommended), make sure the domains table is filled properly. To add a domain, issue the following:

	    insert into domains (name,type) values ('powerdns.com','NATIVE');
	  
The records table can now be filled by with the domain_id set to the id of the domains table row just inserted.

A.5.9. Slave operation

The PostgreSQL backend is fully slave capable. To become a slave of the 'powerdns.com' domain, execute this:

	    insert into domains (name,master,type) values ('powerdns.com','213.244.168.217','SLAVE');
	  
And wait a while for PDNS to pick up the addition - which happens within one minute. There is no need to inform PDNS that a new domain was added. Typical output is:
	    Apr 09 13:34:29 All slave domains are fresh
	    Apr 09 13:35:29 1 slave domain needs checking
	    Apr 09 13:35:29 Domain powerdns.com is stale, master serial 1, our serial 0
	    Apr 09 13:35:30 [gPgSQLBackend] Connected to database
	    Apr 09 13:35:30 AXFR started for 'powerdns.com'
	    Apr 09 13:35:30 AXFR done for 'powerdns.com'
	    Apr 09 13:35:30 [gPgSQLBackend] Closing connection
	  

From now on, PDNS is authoritative for the 'powerdns.com' zone and will respond accordingly for queries within that zone.

Periodically, PDNS schedules checks to see if domains are still fresh. The default slave-cycle-interval is 60 seconds, large installations may need to raise this value. Once a domain has been checked, it will not be checked before its SOA refresh timer has expired. Domains whose status is unknown get checked every 60 seconds by default.

A.5.10. Superslave operation

To configure a supermaster with IP address 10.0.0.11 which lists this installation as 'autoslave.powerdns.com', issue the following:

	    insert into supermasters ('10.0.0.11','autoslave.powerdns.com','internal');
	  
From now on, valid notifies from 10.0.0.11 that list a NS record containing 'autoslave.powerdns.com' will lead to the provisioning of a slave domain under the account 'internal'. See Section 13.2.1 for details.

A.5.11. Master operation

The PostgreSQL backend is fully master capable with automatic discovery of serial changes. Raising the serial number of a domain suffices to trigger PDNS to send out notifications. To configure a domain for master operation instead of the default native replication, issue:

	    insert into domains (name,type) values ('powerdns.com','MASTER');
	  
Make sure that the assigned id in the domains table matches the domain_id field in the records table!