git ssb

15+

ansuz / dnssb



Tree: 970c91583716ce49e23e1a89eb78103d6ecfdfa9

Files: 970c91583716ce49e23e1a89eb78103d6ecfdfa9 / lib / server.js

5264 bytesRaw
1var Pull = require("pull-stream");
2var Ansuz = require("ansuz");
3var Query = require("./query");
4var Dump = require("./dump");
5var Format = require("./format");
6var Net = require("net");
7var Pad = require("pad-ipv6");
8
9var Server = module.exports = {};
10
11var log = {
12 req: function (req, opt) {
13 if (!(opt && opt.verbose)) { return; }
14 var q = req.question[0];
15 console.log();
16 console.log({
17 name: q.name,
18 type: q.type,
19 class: q.class,
20 });
21 },
22 nonCachedQuery: function (req, q) {
23 console.log('dns', req.connection.remoteAddress,
24 q.name, q.class, q.type, q.serial || '');
25 }
26};
27
28function queryKey(q) {
29 return q.name.toLowerCase() + ' ' +
30 (q.class || 'IN') + ' ' + q.type + ' ' +
31 (q.serial||0);
32}
33
34function CachingResolver(sbot, opt) {
35 this.sbot = sbot;
36 this.cache = {/* name+class+type: result */};
37 this.cbs = {/* name+class+type: [callback] */};
38 opt = opt || {};
39 this.verbose = opt.verbose === undefined || opt.verbose;
40}
41
42CachingResolver.prototype.answer = function (req, res) {
43 log.req(req, this.opt);
44
45 // one query per dns message
46 var q = req.question[0];
47
48 // TODO validate queries more carefully
49 if (!q) {
50 console.error("invalid question");
51 res.responseCode = 1; // FORMERR
52 res.end();
53 return;
54 }
55
56 if (q.type === 'IXFR') {
57 q.serial = req.authority[0] && req.authority[0].data.serial || 0
58 }
59
60 this.query(q, req, function (err, result) {
61 if (err) {
62 console.error(err.stack || err);
63 res.responseCode = 2; // SERVFAIL
64 return res.end();
65 }
66 if (result.authoritative) {
67 res.authoritative = true;
68
69 if (!result.domainExists) {
70 res.responseCode = 3; // NXDOMAIN
71 }
72 }
73 /*
74 if (opt && opt.verbose) {
75 var recs = records.map(Format.recordToLine).join(", ")
76 var auths = authorities.map(Format.recordToLine).join(", ")
77 console.log("%s: %s%s", q.name, recs,
78 auths ? '. auths: ' : '', auths);
79 }
80 */
81 res.question = result.questions;
82 res.answer = result.answers;
83 res.authority = result.authorities;
84 res.additional = result.additionals;
85 try {
86 res.end();
87 } catch(e) {
88 console.error(e);
89 res.answer = [];
90 res.authority = [];
91 res.additional = [];
92 res.responseCode = 2; // SERVFAIL
93 try {
94 res.end();
95 } catch(e) {
96 if (e && e.name !== 'This socket has been ended by the other party') {
97 console.error('error serving dns', e)
98 }
99 }
100 }
101 });
102};
103
104CachingResolver.prototype.query = function (q, req, cb) {
105 // TODO: cache IXFR and AXFR responses and handle their invalidation
106 var key = queryKey(q);
107 var result = this.cache[key];
108 if (result) {
109 // cache hit
110 if (result.expires > Date.now()) {
111 return cb(null, result);
112 } else {
113 // expired
114 delete this.cache[key];
115 }
116 }
117
118 var cbs = this.cbs[key];
119 if (cbs) return cbs.push(cb);
120 cbs = this.cbs[key] = [cb];
121
122 if (this.verbose) {
123 log.nonCachedQuery(req, q);
124 }
125
126 var self = this;
127 Query.query(this.sbot, q, function (err, result) {
128 if (!err && result.cache) self.cache[key] = result;
129 while (cbs.length) cbs.shift()(err, result);
130 delete self.cbs[key];
131 });
132};
133
134CachingResolver.prototype.autoPurge = function (interval) {
135 return setInterval(this.purgeExpired.bind(this), interval);
136};
137
138CachingResolver.prototype.purgeExpired = function () {
139 var now = Date.now();
140 for (var key in this.cache) {
141 var result = this.cache[key];
142 if (now > result.expires) {
143 delete this.cache[key];
144 }
145 }
146};
147
148CachingResolver.prototype.autoExpire = function () {
149 // evict cached results when new record would change them
150 var self = this;
151 Pull(this.sbot.messagesByType({
152 type: 'ssb-dns',
153 old: false
154 }),
155 Pull.drain(function (msg) {
156 var record = msg.value.content.record;
157 if (record) delete self.cache[queryKey(record)];
158 }, function (err) {
159 if (err) throw err
160 }));
161};
162
163var createServer = Server.create = function (sbot, port, host, cb, opt) {
164 if (Net.isIPv6(host)) { host = Pad(host); }
165
166 var resolver = new CachingResolver(sbot, opt);
167 resolver.autoPurge(180e3);
168 resolver.autoExpire();
169
170 var Dnsd = require("modern-dnsd");
171 var server = Dnsd.createServer(function(req, res) {
172 resolver.answer(req, res);
173 });
174
175 server.on('error', function (msg, error, conn) {
176 console.error(error ? error.stack : msg)
177 });
178
179 return server.listen(port, host, cb);
180};
181
182Server.listen = function (sbot, port, host, cb, opt) {
183 var server = createServer(sbot, port, host, cb, opt);
184
185 var close = function () {
186 console.error("Server connection lost. Shutting down");
187 process.exit(1);
188 };
189
190 sbot.on('closed', close);
191};
192
193

Built with git-ssb-web