UNPKG

dnschain

Version:

A blockchain-based DNS + HTTPS server that fixes HTTPS security, and more!

145 lines (117 loc) 6.02 kB
### 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()