1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
// Copyright (C) 2023-2024 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <config.h>

#include <asiolink/io_error.h>
#include <util/io.h>
#include <util/str.h>

#include <option_classless_static_route.h><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.

using namespace isc::asiolink;
using namespace isc::util;

namespace isc {
namespace dhcp {

OptionClasslessStaticRoute::OptionClasslessStaticRoute(OptionBufferConstIter begin,
                                                       OptionBufferConstIter end,
                                                       bool convenient_notation)
    : Option(V4, DHO_CLASSLESS_STATIC_ROUTE), static_routes_(), data_len_(0),
      convenient_notation_(convenient_notation) {
    unpack(begin, end);
}

OptionPtr
OptionClasslessStaticRoute::clone() const {
    return (cloneInternal<OptionClasslessStaticRoute>());
}

void
OptionClasslessStaticRoute::pack(isc::util::OutputBuffer& buf, bool check) const {
    // Header = option code and length.
    packHeader(buf, check);
    for (auto const& route : static_routes_) {
        // 1-5 octets of destination descriptor
        auto dest = encodeDestinationDescriptor(route);
        buf.writeData(&dest[0], dest.size());
        // IP address of the router
        buf.writeUint32(std::get<2>(route).toUint32());
    }
}

void
OptionClasslessStaticRoute::unpack(OptionBufferConstIter begin, OptionBufferConstIter end) {
    // Classless Static route option data must contain at least 5 octets.
    // 1 octet - shortest possible destination descriptor (0x00) + 4 octets router IPv4 addr.
    if (distance(begin, end) < 5) {
        isc_throw(OutOfRange, "DHCPv4 OptionClasslessStaticRoute "
                                  << type_ << " has invalid length=" << distance(begin, end)
                                  << ", must be at least 5.");
    }

    if (convenient_notation_) {
        // As an alternative to the binary format,
        // we provide convenience option definition as a string in format:
        // subnet1 - router1 IP addr, subnet2 - router2 IP addr, ...
        // e.g.:
        // 10.0.0.0/8 - 10.2.3.1, 10.229.0.128/25 - 10.1.0.3, ...
        // where destination descriptors will be encoded as per RFC3442.
        std::string config_txt = std::string(begin, end);
        parseConfigData(config_txt);
    } else {
        parseWireData(begin, end);
    }

    calcDataLen();
}

std::string
OptionClasslessStaticRoute::toText(int indent) const {
    std::ostringstream stream;
    std::string in(indent, ' ');  // base indentation
    stream << in << "type=" << type_ << "(CLASSLESS_STATIC_ROUTE), "
           << "len=" << (len() - getHeaderLen());
    int i = 0;
    for (auto const& route : static_routes_) {
        stream << ", Route " << ++i << " (subnet " << std::get<0>(route).toText() << "/"
               << static_cast<int>(std::get<1>(route)) << ", router IP "
               << std::get<2>(route).toText() << ")";
    }

    return (stream.str());
}

uint16_t
OptionClasslessStaticRoute::len() const {
    uint16_t len = getHeaderLen();
    len += data_len_;
    return (len);
}

std::vector<uint8_t>
OptionClasslessStaticRoute::encodeDestinationDescriptor(const StaticRouteTuple& route) {
    // Encoding as per RFC3442
    const std::vector<uint8_t>& subnet = std::get<0>(route).toBytes();
    const uint8_t& mask_width = std::get<1>(route);

    std::vector<uint8_t> res;
    res.push_back(mask_width);
    if (mask_width == 0) {
        // there are no significant octets, destination descriptor is 0 value - one octet long
        return (res);
    }

    uint8_t significant_octets = calcSignificantOctets(mask_width);
    res.insert(res.end(), subnet.begin(), subnet.begin() + significant_octets);

    return (res);
}

uint8_t
OptionClasslessStaticRoute::calcSignificantOctets(const uint8_t& mask_width) {
    return ((mask_width + 7) / 8);
}

void
OptionClasslessStaticRoute::calcDataLen() {
    uint16_t len = 0;
    for (auto const& route : static_routes_) {
        // 1-5 octets of destination descriptor
        len += calcSignificantOctets(std::get<1>(route)) + 1;
        // IP address of the router
        len += V4ADDRESS_LEN;
    }

    data_len_ = len;
}

void
OptionClasslessStaticRoute::parseWireData(OptionBufferConstIter begin, OptionBufferConstIter end) {
    while (begin != end) {
        // check for truncated data for each static route
        if (distance(begin, end) < 5) {
            isc_throw(OutOfRange, "DHCPv4 OptionClasslessStaticRoute "
                                      << type_ << " has invalid length=" << distance(begin, end)
                                      << ", must be at least 5.");
        }

        // 1st octet is a width of subnet mask
        uint8_t mask_width = *begin;
        if (mask_width > 32) {
            isc_throw(BadValue, "DHCPv4 OptionClasslessStaticRoute "
                                    << type_ << " has invalid value, provided width of subnet mask "
                                    << static_cast<int>(mask_width) << " is not valid.");
        }

        uint8_t significant_octets = calcSignificantOctets(mask_width);
        ++begin;

        // once we know haw many significant octets there are, check for truncated data again
        if (distance(begin, end) < (significant_octets + V4ADDRESS_LEN)) {
            isc_throw(OutOfRange,
                      "DHCPv4 OptionClasslessStaticRoute " << type_ << " is truncated.");
        }

        // following octets are significant octets of the subnet nr
        uint32_t subnet_octets;
        IOAddress subnet_nr = asiolink::IOAddress::IPV4_ZERO_ADDRESS();

        switch (significant_octets) {
        case 0:
            // no-op - this is 0.0.0.0/0 subnet
            break;
        case 1:
            subnet_octets = *begin;
            subnet_nr = IOAddress(subnet_octets << 24);
            break;
        case 2:
            subnet_octets = readUint16(&(*begin), distance(begin, end));
            subnet_nr = IOAddress(subnet_octets << 16);
            break;
        case 3:
            // we are reading one octet too much in this case,
            // but since we did check for truncated data before,
            // we are safe do so and mask 4th octet with zeros
            subnet_octets = readUint32(&(*begin), distance(begin, end));
            subnet_nr = IOAddress(subnet_octets & 0xFFFFFF00);
            break;
        case 4:
            subnet_octets = readUint32(&(*begin), distance(begin, end));
            subnet_nr = IOAddress(subnet_octets);
            break;
        }

        begin += significant_octets;

        // last comes router IPv4 address
        IOAddress router_addr = IOAddress(readUint32(&(*begin), distance(begin, end)));
        begin += V4ADDRESS_LEN;

        StaticRouteTuple route = std::make_tuple(subnet_nr, mask_width, router_addr);
        static_routes_.push_back(route);
    }
}

void
OptionClasslessStaticRoute::parseConfigData(const std::string& config_txt) {
    // this option allows more than one static route, so let's separate them using comma
    std::vector<std::string> tokens = str::tokens(config_txt, std::string(","));
    for (auto const& route_str : tokens) {
        std::vector<std::string> parts = str::tokens(str::trim(route_str), std::string("-"));
        if (parts.size() != 2) {
            isc_throw(BadValue, "DHCPv4 OptionClasslessStaticRoute "
                                    << type_
                                    << " has invalid value, option definition must"
                                       " have comma separated routes formatted as in "
                                       "example: 10.229.0.128/25 - 10.229.0.1");
        }

        std::string txt_subnet_prefix = str::trim(parts[0]);

        // Is this prefix/len notation?
        size_t pos = txt_subnet_prefix.find('/');
        if (pos == std::string::npos) {
            isc_throw(BadValue, "DHCPv4 OptionClasslessStaticRoute "
                                    << type_ << " has invalid value, provided IPv4 prefix "
                                    << txt_subnet_prefix << " is not valid.");
        }

        std::string txt_subnet_addr = txt_subnet_prefix.substr(0, pos);
        IOAddress subnet_addr = IOAddress("::");
        try {
            subnet_addr = IOAddress(txt_subnet_addr);
            if (!subnet_addr.isV4()) {
                isc_throw(BadValue, "This is not IPv4 address.");
            }
        } catch (const std::exception& e) {
            isc_throw(BadValue, "DHCPv4 OptionClasslessStaticRoute "
                                    << type_ << " has invalid value, provided subnet_addr "
                                    << txt_subnet_addr << " is not a valid IPv4 address. "
                                    << "Error: " << e.what());
        }

        std::string txt_prefix_len = txt_subnet_prefix.substr(pos + 1);
        int16_t prefix_len = 0;
        try {
            // We should be able to lexically cast IPv4 prefix len to short int.
            // After that len<=32 check is also required.
            prefix_len = boost::lexical_cast<int16_t>(txt_prefix_len);
            if (prefix_len > 32) {
                isc_throw(BadValue, "Provided IPv4 prefix len is out of 0-32 range.");
            }
        } catch (const std::exception& e) {
            isc_throw(BadValue, "DHCPv4 OptionClasslessStaticRoute "
                                    << type_ << " has invalid value, provided prefix len "
                                    << txt_prefix_len << " is not valid. "
                                    << "Error: " << e.what());
        }

        IOAddress router_addr = IOAddress("::");
        std::string txt_router = str::trim(parts[1]);
        try {
            router_addr = IOAddress(txt_router);
            if (!router_addr.isV4()) {
                isc_throw(BadValue, "This is not IPv4 address.");
            }
        } catch (const std::exception& e) {
            isc_throw(BadValue, "DHCPv4 OptionClasslessStaticRoute "
                                    << type_ << " has invalid value, provided router address "
                                    << txt_router << " is not a valid IPv4 address. "
                                    << "Error: " << e.what());
        }

        StaticRouteTuple route = std::make_tuple(subnet_addr, prefix_len, router_addr);
        static_routes_.push_back(route);
    }
}

}  // namespace dhcp
}  // namespace isc