-
Notifications
You must be signed in to change notification settings - Fork 152
Design
Resolver
Designed to be a drop in replacement for the builtin node.js dns module. Every function and constant that the core currently exports is also exported by this module. It also passes the existing test-suite found in node.js for the dns module.
Beyond the core functionality the resolv* functions return an object that facilitate cancelling an active request.
Server
This module also adds a rudimentary server, currently only available over udp. One simply calls createServer(type), the type can either be udp4 or udp6. The result will wrap common socket operations, like bind, close, and address. It will also re-emit the socket events of listen and close. It does not bind by default, the user is responsible for that.
The main interface with the server comes via the 'message' event, which passes two similar objects. The first is a representation of the dns request sent to the server, the second is the same type, but has the header query response bit set, the proper request id, and a copy of the same questions found in the request. There are 4 arrays: question, answer, authority, and additional found in this type, for your response you push resource records into the proper category, and then call .send() to finalize the response.
Several base types are exported through the module, for instance: A, AAAA, PTR, TXT, SRV, MX and others are already defined. You can instantiate instances of these types by calling dns.A(initializing_properties), all resources require name and ttl to be defined in the initializing object. The rest of the properties vary by type.
One can define more types externally by using the registerType interface. Which expects a type name, and an array of fields (described below) which represent how the rdata of a specific type is laid out.
Underlying Design
There are 3 operations common to most types:
- pack -- Based on the fields in the type (or the fields type) return a Buffer with the binary representation of the type (or field).
- The limitation to this is it isn't flexible enough for label compression. For that to work name.pack needs to know the location of other labels, or compression would need to be a second pass.
- We could pass a pre-allocated Buffer, and offset to begin encoding the fields, and return the size written to the Buffer. Then theoretically we know the length that should actually be sent.
- unpack -- Accepts a Buffer and absolute position in the Buffer to begin decoding the binary representation, return the amount read and potentially value when unpacking fields.
- Messages have a member that keeps the Buffer and the records location in that buffer, beacuse labels may be compressed name.unpack needs to unpack relative to the entire message.
- promote -- ResourceRecord or Packet method, returns the descendent type, like A/AAAA or an EDNS packet
The base type is Message, which when initialized expects a member _fields that is an array of objects that adhere to the following pattern:
{
name: // This will result in a propety defined on the type
value: // During initialization the default value for the field, otherwise
// the actual value of the field for this instance
position: // This is populated during unpack, it is not required during
// initialization
pack: // a function that takes a value and returns it in binary via Buffer
unpack: // given a buffer and position, properly unpack and return the following
// {
// read: // The amount read by this operation
// value: // The actual field value
// field_position: // optional, used for absolution position of rdata
// }
// if the following are defined, then its presumed they operate on a previous
// field
get: // a function that accepts the containing type, and returns the field value
set: // a function that accepts the containing type and value to be set
}
When a Message object is instantiated the _fields array is iterated and then for each field a property getter and setter is defined. Actual field values are stored along with the field definition in the array.
ResourceRecords also define the _rdata_fields array, which is a set of type specific fields, these are unpacked when .promote() is called. The registerType interface expects an array of these fields as well.
The following are the available base field types:
- Struct -- Takes a field name and format type, similar to python's struct module, everything is assumed big endian
- Label -- Requires only a field name
- IPAddress -- Requires a field name and address family (4 or 6)
- BufferField -- Requires a field name, and format for how the length of the buffer should be encoded, the length is located before the actual data
The following are the types that depend on a previous field type:
- SubField -- Requires a field name, parent field name, the offset in bits to the field, and the mask indicating the size of field
- CharString -- Requires a field name, assumes the data is packed in an rdata field in the parent, and is the entirety of the rdata