dnschain
Version:
A blockchain-based DNS + HTTPS server that fixes HTTPS security, and more!
145 lines (117 loc) • 6.02 kB
text/coffeescript
###
dnschain
http://dnschain.net
Copyright (c) 2014 okTurtles Foundation
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
###
Transform = require('stream').Transform
# objectMode is on by default
module.exports = (dnschain) ->
StackedScheduler = require('./stacked-scheduler')(dnschain)
# expose these into our namespace
for k of dnschain.globals
eval "var #{k} = dnschain.globals.#{k};"
NAME_RCODE = dns2.consts.NAME_TO_RCODE
RCODE_NAME = dns2.consts.RCODE_TO_NAME
defaults =
name : 'RS'
stackedDelay: 0
resolver : gConf.get 'dns:oldDNS'
answerFilter: (a) -> a.address
reqMaker : (cname) ->
dns2.Request
question: dns2.Question {name:cname, type:'A'} # TODO: is type=A always correct?
server: @resolver
class ResolverStream extends Transform
constructor: (@opts) ->
@opts = _.cloneDeep @opts # clone these for safety in case outside code updates them
@opts.objectMode ?= true
defaultProps = _.keys defaults
# copy property values in @opts for those keys in 'defaults' into this object
_.assign @, _.pick(_.defaults(@opts, defaults), defaultProps)
super _.omit @opts, defaultProps
@scheduler = new StackedScheduler @opts
@requests = {}
@reqCounter = 0
@errCount = 0
@log = gNewLogger @name
cancelRequests: (andStop=false) ->
@log.debug 'cancelling requests%s', if andStop then ' and stopping' else ''
# cancell all active requests (those that were sent)
for id,req of @requests
@log.debug "cancelling req-#{req.rsReqID} (should have same id: #{id})"
req.cancel()
delete @requests[id]
# cancell all pending requests (those to be sent)
@scheduler.cancelAll()
@stopped = andStop
# 'callback' should only be called in req.on 'end'
_transform: (cname, encoding, callback) ->
if typeof cname != 'string'
gErr "cname isn't a string!", cname
if @stopped
@log.debug gLineInfo("stopped. not scheduling req for '#{cname}'")
return callback()
req = @reqMaker(cname)
req.rsReqID = @reqCounter++
q = req.question
answers = []
success = false
reqErr = undefined
reqErrFn = (code, msg...) =>
reqErr = new Error util.format msg...
reqErr.code = code
@log.debug gLineInfo('reqErrfn'), reqErr.message
@errCount += 1
req.on 'message', (err, answer) =>
if err
@log.error gLineInfo("should not have an error here!"), {err:err?.message, answer:answer, q:q}
reqErrFn NAME_RCODE.SERVFAIL, "msg err %j: %j", q, err.message ? err
else
@log.debug gLineInfo("req-#{req.rsReqID} message"), {resolved_q:q, to:answer.answer}
if answer.answer.length > 0
try
answers.push answer.answer...
answer.answer.forEach (a) => @push @answerFilter a
success = true
catch e
# See: https://github.com/okTurtles/dnschain/issues/43
@log.warn gLineInfo("couldn't push answer"), {error: e.message, q:q, cname:cname, answer:answer}
reqErrFn NAME_RCODE.SERVFAIL, "push after EOF for '%j'?: %j", cname, e.message
else
reqErrFn NAME_RCODE.NOTFOUND, "cname not found: %j", cname
req.on 'timeout', =>
@log.debug gLineInfo("req-#{req.rsReqID} timeout"), {q:q}
# TODO: better rcode value? am not aware of a timeout value
# https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml
reqErrFn NAME_RCODE.SERVFAIL, "timeout for '%j': %j", cname, req
req.on 'cancelled', =>
@log.debug gLineInfo("req-#{req.rsReqID} cancelled"), {q:q}
success = false
req.on 'error', (err) =>
@log.warn gLineInfo("req-#{req.rsReqID} error"), {q:q, err:err?.message}
reqErrFn NAME_RCODE.SERVFAIL, "error for '%j': %j", cname, err
req.on 'end', =>
@log.debug gLineInfo("req-#{req.rsReqID} end"), {q:q, answers:answers, success:success}
delete @requests[req.rsReqID]
# it is possible that neither 'success' is true
# nor is 'reqError' defined. This can happen
# when 'cancelRequests' is called on us.
if reqErr
@log.debug gLineInfo("req-#{req.rsReqID} failed"), {err:reqErr.message, q:q}
# we emit 'failed' instead of 'error' so that we can continue
# processing other `cname`s in the pipeline.
@emit 'failed', reqErr
else if success
@emit 'answers', answers
# we always call `callback` without an error value for the same reason that
# that we emit 'failed' instead of 'error' (to continue processing)
callback()
@log.debug gLineInfo("scheduling req-#{req.rsReqID}"), {q:q, cname:cname}
@scheduler.schedule =>
# add it to the active requests when it's actually sent
@requests[req.rsReqID] = req
@log.debug gLineInfo("sending req-#{req.rsReqID}"), {q:q}
req.send()