@sintaxi/dnsd
Version:
Dynamic authoritative name server for Iris services
248 lines (188 loc) • 6.29 kB
JavaScript
// Copyright 2012 Iris Couch, all rights reserved.
//
// Server routines
require('defaultable')(module,
{
}, function(module, exports, DEFS, require) {
var net = require('net')
var util = require('util')
var dgram = require('dgram')
var events = require('events')
var Message = require('./message')
var convenient = require('./convenient')
module.exports = createServer
function createServer(handler) {
return new Server(handler)
}
util.inherits(Server, events.EventEmitter)
function Server (handler) {
var self = this
events.EventEmitter.call(self)
self.log = console
self.zones = {}
if(handler)
self.on('request', handler)
self.udp = dgram.createSocket('udp4')
self.tcp = net.createServer()
self.udp.on('close', function() { self.close() })
self.tcp.on('close', function() { self.close() })
self.udp.on('error', function(er) { self.emit('error', er) })
self.tcp.on('error', function(er) { self.emit('error', er) })
self.tcp.on('connection', function(connection) { self.on_tcp_connection(connection) })
self.udp.on('message', function(msg, rinfo) { self.on_udp(msg, rinfo) })
var listening = {'tcp':false, 'udp':false}
self.udp.once('listening', function() {
listening.udp = true
if(listening.tcp)
self.emit('listening')
})
self.tcp.once('listening', function() {
listening.tcp = true
if(listening.udp)
self.emit('listening')
})
}
Server.prototype.zone = function(zone, server, admin, serial, refresh, retry, expire, ttl) {
var self = this
, record = zone
if(typeof record != 'object')
record = { 'class': 'IN'
, 'type' : 'SOA'
, 'name' : zone
, 'data' : { 'mname': server
, 'rname': admin
, 'serial': convenient.serial(serial)
, 'refresh': convenient.seconds(refresh)
, 'retry' : convenient.seconds(retry)
, 'expire' : convenient.seconds(expire)
, 'ttl' : convenient.seconds(ttl || 0)
}
}
self.zones[record.name] = record
return self
}
Server.prototype.listen = function(port, ip, callback) {
var self = this
if(typeof ip === 'function') {
callback = ip
ip = null
}
self.port = port
self.ip = ip || '0.0.0.0'
if(typeof callback === 'function')
self.on('listening', callback)
self.udp.bind(port, ip)
self.tcp.listen(port, ip)
return self
}
Server.prototype.close = function() {
var self = this
if(self.udp._receiving)
self.udp.close()
if(self.tcp._handle)
self.tcp.close(function() {
self.emit('close')
})
}
Server.prototype.unref = function() {
this.udp.unref()
this.tcp.unref()
}
Server.prototype.ref = function() {
this.udp.ref()
this.tcp.ref()
}
Server.prototype.on_tcp_connection = function(connection) {
var self = this
var length = null
, bufs = []
connection.type = 'tcp'
connection.server = self
connection.on('error', function(er) { self.emit('error', er) })
connection.on('data', function(data) {
bufs.push(data)
var bytes_received = bufs.reduce(function(state, buf) { return state + buf.length }, 0)
if(length === null && bytes_received >= 2) {
var so_far = Buffer.concat(bufs) // Flatten them all together, it's probably not much data.
length = so_far.readUInt16BE(0)
bufs = [ so_far.slice(2) ]
}
if(length !== null && bytes_received == 2 + length) {
// All of the data (plus the 2-byte length prefix) is received.
var data = Buffer.concat(bufs)
, req = new Request(data, connection)
, res = new Response(data, connection)
self.emit('request', req, res)
}
})
}
Server.prototype.on_udp = function(data, rinfo) {
var self = this
// Provide something that feels like a net.Socket, which in turn feels like the http API.
var connection = { 'type' : self.udp.type
, 'remoteAddress': rinfo.address
, 'remotePort' : rinfo.port
, 'server' : self
, 'send' : function() { self.udp.send.apply(self.udp, arguments) }
, 'destroy' : function() {}
, 'end' : function() {}
}
var req = new Request(data, connection)
, res = new Response(data, connection)
self.emit('request', req, res)
}
util.inherits(Request, Message)
function Request (data, connection) {
var self = this
Message.call(self, data)
self.connection = connection
}
Request.prototype.toJSON = function() {
var self = this
var obj = {}
Object.keys(self).forEach(function(key) {
if(key != 'connection')
obj[key] = self[key]
})
return obj
}
util.inherits(Response, Message)
function Response (data, connection) {
var self = this
Message.call(self, data)
self.question = self.question || []
self.answer = self.answer || []
self.authority = self.authority || []
self.additional = self.additional || []
self.connection = connection
convenient.init_response(self)
}
Response.prototype.toJSON = Request.prototype.toJSON
Response.prototype.end = function(value) {
var self = this
var msg = convenient.final_response(self, value)
, data = msg.toBinary()
if(self.connection.type == 'udp4' && data.length > 512)
return self.emit('error', 'UDP responses greater than 512 bytes not yet implemented')
else if(self.connection.type == 'udp4')
self.connection.send(data, 0, data.length, self.connection.remotePort, self.connection.remoteAddress, function(er) {
if(er)
self.emit('error', er)
})
else if(self.connection.type == 'tcp') {
// Add the data length prefix.
var length = data.length
data = Buffer.concat([ new Buffer([length >> 8, length & 255]), data ])
if (self.connection._readableState.ended && self.connection._writableState.ended){
self.connection.end()
}else{
self.connection.end(data, function(er) {
if(er)
self.emit('error', er)
})
}
}
else
self.emit('error', new Error('Unknown connection type: ' + self.connection.type))
}
}) // defaultable