diff --git a/.gitignore b/.gitignore index ad46b30..a8ff1e7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.DS_Store + # Logs logs *.log diff --git a/README.md b/README.md new file mode 100755 index 0000000..c206290 --- /dev/null +++ b/README.md @@ -0,0 +1,95 @@ +# Xip Dns Server + +[xip.lhjmmc.cn](https://xip.lhjmmc.cn) + +## Useage + +1. set a NX record to your domain (such as `xip.lhjmmc.cn`) + +``` + ... + NX xip.lhjmmc.cn your_server_ip + ... +``` + +2. deploy + +```bash +## clone +git clone https://github.com/hjmmc/xip-dns-server.git +cd xip-dns-server + +## docker-compose +docker-compose up -d +``` + +check nsedit is success running [http://your_server_ip:5380](http://your_server_ip:5380) + +add your zone in powerdns by nsedit or curl + +```bash +curl -X POST --data '{"name":"xip.lhjmmc.cn.", "kind": "Native", "masters": [], "nameservers": ["ns1.xip.lhjmmc.cn.", "ns2.xip.lhjmmc.cn."]}' -v -H 'X-API-Key: 123456' http://localhost:5381/api/v1/servers/localhost/zones +``` + +add some A record for your domain + +``` + A xip.lhjmmc.cn your_server_ip + A *.xip.lhjmmc.cn your_server_ip +``` + +check dns server by nslookup + +```bash +nslookup xip.lhjmmc.cn +nslookup 192-168-1-1.xip.lhjmmc.cn +``` + +3. gen certs + +```bash +## http://acme.sh/ +export PDNS_Url="http://localhost:5381" +export PDNS_ServerId="localhost" +export PDNS_Token="123456" +export PDNS_Ttl=60 +acme.sh --issue --dns dns_pdns -d xip.lhjmmc.cn -d *.xip.lhjmmc.cn + +## install-cert +mkdir /var/www/html/xip.lhjmmc.cn +acme.sh --install-cert -d xip.lhjmmc.cn --cert-file /var/www/html/xip.lhjmmc.cn/cert.pem --key-file /var/www/html/xip.lhjmmc.cn/key.pem --fullchain-file /var/www/html/xip +.lhjmmc.cn/fullchain.pem --reloadcmd "chmod 777 /var/www/html/xip.lhjmmc.cn/*" +``` + +4. Share cert by nginx conf + +``` +server { + listen 80; + listen 443 ssl; + server_name xip.lhjmmc.cn; + + #charset koi8-r; + #access_log /var/log/nginx/host.access.log main; + #SSL Support + ssl_certificate /var/www/html/xip.lhjmmc.cn/fullchain.pem; + ssl_certificate_key /var/www/html/xip.lhjmmc.cn/key.pem; + ssl_session_timeout 5m; + ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULLL:!MD5:!ADH:!RC4; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_prefer_server_ciphers on; + + location / { + root /var/www/html/xip.lhjmmc.cn; + index index.html index.htm; + } +} +``` + +## Thands + +[Mikroways/docker-powerdns](https://github.com/Mikroways/docker-powerdns) + +[techguy613/native-dns-packet](https://github.com/techguy613/native-dns-packet) + +[acme.sh](https://acme.sh) \ No newline at end of file diff --git a/dns-proxy/Dockerfile b/dns-proxy/Dockerfile new file mode 100755 index 0000000..9cd738f --- /dev/null +++ b/dns-proxy/Dockerfile @@ -0,0 +1,14 @@ +FROM node:10 + +RUN mkdir -p /home/dns-proxy +WORKDIR /home/dns-proxy +COPY . /home/dns-proxy + +ENV DNS_SEV 127.0.0.1 +ENV DNS_PORT 53 + +RUN npm install + +EXPOSE 53/udp + +CMD [ "npm", "start" ] \ No newline at end of file diff --git a/dns-proxy/Makefile b/dns-proxy/Makefile new file mode 100755 index 0000000..9313c01 --- /dev/null +++ b/dns-proxy/Makefile @@ -0,0 +1 @@ +docker build -t dns_proxy . \ No newline at end of file diff --git a/dns-proxy/dns_proxy.js b/dns-proxy/dns_proxy.js new file mode 100755 index 0000000..2f0936f --- /dev/null +++ b/dns-proxy/dns_proxy.js @@ -0,0 +1,78 @@ +const dgram = require('dgram') +const server = dgram.createSocket('udp4') + +const Packet = require('./native-dns-packet') + +const DNS_SEV = process.env.DNS_SEV || '127.0.0.1' +const DNS_PORT = process.env.DNS_PORT || 53 + +console.log('DNS:', DNS_SEV, DNS_PORT) + +const ipReg = /(\d{1,3})[\.-](\d{1,3})[\.-](\d{1,3})[\.-](\d{1,3})/ + +function proxy(buffer, req, rinfo) { + console.log('req', buffer.toString('hex')) + console.log('req', JSON.stringify(req)) + const client = dgram.createSocket('udp4'); + + client.on('error', (err) => { + console.log(`client error:` + err.stack) + client.close(); + }) + client.on('message', (resBuf, fbRinfo) => { + console.log("res", resBuf.toString('hex')); //获取响应报文 + let res = Packet.parse(resBuf) + console.log('res', JSON.stringify(res)) + server.send(resBuf, rinfo.port, rinfo.address, (err) => { + err && console.log(err); + }); + client.close(); + }) + + client.send(buffer, DNS_PORT, DNS_SEV, (err) => { + if (err) { + console.log(err); + client.close(); + } + }); +} + +server.on('message', (buffer, rinfo) => { + let req = Packet.parse(buffer) + if (req.question.length == 1) { + let q = req.question[0] + if (q.type == 1 && ipReg.test(q.name)) { + req.header.qr = 1 + req.header.ra = 1 + req.answer = [{ + "name": q.name, + "type": 1, + "class": 1, + "ttl": 3600, + "address": ipReg.exec(q.name).slice(1).join('.') + }] + let buf = Buffer.allocUnsafe(2048) + let len = Packet.write(buf, req) + let resBuf = buf.slice(0, len) + + console.log("res1", resBuf.toString('hex')); //获取响应报文 + console.log('res1', JSON.stringify(req)) + console.log('res1', ipReg.exec(q.name).slice(1).join('.')) + server.send(resBuf, rinfo.port, rinfo.address, (err) => { + err && console.log(err); + }) + return + } + } + proxy(buffer, req, rinfo); //转发 +}) + +server.on('error', (err) => { + console.log('server error:' + err.stack) + server.close() +}) +server.on('listening', () => { + const addr = server.address() + console.log(`run ${addr.address}:${addr.port}`) +}) +server.bind(53); \ No newline at end of file diff --git a/dns-proxy/native-dns-packet/.gitignore b/dns-proxy/native-dns-packet/.gitignore new file mode 100755 index 0000000..cfd7bfe --- /dev/null +++ b/dns-proxy/native-dns-packet/.gitignore @@ -0,0 +1,4 @@ +node_modules* +v8.log +*.swp +.idea/ \ No newline at end of file diff --git a/dns-proxy/native-dns-packet/LICENSE b/dns-proxy/native-dns-packet/LICENSE new file mode 100755 index 0000000..6639691 --- /dev/null +++ b/dns-proxy/native-dns-packet/LICENSE @@ -0,0 +1,19 @@ +Copyright 2011 Timothy J Fontaine + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the 'Software'), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE diff --git a/dns-proxy/native-dns-packet/README.md b/dns-proxy/native-dns-packet/README.md new file mode 100755 index 0000000..429254a --- /dev/null +++ b/dns-proxy/native-dns-packet/README.md @@ -0,0 +1,51 @@ +native-dns-packet +----------------- + + * `Packet.parse(buffer)` returns an instance of `Packet` + * `Packet.write(buffer, packet)` writes the given packet into the buffer, +truncating where appropriate + +```javascript +var Packet = function () { + this.header = { + id: 0, + qr: 0, + opcode: 0, + aa: 0, + tc: 0, + rd: 1, + ra: 0, + res1: 0, + res2: 0, + res3: 0, + rcode: 0 + }; + this.question = []; + this.answer = []; + this.authority = []; + this.additional = []; + this.edns_options = []; + this.payload = undefined; +}; +``` + +## History + +###### 0.1.1 - October 5, 2014 + +- Fixing NPM tagging issue... + +###### 0.1.0 - October 2, 2014 + +- Added TLSA support +- Fixed EDNS & NAPTR support + deprecates some EDNS fields on Packet +- Now includes support for forwarding EDNS responses (Packet.edns) +- Added many TODOs with suggested improvements +- Added many links to GH issues and RFCs +- Cleaned up code a bit to better please linters +- Added deprecation notices (see parseOpt) +- Handle unhandled RRs on writing packet instead of throwing exception. +- edns/opt should use BufferCursor.copy (Fixes #11) +- Updated `package.json` to include all authors +- Merged tj's `master` branch to add License info +- Updated README to include history of changes diff --git a/dns-proxy/native-dns-packet/consts.js b/dns-proxy/native-dns-packet/consts.js new file mode 100755 index 0000000..4fb25b7 --- /dev/null +++ b/dns-proxy/native-dns-packet/consts.js @@ -0,0 +1,206 @@ +// Copyright 2011 Timothy J Fontaine +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE + +'use strict'; + +function reverse_map(src) { + var dst = {}, + k; + + for (k in src) { + if (src.hasOwnProperty(k)) { + dst[src[k]] = k; + } + } + return dst; +} + +/* http://www.iana.org/assignments/dns-parameters */ +var NAME_TO_QTYPE = exports.NAME_TO_QTYPE = { + A: 1, + NS: 2, + MD: 3, + MF: 4, + CNAME: 5, + SOA: 6, + MB: 7, + MG: 8, + MR: 9, + 'NULL': 10, + WKS: 11, + PTR: 12, + HINFO: 13, + MINFO: 14, + MX: 15, + TXT: 16, + RP: 17, + AFSDB: 18, + X25: 19, + ISDN: 20, + RT: 21, + NSAP: 22, + 'NSAP-PTR': 23, + SIG: 24, + KEY: 25, + PX: 26, + GPOS: 27, + AAAA: 28, + LOC: 29, + NXT: 30, + EID: 31, + NIMLOC: 32, + SRV: 33, + ATMA: 34, + NAPTR: 35, + KX: 36, + CERT: 37, + A6: 38, + DNAME: 39, + SINK: 40, + OPT: 41, + APL: 42, + DS: 43, + SSHFP: 44, + IPSECKEY: 45, + RRSIG: 46, + NSEC: 47, + DNSKEY: 48, + DHCID: 49, + NSEC3: 50, + NSEC3PARAM: 51, + TLSA: 52, + HIP: 55, + NINFO: 56, + RKEY: 57, + TALINK: 58, + CDS: 59, + SPF: 99, + UINFO: 100, + UID: 101, + GID: 102, + UNSPEC: 103, + TKEY: 249, + TSIG: 250, + IXFR: 251, + AXFR: 252, + MAILB: 253, + MAILA: 254, + ANY: 255, + URI: 256, + CAA: 257, + TA: 32768, + DLV: 32769 +}; +exports.QTYPE_TO_NAME = reverse_map(NAME_TO_QTYPE); + +exports.nameToQtype = function(n) { + return NAME_TO_QTYPE[n.toUpperCase()]; +}; + +exports.qtypeToName = function(t) { + return exports.QTYPE_TO_NAME[t]; +}; + +var NAME_TO_QCLASS = exports.NAME_TO_QCLASS = { + IN: 1 +}; +exports.QCLASS_TO_NAME = reverse_map(NAME_TO_QCLASS); + +exports.FAMILY_TO_QTYPE = { + 4: NAME_TO_QTYPE.A, + 6: NAME_TO_QTYPE.AAAA +}; +exports.QTYPE_TO_FAMILY = {}; +exports.QTYPE_TO_FAMILY[exports.NAME_TO_QTYPE.A] = 4; +exports.QTYPE_TO_FAMILY[exports.NAME_TO_QTYPE.AAAA] = 6; + +exports.NAME_TO_RCODE = { + NOERROR: 0, + FORMERR: 1, + SERVFAIL: 2, + NOTFOUND: 3, + NOTIMP: 4, + REFUSED: 5, + YXDOMAIN: 6, //Name Exists when it should not + YXRRSET: 7, //RR Set Exists when it should not + NXRRSET: 8, //RR Set that should exist does not + NOTAUTH: 9, + NOTZONE: 10, + BADVERS: 16, + BADSIG: 16, // really? + BADKEY: 17, + BADTIME: 18, + BADMODE: 19, + BADNAME: 20, + BADALG: 21, + BADTRUNC: 22 +}; +exports.RCODE_TO_NAME = reverse_map(exports.NAME_TO_RCODE); + +// http://www.iana.org/assignments/dns-sec-alg-numbers +var DNSSEC_ALGO_NAME_TO_NUM = exports.DNSSEC_ALGO_NAME_TO_NUM = { + RSAMD5: 1, + DH: 2, + DSA: 3, + RSASHA1: 5, + DSANSEC3SHA1: 6, + RSASHA1NSEC3SHA1: 7, + RSASHA256: 8, + RSASHA512: 10, + ECCGOST: 12, + ECDSAP256SHA256: 13, + ECDSAP384SHA384: 14, + INDIRECT: 252, + PRIVATEDNS: 253, + PRIVATEOID: 254 +}; + +exports.DNSSEC_NUM_TO_ALGO_NAME = reverse_map(DNSSEC_ALGO_NAME_TO_NUM); + +// https://www.ietf.org/rfc/rfc4034.txt +var DNSKEY_NAME_TO_NUM = exports.DNSKEY_NAME_TO_NUM = { + NONE: 0, + ZSK: 256, + KSK: 257 +}; + +exports.DNSKEY_NUM_TO_NAME = reverse_map(DNSKEY_NAME_TO_NUM); + +var DIGEST_TO_NUM = exports.DIGEST_TO_NUM = { + SHA1: 1, + SHA256: 2, + GOST3411: 3, + SHA384: 4 +}; + +exports.DIGEST_NAME_TO_NUM = reverse_map(DIGEST_TO_NUM); + +exports.BADNAME = 'EBADNAME'; +exports.BADRESP = 'EBADRESP'; +exports.CONNREFUSED = 'ECONNREFUSED'; +exports.DESTRUCTION = 'EDESTRUCTION'; +exports.REFUSED = 'EREFUSED'; +exports.FORMERR = 'EFORMERR'; +exports.NODATA = 'ENODATA'; +exports.NOMEM = 'ENOMEM'; +exports.NOTFOUND = 'ENOTFOUND'; +exports.NOTIMP = 'ENOTIMP'; +exports.SERVFAIL = 'ESERVFAIL'; +exports.TIMEOUT = 'ETIMEOUT'; diff --git a/dns-proxy/native-dns-packet/index.js b/dns-proxy/native-dns-packet/index.js new file mode 100755 index 0000000..a7c5d23 --- /dev/null +++ b/dns-proxy/native-dns-packet/index.js @@ -0,0 +1,2 @@ +module.exports = require('./packet'); +module.exports.consts = require('./consts'); diff --git a/dns-proxy/native-dns-packet/package.json b/dns-proxy/native-dns-packet/package.json new file mode 100755 index 0000000..1120afe --- /dev/null +++ b/dns-proxy/native-dns-packet/package.json @@ -0,0 +1,37 @@ +{ + "name": "native-dns-packet", + "version": "0.1.2", + "authors": [ + "Timothy J Fontaine (http://atxconsulting.com)", + "Greg Slepak (https://twitter.com/taoeffect)", + "Matthieu Rakotojaona" + ], + "description": "Raw DNS Packet Parsing and Writing", + "keywords": [ + "dns", + "parsing" + ], + "homepage": "http://github.com/tjfontaine/native-dns-packet", + "bugs": { + "url": "http://github.com/tjfontaine/native-dns-packet/issues" + }, + "repository": { + "type": "git", + "url": "http://github.com/tjfontaine/native-dns-packet.git" + }, + "main": "index.js", + "engines": { + "node": ">= 0.5.0" + }, + "license": "MIT", + "scripts": { + "test": "tap ./test/*.js" + }, + "dependencies": { + "buffercursor": ">= 0.0.12", + "ipaddr.js": ">= 0.1.1" + }, + "devDependencies": { + "tap": ">= 0.4.3" + } +} diff --git a/dns-proxy/native-dns-packet/packet.js b/dns-proxy/native-dns-packet/packet.js new file mode 100755 index 0000000..7cd5bd1 --- /dev/null +++ b/dns-proxy/native-dns-packet/packet.js @@ -0,0 +1,1144 @@ +// Copyright 2011 Timothy J Fontaine +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the 'Software'), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE + +// TODO: change the default UDP packet size that node-dns sends +// from 4096 to conform to these: +// - [requestor's payload size](https://tools.ietf.org/html/rfc6891#section-6.2.3) +// - [responders's payload size](https://tools.ietf.org/html/rfc6891#section-6.2.4) + +'use strict'; + +var consts = require('./consts'), + BufferCursor = require('buffercursor'), + BufferCursorOverflow = BufferCursor.BufferCursorOverflow, + ipaddr = require('ipaddr.js'), + assert = require('assert'), + util = require('util'); + +function assertUndefined(val, msg) { + assert(typeof val != 'undefined', msg); +} + +function hasType(type) { + return this.types.indexOf(type) !== -1; +} + +var Packet = module.exports = function() { + this.header = { + id: 0, + qr: 0, + opcode: 0, + aa: 0, + tc: 0, + rd: 1, + ra: 0, + res1: 0, + ad: 0, + cd: 0, + rcode: 0 + }; + this.question = []; + this.answer = []; + this.authority = []; + this.additional = []; + this.edns_options = []; // TODO: DEPRECATED! Use `.edns.options` instead! + this.payload = undefined; // TODO: DEPRECATED! Use `.edns.payload` instead! +}; + +var LABEL_POINTER = exports.LABEL_POINTER = 0xC0; + +var isPointer = exports.isPointer = function(len) { + return (len & LABEL_POINTER) === LABEL_POINTER; +}; + +var nameUnpack = exports.nameUnpack = function(buff) { + var len, comp, rawStart, rawEnd, end, pos, part, combine = '', raw; + + rawStart = buff.tell(); + len = buff.readUInt8(); + comp = false; + end = buff.tell(); + rawEnd = buff.tell(); + + while (len !== 0) { + if (isPointer(len)) { + len -= LABEL_POINTER; + len = len << 8; + pos = len + buff.readUInt8(); + rawStart = pos; + if (!comp) + end = buff.tell(); + buff.seek(pos); + len = buff.readUInt8(); + comp = true; + continue; + } + + part = buff.toString('ascii', len); + + if (combine.length) + combine = combine + '.' + part; + else + combine = part; + + len = buff.readUInt8(); + if(comp) + rawEnd = buff.tell(); + + if (!comp) { + end = buff.tell(); + rawEnd = buff.tell(); + } + } + + if(len === 0) + combine += '.'; + + // Return Raw Name Data + buff.seek(rawStart); + raw = new BufferCursor(new Buffer(rawEnd-rawStart)); + raw.copy(buff, rawStart, rawEnd); + raw.seek(0); + var rawBytes = raw.toByteArray(); + + buff.seek(end); + + return { + name: combine, + raw: rawBytes + }; +}; + +var namePack = exports.namePack = function(str, buff, index, canonical) { + var offset, dot, part; + var isCanonical = canonical || false; + + if(str == ".") { + buff.writeUInt8(0); + return; + } + + while (str) { + if (index[str] && !isCanonical) { + offset = (LABEL_POINTER << 8) + index[str]; + buff.writeUInt16BE(offset); + break; + } else { + index[str] = buff.tell(); + dot = str.indexOf('.'); + if (dot > -1) { + part = str.slice(0, dot); + str = str.slice(dot + 1); + } else { + part = str; + str = undefined; + } + buff.writeUInt8(part.length); + buff.write(part, part.length, 'ascii'); + } + } + + if (!str) { + buff.writeUInt8(0); + } +}; + +/* Handle TypeBitmap data types */ +function parseTypeBitmap(buff) { + var types = []; + var lastbase = -1; + while(!buff.eof()) { + + if((buff.length - buff.tell()) < 2) throw "invalid bitmap descriptor"; + + var mapbase = buff.readUInt8(); + if(mapbase < lastbase) throw "invalid ordering"; + + var maplength = buff.readUInt8(); + if(maplength > (buff.length - buff.tell())) throw "invalid bitmap"; + + + for(var i = 0; i < maplength; i++) { + var current = buff.readUInt8(); + if(current === 0) continue; + for(var j = 0; j < 8; j++) { + if((current & (1 << (7-j))) === 0) continue; + var typecode = mapbase * 256 + i*8 + j; + types.push(typecode); + } + } + } + return types; +} + +function writeTypeBitmap(buff, types) { + if(types.length === 0) return; + + var mapbase = -1; + var map = []; + for(var i = 0; i < types.length; i++) { + var t = types[i]; + var base = t >> 8; + if (base !== mapbase) { + if(map.length > 0) { + mapToWire(buff, map, mapbase); + map = []; + } + mapbase = base; + } + map.push(t); + map.sort(function(a, b){return a-b}); + } + mapToWire(buff, map, mapbase); +} + +function mapToWire(buff, map, mapbase) { + var arraymax = map[map.length - 1] & 0xFF; + var arraylength = Math.floor(arraymax / 8) + 1; + var array = Array.apply(null, new Array(arraylength)).map(Number.prototype.valueOf, 0); + + buff.writeUInt8(mapbase); + buff.writeUInt8(arraylength); + for(var i = 0; i < map.length; i++) { + var typecode = map[i]; + array[Math.floor((typecode & 0xFF) / 8)] |= (1 << ( 7 - typecode % 8)); + } + for(var j = 0; j < arraylength; j++) { + buff.writeUInt8(array[j]); + } +} + +var + WRITE_HEADER = 100001, + WRITE_TRUNCATE = 100002, + WRITE_QUESTION = 100003, + WRITE_RESOURCE_RECORD = 100004, + WRITE_RESOURCE_WRITE = 100005, + WRITE_RESOURCE_DONE = 100006, + WRITE_RESOURCE_END = 100007, + WRITE_EDNS = 100008, + WRITE_END = 100009, + WRITE_A = consts.NAME_TO_QTYPE.A, + WRITE_AAAA = consts.NAME_TO_QTYPE.AAAA, + WRITE_NS = consts.NAME_TO_QTYPE.NS, + WRITE_CNAME = consts.NAME_TO_QTYPE.CNAME, + WRITE_PTR = consts.NAME_TO_QTYPE.PTR, + WRITE_SPF = consts.NAME_TO_QTYPE.SPF, + WRITE_MX = consts.NAME_TO_QTYPE.MX, + WRITE_SRV = consts.NAME_TO_QTYPE.SRV, + WRITE_TXT = consts.NAME_TO_QTYPE.TXT, + WRITE_SOA = consts.NAME_TO_QTYPE.SOA, + WRITE_OPT = consts.NAME_TO_QTYPE.OPT, + WRITE_NAPTR = consts.NAME_TO_QTYPE.NAPTR, + WRITE_DS = consts.NAME_TO_QTYPE.DS, + WRITE_RRSIG = consts.NAME_TO_QTYPE.RRSIG, + WRITE_DNSKEY = consts.NAME_TO_QTYPE.DNSKEY, + WRITE_NSEC3 = consts.NAME_TO_QTYPE.NSEC3, + WRITE_NSEC3PARAM = consts.NAME_TO_QTYPE.NSEC3PARAM, + WRITE_TLSA = consts.NAME_TO_QTYPE.TLSA; + +var writeHeader = Packet.writeHeader = function(buff, packet) { + assert(packet.header, 'Packet requires "header"'); + buff.writeUInt16BE(packet.header.id & 0xFFFF); + var val = 0; + val += (packet.header.qr << 15) & 0x8000; + val += (packet.header.opcode << 11) & 0x7800; + val += (packet.header.aa << 10) & 0x400; + val += (packet.header.tc << 9) & 0x200; + val += (packet.header.rd << 8) & 0x100; + val += (packet.header.ra << 7) & 0x80; + val += (packet.header.res1 << 6) & 0x40; + val += (packet.header.ad << 5) & 0x20; + val += (packet.header.cd << 4) & 0x10; + val += packet.header.rcode & 0xF; + buff.writeUInt16BE(val & 0xFFFF); + assert(packet.question.length == 1, 'DNS requires one question'); + // aren't used + buff.writeUInt16BE(1); + // answer offset 6 + buff.writeUInt16BE(packet.answer.length & 0xFFFF); + // authority offset 8 + buff.writeUInt16BE(packet.authority.length & 0xFFFF); + // additional offset 10 + buff.writeUInt16BE(packet.additional.length & 0xFFFF); + return WRITE_QUESTION; +}; + +var writeTruncate = Packet.writeTruncate = function(buff, packet, section, val) { + // XXX FIXME TODO truncation is currently done wrong. + // Quote rfc2181 section 9 + // The TC bit should not be set merely because some extra information + // could have been included, but there was insufficient room. This + // includes the results of additional section processing. In such cases + // the entire RRSet that will not fit in the response should be omitted, + // and the reply sent as is, with the TC bit clear. If the recipient of + // the reply needs the omitted data, it can construct a query for that + // data and send that separately. + // + // TODO IOW only set TC if we hit it in ANSWERS otherwise make sure an + // entire RRSet is removed during a truncation. + var pos; + + buff.seek(2); + val = buff.readUInt16BE(); + val |= (1 << 9) & 0x200; + buff.seek(2); + buff.writeUInt16BE(val); + switch (section) { + case 'answer': + pos = 6; + // seek to authority and clear it and additional out + buff.seek(8); + buff.writeUInt16BE(0); + buff.writeUInt16BE(0); + break; + case 'authority': + pos = 8; + // seek to additional and clear it out + buff.seek(10); + buff.writeUInt16BE(0); + break; + case 'additional': + pos = 10; + break; + } + buff.seek(pos); + buff.writeUInt16BE(count - 1); // TODO: count not defined! + buff.seek(last_resource); // TODO: last_resource not defined! + return WRITE_END; +}; + +var writeQuestion = Packet.writeQuestion = function(buff, val, label_index) { + assert(val, 'Packet requires a question'); + assertUndefined(val.name, 'Question requires a "name"'); + assertUndefined(val.type, 'Question requires a "type"'); + assertUndefined(val.class, 'Questionn requires a "class"'); + namePack(val.name, buff, label_index); + buff.writeUInt16BE(val.type & 0xFFFF); + buff.writeUInt16BE(val.class & 0xFFFF); + return WRITE_RESOURCE_RECORD; +}; + +var writeResource = Packet.writeResource = function(buff, val, label_index, rdata) { + assert(val, 'Resource must be defined'); + assertUndefined(val.name, 'Resource record requires "name"'); + assertUndefined(val.type, 'Resource record requires "type"'); + assertUndefined(val.class, 'Resource record requires "class"'); + assertUndefined(val.ttl, 'Resource record requires "ttl"'); + namePack(val.name, buff, label_index); + buff.writeUInt16BE(val.type & 0xFFFF); + buff.writeUInt16BE(val.class & 0xFFFF); + buff.writeUInt32BE(val.ttl & 0xFFFFFFFF); + rdata.pos = buff.tell(); + buff.writeUInt16BE(0); // if there is rdata, then this value will be updated + // to the correct value by 'writeResourceDone' + return val.type; +}; + +var writeResourceDone = Packet.writeResourceDone = function(buff, rdata) { + var pos = buff.tell(); + buff.seek(rdata.pos); + buff.writeUInt16BE(pos - rdata.pos - 2); + buff.seek(pos); + return WRITE_RESOURCE_RECORD; +}; + +var writeIp = Packet.writeIp = function(buff, val) { + //TODO XXX FIXME -- assert that address is of proper type + assertUndefined(val.address, 'A/AAAA record requires "address"'); + val = ipaddr.parse(val.address).toByteArray(); + val.forEach(function(b) { + buff.writeUInt8(b); + }); + return WRITE_RESOURCE_DONE; +}; + +var writeCname = Packet.writeCname = function(buff, val, label_index) { + assertUndefined(val.data, 'NS/CNAME/PTR record requires "data"'); + namePack(val.data, buff, label_index); + return WRITE_RESOURCE_DONE; +}; + +// For see: http://tools.ietf.org/html/rfc1035#section-3.3 +// For TXT: http://tools.ietf.org/html/rfc1035#section-3.3.14 +var writeTxt = Packet.writeTxt = function(buff, val) { + //TODO XXX FIXME -- split on max char string and loop + assertUndefined(val.data, 'TXT record requires "data"'); + for (var i=0,len=val.data.length; i> 15; + packet.header.opcode = (val & 0x7800) >> 11; + packet.header.aa = (val & 0x400) >> 10; + packet.header.tc = (val & 0x200) >> 9; + packet.header.rd = (val & 0x100) >> 8; + packet.header.ra = (val & 0x80) >> 7; + packet.header.res1 = (val & 0x40) >> 6; + packet.header.ad = (val & 0x20) >> 5; + packet.header.cd = (val & 0x10) >> 4; + packet.header.rcode = (val & 0xF); + packet.question = new Array(msg.readUInt16BE()); + packet.answer = new Array(msg.readUInt16BE()); + packet.authority = new Array(msg.readUInt16BE()); + packet.additional = new Array(msg.readUInt16BE()); + return PARSE_QUESTION; +} + +function parseQuestion(msg, packet) { + var val = {}; + var nameret = nameUnpack(msg); + val.name = nameret.name; + val.nameRaw = nameret.raw; + val.type = msg.readUInt16BE(); + val.class = msg.readUInt16BE(); + packet.question[0] = val; + assert(packet.question.length === 1); + // TODO handle qdcount > 1 in practice no one sends this + return PARSE_RESOURCE_RECORD; +} + +function parseRR(msg, val, rdata) { + var nameret = nameUnpack(msg); + val.name = nameret.name; + val.nameRaw = nameret.raw; + val.type = msg.readUInt16BE(); + val.class = msg.readUInt16BE(); + val.ttl = msg.readUInt32BE(); + rdata.len = msg.readUInt16BE(); + return val.type; +} + +function parseA(val, msg) { + val.address = '' + + msg.readUInt8() + + '.' + msg.readUInt8() + + '.' + msg.readUInt8() + + '.' + msg.readUInt8(); + return PARSE_RESOURCE_DONE; +} + +function parseAAAA(val, msg) { + var address = ''; + var compressed = false; + + for (var i = 0; i < 8; i++) { + if (i > 0) address += ':'; + // TODO zero compression + address += msg.readUInt16BE().toString(16); + } + val.address = address; + return PARSE_RESOURCE_DONE; +} + +function parseCname(val, msg) { + var nameret = nameUnpack(msg); + val.data = nameret.name; + val.dataRaw = nameret.raw; + return PARSE_RESOURCE_DONE; +} + +function parseTxt(val, msg, rdata) { + val.data = []; + var end = msg.tell() + rdata.len; + while (msg.tell() != end) { + var len = msg.readUInt8(); + val.data.push(msg.toString('utf8', len)); + } + return PARSE_RESOURCE_DONE; +} + +function parseMx(val, msg, rdata) { + val.priority = msg.readUInt16BE(); + var nameret = nameUnpack(msg); + val.exchange = nameret.name; + val.exchangeRaw = nameret.raw; + return PARSE_RESOURCE_DONE; +} + +// TODO: SRV fixture failing for '_xmpp-server._tcp.gmail.com.srv.js' +// https://tools.ietf.org/html/rfc2782 +function parseSrv(val, msg) { + val.priority = msg.readUInt16BE(); + val.weight = msg.readUInt16BE(); + val.port = msg.readUInt16BE(); + var nameret = nameUnpack(msg); + val.target = nameret.name; + val.targetRaw = nameret.raw; + return PARSE_RESOURCE_DONE; +} + +function parseSoa(val, msg) { + var nameret = nameUnpack(msg); + val.primary = nameret.name; + val.primaryRaw = nameret.raw; + + nameret = nameUnpack(msg); + val.admin = nameret.name; + val.adminRaw = nameret.raw; + + val.serial = msg.readUInt32BE(); + val.refresh = msg.readInt32BE(); + val.retry = msg.readInt32BE(); + val.expiration = msg.readInt32BE(); + val.minimum = msg.readInt32BE(); + return PARSE_RESOURCE_DONE; +} + +// http://tools.ietf.org/html/rfc3403#section-4.1 +function parseNaptr(val, msg) { + val.order = msg.readUInt16BE(); + val.preference = msg.readUInt16BE(); + var len = msg.readUInt8(); + val.flags = msg.toString('ascii', len); + len = msg.readUInt8(); + val.service = msg.toString('ascii', len); + len = msg.readUInt8(); + val.regexp = msg.toString('ascii', len); + + var nameret = nameUnpack(msg); + val.replacement = nameret.name; + val.replacementRaw = nameret.raw; + return PARSE_RESOURCE_DONE; +} + +function parseTlsa(val, msg, rdata) { + val.usage = msg.readUInt8(); + val.selector = msg.readUInt8(); + val.matchingtype = msg.readUInt8(); + val.buff = msg.slice(rdata.len - 3).buffer; // 3 because of the 3 UInt8s above. + return PARSE_RESOURCE_DONE; +} + +// https://tools.ietf.org/html/rfc6891#section-6.1.2 +// https://tools.ietf.org/html/rfc2671#section-4.4 +// - [payload size selection](https://tools.ietf.org/html/rfc6891#section-6.2.5) +function parseOpt(val, msg, rdata, packet) { + // assert first entry in additional + rdata.buf = msg.slice(rdata.len); + + val.rcode = ((val.ttl & 0xFF000000) >> 20) + packet.header.rcode; + val.version = (val.ttl >> 16) & 0xFF; + val.do = (val.ttl >> 15) & 1; + val.z = val.ttl & 0x7F; + val.options = []; + + packet.edns = val; + packet.edns_version = val.version; // TODO: return BADVERS for unsupported version! (Section 6.1.3) + + // !! BEGIN DEPRECATION NOTICE !! + // THESE FIELDS MAY BE REMOVED IN THE FUTURE! + packet.edns_options = val.options; + packet.payload = val.class; + // !! END DEPRECATION NOTICE !! + + while (!rdata.buf.eof()) { + val.options.push({ + code: rdata.buf.readUInt16BE(), + data: rdata.buf.slice(rdata.buf.readUInt16BE()).buffer + }); + } + return PARSE_RESOURCE_DONE; +} + +function parseDs(val, msg, rdata) { + var startPos = msg.tell(); + val.keytag = msg.readUInt16BE(); + val.algorithm = msg.readUInt8(); + val.digestType = msg.readUInt8(); + val.digest = msg.slice(rdata.len - (msg.tell() - startPos)); + return PARSE_RESOURCE_DONE; +} + +function parseRrsig(val, msg, rdata) { + var startPos = msg.tell(); + val.typeCovered = msg.readUInt16BE(); + val.algorithm = msg.readUInt8(); + val.labels = msg.readUInt8(); + val.originalTtl = msg.readUInt32BE(); + val.signatureExpiration = new Date(msg.readUInt32BE() * 1000); + val.signatureInception = new Date(msg.readUInt32BE() * 1000); + val.keytag = msg.readUInt16BE(); + + var nameret = nameUnpack(msg); + val.signerName = nameret.name; + val.signerNameRaw = nameret.raw; + + val.signature = msg.slice(rdata.len - (msg.tell() - startPos)); + return PARSE_RESOURCE_DONE; +} + +function parseDnskey(val, msg, rdata) { + var startPos = msg.tell(); + val.flags = msg.readUInt16BE(); + val.protocol = msg.readUInt8(); + val.algorithm = msg.readUInt8(); + val.publicKey = msg.slice(rdata.len - (msg.tell() - startPos)); + return PARSE_RESOURCE_DONE; +} + +function parseNsec(val, msg, rdata) { + var startPos = msg.tell(); + var nameret = nameUnpack(msg); + val.next = nameret.name; + val.nextRaw = nameret.raw; + val.types = parseTypeBitmap(msg.slice(startPos + rdata.len - msg.tell())); + + val.hasType = hasType; + return PARSE_RESOURCE_DONE; +} + +function parseNsec3(val, msg, rdata) { + var startPos = msg.tell(); + + val.hashAlgorithm = msg.readUInt8(); + val.flags = msg.readUInt8(); + val.iterations = msg.readUInt16BE(); + + var saltLen = msg.readUInt8(); + val.salt = msg.slice(saltLen); + + var hashLen = msg.readUInt8(); + val.nextHashedOwnerName = msg.slice(hashLen); + + val.types = parseTypeBitmap(msg.slice(startPos + rdata.len - msg.tell())); + + val.hasType = hasType; + return PARSE_RESOURCE_DONE; +} + +function parseNsec3param(val, msg) { + val.hashAlgorithm = msg.readUInt8(); + val.flags = msg.readUInt8(); + val.iterations = msg.readUInt16BE(); + + var nameret = nameUnpack(msg); + val.salt = nameret.name; + val.saltRaw = nameret.raw; +} + +var + PARSE_HEADER = 100000, + PARSE_QUESTION = 100001, + PARSE_RESOURCE_RECORD = 100002, + PARSE_RR_UNPACK = 100003, + PARSE_RESOURCE_DONE = 100004, + PARSE_END = 100005, + PARSE_A = consts.NAME_TO_QTYPE.A, + PARSE_NS = consts.NAME_TO_QTYPE.NS, + PARSE_CNAME = consts.NAME_TO_QTYPE.CNAME, + PARSE_SOA = consts.NAME_TO_QTYPE.SOA, + PARSE_PTR = consts.NAME_TO_QTYPE.PTR, + PARSE_MX = consts.NAME_TO_QTYPE.MX, + PARSE_TXT = consts.NAME_TO_QTYPE.TXT, + PARSE_AAAA = consts.NAME_TO_QTYPE.AAAA, + PARSE_SRV = consts.NAME_TO_QTYPE.SRV, + PARSE_NAPTR = consts.NAME_TO_QTYPE.NAPTR, + PARSE_OPT = consts.NAME_TO_QTYPE.OPT, + PARSE_DS = consts.NAME_TO_QTYPE.DS, + PARSE_RRSIG = consts.NAME_TO_QTYPE.RRSIG, + PARSE_DNSKEY = consts.NAME_TO_QTYPE.DNSKEY, + PARSE_NSEC = consts.NAME_TO_QTYPE.NSEC, + PARSE_NSEC3 = consts.NAME_TO_QTYPE.NSEC3, + PARSE_NSEC3PARAM = consts.NAME_TO_QTYPE.NSEC3PARAM, + PARSE_SPF = consts.NAME_TO_QTYPE.SPF, + PARSE_TLSA = consts.NAME_TO_QTYPE.TLSA; + + +Packet.parse = function(msg) { + var state, + pos, + val, + rdata, + section, + count; + + var packet = new Packet(); + + pos = 0; + state = PARSE_HEADER; + + msg = new BufferCursor(msg); + + while (true) { + switch (state) { + case PARSE_HEADER: + state = parseHeader(msg, packet); + break; + case PARSE_QUESTION: + state = parseQuestion(msg, packet); + section = 'answer'; + count = 0; + break; + case PARSE_RESOURCE_RECORD: + // console.log('PARSE_RESOURCE_RECORD: count = %d, %s.len = %d', count, section, packet[section].length); + if (count === packet[section].length) { + switch (section) { + case 'answer': + section = 'authority'; + count = 0; + break; + case 'authority': + section = 'additional'; + count = 0; + break; + case 'additional': + state = PARSE_END; + break; + } + } else { + state = PARSE_RR_UNPACK; + } + break; + case PARSE_RR_UNPACK: + val = {}; + rdata = {}; + state = parseRR(msg, val, rdata); + break; + case PARSE_RESOURCE_DONE: + packet[section][count++] = val; + state = PARSE_RESOURCE_RECORD; + break; + case PARSE_A: + state = parseA(val, msg); + break; + case PARSE_AAAA: + state = parseAAAA(val, msg); + break; + case PARSE_NS: + case PARSE_CNAME: + case PARSE_PTR: + state = parseCname(val, msg); + break; + case PARSE_SPF: + case PARSE_TXT: + state = parseTxt(val, msg, rdata); + break; + case PARSE_MX: + state = parseMx(val, msg); + break; + case PARSE_SRV: + state = parseSrv(val, msg); + break; + case PARSE_SOA: + state = parseSoa(val, msg); + break; + case PARSE_OPT: + state = parseOpt(val, msg, rdata, packet); + break; + case PARSE_NAPTR: + state = parseNaptr(val, msg); + break; + case PARSE_DS: + state = parseDs(val, msg, rdata); + break; + case PARSE_RRSIG: + state = parseRrsig(val, msg, rdata); + break; + case PARSE_DNSKEY: + state = parseDnskey(val, msg, rdata); + break; + case PARSE_NSEC: + state = parseNsec(val, msg, rdata); + break; + case PARSE_NSEC3: + state = parseNsec3(val, msg, rdata); + break; + case PARSE_NSEC3PARAM: + state = parseNsec3param(val, msg); + break; + case PARSE_TLSA: + state = parseTlsa(val, msg, rdata); + break; + case PARSE_END: + return packet; + default: + //console.log(state, val); + val.data = msg.slice(rdata.len); + state = PARSE_RESOURCE_DONE; + break; + } + } +}; + +var buildDnssecRequestPacket = function (opts) { + + var qtype; + + qtype = opts.type || consts.NAME_TO_QTYPE.A; + if (typeof(qtype) === 'string' || qtype instanceof String) + qtype = consts.nameToQtype(qtype.toUpperCase()); + + if (!qtype || typeof(qtype) !== 'number') + throw new Error("Question type must be defined and be valid"); + + return { + answer: [], + authority: [], + additional: [], + do: true, + edns_options: [], + edns_version: 0, + header: { + id: 4326, + rd: 1 + }, + payload: 4096, + question: [{ + name: opts.name, + type: qtype, + class: consts.NAME_TO_QCLASS.IN + }], + try_edns: true + } +}; \ No newline at end of file diff --git a/dns-proxy/native-dns-packet/test/fixtures/8.8.8.8.in-addr.arpa.ptr.bin b/dns-proxy/native-dns-packet/test/fixtures/8.8.8.8.in-addr.arpa.ptr.bin new file mode 100755 index 0000000..af108f2 Binary files /dev/null and b/dns-proxy/native-dns-packet/test/fixtures/8.8.8.8.in-addr.arpa.ptr.bin differ diff --git a/dns-proxy/native-dns-packet/test/fixtures/8.8.8.8.in-addr.arpa.ptr.js b/dns-proxy/native-dns-packet/test/fixtures/8.8.8.8.in-addr.arpa.ptr.js new file mode 100755 index 0000000..ff9e2ab --- /dev/null +++ b/dns-proxy/native-dns-packet/test/fixtures/8.8.8.8.in-addr.arpa.ptr.js @@ -0,0 +1,23 @@ +{ header: + { id: 39980, + qr: 1, + opcode: 0, + aa: 0, + tc: 0, + rd: 1, + ra: 1, + res1: 0, + ad: 0, + cd: 0, + rcode: 0 }, + question: [ { name: '8.8.8.8.IN-ADDR.ARPA.', type: 12, class: 1 } ], + answer: + [ { name: '8.8.8.8.IN-ADDR.ARPA.', + type: 12, + class: 1, + ttl: 21598, + data: 'google-public-dns-a.google.com.' } ], + authority: [], + additional: [], + edns_options: [], + payload: undefined } diff --git a/dns-proxy/native-dns-packet/test/fixtures/_443._tcp.fedoraproject.org.bin b/dns-proxy/native-dns-packet/test/fixtures/_443._tcp.fedoraproject.org.bin new file mode 100755 index 0000000..feb7d14 Binary files /dev/null and b/dns-proxy/native-dns-packet/test/fixtures/_443._tcp.fedoraproject.org.bin differ diff --git a/dns-proxy/native-dns-packet/test/fixtures/_443._tcp.fedoraproject.org.js b/dns-proxy/native-dns-packet/test/fixtures/_443._tcp.fedoraproject.org.js new file mode 100755 index 0000000..430bc37 --- /dev/null +++ b/dns-proxy/native-dns-packet/test/fixtures/_443._tcp.fedoraproject.org.js @@ -0,0 +1,28 @@ +{ header: + { id: 62108, + qr: 1, + opcode: 0, + aa: 0, + tc: 0, + rd: 1, + ra: 1, + res1: 0, + ad: 1, + cd: 0, + rcode: 0 }, + question: [ { name: '_443._tcp.fedoraproject.org.', type: 52, class: 1 } ], + answer: + [ { name: '_443._tcp.fedoraproject.org.', + type: 52, + class: 1, + ttl: 268, + usage: 0, + selector: 0, + matchingtype: 1, + buff: new Buffer([0xd4, 0xc4, 0xc9, 0x98, 0x19, 0xf3, 0xa5, 0xf2, 0xc6, 0x26, 0x1c, 0x94, 0x44, 0xc6, 0x2a, 0x8b, 0x26, 0x3b, 0x39, 0xbc, 0x6a, 0xcc, 0xe3, 0x5c, 0xdc, 0xab, 0xe2, 0x72, 0xd5, 0x03, 0x7f, 0xb2]) } ], + authority: [], + additional: [{ name: '.', type: 41, class: 4096, ttl: 0, rcode: 0, version: 0, do: 0, z: 0, options: []}], + edns: { name: '.', type: 41, class: 4096, ttl: 0, rcode: 0, version: 0, do: 0, z: 0, options: []}, + edns_options: [], + payload: 4096, + edns_version: 0} diff --git a/dns-proxy/native-dns-packet/test/fixtures/_xmpp-server._tcp.gmail.com.srv.bin b/dns-proxy/native-dns-packet/test/fixtures/_xmpp-server._tcp.gmail.com.srv.bin new file mode 100755 index 0000000..f7d0d7d Binary files /dev/null and b/dns-proxy/native-dns-packet/test/fixtures/_xmpp-server._tcp.gmail.com.srv.bin differ diff --git a/dns-proxy/native-dns-packet/test/fixtures/_xmpp-server._tcp.gmail.com.srv.js b/dns-proxy/native-dns-packet/test/fixtures/_xmpp-server._tcp.gmail.com.srv.js new file mode 100755 index 0000000..e803276 --- /dev/null +++ b/dns-proxy/native-dns-packet/test/fixtures/_xmpp-server._tcp.gmail.com.srv.js @@ -0,0 +1,58 @@ +{ header: + { id: 42682, + qr: 1, + opcode: 0, + aa: 0, + tc: 0, + rd: 1, + ra: 1, + res1: 0, + ad: 0, + cd: 0, + rcode: 0 }, + question: [ { name: '_xmpp-server._tcp.gmail.com.', type: 33, class: 1 } ], + answer: + [ { name: '_xmpp-server._tcp.gmail.com.', + type: 33, + class: 1, + ttl: 900, + priority: 5, + weight: 0, + port: 5269, + target: 'xmpp-server.l.google.com.' }, + { name: '_xmpp-server._tcp.gmail.com.', + type: 33, + class: 1, + ttl: 900, + priority: 20, + weight: 0, + port: 5269, + target: 'alt3.xmpp-server.l.google.com.' }, + { name: '_xmpp-server._tcp.gmail.com.', + type: 33, + class: 1, + ttl: 900, + priority: 20, + weight: 0, + port: 5269, + target: 'alt4.xmpp-server.l.google.com.' }, + { name: '_xmpp-server._tcp.gmail.com.', + type: 33, + class: 1, + ttl: 900, + priority: 20, + weight: 0, + port: 5269, + target: 'alt2.xmpp-server.l.google.com.' }, + { name: '_xmpp-server._tcp.gmail.com.', + type: 33, + class: 1, + ttl: 900, + priority: 20, + weight: 0, + port: 5269, + target: 'alt1.xmpp-server.l.google.com.' } ], + authority: [], + additional: [], + edns_options: [], + payload: undefined } diff --git a/dns-proxy/native-dns-packet/test/fixtures/aol.com.txt.bin b/dns-proxy/native-dns-packet/test/fixtures/aol.com.txt.bin new file mode 100755 index 0000000..8919e30 Binary files /dev/null and b/dns-proxy/native-dns-packet/test/fixtures/aol.com.txt.bin differ diff --git a/dns-proxy/native-dns-packet/test/fixtures/aol.com.txt.js b/dns-proxy/native-dns-packet/test/fixtures/aol.com.txt.js new file mode 100755 index 0000000..6c9923c --- /dev/null +++ b/dns-proxy/native-dns-packet/test/fixtures/aol.com.txt.js @@ -0,0 +1,28 @@ +{ header: + { id: 9551, + qr: 1, + opcode: 0, + aa: 0, + tc: 0, + rd: 1, + ra: 1, + res1: 0, + ad: 0, + cd: 0, + rcode: 0 }, + question: [ { name: 'aol.com.', type: 16, class: 1 } ], + answer: + [ { name: 'aol.com.', + type: 16, + class: 1, + ttl: 1926, + data: ['spf2.0/pra ptr:mx.aol.com ?all'] }, + { name: 'aol.com.', + type: 16, + class: 1, + ttl: 1926, + data: ['v=spf1 ptr:mx.aol.com ?all'] } ], + authority: [], + additional: [], + edns_options: [], + payload: undefined } diff --git a/dns-proxy/native-dns-packet/test/fixtures/b.8.4.0.6.9.e.f.f.f.1.9.c.3.0.f.0.0.0.0.0.0.0.0.3.0.c.3.0.0.6.2.ip6.arpa.ptr.bin b/dns-proxy/native-dns-packet/test/fixtures/b.8.4.0.6.9.e.f.f.f.1.9.c.3.0.f.0.0.0.0.0.0.0.0.3.0.c.3.0.0.6.2.ip6.arpa.ptr.bin new file mode 100755 index 0000000..47a5fe1 Binary files /dev/null and b/dns-proxy/native-dns-packet/test/fixtures/b.8.4.0.6.9.e.f.f.f.1.9.c.3.0.f.0.0.0.0.0.0.0.0.3.0.c.3.0.0.6.2.ip6.arpa.ptr.bin differ diff --git a/dns-proxy/native-dns-packet/test/fixtures/b.8.4.0.6.9.e.f.f.f.1.9.c.3.0.f.0.0.0.0.0.0.0.0.3.0.c.3.0.0.6.2.ip6.arpa.ptr.js b/dns-proxy/native-dns-packet/test/fixtures/b.8.4.0.6.9.e.f.f.f.1.9.c.3.0.f.0.0.0.0.0.0.0.0.3.0.c.3.0.0.6.2.ip6.arpa.ptr.js new file mode 100755 index 0000000..5e07ca9 --- /dev/null +++ b/dns-proxy/native-dns-packet/test/fixtures/b.8.4.0.6.9.e.f.f.f.1.9.c.3.0.f.0.0.0.0.0.0.0.0.3.0.c.3.0.0.6.2.ip6.arpa.ptr.js @@ -0,0 +1,26 @@ +{ header: + { id: 37466, + qr: 1, + opcode: 0, + aa: 0, + tc: 0, + rd: 1, + ra: 1, + res1: 0, + ad: 0, + cd: 0, + rcode: 0 }, + question: + [ { name: 'b.8.4.0.6.9.e.f.f.f.1.9.c.3.0.f.0.0.0.0.0.0.0.0.3.0.c.3.0.0.6.2.IP6.ARPA.', + type: 12, + class: 1 } ], + answer: + [ { name: 'b.8.4.0.6.9.e.f.f.f.1.9.c.3.0.f.0.0.0.0.0.0.0.0.3.0.c.3.0.0.6.2.IP6.ARPA.', + type: 12, + class: 1, + ttl: 21598, + data: 'alittletothewright.com.' } ], + authority: [], + additional: [], + edns_options: [], + payload: undefined } diff --git a/dns-proxy/native-dns-packet/test/fixtures/irc6.geo.oftc.net.aaaa.bin b/dns-proxy/native-dns-packet/test/fixtures/irc6.geo.oftc.net.aaaa.bin new file mode 100755 index 0000000..277f85f Binary files /dev/null and b/dns-proxy/native-dns-packet/test/fixtures/irc6.geo.oftc.net.aaaa.bin differ diff --git a/dns-proxy/native-dns-packet/test/fixtures/irc6.geo.oftc.net.aaaa.js b/dns-proxy/native-dns-packet/test/fixtures/irc6.geo.oftc.net.aaaa.js new file mode 100755 index 0000000..1b39e57 --- /dev/null +++ b/dns-proxy/native-dns-packet/test/fixtures/irc6.geo.oftc.net.aaaa.js @@ -0,0 +1,33 @@ +{ header: + { id: 11697, + qr: 1, + opcode: 0, + aa: 0, + tc: 0, + rd: 1, + ra: 1, + res1: 0, + ad: 0, + cd: 0, + rcode: 0 }, + question: [ { name: 'irc6.geo.oftc.net.', type: 28, class: 1 } ], + answer: + [ { name: 'irc6.geo.oftc.net.', + type: 28, + class: 1, + ttl: 59, + address: '2607:f8f0:610:4000:211:11ff:fe1c:7bec' }, + { name: 'irc6.geo.oftc.net.', + type: 28, + class: 1, + ttl: 59, + address: '2a01:238:42d7:aa00:6667:6697:9999:42' }, + { name: 'irc6.geo.oftc.net.', + type: 28, + class: 1, + ttl: 59, + address: '2001:41b8:202:deb:4200:ff:fe00:6667' } ], + authority: [], + additional: [], + edns_options: [], + payload: undefined } diff --git a/dns-proxy/native-dns-packet/test/fixtures/linode.com.ns.bin b/dns-proxy/native-dns-packet/test/fixtures/linode.com.ns.bin new file mode 100755 index 0000000..1a0db20 Binary files /dev/null and b/dns-proxy/native-dns-packet/test/fixtures/linode.com.ns.bin differ diff --git a/dns-proxy/native-dns-packet/test/fixtures/linode.com.ns.js b/dns-proxy/native-dns-packet/test/fixtures/linode.com.ns.js new file mode 100755 index 0000000..135184b --- /dev/null +++ b/dns-proxy/native-dns-packet/test/fixtures/linode.com.ns.js @@ -0,0 +1,43 @@ +{ header: + { id: 8381, + qr: 1, + opcode: 0, + aa: 0, + tc: 0, + rd: 1, + ra: 1, + res1: 0, + ad: 0, + cd: 0, + rcode: 0 }, + question: [ { name: 'linode.com.', type: 2, class: 1 } ], + answer: + [ { name: 'linode.com.', + type: 2, + class: 1, + ttl: 14769, + data: 'ns4.linode.com.' }, + { name: 'linode.com.', + type: 2, + class: 1, + ttl: 14769, + data: 'ns2.linode.com.' }, + { name: 'linode.com.', + type: 2, + class: 1, + ttl: 14769, + data: 'ns1.linode.com.' }, + { name: 'linode.com.', + type: 2, + class: 1, + ttl: 14769, + data: 'ns5.linode.com.' }, + { name: 'linode.com.', + type: 2, + class: 1, + ttl: 14769, + data: 'ns3.linode.com.' } ], + authority: [], + additional: [], + edns_options: [], + payload: undefined } diff --git a/dns-proxy/native-dns-packet/test/fixtures/microsoft.com.mx.bin b/dns-proxy/native-dns-packet/test/fixtures/microsoft.com.mx.bin new file mode 100755 index 0000000..e5bba9d Binary files /dev/null and b/dns-proxy/native-dns-packet/test/fixtures/microsoft.com.mx.bin differ diff --git a/dns-proxy/native-dns-packet/test/fixtures/microsoft.com.mx.js b/dns-proxy/native-dns-packet/test/fixtures/microsoft.com.mx.js new file mode 100755 index 0000000..ba8e192 --- /dev/null +++ b/dns-proxy/native-dns-packet/test/fixtures/microsoft.com.mx.js @@ -0,0 +1,25 @@ +{ header: + { id: 46385, + qr: 1, + opcode: 0, + aa: 0, + tc: 0, + rd: 1, + ra: 1, + res1: 0, + ad: 0, + cd: 0, + rcode: 0 }, + question: [ { name: 'microsoft.com.', type: 15, class: 1 } ], + answer: + [ { name: 'microsoft.com.', + type: 15, + class: 1, + ttl: 197, + priority: 10, + exchange: 'microsoft-com.mail.protection.outlook.com.' } ], + authority: [], + additional: [], + edns_options: [], + payload: undefined, +} diff --git a/dns-proxy/native-dns-packet/test/fixtures/www.google.com.a.bin b/dns-proxy/native-dns-packet/test/fixtures/www.google.com.a.bin new file mode 100755 index 0000000..623798c Binary files /dev/null and b/dns-proxy/native-dns-packet/test/fixtures/www.google.com.a.bin differ diff --git a/dns-proxy/native-dns-packet/test/fixtures/www.google.com.a.js b/dns-proxy/native-dns-packet/test/fixtures/www.google.com.a.js new file mode 100755 index 0000000..fa24708 --- /dev/null +++ b/dns-proxy/native-dns-packet/test/fixtures/www.google.com.a.js @@ -0,0 +1,44 @@ +{ header: + { id: 49060, + qr: 1, + opcode: 0, + aa: 0, + tc: 0, + rd: 1, + ra: 1, + res1: 0, + ad: 0, + cd: 0, + rcode: 0 }, + question: [ { name: 'www.google.com.', type: 1, class: 1 } ], + answer: + [ { name: 'www.google.com.', + type: 1, + class: 1, + ttl: 29, + address: '74.125.239.112' }, + { name: 'www.google.com.', + type: 1, + class: 1, + ttl: 29, + address: '74.125.239.116' }, + { name: 'www.google.com.', + type: 1, + class: 1, + ttl: 29, + address: '74.125.239.113' }, + { name: 'www.google.com.', + type: 1, + class: 1, + ttl: 29, + address: '74.125.239.115' }, + { name: 'www.google.com.', + type: 1, + class: 1, + ttl: 29, + address: '74.125.239.114' } ], + authority: [], + additional: [], + edns_options: [], + payload: undefined, +} diff --git a/dns-proxy/native-dns-packet/test/fixtures/www.linode.com.a.bin b/dns-proxy/native-dns-packet/test/fixtures/www.linode.com.a.bin new file mode 100755 index 0000000..ba94308 Binary files /dev/null and b/dns-proxy/native-dns-packet/test/fixtures/www.linode.com.a.bin differ diff --git a/dns-proxy/native-dns-packet/test/fixtures/www.linode.com.a.js b/dns-proxy/native-dns-packet/test/fixtures/www.linode.com.a.js new file mode 100755 index 0000000..8938f00 --- /dev/null +++ b/dns-proxy/native-dns-packet/test/fixtures/www.linode.com.a.js @@ -0,0 +1,33 @@ +{ header: + { id: 46894, + qr: 1, + opcode: 0, + aa: 0, + tc: 0, + rd: 1, + ra: 1, + res1: 0, + ad: 0, + cd: 0, + rcode: 0 }, + question: [ { name: 'www.linode.com.', type: 1, class: 1 } ], + answer: + [ { name: 'www.linode.com.', + type: 1, + class: 1, + ttl: 291, + address: '72.14.191.202' }, + { name: 'www.linode.com.', + type: 1, + class: 1, + ttl: 291, + address: '69.164.200.202' }, + { name: 'www.linode.com.', + type: 1, + class: 1, + ttl: 291, + address: '72.14.180.202' } ], + authority: [], + additional: [], + edns_options: [], + payload: undefined } diff --git a/dns-proxy/native-dns-packet/test/fixtures/www.nodejs.org.cname.bin b/dns-proxy/native-dns-packet/test/fixtures/www.nodejs.org.cname.bin new file mode 100755 index 0000000..f21b3ca Binary files /dev/null and b/dns-proxy/native-dns-packet/test/fixtures/www.nodejs.org.cname.bin differ diff --git a/dns-proxy/native-dns-packet/test/fixtures/www.nodejs.org.cname.js b/dns-proxy/native-dns-packet/test/fixtures/www.nodejs.org.cname.js new file mode 100755 index 0000000..3e8cd56 --- /dev/null +++ b/dns-proxy/native-dns-packet/test/fixtures/www.nodejs.org.cname.js @@ -0,0 +1,23 @@ +{ header: + { id: 32764, + qr: 1, + opcode: 0, + aa: 0, + tc: 0, + rd: 1, + ra: 1, + res1: 0, + ad: 0, + cd: 0, + rcode: 0 }, + question: [ { name: 'www.nodejs.org.', type: 5, class: 1 } ], + answer: + [ { name: 'www.nodejs.org.', + type: 5, + class: 1, + ttl: 3600, + data: 'nodejs.org.' } ], + authority: [], + additional: [], + edns_options: [], + payload: undefined } diff --git a/dns-proxy/native-dns-packet/test/fixtures/www.yahoo.com.a.bin b/dns-proxy/native-dns-packet/test/fixtures/www.yahoo.com.a.bin new file mode 100755 index 0000000..e5d4da3 Binary files /dev/null and b/dns-proxy/native-dns-packet/test/fixtures/www.yahoo.com.a.bin differ diff --git a/dns-proxy/native-dns-packet/test/fixtures/www.yahoo.com.a.js b/dns-proxy/native-dns-packet/test/fixtures/www.yahoo.com.a.js new file mode 100755 index 0000000..9e03115 --- /dev/null +++ b/dns-proxy/native-dns-packet/test/fixtures/www.yahoo.com.a.js @@ -0,0 +1,44 @@ +{ header: + { id: 23208, + qr: 1, + opcode: 0, + aa: 0, + tc: 0, + rd: 1, + ra: 1, + res1: 0, + ad: 0, + cd: 0, + rcode: 0 }, + question: [ { name: 'www.yahoo.com.', type: 1, class: 1 } ], + answer: + [ { name: 'www.yahoo.com.', + type: 5, + class: 1, + ttl: 284, + data: 'fd-fp3.wg1.b.yahoo.com.' }, + { name: 'fd-fp3.wg1.b.yahoo.com.', + type: 5, + class: 1, + ttl: 285, + data: 'ds-fp3.wg1.b.yahoo.com.' }, + { name: 'ds-fp3.wg1.b.yahoo.com.', + type: 5, + class: 1, + ttl: 45, + data: 'ds-any-fp3-lfb.wa1.b.yahoo.com.' }, + { name: 'ds-any-fp3-lfb.wa1.b.yahoo.com.', + type: 5, + class: 1, + ttl: 285, + data: 'ds-any-fp3-real.wa1.b.yahoo.com.' }, + { name: 'ds-any-fp3-real.wa1.b.yahoo.com.', + type: 1, + class: 1, + ttl: 45, + address: '206.190.36.45' } ], + authority: [], + additional: [], + edns_options: [], + payload: undefined, +} diff --git a/dns-proxy/native-dns-packet/test/parse.js b/dns-proxy/native-dns-packet/test/parse.js new file mode 100755 index 0000000..655a75c --- /dev/null +++ b/dns-proxy/native-dns-packet/test/parse.js @@ -0,0 +1,31 @@ +var fs = require('fs'); +var path = require('path'); +var vm = require('vm'); + +var Packet = require('../packet'); + +var test = require('tap').test; + +var fixtureDir = path.join(__dirname, 'fixtures'); + +var files = fs.readdirSync(fixtureDir).filter(function (f) { return /\.bin$/.test(f); }); + +files.forEach(function (file) { + test('can parse ' + file, function (t) { + var bin = fs.readFileSync(path.join(fixtureDir, file)); + var jsFile = path.join(fixtureDir, file.replace(/\.bin$/, '.js')); + var js = 'foo = ' + fs.readFileSync(jsFile, 'utf8'); + js = vm.runInThisContext(js, jsFile); + var ret = Packet.parse(bin); + + // Remove Raw Data from RTRIP for comparison + var i; + for(i = 0; i < ret.question.length; i++) { delete ret.question[i].nameRaw; } + for(i = 0; i < ret.answer.length; i++) { delete ret.answer[i].nameRaw; delete ret.answer[i].dataRaw; delete ret.answer[i].targetRaw; delete ret.answer[i].exchangeRaw} + for(i = 0; i < ret.additional.length; i++) { delete ret.additional[i].nameRaw;} + for(i = 0; i < ret.authority.length; i++) { delete ret.authority[i].nameRaw;} + + t.equivalent(ret, js); + t.end(); + }); +}); diff --git a/dns-proxy/native-dns-packet/test/write.js b/dns-proxy/native-dns-packet/test/write.js new file mode 100755 index 0000000..e04901e --- /dev/null +++ b/dns-proxy/native-dns-packet/test/write.js @@ -0,0 +1,37 @@ +var fs = require('fs'); +var path = require('path'); +var vm = require('vm'); + +var Packet = require('../packet'); + +var test = require('tap').test; + +var fixtureDir = path.join(__dirname, 'fixtures'); + +var files = fs.readdirSync(fixtureDir).filter(function (f) { + return /\.js$/.test(f); +}); + +files.forEach(function (file) { + test('can parse ' + file, function (t) { + var js = 'foo = ' + fs.readFileSync(path.join(fixtureDir, file), 'utf8'); + js = vm.runInThisContext(js, file); + var buff = new Buffer(4096); + var written = Packet.write(buff, js); + var binFile = path.join(fixtureDir, file.replace(/\.js$/, '.bin')); + var bin = fs.readFileSync(binFile); + var rtrip = Packet.parse(buff.slice(0, written)); + + // Remove Raw Data from RTRIP for comparison + var i; + for(i = 0; i < rtrip.question.length; i++) { delete rtrip.question[i].nameRaw; } + for(i = 0; i < rtrip.answer.length; i++) { delete rtrip.answer[i].nameRaw; delete rtrip.answer[i].dataRaw; delete rtrip.answer[i].targetRaw; delete rtrip.answer[i].exchangeRaw} + for(i = 0; i < rtrip.additional.length; i++) { delete rtrip.additional[i].nameRaw;} + for(i = 0; i < rtrip.authority.length; i++) { delete rtrip.authority[i].nameRaw;} + + t.equivalent(written, bin.length, {}, {testMsgLen: file}); + t.equivalent(buff.slice(0, written), bin, {}, {testBin: file}); + t.equivalent(rtrip, js, {}, {testObj: file}); + t.end(); + }); +}); diff --git a/dns-proxy/package.json b/dns-proxy/package.json new file mode 100755 index 0000000..97c05a7 --- /dev/null +++ b/dns-proxy/package.json @@ -0,0 +1,19 @@ +{ + "name": "dnsproxy", + "version": "1.0.0", + "description": "", + "main": "dns_proxy.js", + "scripts": { + "start": "node dns_proxy.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "buffercursor": ">= 0.0.12", + "ipaddr.js": ">= 0.1.1" + }, + "devDependencies": { + "tap": ">= 0.4.3" + } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100755 index 0000000..8369874 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,54 @@ +version: '2.2' + +networks: + pdns: + +volumes: + powerdns-db: +services: + pdns: + container_name: pdns + image: pdns:latest + restart: always + build: ./docker-powerdns + environment: + API_KEY: '123456' + ports: + - '127.0.0.1:5353:53' + - '127.0.0.1:5353:53/udp' + - '127.0.0.1:5381:8081' + networks: + - pdns + volumes: + - powerdns-db:/var/lib/powerdns + + nsedit: + container_name: nsedit + image: fabrix/nsedit + restart: always + environment: + PDNSAPIIP: "pdns" + PDNSAPIPWD: "123456" + ports: + - '5380:8080' + networks: + - pdns + depends_on: + pdns: + condition: service_started + + dns_proxy: + container_name: dns_proxy + image: dns_proxy:latest + restart: always + build: ./dns-proxy + environment: + DNS_SEV: "pdns" + DNS_PORT: "53" + ports: + - '53:53/udp' + networks: + - pdns + depends_on: + pdns: + condition: service_started \ No newline at end of file diff --git a/docker-powerdns/Dockerfile b/docker-powerdns/Dockerfile new file mode 100755 index 0000000..c392b5b --- /dev/null +++ b/docker-powerdns/Dockerfile @@ -0,0 +1,16 @@ +FROM tcely/powerdns-server + +RUN apk add -U sqlite gettext + +ADD ./pdns.conf /etc/pdns/pdns.conf.tpl +ADD ./sqlite-init.sql /root/sqlite-init.sql +ADD ./entrypoint.sh /entrypoint.sh + +ENV API_KEY 123456 + +ENTRYPOINT ["/entrypoint.sh" ] + +CMD ["--disable-syslog=yes"] + +EXPOSE 8081 53 53/udp + diff --git a/docker-powerdns/Makefile b/docker-powerdns/Makefile new file mode 100755 index 0000000..dd8697a --- /dev/null +++ b/docker-powerdns/Makefile @@ -0,0 +1 @@ +docker build -t pnds . \ No newline at end of file diff --git a/docker-powerdns/README.md b/docker-powerdns/README.md new file mode 100755 index 0000000..8a74ae1 --- /dev/null +++ b/docker-powerdns/README.md @@ -0,0 +1,21 @@ +# Power DNS server + +This image serves a PwerDNS server. It allows configuring: + +* socket-dir +* allow-axfr-ips +* gsqlite3-database + +Those parameters are set via environment variables: + +* SOCKET_DIR +* AXFR_IPS +* SQLITE_DATABASE +* API_KEY: if not set, will be autogenerated randomly + +For example, sqlite database shall be in a docker volume +======= +* *SOCKET_DIR:* where unix socket will be saved. +* *AXFR_IPS:* By default uses 127.0.0.0/8, but can be changed if your pdns will be part of a master,slave dns cluster. However, PowerDNS uses [other mechanisms for replication]((https://doc.powerdns.com/authoritative/modes-of-operation.html) +* *SQLITE_DATABASE:* sqlite db path. By default `/var/lib/powerdns/pdns.sqlite3`. Be careful to save this file in a volume. +* *API_KEY:* which API KEY will be used for REST API. If not defined, a random one will be generated. For production installations setting this variable is mandatory. diff --git a/docker-powerdns/entrypoint.sh b/docker-powerdns/entrypoint.sh new file mode 100755 index 0000000..2aa0e17 --- /dev/null +++ b/docker-powerdns/entrypoint.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +set -e + +AXFR_IPS=${AXFR_IPS:-'127.0.0.0/8,::1'} +SQLITE_DATABASE=${SQLITE_DATABASE:-'/var/lib/powerdns/pdns.sqlite3'} +SOCKET_DIR=${SOCKET_DIR:-'/var/run'} +API_KEY=${API_KEY:-$(cat /dev/urandom | tr -dc "a-zA-Z0-9" | fold -w 16 | head -1)} +export AXFR_IPS SQLITE_DATABASE SOCKET_DIR API_KEY + +test -f $SQLITE_DATABASE || \ + sqlite3 $SQLITE_DATABASE < /root/sqlite-init.sql + +chown -R pdns $(dirname $SQLITE_DATABASE) + +envsubst '$AXFR_IPS $SQLITE_DATABASE $SOCKET_DIR $API_KEY' < /etc/pdns/pdns.conf.tpl > /etc/pdns/pdns.conf + +/usr/local/sbin/pdns_server $@ diff --git a/docker-powerdns/pdns.conf b/docker-powerdns/pdns.conf new file mode 100755 index 0000000..e3a77f5 --- /dev/null +++ b/docker-powerdns/pdns.conf @@ -0,0 +1,13 @@ +api=yes +api-key=$API_KEY +local-address=0.0.0.0 +setgid=pdns +setuid=pdns +socket-dir=$SOCKET_DIR +allow-axfr-ips=$AXFR_IPS +webserver=yes +webserver-allow-from=0.0.0.0/0 +webserver-address=0.0.0.0 +launch=gsqlite3 +gsqlite3-database=$SQLITE_DATABASE +gsqlite3-dnssec=on diff --git a/docker-powerdns/sqlite-init.sql b/docker-powerdns/sqlite-init.sql new file mode 100755 index 0000000..4748a8d --- /dev/null +++ b/docker-powerdns/sqlite-init.sql @@ -0,0 +1,92 @@ +PRAGMA foreign_keys = 1; + +CREATE TABLE domains ( + id INTEGER PRIMARY KEY, + name VARCHAR(255) NOT NULL COLLATE NOCASE, + master VARCHAR(128) DEFAULT NULL, + last_check INTEGER DEFAULT NULL, + type VARCHAR(6) NOT NULL, + notified_serial INTEGER DEFAULT NULL, + account VARCHAR(40) DEFAULT NULL +); + +CREATE UNIQUE INDEX name_index ON domains(name); + + +CREATE TABLE records ( + id INTEGER PRIMARY KEY, + domain_id INTEGER DEFAULT NULL, + name VARCHAR(255) DEFAULT NULL, + type VARCHAR(10) DEFAULT NULL, + content VARCHAR(65535) DEFAULT NULL, + ttl INTEGER DEFAULT NULL, + prio INTEGER DEFAULT NULL, + change_date INTEGER DEFAULT NULL, + disabled BOOLEAN DEFAULT 0, + ordername VARCHAR(255), + auth BOOL DEFAULT 1, + FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE ON UPDATE 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 INDEX orderindex ON records(ordername); + + +CREATE TABLE supermasters ( + ip VARCHAR(64) NOT NULL, + nameserver VARCHAR(255) NOT NULL COLLATE NOCASE, + account VARCHAR(40) NOT NULL +); + +CREATE UNIQUE INDEX ip_nameserver_pk ON supermasters(ip, nameserver); + + +CREATE TABLE comments ( + id INTEGER PRIMARY KEY, + domain_id INTEGER NOT NULL, + name VARCHAR(255) NOT NULL, + type VARCHAR(10) NOT NULL, + modified_at INT NOT NULL, + account VARCHAR(40) DEFAULT NULL, + comment VARCHAR(65535) NOT NULL, + FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE ON UPDATE CASCADE +); + +CREATE INDEX comments_domain_id_index ON comments (domain_id); +CREATE INDEX comments_nametype_index ON comments (name, type); +CREATE INDEX comments_order_idx ON comments (domain_id, modified_at); + + +CREATE TABLE domainmetadata ( + id INTEGER PRIMARY KEY, + domain_id INT NOT NULL, + kind VARCHAR(32) COLLATE NOCASE, + content TEXT, + FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE ON UPDATE CASCADE +); + +CREATE INDEX domainmetaidindex ON domainmetadata(domain_id); + + +CREATE TABLE cryptokeys ( + id INTEGER PRIMARY KEY, + domain_id INT NOT NULL, + flags INT NOT NULL, + active BOOL, + content TEXT, + FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE ON UPDATE CASCADE +); + +CREATE INDEX domainidindex ON cryptokeys(domain_id); + + +CREATE TABLE tsigkeys ( + id INTEGER PRIMARY KEY, + name VARCHAR(255) COLLATE NOCASE, + algorithm VARCHAR(50) COLLATE NOCASE, + secret VARCHAR(255) +); + +CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm);