dnsd-name
Version:
Dynamic authoritative name server for Iris services
279 lines (219 loc) • 6.8 kB
JavaScript
// Copyright 2012 Iris Couch, all rights reserved.
//
// Server routines
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({
type: 'udp4',
reuseAddr: true
})
self.udp6 = dgram.createSocket({
type: 'udp6',
reuseAddr: true
})
self.tcp = net.createServer()
self.udp.on('close', function () {
self.close()
})
self.udp6.on('close', function () {
self.close()
})
self.tcp.on('close', function () {
self.close()
})
self.udp.on('error', function (er) {
self.emit('error', er)
})
self.udp6.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)
})
self.udp6.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,'0.0.0.0')
//self.udp6.bind(port,'::')
self.tcp.listen(port)
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('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) {
console.log(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])
self.connection.end(data, function (er) {
if (er)
self.emit('error', er)
})
}
else
self.emit('error', new Error('Unknown connection type: ' + self.connection.type))
}