├── .gitignore ├── LICENSE ├── README.md ├── example-dhcpinform.js ├── example-server.js ├── example └── client.js ├── lib ├── client.js ├── index.js ├── parser.js ├── protocol.js └── server.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2014 Andrew Paprocki 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | dhcpjs provides native DHCP support in Node.js. 2 | 3 | ## Introduction 4 | 5 | Currently, this project just provides simple client and server protocol APIs 6 | which allow an application to consume DHCP messages broadcast to the network 7 | as JS objects. 8 | 9 | The module can be used to sniff DHCP traffic, and a skeleton client example 10 | is provided to show how the module can be used to build a full DHCP client. 11 | 12 | In the future, a full DHCP client and/or server may be implemented as a 13 | separate module with a dependency on this one. In addition, I am interested in creating a DHCP fuzzer and mis-behaved clients to help with testing servers and 14 | DoS scenarios. 15 | 16 | ## Usage 17 | 18 | ```js 19 | var util = require('util'); 20 | var dhcpjs = require('dhcpjs'); 21 | var server = dhcpjs.createServer(); 22 | server.on('message', function(m) { 23 | console.log(util.inspect(m, false, 3)); 24 | }); 25 | server.on('listening', function(address) { 26 | console.log('listening on ' + address); 27 | }); 28 | server.bind(); 29 | ``` 30 | 31 | The example must be executed as root because it binds to port 67. The same 32 | example code also works with the createClient() function which binds to port 33 | 68. To run: 34 | 35 | sudo node example.js 36 | 37 | When a DHCP message is received, output similar to this will be printed: 38 | 39 | ```js 40 | { op: { value: 1, name: 'BOOTPREQUEST' }, 41 | hlen: 6, 42 | hops: 0, 43 | xid: 2078975723, 44 | secs: 0, 45 | flags: 0, 46 | ciaddr: '10.0.1.9', 47 | yiaddr: undefined, 48 | siaddr: undefined, 49 | giaddr: undefined, 50 | chaddr: 51 | { type: { value: 1, name: 'HW_ETHERNET' }, 52 | address: '00:23:4e:ff:ff:ff' }, 53 | sname: '', 54 | file: '', 55 | magic: 1669485411, 56 | options: 57 | { dhcpMessageType: { value: 3, name: 'DHCPREQUEST' }, 58 | clientIdentifier: 59 | { type: { value: 1, name: 'HW_ETHERNET' }, 60 | address: '00:23:4e:ff:ff:ff' }, 61 | hostName: 'IDEAPAD', 62 | fullyQualifiedDomainName: { flags: 0, name: 'IDEAPAD.' }, 63 | vendorClassIdentifier: 'MSFT 5.0', 64 | parameterRequestList: [ 1, 15, 3, 6, 44, 46, 47, 31, 33, 249, 43 ], 65 | vendorOptions: { '220': } } } 66 | ``` 67 | 68 | To see the skeleton DHCP client operate, edit example-client.js and modify the 69 | ethernet address and client identifier to match those on your computer. Run: 70 | 71 | sudo node example-client.js 72 | 73 | This will send a DHCPDISCOVER packet to the network and any proper DHCP server 74 | will respond with a DHCPOFFER packet that will be printed to the console. 75 | 76 | ## Installation 77 | 78 | npm install dhcpjs 79 | 80 | ## License 81 | 82 | This module is released under the MIT license. 83 | 84 | ## Bugs 85 | 86 | See . 87 | -------------------------------------------------------------------------------- /example-dhcpinform.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Andrew Paprocki 2 | 3 | var dhcpjs = require('dhcpjs'); 4 | var util = require('util'); 5 | var os = require('os'); 6 | 7 | var client = new dhcpjs.Client(); 8 | client.on('message', function(pkt) { 9 | console.log('message:', util.inspect(pkt, false, 3)); 10 | }); 11 | client.on('dhcpOffer', function(pkt) { 12 | console.log('dhcpOffer:', util.inspect(pkt, false, 3)); 13 | }); 14 | client.on('dhcpAck', function(pkt) { 15 | console.log('dhcpAck:', util.inspect(pkt, false, 3)); 16 | }); 17 | client.on('dhcpNak', function(pkt) { 18 | console.log('dhcpNak:', util.inspect(pkt, false, 3)); 19 | }); 20 | client.on('listening', function(addr) { 21 | console.log('listening on', addr); 22 | }); 23 | client.bind('0.0.0.0', 68, function() { 24 | console.log('bound to 0.0.0.0:68'); 25 | }); 26 | 27 | // Configure a DHCPINFORM packet: 28 | // xid Transaction ID. This is a counter that the DHCP 29 | // client should maintain and increment every time 30 | // a packet is broadcast. 31 | // 32 | // chaddr Ethernet address of the interface. 33 | // 34 | // ciaddr Pre-configured client network address. 35 | // 36 | // options Object containing keys that map to DHCP options. 37 | // 38 | // dhcpMessageType Option indicating a DHCP protocol message (as 39 | // opposed to a plain BOOTP protocol message). 40 | // 41 | // clientIdentifier Option indicating a client-configured unique name 42 | // to be used to disambiguate the lease on the server. 43 | 44 | var xid = 1; 45 | var interfaces = os.getNetworkInterfaces(); 46 | 47 | for (var interface in interfaces) { 48 | var addresses = interfaces[interface]; 49 | for (var address in addresses) { 50 | if (addresses[address].family === 'IPv4' && 51 | !addresses[address].internal) { 52 | console.log(util.inspect(addresses[address], false)); 53 | var pkt = { 54 | xid: xid++, 55 | chaddr: addresses[address].mac, 56 | ciaddr: addresses[address].address, 57 | options: { 58 | dhcpMessageType: dhcpjs.Protocol.DHCPMessageType.DHCPINFORM, 59 | clientIdentifier: 'MyMachine', 60 | } 61 | } 62 | var inform = client.createPacket(pkt); 63 | client.broadcastPacket(inform, undefined, function() { 64 | console.log('dhcpInform ['+interface+': '+pkt.ciaddr+']: sent'); 65 | }); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /example-server.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var dhcpjs = require('dhcpjs'); 3 | var server = dhcpjs.createServer(); 4 | server.on('message', function(m) { 5 | console.log(util.inspect(m, false, 3)); 6 | }); 7 | server.on('listening', function(address) { 8 | console.log('listening on ' + address); 9 | }); 10 | server.bind(); 11 | // Or to specify the port: (usefull for testing) 12 | //server.bind(null,1067); 13 | -------------------------------------------------------------------------------- /example/client.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // Copyright (c) 2014 Andrew Paprocki 4 | 5 | var os = require('os') 6 | 7 | var util = require('util'); 8 | 9 | var dhcpjs = require('..'); 10 | 11 | 12 | var client = new dhcpjs.Client(); 13 | 14 | client.on('message', function(pkt) { 15 | console.log('message:', util.inspect(pkt, false, 3)); 16 | }); 17 | client.on('dhcpOffer', function(pkt) { 18 | console.log('dhcpOffer:', util.inspect(pkt, false, 3)); 19 | }); 20 | client.on('dhcpAck', function(pkt) { 21 | console.log('dhcpAck:', util.inspect(pkt, false, 3)); 22 | }); 23 | client.on('dhcpNak', function(pkt) { 24 | console.log('dhcpNak:', util.inspect(pkt, false, 3)); 25 | }); 26 | client.on('listening', function(addr) { 27 | console.log('listening on', addr); 28 | }); 29 | client.bind('0.0.0.0', 68, function() { 30 | console.log('bound to 0.0.0.0:68'); 31 | }); 32 | 33 | 34 | var interfaces = os.getNetworkInterfaces() 35 | 36 | for(var name in interfaces) 37 | { 38 | // Configure a DHCPDISCOVER packet: 39 | // xid 0x01 Transaction ID. This is a counter that the DHCP 40 | // client should maintain and increment every time 41 | // a packet is broadcast. 42 | // 43 | // chaddr Ethernet address of the interface being configured 44 | // 45 | // options Object containing keys that map to DHCP options 46 | // 47 | // dhcpMessageType Option indicating a DHCP protocol message (as 48 | // opposed to a plain BOOTP protocol message) 49 | // 50 | // clientIdentifier Option indicating a client-configured unique name 51 | // to be used to disambiguate the lease on the server 52 | var discover = client.createDiscoverPacket( 53 | { 54 | xid: 0x01, 55 | chaddr: interfaces[name][0].mac, 56 | options: 57 | { 58 | dhcpMessageType: dhcpjs.Protocol.DHCPMessageType.DHCPDISCOVER, 59 | clientIdentifier: 'MyMachine', 60 | } 61 | }); 62 | 63 | client.broadcastPacket(discover, undefined, function() 64 | { 65 | console.log('dhcpDiscover ('+name+'): sent'); 66 | }); 67 | } 68 | -------------------------------------------------------------------------------- /lib/client.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 Andrew Paprocki 2 | 3 | var EventEmitter = require('events').EventEmitter; 4 | var util = require('util'); 5 | var dgram = require('dgram'); 6 | var V4Address = require('ip-address').Address4; 7 | var parser = require('./parser'); 8 | var protocol = require('./protocol'); 9 | 10 | function Client(options) { 11 | if (options) { 12 | if (typeof(options) !== 'object') 13 | throw new TypeError('options must be an object'); 14 | } else { 15 | options = {}; 16 | } 17 | 18 | var self = this; 19 | EventEmitter.call(this, options); 20 | 21 | this.client = dgram.createSocket('udp4'); 22 | this.client.on('message', function(msg) { 23 | var pkt = parser.parse(msg); 24 | switch (pkt.options.dhcpMessageType.value) { 25 | case protocol.DHCPMessageType.DHCPOFFER.value: 26 | self.emit('dhcpOffer', pkt); 27 | break; 28 | case protocol.DHCPMessageType.DHCPACK.value: 29 | self.emit('dhcpAck', pkt); 30 | break; 31 | case protocol.DHCPMessageType.DHCPNAK.value: 32 | self.emit('dhcpNak', pkt); 33 | break; 34 | default: 35 | assert(!'Client: received unhandled DHCPMessageType ' + 36 | pkt.options.dhcpMessageType.value); 37 | } 38 | }); 39 | this.client.on('listening', function() { 40 | var address = self.client.address(); 41 | self.emit('listening', address.address + ':' + address.port); 42 | }); 43 | } 44 | util.inherits(Client, EventEmitter); 45 | module.exports = Client; 46 | 47 | Client.prototype.bind = function(host, port, cb) { 48 | var that = this; 49 | if (!port) port = 68; 50 | this.client.bind(port, host, function() { 51 | that.client.setBroadcast(true); 52 | if (cb && cb instanceof Function) { 53 | process.nextTick(cb); 54 | } 55 | }); 56 | } 57 | 58 | Client.prototype.broadcastPacket = function(pkt, options, cb) { 59 | var port = 67; 60 | var host = '255.255.255.255'; 61 | if (options) { 62 | if ('port' in options) port = options.port; 63 | if ('host' in options) host = options.host; 64 | } 65 | this.client.send(pkt, 0, pkt.length, port, host, cb); 66 | } 67 | 68 | Client.prototype.createPacket = function(pkt) { 69 | if (!('xid' in pkt)) 70 | throw new Error('pkt.xid required'); 71 | 72 | var ci = new Buffer(('ciaddr' in pkt) ? 73 | new V4Address(pkt.ciaddr).toArray() : [0, 0, 0, 0]); 74 | var yi = new Buffer(('yiaddr' in pkt) ? 75 | new V4Address(pkt.yiaddr).toArray() : [0, 0, 0, 0]); 76 | var si = new Buffer(('siaddr' in pkt) ? 77 | new V4Address(pkt.siaddr).toArray() : [0, 0, 0, 0]); 78 | var gi = new Buffer(('giaddr' in pkt) ? 79 | new V4Address(pkt.giaddr).toArray() : [0, 0, 0, 0]); 80 | 81 | if (!('chaddr' in pkt)) 82 | throw new Error('pkt.chaddr required'); 83 | var hw = new Buffer(pkt.chaddr.split(':').map(function(part) { 84 | return parseInt(part, 16); 85 | })); 86 | if (hw.length !== 6) 87 | throw new Error('pkt.chaddr malformed, only ' + hw.length + ' bytes'); 88 | 89 | var p = new Buffer(1500); 90 | var i = 0; 91 | 92 | p.writeUInt8(pkt.op, i++); 93 | p.writeUInt8(pkt.htype, i++); 94 | p.writeUInt8(pkt.hlen, i++); 95 | p.writeUInt8(pkt.hops, i++); 96 | p.writeUInt32BE(pkt.xid, i); i += 4; 97 | p.writeUInt16BE(pkt.secs, i); i += 2; 98 | p.writeUInt16BE(pkt.flags, i); i += 2; 99 | ci.copy(p, i); i += ci.length; 100 | yi.copy(p, i); i += yi.length; 101 | si.copy(p, i); i += si.length; 102 | gi.copy(p, i); i += gi.length; 103 | hw.copy(p, i); i += hw.length; 104 | p.fill(0, i, i + 10); i += 10; // hw address padding 105 | p.fill(0, i, i + 192); i += 192; 106 | p.writeUInt32BE(0x63825363, i); i += 4; 107 | 108 | if (pkt.options && 'requestedIpAddress' in pkt.options) { 109 | p.writeUInt8(50, i++); // option 50 110 | var requestedIpAddress = new Buffer( 111 | new V4Address(pkt.options.requestedIpAddress).toArray()); 112 | p.writeUInt8(requestedIpAddress.length, i++); 113 | requestedIpAddress.copy(p, i); i += requestedIpAddress.length; 114 | } 115 | if (pkt.options && 'dhcpMessageType' in pkt.options) { 116 | p.writeUInt8(53, i++); // option 53 117 | p.writeUInt8(1, i++); // length 118 | p.writeUInt8(pkt.options.dhcpMessageType.value, i++); 119 | } 120 | if (pkt.options && 'serverIdentifier' in pkt.options) { 121 | p.writeUInt8(54, i++); // option 54 122 | var serverIdentifier = new Buffer( 123 | new V4Address(pkt.options.serverIdentifier).toArray()); 124 | p.writeUInt8(serverIdentifier.length, i++); 125 | serverIdentifier.copy(p, i); i += serverIdentifier.length; 126 | } 127 | if (pkt.options && 'parameterRequestList' in pkt.options) { 128 | p.writeUInt8(55, i++); // option 55 129 | var parameterRequestList = new Buffer(pkt.options.parameterRequestList); 130 | if (parameterRequestList.length > 16) 131 | throw new Error('pkt.options.parameterRequestList malformed'); 132 | p.writeUInt8(parameterRequestList.length, i++); 133 | parameterRequestList.copy(p, i); i += parameterRequestList.length; 134 | } 135 | if (pkt.options && 'clientIdentifier' in pkt.options) { 136 | var clientIdentifier = new Buffer(pkt.options.clientIdentifier); 137 | var optionLength = 1 + clientIdentifier.length; 138 | if (optionLength > 0xff) 139 | throw new Error('pkt.options.clientIdentifier malformed'); 140 | p.writeUInt8(61, i++); // option 61 141 | p.writeUInt8(optionLength, i++); // length 142 | p.writeUInt8(0, i++); // hardware type 0 143 | clientIdentifier.copy(p, i); i += clientIdentifier.length; 144 | } 145 | if (pkt.options && 'vendorOptions' in pkt.options) { 146 | for (var key in pkt.options.vendorOptions) { 147 | var value = pkt.options.vendorOptions[key]; 148 | p.writeUInt8(parseInt(key), i++); // option 'key' 149 | p.writeUInt8(value.length, i++); // length 150 | value.copy(p, i); i += value.length; 151 | } 152 | } 153 | 154 | // option 255 - end 155 | p.writeUInt8(0xff, i++); 156 | 157 | // padding 158 | if ((i % 2) > 0) { 159 | p.writeUInt8(0, i++); 160 | } else { 161 | p.writeUInt16BE(0, i++); 162 | } 163 | 164 | var remaining = 300 - i; 165 | if (remaining) { 166 | p.fill(0, i, i + remaining); i+= remaining; 167 | } 168 | 169 | //console.log('createPacket:', i, 'bytes'); 170 | return p.slice(0, i); 171 | } 172 | 173 | Client.prototype.createDiscoverPacket = function(user) { 174 | var pkt = { 175 | op: 0x01, 176 | htype: 0x01, 177 | hlen: 0x06, 178 | hops: 0x00, 179 | xid: 0x00000000, 180 | secs: 0x0000, 181 | flags: 0x0000, 182 | ciaddr: '0.0.0.0', 183 | yiaddr: '0.0.0.0', 184 | siaddr: '0.0.0.0', 185 | giaddr: '0.0.0.0', 186 | }; 187 | if ('xid' in user) pkt.xid = user.xid; 188 | if ('chaddr' in user) pkt.chaddr = user.chaddr; 189 | if ('options' in user) pkt.options = user.options; 190 | return Client.prototype.createPacket(pkt); 191 | } 192 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 Andrew Paprocki 2 | 3 | var Server = require('./server'); 4 | var Client = require('./client'); 5 | var Protocol = require('./protocol'); 6 | 7 | module.exports = { 8 | Server: Server, 9 | createServer: function(options, socket_opts) { 10 | return new Server(options, socket_opts); 11 | }, 12 | Client: Client, 13 | createClient: function(options) { 14 | return new Client(options); 15 | }, 16 | Protocol: Protocol 17 | } 18 | -------------------------------------------------------------------------------- /lib/parser.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 Andrew Paprocki 2 | 3 | var assert = require('assert'); 4 | var protocol = require('./protocol'); 5 | 6 | module.exports.parse = function(msg, rinfo) { 7 | function trimNulls(str) { 8 | var idx = str.indexOf('\u0000'); 9 | return (-1 === idx) ? str : str.substr(0, idx); 10 | } 11 | function readIpRaw(msg, offset) { 12 | if (0 === msg.readUInt8(offset)) 13 | return undefined; 14 | return '' + 15 | msg.readUInt8(offset++) + '.' + 16 | msg.readUInt8(offset++) + '.' + 17 | msg.readUInt8(offset++) + '.' + 18 | msg.readUInt8(offset++); 19 | } 20 | function readIp(msg, offset, obj, name) { 21 | var len = msg.readUInt8(offset++); 22 | assert.strictEqual(len, 4); 23 | obj[name] = readIpRaw(msg, offset); 24 | return offset + len; 25 | } 26 | function readString(msg, offset, obj, name) { 27 | var len = msg.readUInt8(offset++); 28 | obj[name] = msg.toString('ascii', offset, offset + len); 29 | offset += len; 30 | return offset; 31 | } 32 | function readAddressRaw(msg, offset, len) { 33 | var addr = ''; 34 | while (len-- > 0) { 35 | var b = msg.readUInt8(offset++); 36 | addr += (b + 0x100).toString(16).substr(-2); 37 | if (len > 0) { 38 | addr += ':'; 39 | } 40 | } 41 | return addr; 42 | } 43 | function readHex(msg, offset, obj, name) { 44 | var len = msg.readUInt8(offset++); 45 | obj[name] = readHexRaw(msg, offset, len); 46 | offset += len; 47 | return offset; 48 | } 49 | function readHexRaw(msg, offset, len) { 50 | var data = ''; 51 | while (len-- > 0) { 52 | var b = msg.readUInt8(offset++); 53 | data += (b + 0x100).toString(16).substr(-2); 54 | } 55 | return data; 56 | } 57 | //console.log(rinfo.address + ':' + rinfo.port + '/' + msg.length + 'b'); 58 | var p = { 59 | op: protocol.BOOTPMessageType.get(msg.readUInt8(0)), 60 | // htype is combined into chaddr field object 61 | hlen: msg.readUInt8(2), 62 | hops: msg.readUInt8(3), 63 | xid: msg.readUInt32BE(4), 64 | secs: msg.readUInt16BE(8), 65 | flags: msg.readUInt16BE(10), 66 | ciaddr: readIpRaw(msg, 12), 67 | yiaddr: readIpRaw(msg, 16), 68 | siaddr: readIpRaw(msg, 20), 69 | giaddr: readIpRaw(msg, 24), 70 | chaddr: protocol.createHardwareAddress( 71 | protocol.ARPHardwareType.get(msg.readUInt8(1)), 72 | readAddressRaw(msg, 28, msg.readUInt8(2))), 73 | sname: trimNulls(msg.toString('ascii', 44, 108)), 74 | file: trimNulls(msg.toString('ascii', 108, 236)), 75 | magic: msg.readUInt32BE(236), 76 | options: {} 77 | }; 78 | var offset = 240; 79 | var code = 0; 80 | while (code != 255 && offset < msg.length) { 81 | code = msg.readUInt8(offset++); 82 | switch (code) { 83 | case 0: continue; // pad 84 | case 255: break; // end 85 | case 1: { // subnetMask 86 | offset = readIp(msg, offset, p.options, 'subnetMask'); 87 | break; 88 | } 89 | case 2: { // timeOffset 90 | var len = msg.readUInt8(offset++); 91 | assert.strictEqual(len, 4); 92 | p.options.timeOffset = msg.readUInt32BE(offset); 93 | offset += len; 94 | break; 95 | } 96 | case 3: { // routerOption 97 | var len = msg.readUInt8(offset++); 98 | assert.strictEqual(len % 4, 0); 99 | p.options.routerOption = []; 100 | while (len > 0) { 101 | p.options.routerOption.push(readIpRaw(msg, offset)); 102 | offset += 4; 103 | len -= 4; 104 | } 105 | break; 106 | } 107 | case 4: { // timeServerOption 108 | var len = msg.readUInt8(offset++); 109 | assert.strictEqual(len % 4, 0); 110 | p.options.timeServerOption = []; 111 | while (len > 0) { 112 | p.options.timeServerOption.push(readIpRaw(msg, offset)); 113 | offset += 4; 114 | len -= 4; 115 | } 116 | break; 117 | } 118 | case 6: { // domainNameServerOption 119 | var len = msg.readUInt8(offset++); 120 | assert.strictEqual(len % 4, 0); 121 | p.options.domainNameServerOption = []; 122 | while (len > 0) { 123 | p.options.domainNameServerOption.push( 124 | readIpRaw(msg, offset)); 125 | offset += 4; 126 | len -= 4; 127 | } 128 | break; 129 | } 130 | case 12: { // hostName 131 | offset = readString(msg, offset, p.options, 'hostName'); 132 | break; 133 | } 134 | case 15: { // domainName 135 | offset = readString(msg, offset, p.options, 'domainName'); 136 | break; 137 | } 138 | case 43: { // vendorOptions 139 | var len = msg.readUInt8(offset++); 140 | p.options.vendorOptions = {}; 141 | while (len > 0) { 142 | var vendop = msg.readUInt8(offset++); 143 | var vendoplen = msg.readUInt8(offset++); 144 | var buf = new Buffer(vendoplen); 145 | msg.copy(buf, 0, offset, offset + vendoplen); 146 | p.options.vendorOptions[vendop] = buf; 147 | len -= 2 + vendoplen; 148 | offset += vendoplen; 149 | } 150 | break; 151 | } 152 | case 50: { // requestedIpAddress 153 | offset = readIp(msg, offset, p.options, 'requestedIpAddress'); 154 | break; 155 | } 156 | case 51: { // ipAddressLeaseTime 157 | var len = msg.readUInt8(offset++); 158 | assert.strictEqual(len, 4); 159 | p.options.ipAddressLeaseTime = 160 | msg.readUInt32BE(offset); 161 | offset += 4; 162 | break; 163 | } 164 | case 52: { // optionOverload 165 | var len = msg.readUInt8(offset++); 166 | assert.strictEqual(len, 1); 167 | p.options.optionOverload = msg.readUInt8(offset++); 168 | break; 169 | } 170 | case 53: { // dhcpMessageType 171 | var len = msg.readUInt8(offset++); 172 | assert.strictEqual(len, 1); 173 | var mtype = msg.readUInt8(offset++); 174 | assert.ok(1 <= mtype); 175 | assert.ok(8 >= mtype); 176 | p.options.dhcpMessageType = protocol.DHCPMessageType.get(mtype); 177 | break; 178 | } 179 | case 54: { // serverIdentifier 180 | offset = readIp(msg, offset, p.options, 'serverIdentifier'); 181 | break; 182 | } 183 | case 55: { // parameterRequestList 184 | var len = msg.readUInt8(offset++); 185 | p.options.parameterRequestList = []; 186 | while (len-- > 0) { 187 | var option = msg.readUInt8(offset++); 188 | p.options.parameterRequestList.push(option); 189 | } 190 | break; 191 | } 192 | case 57: { // maximumMessageSize 193 | var len = msg.readUInt8(offset++); 194 | assert.strictEqual(len, 2); 195 | p.options.maximumMessageSize = msg.readUInt16BE(offset); 196 | offset += len; 197 | break; 198 | } 199 | case 58: { // renewalTimeValue 200 | var len = msg.readUInt8(offset++); 201 | assert.strictEqual(len, 4); 202 | p.options.renewalTimeValue = msg.readUInt32BE(offset); 203 | offset += len; 204 | break; 205 | } 206 | case 59: { // rebindingTimeValue 207 | var len = msg.readUInt8(offset++); 208 | assert.strictEqual(len, 4); 209 | p.options.rebindingTimeValue = msg.readUInt32BE(offset); 210 | offset += len; 211 | break; 212 | } 213 | case 60: { // vendorClassIdentifier 214 | offset = readString(msg, offset, p.options, 215 | 'vendorClassIdentifier'); 216 | break; 217 | } 218 | case 61: { // clientIdentifier 219 | var len = msg.readUInt8(offset++); 220 | p.options.clientIdentifier = 221 | protocol.createHardwareAddress( 222 | protocol.ARPHardwareType.get(msg.readUInt8(offset)), 223 | readAddressRaw(msg, offset + 1, len - 1)); 224 | offset += len; 225 | break; 226 | } 227 | case 72: { // defaultWWWServers 228 | var len = msg.readUInt8(offset++); 229 | assert.strictEqual(len % 4, 0); 230 | p.options.deafultWWWSevers = []; 231 | while (len > 0) { 232 | p.options.deafultWWWSevers.push(readIpRaw(msg, offset)); 233 | offset += 4; 234 | len -= 4; 235 | } 236 | break; 237 | } 238 | case 81: { // fullyQualifiedDomainName 239 | var len = msg.readUInt8(offset++); 240 | p.options.fullyQualifiedDomainName = { 241 | flags: msg.readUInt8(offset), 242 | name: msg.toString('ascii', offset + 3, offset + len) 243 | }; 244 | offset += len; 245 | break; 246 | } 247 | case 82: { // relayAgentInformation (RFC 3046) 248 | var len = msg.readUInt8(offset++); 249 | p.options.relayAgentInformation = {}; 250 | var cur = offset; 251 | offset += len; 252 | while (cur < offset) { 253 | var subopt = msg.readUInt8(cur++); 254 | switch (subopt) { 255 | case 1: { // agentCircuitId (RFC 3046) 256 | cur = readHex(msg, cur, 257 | p.options.relayAgentInformation, 258 | 'agentCircuitId'); 259 | break; 260 | } 261 | case 2: { // agentRemoteId (RFC 3046) 262 | cur = readHex(msg, cur, 263 | p.options.relayAgentInformation, 264 | 'agentRemoteId'); 265 | break; 266 | } 267 | case 4: { // docsisDeviceClass (RFC 3256) 268 | var sublen = msg.readUInt8(cur++); 269 | assert.strictEqual(sublen, 4); 270 | p.options.relayAgentInformation.docsisDeviceClass = 271 | msg.readUInt32(cur); 272 | cur += sublen; 273 | break; 274 | } 275 | case 5: { // linkSelection (RFC 3527) 276 | assert.strictEqual(sublen, 4); 277 | cur = readIp(msg, cur, 278 | p.options.relayAgentInformation, 279 | 'linkSelectionSubnet'); 280 | break; 281 | } 282 | case 6: { // subscriberId (RFC 3993) 283 | cur = readString(msg, cur, 284 | p.options.relayAgentInformation, 285 | 'subscriberId'); 286 | break; 287 | } 288 | default: { 289 | console.log('Unhandled DHCP option 82 sub-option ' + 290 | subopt + ", len " + sublen); 291 | var sublen = msg.readUInt8(cur++); 292 | cur += sublen; 293 | break; 294 | } 295 | } 296 | } 297 | break; 298 | } 299 | case 118: { // subnetSelection 300 | offset = readIp(msg, offset, p.options, 'subnetAddress'); 301 | break; 302 | } 303 | case 252: { // The Web Proxy Auto Discovery (WPAD) 304 | offset = readString(msg, offset, p.options, 'wpadUrl'); 305 | break; 306 | } 307 | default: { 308 | var len = msg.readUInt8(offset++); 309 | console.log('Unhandled DHCP option ' + code + '/' + len + 'b'); 310 | offset += len; 311 | break; 312 | } 313 | } 314 | } 315 | return p; 316 | }; 317 | -------------------------------------------------------------------------------- /lib/protocol.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 Andrew Paprocki 2 | 3 | var createEnum = function(v, n) { 4 | function Enum(value, name) { 5 | this.value = value; 6 | this.name = name; 7 | } 8 | Enum.prototype.toString = function() { return this.name; }; 9 | Enum.prototype.valueOf = function() { return this.value; }; 10 | return Object.freeze(new Enum(v, n)); 11 | } 12 | 13 | var createHardwareAddress = function(t, a) { 14 | return Object.freeze({ type: t, address: a }); 15 | } 16 | 17 | module.exports = { 18 | createHardwareAddress: createHardwareAddress, 19 | 20 | BOOTPMessageType: Object.freeze({ 21 | BOOTPREQUEST: createEnum(1, 'BOOTPREQUEST'), 22 | BOOTPREPLY: createEnum(2, 'BOOTPREPLY'), 23 | get: function(value) { 24 | for (key in this) { 25 | var obj = this[key]; 26 | if (obj == value) 27 | return obj; 28 | } 29 | return undefined; 30 | } 31 | }), 32 | 33 | // rfc1700 hardware types 34 | ARPHardwareType: Object.freeze({ 35 | HW_ETHERNET: createEnum(1, 'HW_ETHERNET'), 36 | HW_EXPERIMENTAL_ETHERNET: createEnum(2, 'HW_EXPERIMENTAL_ETHERNET'), 37 | HW_AMATEUR_RADIO_AX_25: createEnum(3, 'HW_AMATEUR_RADIO_AX_25'), 38 | HW_PROTEON_TOKEN_RING: createEnum(4, 'HW_PROTEON_TOKEN_RING'), 39 | HW_CHAOS: createEnum(5, 'HW_CHAOS'), 40 | HW_IEEE_802_NETWORKS: createEnum(6, 'HW_IEEE_802_NETWORKS'), 41 | HW_ARCNET: createEnum(7, 'HW_ARCNET'), 42 | HW_HYPERCHANNEL: createEnum(8, 'HW_HYPERCHANNEL'), 43 | HW_LANSTAR: createEnum(9, 'HW_LANSTAR'), 44 | get: function(value) { 45 | for (key in this) { 46 | var obj = this[key]; 47 | if (obj == value) 48 | return obj; 49 | } 50 | return undefined; 51 | } 52 | }), 53 | 54 | // rfc1533 code 53 dhcpMessageType 55 | DHCPMessageType: Object.freeze({ 56 | DHCPDISCOVER: createEnum(1, 'DHCPDISCOVER'), 57 | DHCPOFFER: createEnum(2, 'DHCPOFFER'), 58 | DHCPREQUEST: createEnum(3, 'DHCPREQUEST'), 59 | DHCPDECLINE: createEnum(4, 'DHCPDECLINE'), 60 | DHCPACK: createEnum(5, 'DHCPACK'), 61 | DHCPNAK: createEnum(6, 'DHCPNAK'), 62 | DHCPRELEASE: createEnum(7, 'DHCPRELEASE'), 63 | DHCPINFORM: createEnum(8, 'DHCPINFORM'), 64 | get: function(value) { 65 | for (key in this) { 66 | var obj = this[key]; 67 | if (obj == value) 68 | return obj; 69 | } 70 | return undefined; 71 | } 72 | }) 73 | } 74 | -------------------------------------------------------------------------------- /lib/server.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 Andrew Paprocki 2 | 3 | var EventEmitter = require('events').EventEmitter; 4 | var util = require('util'); 5 | var dgram = require('dgram'); 6 | var parser = require('./parser'); 7 | 8 | function Server(options, socket_opts) { 9 | if (options) { 10 | if (typeof(options) !== 'object') 11 | throw new TypeError('Server options must be an object'); 12 | } else { 13 | options = {}; 14 | } 15 | var self = this; 16 | EventEmitter.call(this, options); 17 | var socketOpts = (socket_opts? socket_opts : 'udp4'); 18 | this.server = dgram.createSocket(socketOpts); 19 | this.server.on('message', function(msg, rinfo) { 20 | try { 21 | var data = parser.parse(msg, rinfo); 22 | self.emit('message', data); 23 | } catch (e) { 24 | if (!self.emit('error', e)) { 25 | throw e; 26 | } 27 | } 28 | }); 29 | this.server.on('listening', function() { 30 | var address = self.server.address(); 31 | self.emit('listening', address.address + ':' + address.port); 32 | }); 33 | } 34 | util.inherits(Server, EventEmitter); 35 | module.exports = Server; 36 | 37 | Server.prototype.bind = function(host,port) { 38 | var _this = this; 39 | if (!port) port = 67; 40 | this.server.bind(port, host, function() { 41 | _this.server.setBroadcast(true); 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dhcpjs", 3 | "description": "DHCP client and server APIs", 4 | "keywords": [ 5 | "dhcp", "bootp", "dhclient" 6 | ], 7 | "version": "0.5.3", 8 | "author": "Andrew Paprocki ", 9 | "license": "MIT", 10 | "homepage": "http://github.com/apaprocki/node-dhcpjs", 11 | "main": "lib/index.js", 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/apaprocki/node-dhcpjs.git" 15 | }, 16 | "dependencies": { 17 | "ip-address": ">=5.0.0" 18 | }, 19 | "engines": { 20 | "node": ">=0.10" 21 | } 22 | } 23 | --------------------------------------------------------------------------------