pkt_transform.cc 8.56 KB
Newer Older
1
// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
2 3 4 5 6 7 8 9 10 11 12 13 14
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.

15 16
#include <config.h>

17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
#include <iostream>

#include <exceptions/exceptions.h>
#include <dhcp/option.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/dhcp6.h>

#include "pkt_transform.h"
#include "localized_option.h"

using namespace std;
using namespace isc;
using namespace dhcp;

namespace isc {
namespace perfdhcp {

bool
PktTransform::pack(const Option::Universe universe,
                   const OptionBuffer& in_buffer,
37
                   const OptionCollection& options,
38 39 40 41 42 43 44 45 46 47 48
                   const size_t transid_offset,
                   const uint32_t transid,
                   util::OutputBuffer& out_buffer) {

    // Always override the packet if function is called.
    out_buffer.clear();
    // Write whole buffer to output buffer.
    out_buffer.writeData(&in_buffer[0], in_buffer.size());

    uint8_t transid_len = (universe == Option::V6) ? 3 : 4;

49
    if ((transid_offset + transid_len >= in_buffer.size()) ||
50 51 52 53 54 55 56 57
        (transid_offset == 0)) {
        cout << "Failed to build packet: provided transaction id offset: "
             << transid_offset <<  " is out of bounds (expected 1.."
             << in_buffer.size()-1 << ")." << endl;
        return (false);
    }

    try {
58
        size_t offset_ptr = transid_offset;
59
        if (universe == Option::V4) {
60
            out_buffer.writeUint8At(transid >> 24 & 0xFF, offset_ptr++);
61
        }
62 63 64
        out_buffer.writeUint8At(transid >> 16 & 0xFF, offset_ptr++);
        out_buffer.writeUint8At(transid >> 8 & 0xFF, offset_ptr++);
        out_buffer.writeUint8At(transid & 0xFF, offset_ptr++);
65 66 67 68

        // We already have packet template stored in output buffer
        // but still some options have to be updated if client
        // specified them along with their offsets in the buffer.
69
        PktTransform::packOptions(in_buffer, options, out_buffer);
70 71 72 73 74 75 76 77 78 79
    } catch (const isc::BadValue& e) {
        cout << "Building packet failed: " << e.what() << endl;
        return (false);
    }
    return (true);
}

bool
PktTransform::unpack(const Option::Universe universe,
                     const OptionBuffer& in_buffer,
80
                     const OptionCollection& options,
81 82 83 84 85 86 87 88 89 90 91 92 93 94
                     const size_t transid_offset,
                     uint32_t& transid) {

    uint8_t transid_len = (universe == Option::V6) ? 3 : 4;

    // Validate transaction id offset.
    if ((transid_offset + transid_len + 1 > in_buffer.size()) ||
        (transid_offset == 0)) {
        cout << "Failed to parse packet: provided transaction id offset: "
             << transid_offset <<  " is out of bounds (expected 1.."
             << in_buffer.size()-1 << ")." << endl;
        return (false);
    }

95 96 97 98 99 100 101 102 103
    // Read transaction id from the buffer.
    // For DHCPv6 we transaction id is 3 bytes long so the high byte
    // of transid will be zero.
    OptionBufferConstIter it = in_buffer.begin() + transid_offset;
    transid = 0;
    for (int i = 0; i < transid_len; ++i, ++it) {
        // Read next byte and shift it left to its position in
        // transid (shift by the number of bytes read so far.
        transid += *it << (transid_len - i - 1) * 8;
104 105 106
    }

    try {
107
        PktTransform::unpackOptions(in_buffer, options);
108 109 110 111 112 113 114 115 116 117
    } catch (const isc::BadValue& e) {
        cout << "Packet parsing failed: " << e.what() << endl;
        return (false);
    }

    return (true);
}

void
PktTransform::packOptions(const OptionBuffer& in_buffer,
118
                          const OptionCollection& options,
119
                          util::OutputBuffer& out_buffer) {
120 121 122 123
    try {
        // If there are any options on the list, we will use provided
        // options offsets to override them in the output buffer
        // with new contents.
124
        for (OptionCollection::const_iterator it = options.begin();
125 126 127 128
             it != options.end(); ++it) {
            // Get options with their position (offset).
            boost::shared_ptr<LocalizedOption> option =
                boost::dynamic_pointer_cast<LocalizedOption>(it->second);
129 130 131
            if (option == NULL) {
                isc_throw(isc::BadValue, "option is null");
            }
132 133 134 135 136 137 138 139 140
            uint32_t offset = option->getOffset();
            if ((offset == 0) ||
                (offset + option->len() > in_buffer.size())) {
                isc_throw(isc::BadValue,
                          "option offset for option: " << option->getType()
                          << " is out of bounds (expected 1.."
                          << in_buffer.size() - option->len() << ")");
            }

141 142 143 144 145 146 147 148 149
            // Create temporary buffer to store option contents.
            util::OutputBuffer buf(option->len());
            // Pack option contents into temporary buffer.
            option->pack(buf);
            // OutputBuffer class has nice functions that write
            // data at the specified position so we can use it to
            // inject contents of temporary buffer to output buffer.
            const uint8_t *buf_data =
                static_cast<const uint8_t*>(buf.getData());
150
            for (size_t i = 0; i < buf.getLength(); ++i) {
151 152
                out_buffer.writeUint8At(buf_data[i], offset + i);
            }
153 154 155 156 157 158 159 160
        }
    }
    catch (const Exception&) {
        isc_throw(isc::BadValue, "failed to pack options into buffer.");
    }
}

void
161
PktTransform::unpackOptions(const OptionBuffer& in_buffer,
162 163
                            const OptionCollection& options) {
    for (OptionCollection::const_iterator it = options.begin();
164 165 166 167
         it != options.end(); ++it) {

        boost::shared_ptr<LocalizedOption> option =
            boost::dynamic_pointer_cast<LocalizedOption>(it->second);
168 169 170
        if (option == NULL) {
            isc_throw(isc::BadValue, "option is null");
        }
171 172 173 174
        size_t opt_pos = option->getOffset();
        if (opt_pos == 0) {
            isc_throw(isc::BadValue, "failed to unpack packet from raw buffer "
                      "(Option position not specified)");
175
        } else if (opt_pos + option->getHeaderLen() > in_buffer.size()) {
176 177 178 179 180 181 182 183
            isc_throw(isc::BadValue,
                      "failed to unpack options from from raw buffer "
                      "(Option position out of bounds)");
        }

        size_t offset = opt_pos;
        size_t offset_step = 1;
        uint16_t opt_type = 0;
184
        if (option->getUniverse() == Option::V6) {
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
            offset_step = 2;
            // For DHCPv6 option type is in first two octets.
            opt_type = in_buffer[offset] * 256 + in_buffer[offset + 1];
        } else {
            // For DHCPv4 option type is in first octet.
            opt_type = in_buffer[offset];
        }
        // Check if we got expected option type.
        if (opt_type != option->getType()) {
            isc_throw(isc::BadValue,
                      "failed to unpack option from raw buffer "
                      "(option type mismatch)");
        }

        // Get option length which is supposed to be after option type.
        offset += offset_step;
201 202 203 204
        const uint16_t opt_len =
            (option->getUniverse() == Option::V6) ?
            in_buffer[offset] * 256 + in_buffer[offset + 1] :
            in_buffer[offset];
205

206
        // Check if packet is not truncated.
207
        if (offset + option->getHeaderLen() + opt_len > in_buffer.size()) {
208 209 210 211 212 213 214 215 216 217 218
            isc_throw(isc::BadValue,
                      "failed to unpack option from raw buffer "
                      "(option truncated)");
        }

        // Seek to actual option data and replace it.
        offset += offset_step;
        option->setData(in_buffer.begin() + offset,
                        in_buffer.begin() + offset + opt_len);
    }
}
219

220 221 222 223
void
PktTransform::writeAt(dhcp::OptionBuffer& in_buffer, size_t dest_pos,
                      dhcp::OptionBuffer::iterator first,
                      dhcp::OptionBuffer::iterator last) {
224
    memcpy(&in_buffer[dest_pos], &(*first), std::distance(first, last));
225
}
226 227 228

} // namespace perfdhcp
} // namespace isc