RackHD/on-dhcp-proxy

View on GitHub
lib/packet.js

Summary

Maintainability
F
1 wk
Test Coverage
/*
Copyright (c) 2011-2014 Andrew Paprocki

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE
 */

// Modified from http://github.com/apaprocki/node-dhcpjs

"use strict";

var di = require('di'),
    v4 = require('ipv6').v4;

module.exports = PacketFactory;
di.annotate(PacketFactory, new di.Provide('DHCP.packet'));
di.annotate(PacketFactory, new di.Inject('DHCP.protocol', 'Services.Configuration', '_'));
function PacketFactory(protocol, configuration, _) {
    var packetFunctions = {
        // Expose this for test
        createPacketBuffer: createPacketBuffer,
        createProxyDhcpAck: function(clientPacket, bootFileName) {
            var packet = {};
            var isPXEefi = false;
            _.forEach(clientPacket, function(value, key) {
                packet[key] = value;
            });
            packet.op = protocol.BOOTPMessageType.BOOTPREPLY.value;
            packet.htype = protocol.ARPHardwareType.HW_ETHERNET.value;
            packet.fname = bootFileName;

            // Necessary, at least on vbox
            packet.siaddr = configuration.get('tftpBindAddress', '10.1.1.1');
            // Not necessary, at least on vbox, but perhaps other clients will require these fields?
            packet.sname = configuration.get('tftpBindAddress', '10.1.1.1');

            //EFI PXE listen on a different port => tell the server
            if ((packet.options.userClass === undefined) &&
               (packet.options.archType === 6 || // EFI32
                packet.options.archType === 7 || packet.options.archType === 9)) { // EFIx64
                isPXEefi = true;
            }
            packet.options = {};
            //EFI pxe use option 67 for bootfilename.
            //Since option 67 doesn't include a field for string length,
            //bootfilname needs to be null-terminated
            packet.options.bootFileName = bootFileName + '\0';

            // DHCP MESSAGE TYPES
            packet.options.dhcpMessageType =
                protocol.DHCPMessageType.DHCPACK.value;

            var packetBuffer = packetFunctions.createPacketBuffer(packet);
            return {
                packetBuffer: packetBuffer,
                destination: packet.ciaddr,
                isefi : isPXEefi
            };
        }
    };

    return packetFunctions;
}

// Modified from http://github.com/apaprocki/node-dhcpjs
function createPacketBuffer(pkt) {
    if (!('xid' in pkt)) {
        throw new Error('pkt.xid required');
    }

    // client ip address
    var ci = new Buffer(pkt.ciaddr ?
        new v4.Address(pkt.ciaddr).toArray() : [0, 0, 0, 0]);
    // your ip address
    var yi = new Buffer(pkt.yiaddr ?
        new v4.Address(pkt.yiaddr).toArray() : [0, 0, 0, 0]);
    // server ip address
    var si = new Buffer(pkt.siaddr ?
        new v4.Address(pkt.siaddr).toArray() : [0, 0, 0, 0]);
    // gateway ip address
    var gi = new Buffer(pkt.giaddr ?
        new v4.Address(pkt.giaddr).toArray() : [0, 0, 0, 0]);
    // Right now just assume we always have an IP as the server hostname
    // server hostname
    var sname = new Buffer(pkt.sname ? pkt.sname : '');
    // boot file name
    var fname = new Buffer(pkt.fname ? pkt.fname : '');

    if (!('chaddr' in pkt)) {
        throw new Error('pkt.chaddr required');
    }
    if (sname.length > 64) {
        throw new Error('sname field too long (64 bytes max): ' + sname.toString());
    }
    if (fname.length > 128) {
        throw new Error('fname field too long (128 bytes max): ' + fname.toString());
    }

    var hw;
    // If this is coming from a client, it's a string
    if (typeof pkt.chaddr === 'string') {
        hw = new Buffer(pkt.chaddr.split(':').map(function (part) {
            return parseInt(part, 16);
        }));
        // If this is coming from the server, it's an object
    } else {
        hw = new Buffer(pkt.chaddr.address.split(':').map(function (part) {
            return parseInt(part, 16);
        }));
    }
    if (hw.length !== 6) {
        throw new Error('pkt.chaddr malformed, only ' + hw.length + ' bytes');
    }

    var p = new Buffer(1500);
    var i = 0;


    if(typeof pkt.op === 'object'){
        p.writeUInt8(pkt.op.value, i);
    }
    else{
        p.writeUInt8(pkt.op || 0, i);
    }
    i += 1;
    //packet Type may be pkt.type or pkt.chaddr.type
    if(typeof pkt.htype === 'object'){
        p.writeUInt8(pkt.htype.value, i);
    }
    else if(typeof pkt.chaddr.type === 'object'){
            p.writeUInt8(pkt.chaddr.type.value, i);
    }
    else if(typeof pkt.chaddr.type === undefined) {
        p.writeUInt8(pkt.htype || 0, i);
    }
    else{
        p.writeUInt8(pkt.chaddr.type, i);
    }
    i += 1;
    p.writeUInt8(pkt.hlen || 0, i);
    i += 1;
    p.writeUInt8(pkt.hops || 0, i);
    i += 1;
    p.writeUInt32BE(pkt.xid || 0, i);
    i += 4;
    p.writeUInt16BE(pkt.secs || 0, i);
    i += 2;
    p.writeUInt16BE(pkt.flags || 0, i);
    i += 2;
    ci.copy(p, i);
    i += ci.length;
    yi.copy(p, i);
    i += yi.length;
    si.copy(p, i);
    i += si.length;
    gi.copy(p, i);
    i += gi.length;
    hw.copy(p, i);
    i += hw.length;
    p.fill(0, i, i + 10);
    i += 10; // hw address padding

    sname.copy(p, i);
    i += sname.length;
    p.fill(0, i, i + 64 - sname.length); // fill in the rest of the sname padding
    i += 64 - sname.length;
    // TODO: Only add if option overload
    //p.writeUInt8(255, i);
    //i += 1;

    fname.copy(p, i);
    i += fname.length;
    p.fill(0, i, i + 128 - sname.length); // fill in the rest of the fname padding
    i += 128 - fname.length;
    // TODO: Only add if option overload
    //p.writeUInt8(255, i);
    //i += 1;

    p.writeUInt32BE(0x63825363, i);
    i += 4;

    if (pkt.options && pkt.options.subnetMask) {            // option 1
        p.writeUInt8(1, i);
        i += 1;
        var subnetMask = new Buffer(new v4.Address(pkt.options.subnetMask).toArray());
        p.writeUInt8(subnetMask.length, i);
        i += 1;
        subnetMask.copy(p, i);
        i += subnetMask.length;
    }
    if (pkt.options && pkt.options.timeOffset) {            // option 2
        p.writeUInt8(2, i);
        i += 1; // option 2
        p.writeUInt8(4, i);
        i += 1; // length
        p.writeUInt32BE(pkt.options.timeOffset, i);
        i += 4;
    }
    if (pkt.options && pkt.options.routerOptions) {         // option 3
        p.writeUInt8(3, i);
        i += 1; // option 3
        // If routerOptions is not an array by human error,
        // it's probably a string and we should just make it an array.
        var routerOptions = pkt.options.routerOptions;
        if (typeof routerOptions !== 'object') {
            routerOptions = [routerOptions];
        }
        // Let's hope routerOptions.length * 4 can fit in one byte :)
        // To quote Joe: "probably OSPF would blow up before that!"
        p.writeUInt8(routerOptions.length * 4, i);
        i += 1;
        routerOptions.forEach(function (router) {
            var routerBuffer = new Buffer(
                new v4.Address(router).toArray());
            routerBuffer.copy(p, i);
            i += routerBuffer.length;
        });
    }
    if(pkt.options && pkt.options.timeServerOption) {       //option 4
        p.writeUInt8(4, i);
        i += 1; // option 4
        // If timeServerOption is not an array by human error,
        // it's probably a string and we should just make it an array.
        var timeServerOptions = pkt.options.timeServerOption;
        if (typeof timeServerOptions !== 'object') {
            timeServerOptions = [timeServerOptions];
        }
        // Let's hope timeServerOptions.length * 4 can fit in one byte :)
        // To quote Joe: "probably OSPF would blow up before that!"
        p.writeUInt8(timeServerOptions.length * 4, i);
        i += 1;
        timeServerOptions.forEach(function (timeServer) {
            var timeServerBuffer = new Buffer(
                new v4.Address(timeServer).toArray());
            timeServerBuffer.copy(p, i);
            i += timeServerBuffer.length;
        });
    }
    if(pkt.options && pkt.options.domainNameServerOption) { //option 6
        p.writeUInt8(6, i);
        i += 1; // option 6
        // If domainNameServerOption is not an array by human error,
        // it's probably a string and we should just make it an array.
        var domainNameServerOptions = pkt.options.domainNameServerOption;
        if (typeof domainNameServerOptions !== 'object') {
            domainNameServerOptions = [domainNameServerOptions];
        }
        // Let's hope domainNameServerOptions.length * 4 can fit in one byte :)
        p.writeUInt8(domainNameServerOptions.length * 4, i);
        i += 1;
        domainNameServerOptions.forEach(function (domainNameServer) {
            var domainNameServerBuffer = new Buffer(
                new v4.Address(domainNameServer).toArray());
            domainNameServerBuffer.copy(p, i);
            i += domainNameServerBuffer.length;
        });
    }
    if(pkt.options && pkt.options.hostName) {               //option 12
        p.writeUInt8(12, i);
        i += 1; // option 12
        var hostName = new Buffer(pkt.options.hostName);
        p.writeUInt8(hostName.length, i);
        i += 1;
        hostName.copy(p, i);
        i += hostName.length;
    }
    if(pkt.options && pkt.options.domainName) {             //option 15
        p.writeUInt8(15, i);
        i += 1; // option 15
        var domainName = new Buffer(pkt.options.domainName);
        p.writeUInt8(domainName.length, i);
        i += 1;
        domainName.copy(p, i);
        i += domainName.length;
    }
    if (pkt.options && pkt.options.broadcastAddress) {      // option 28
        p.writeUInt8(28, i);
        i += 1; // option 28
        var broadcastAddress = new Buffer(
            new v4.Address(pkt.options.broadcastAddress).toArray());
        p.writeUInt8(broadcastAddress.length, i);
        i += 1; // length
        broadcastAddress.copy(p, i);
        i += broadcastAddress.length;
    }
    if (pkt.options && pkt.options.vendorOptions) {    // option 43
        p.writeUInt8(43, i);
        i += 1; // option 43
        var vendorOptions = pkt.options.vendorOptions;

        //Find the length of the venderoption buffer
        var venLength = 0;
        for (var key in vendorOptions) {
            if (vendorOptions.hasOwnProperty(key)) {
                venLength += 2 + vendorOptions[key].length;
            }
        }

        p.writeUInt8(venLength, i);
        i += 1;
        for (key in vendorOptions) {
            if (vendorOptions.hasOwnProperty(key)) {
                p.writeUInt8(parseInt(key), i);   //write key
                i += 1;
                var valueBuf = new Buffer(vendorOptions[key]);
                p.writeUInt8(valueBuf.length, i);  //write value length
                i += 1;
                valueBuf.copy(p, i);        //write value
                i += valueBuf.length;
            }
        }
    }
    if (pkt.options && pkt.options.requestedIpAddress) {    // option 50
        p.writeUInt8(50, i);
        i += 1;  // option 50
        var requestedIpAddress = new Buffer(
            new v4.Address(pkt.options.requestedIpAddress).toArray());
        p.writeUInt8(requestedIpAddress.length, i);
        i += 1; // length
        requestedIpAddress.copy(p, i);
        i += requestedIpAddress.length;
    }
    if (pkt.options && pkt.options.ipAddressLeaseTime) {    // option 51
        p.writeUInt8(51, i);
        i += 1; // option 51
        p.writeUInt8(4, i);
        i += 1; // length
        p.writeUInt32BE(pkt.options.ipAddressLeaseTime, i);
        i += 4;
    }
    if (pkt.options && pkt.options.optionOverload) {        // option 52
        p.writeUInt8(52, i);
        i += 1; // option 52
        p.writeUInt8(1, i);
        i += 1; // length
        p.writeUInt8(pkt.options.optionOverload, i);
        i += 1;
    }
    if (pkt.options && pkt.options.dhcpMessageType) {       // option 53
        p.writeUInt8(53, i);
        i += 1; // option 53
        p.writeUInt8(1, i);
        i += 1;  // length

        if(typeof pkt.options.dhcpMessageType === 'object'){
            p.writeUInt8(pkt.options.dhcpMessageType.value, i);
        }
        else{
            p.writeUInt8(pkt.options.dhcpMessageType, i);
        }
        i += 1;
    }
    if (pkt.options && pkt.options.serverIdentifier) {      // option 54
        p.writeUInt8(54, i);
        i += 1; // option 54
        var serverIdentifier = new Buffer(
            new v4.Address(pkt.options.serverIdentifier).toArray());
        p.writeUInt8(serverIdentifier.length, i);
        i += 1;
        serverIdentifier.copy(p, i);
        i += serverIdentifier.length;
    }
    if (pkt.options && pkt.options.parameterRequestList) {  // option 55
        p.writeUInt8(55, i);
        i += 1; // option 55
        var parameterRequestList = new Buffer(pkt.options.parameterRequestList);
        if (parameterRequestList.length > 0xff) {
            throw new Error('pkt.options.parameterRequestList malformed');
        }
        p.writeUInt8(parameterRequestList.length, i);
        i += 1;
        parameterRequestList.copy(p, i);
        i += parameterRequestList.length;
    }
    if (pkt.options && pkt.options.maximumMessageSize) {      // option 57
        p.writeUInt8(57, i);
        i += 1; // option 57
        p.writeUInt8(2, i);
        i += 1; // length
        p.writeUInt16BE(pkt.options.maximumMessageSize, i);
        i += 2;
    }
    if (pkt.options && pkt.options.renewalTimeValue) {      // option 58
        p.writeUInt8(58, i);
        i += 1; // option 58
        p.writeUInt8(4, i);
        i += 1; // length
        p.writeUInt32BE(pkt.options.renewalTimeValue, i);
        i += 4;
    }
    if (pkt.options && pkt.options.rebindingTimeValue) {    // option 59
        p.writeUInt8(59, i);
        i += 1; // option 59
        p.writeUInt8(4, i);
        i += 1; // length
        p.writeUInt32BE(pkt.options.rebindingTimeValue, i);
        i += 4;
    }
    if (pkt.options && pkt.options.vendorClassIdentifier) {   // option 60
        p.writeUInt8(60, i);
        i += 1; // option 60
        var vendorClassIdentifier = new Buffer(pkt.options.vendorClassIdentifier);
        p.writeUInt8(vendorClassIdentifier.length, i);
        i += 1;
        vendorClassIdentifier.copy(p, i);
        i += vendorClassIdentifier.length;
    }
    if (pkt.options && pkt.options.clientIdentifier) {      // option 61
        p.writeUInt8(61, i);
        i += 1;           // option 61
        var add = pkt.options.clientIdentifier.address;
        var addBuf = new Buffer(add.split(':').map(function (part) {
            return parseInt(part, 16);
        }));
        p.writeUInt8(addBuf.length+1, i);  //add length of buffer
        i += 1; // length
        if (addBuf.length !== 6) {
            throw new Error('pkt.options.clientIdentifier.address malformed, only ' +
                add.length + ' bytes');
        }
        //Add type to buffer
        if(typeof pkt.options.clientIdentifier.type !== 'object'){
            p.writeUInt8(pkt.options.clientIdentifier.type, i);
        }
        else {
            p.writeUInt8(pkt.options.clientIdentifier.type.value, i);

        }
        i += 1; // hardware type 0
        addBuf.copy(p, i);
        i += addBuf.length;
    }
    if (pkt.options && pkt.options.bootFileName) {          // option 67
        p.writeUInt8(67, i);
        i += 1; // option 67
        var bootFileName = new Buffer(pkt.options.bootFileName);
        p.writeUInt8(bootFileName.length, i);
        i += 1;
        bootFileName.copy(p, i);
        i += bootFileName.length;
    }
    if (pkt.options && pkt.options.userClass) {          // option 77
        p.writeUInt8(77, i);
        i += 1; // option 60
        var userClass = new Buffer(pkt.options.userClass);
        p.writeUInt8(userClass.length, i);
        i += 1;
        userClass.copy(p, i);
        i += userClass.length;
    }
    if (pkt.options && pkt.options.fullyQualifiedDomainName) {          // option 81
        p.writeUInt8(81, i);
        i += 1; // option 81
        var name = new Buffer(pkt.options.fullyQualifiedDomainName.name);  //name
        p.writeUInt8(name.length+3, i);
        i += 1;
        p.writeUInt8(pkt.options.fullyQualifiedDomainName.flags, i); //flag
        i += 3;
        name.copy(p, i);
        i += name.length;
    }
    if (pkt.options && pkt.options.archType) {                  // option 93
        p.writeUInt8(93, i);
        i += 1; // option 57
        p.writeUInt8(2, i);
        i += 1; // length
        p.writeUInt16BE(pkt.options.archType, i);
        i += 2;
    }
    if (pkt.options && pkt.options.subnetAddress) {          // option 118
        p.writeUInt8(118, i);
        i += 1; // option 118
        var subnetAddress = new Buffer(
            new v4.Address(pkt.options.subnetAddress).toArray());
        p.writeUInt8(subnetAddress.length, i);
        i += 1;
        subnetAddress.copy(p, i);
        i += subnetAddress.length;
    }

    // option 255 - end
    p.writeUInt8(0xff, i);
    i += 1;

    // padding
    if ((i % 2) > 0) {
        p.writeUInt8(0, i);
        i += 1;
    } else {
        p.writeUInt16BE(0, i);
        i += 1;
    }

    var remaining = 300 - i;
    if (remaining > 0) {
        p.fill(0, i, i + remaining);
        i += remaining;
    }

    return p.slice(0, i);
}