host_reservation_parser.cc 17.1 KB
Newer Older
1
// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
2
//
3
4
5
// 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/.
6

7
#include <config.h>
8
#include <asiolink/io_address.h>
9
#include <dhcpsrv/cfgmgr.h>
10
#include <dhcpsrv/parsers/dhcp_parsers.h>
11
#include <dhcpsrv/parsers/host_reservation_parser.h>
12
#include <boost/foreach.hpp>
13
#include <boost/lexical_cast.hpp>
14
#include <algorithm>
15
#include <sys/socket.h>
16
#include <sstream>
17
18
19
#include <string>

using namespace isc::asiolink;
20
using namespace isc::data;
21

22
23
24
25
26
27
namespace {

/// @brief Returns set of the supported parameters for DHCPv4.
///
/// This function returns the set of supported parameters for
/// host reservation in DHCPv4.
28
29
30
31
32
33
34
35
36
///
/// @param identifiers_only Indicates if the function should only
/// return supported host identifiers (if true) or all supported
/// parameters (if false).
const std::set<std::string>&
getSupportedParams4(const bool identifiers_only = false) {
    // Holds set of host identifiers.
    static std::set<std::string> identifiers_set;
    // Holds set of all supported parameters, including identifiers.
37
    static std::set<std::string> params_set;
38
39
40
41
    // If this is first execution of this function, we need
    // to initialize the set.
    if (identifiers_set.empty()) {
        identifiers_set.insert("hw-address");
42
        identifiers_set.insert("duid");
43
        identifiers_set.insert("circuit-id");
44
        identifiers_set.insert("client-id");
45
46
    }
    // Copy identifiers and add all other parameters.
47
    if (params_set.empty()) {
48
49
50
51
        params_set = identifiers_set;
        params_set.insert("hostname");
        params_set.insert("ip-address");
        params_set.insert("option-data");
52
        params_set.insert("next-server");
53
        params_set.insert("server-hostname");
54
        params_set.insert("boot-file-name");
55
        params_set.insert("client-classes");
56
    }
57
    return (identifiers_only ? identifiers_set : params_set);
58
59
}

60
/// @brief Returns set of the supported parameters for DHCPv6.
61
62
63
///
/// This function returns the set of supported parameters for
/// host reservation in DHCPv6.
64
65
66
67
68
69
70
71
72
///
/// @param identifiers_only Indicates if the function should only
/// return supported host identifiers (if true) or all supported
/// parameters (if false).
const std::set<std::string>&
getSupportedParams6(const bool identifiers_only = false) {
    // Holds set of host identifiers.
    static std::set<std::string> identifiers_set;
    // Holds set of all supported parameters, including identifiers.
73
    static std::set<std::string> params_set;
74
75
76
77
    // If this is first execution of this function, we need
    // to initialize the set.
    if (identifiers_set.empty()) {
        identifiers_set.insert("hw-address");
78
        identifiers_set.insert("duid");
79
80
    }
    // Copy identifiers and add all other parameters.
81
    if (params_set.empty()) {
82
83
84
85
86
        params_set = identifiers_set;
        params_set.insert("hostname");
        params_set.insert("ip-addresses");
        params_set.insert("prefixes");
        params_set.insert("option-data");
87
        params_set.insert("client-classes");
88
    }
89
    return (identifiers_only ? identifiers_set : params_set);
90
91
92
93
}

}

94
95
96
namespace isc {
namespace dhcp {

97
void
98
99
100
HostReservationParser::parse(const SubnetID& subnet_id,
                             isc::data::ConstElementPtr reservation_data) {
    parseInternal(subnet_id, reservation_data);
101
102
103
}

void
104
HostReservationParser::parseInternal(const SubnetID&,
105
                                     isc::data::ConstElementPtr reservation_data) {
106
107
108
109
    std::string identifier;
    std::string identifier_name;
    std::string hostname;

110
111
112
113
    try {
        // Gather those parameters that are common for both IPv4 and IPv6
        // reservations.
        BOOST_FOREACH(ConfigPair element, reservation_data->mapValue()) {
114
115
116
117
118
119
            // Check if we support this parameter.
            if (!isSupportedParameter(element.first)) {
                isc_throw(DhcpConfigError, "unsupported configuration"
                          " parameter '" << element.first << "'");
            }

120
121
122
123
124
            if (isIdentifierParameter(element.first)) {
                if (!identifier.empty()) {
                    isc_throw(DhcpConfigError, "the '" << element.first
                              << "' and '" << identifier_name
                              << "' are mutually exclusive");
125
126
127
128
129
130
                }
                identifier = element.second->stringValue();
                identifier_name = element.first;

            } else if (element.first == "hostname") {
                hostname = element.second->stringValue();
131

132
133
134
            }
        }

135
        // Host identifier is a must.
136
        if (identifier_name.empty()) {
137
138
139
140
141
142
143
144
145
146
147
148
149
150
            // If there is no identifier specified, we have to display an
            // error message and include the information what identifiers
            // are supported.
            std::ostringstream s;
            BOOST_FOREACH(std::string param_name, getSupportedParameters(true)) {
                if (s.tellp() != std::streampos(0)) {
                    s << ", ";
                }
                s << param_name;
            }
            isc_throw(DhcpConfigError, "one of the supported identifiers must"
                      " be specified for host reservation: "
                      << s.str());

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
        }

        // Create a host object from the basic parameters we already parsed.
        host_.reset(new Host(identifier, identifier_name, SubnetID(0),
                             SubnetID(0), IOAddress("0.0.0.0"), hostname));

    } catch (const std::exception& ex) {
        // Append line number where the error occurred.
        isc_throw(DhcpConfigError, ex.what() << " ("
                  << reservation_data->getPosition() << ")");
    }
}

void
HostReservationParser::addHost(isc::data::ConstElementPtr reservation_data) {
    try {
        CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host_);

    } catch (const std::exception& ex) {
        // Append line number to the exception string.
        isc_throw(DhcpConfigError, ex.what() << " ("
                  << reservation_data->getPosition() << ")");
    }
}

176
177
178
179
180
bool
HostReservationParser::isIdentifierParameter(const std::string& param_name) const {
    return (getSupportedParameters(true).count(param_name) > 0);
}

181
182
183
184
185
bool
HostReservationParser::isSupportedParameter(const std::string& param_name) const {
    return (getSupportedParameters(false).count(param_name) > 0);
}

186
void
187
188
189
HostReservationParser4::parseInternal(const SubnetID& subnet_id,
                                      isc::data::ConstElementPtr reservation_data) {
    HostReservationParser::parseInternal(subnet_id, reservation_data);
190

191
    host_->setIPv4SubnetID(subnet_id);
192
193

    BOOST_FOREACH(ConfigPair element, reservation_data->mapValue()) {
194
195
196
197
198
        // For 'option-data' element we will use another parser which
        // already returns errors with position appended, so don't
        // surround it with try-catch.
        if (element.first == "option-data") {
            CfgOptionPtr cfg_option = host_->getCfgOption4();
199
200
201
202
203
204

            // This parser is converted to SimpleParser already. It
            // parses the Element structure immediately, there's no need
            // to go through build/commit phases.
            OptionDataListParser parser(AF_INET);
            parser.parse(cfg_option, element.second);
205
206
207
208
209
210
211
212

       // Everything else should be surrounded with try-catch to append
       // position.
        } else {
            try {
                if (element.first == "ip-address") {
                    host_->setIPv4Reservation(IOAddress(element.second->
                                                        stringValue()));
213
                } else if (element.first == "next-server") {
214
                    host_->setNextServer(IOAddress(element.second->stringValue()));
215

216
                } else if (element.first == "server-hostname") {
217
218
219
220
                    host_->setServerHostname(element.second->stringValue());

                } else if (element.first == "boot-file-name") {
                    host_->setBootFileName(element.second->stringValue());
221
222
223
224
225
226

                } else if (element.first == "client-classes") {
                    BOOST_FOREACH(ConstElementPtr class_element,
                                  element.second->listValue()) {
                        host_->addClientClass4(class_element->stringValue());
                    }
227
                }
228

229
230
231
            } catch (const std::exception& ex) {
                // Append line number where the error occurred.
                isc_throw(DhcpConfigError, ex.what() << " ("
232
                          << element.second->getPosition() << ")");
233
234
235
236
237
238
239
            }
        }
    }

    addHost(reservation_data);
}

240
241
242
const std::set<std::string>&
HostReservationParser4::getSupportedParameters(const bool identifiers_only) const {
    return (getSupportedParams4(identifiers_only));
243
244
}

245
void
246
247
248
HostReservationParser6::parseInternal(const SubnetID& subnet_id,
                                      isc::data::ConstElementPtr reservation_data) {
    HostReservationParser::parseInternal(subnet_id, reservation_data);
249

250
    host_->setIPv6SubnetID(subnet_id);
251
252

    BOOST_FOREACH(ConfigPair element, reservation_data->mapValue()) {
253
254
255
256
257
258
        // Parse option values. Note that the configuration option parser
        // returns errors with position information appended, so there is no
        // need to surround it with try-clause (and rethrow with position
        // appended).
        if (element.first == "option-data") {
            CfgOptionPtr cfg_option = host_->getCfgOption6();
259
260
261
262
263
264

            // This parser is converted to SimpleParser already. It
            // parses the Element structure immediately, there's no need
            // to go through build/commit phases.
            OptionDataListParser parser(AF_INET6);
            parser.parse(cfg_option, element.second);
265
266

        } else if (element.first == "ip-addresses" || element.first == "prefixes") {
267
268
269
270
271
            BOOST_FOREACH(ConstElementPtr prefix_element,
                          element.second->listValue()) {
                try {
                    // For the IPv6 address the prefix length is 128 and the
                    // value specified in the list is a reserved address.
272
                    IPv6Resrv::Type resrv_type = IPv6Resrv::TYPE_NA;
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
                    std::string prefix = prefix_element->stringValue();
                    uint8_t prefix_len = 128;

                    // If we're dealing with prefixes, instead of addresses,
                    // we will have to extract the prefix length from the value
                    // specified in the following format: 2001:db8:2000::/64.
                    if (element.first == "prefixes") {
                        // The slash is mandatory for prefixes. If there is no
                        // slash, return an error.
                        size_t len_pos  = prefix.find('/');
                        if (len_pos == std::string::npos) {
                            isc_throw(DhcpConfigError, "prefix reservation"
                                      " requires prefix length be specified"
                                      " in '" << prefix << "'");

                        // If there is nothing after the slash, we should also
                        // report an error.
                        } else if (len_pos >= prefix.length() - 1) {
                            isc_throw(DhcpConfigError, "prefix '" <<  prefix
                                      << "' requires length after '/'");

                        }

                        // Convert the prefix length from the string to the
                        // number. Note, that we don't use the uint8_t type
                        // as the lexical cast would expect a chracter, e.g.
                        // 'a', instead of prefix length, e.g. '64'.
                        try {
                            prefix_len = boost::lexical_cast<
                                unsigned int>(prefix.substr(len_pos + 1));

                        } catch (const boost::bad_lexical_cast&) {
                            isc_throw(DhcpConfigError, "prefix length value '"
                                      << prefix.substr(len_pos + 1)
                                      << "' is invalid");
                        }

                        // Remove the  slash character and the prefix length
                        // from the parsed value.
                        prefix.erase(len_pos);
313
314
315

                        // Finally, set the reservation type.
                        resrv_type = IPv6Resrv::TYPE_PD;
316
317
318
                    }

                    // Create a reservation for an address or prefix.
319
320
                    host_->addReservation(IPv6Resrv(resrv_type,
                                                    IOAddress(prefix),
321
322
323
324
325
326
327
328
                                                    prefix_len));

                } catch (const std::exception& ex) {
                    // Append line number where the error occurred.
                    isc_throw(DhcpConfigError, ex.what() << " ("
                              << prefix_element->getPosition() << ")");
                }
            }
329
330
331
332
333
334
335
336
337
338
339
340
341


        } else if (element.first == "client-classes") {
            try {
                BOOST_FOREACH(ConstElementPtr class_element,
                              element.second->listValue()) {
                    host_->addClientClass6(class_element->stringValue());
                }
            } catch (const std::exception& ex) {
                // Append line number where the error occurred.
                isc_throw(DhcpConfigError, ex.what() << " ("
                          << element.second->getPosition() << ")");
            }
342
343
344
345
346
        }
    }

    // This may fail, but the addHost function will handle this on its own.
    addHost(reservation_data);
347
348
}

349
350
351
const std::set<std::string>&
HostReservationParser6::getSupportedParameters(const bool identifiers_only) const {
    return (getSupportedParams6(identifiers_only));
352
353
}

354
355
356
357
358
HostReservationIdsParser::HostReservationIdsParser()
    : staging_cfg_() {
}

void
359
360
361
362
363
364
HostReservationIdsParser::parse(isc::data::ConstElementPtr ids_list) {
    parseInternal(ids_list);
}

void
HostReservationIdsParser::parseInternal(isc::data::ConstElementPtr ids_list) {
365
366
    // Remove existing identifier types.
    staging_cfg_->clearIdentifierTypes();
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417

    BOOST_FOREACH(ConstElementPtr element, ids_list->listValue()) {
        std::string id_name = element->stringValue();
        try {
            if (id_name != "auto") {
                if (!isSupportedIdentifier(id_name)) {
                    isc_throw(isc::BadValue, "unsupported identifier '"
                              << id_name << "'");
                }
                staging_cfg_->addIdentifierType(id_name);

            } else {
                // 'auto' is mutually exclusive with other values. If there
                // are any values in the configuration already it means that
                // some other values have already been specified.
                if (!staging_cfg_->getIdentifierTypes().empty()) {
                    isc_throw(isc::BadValue, "if 'auto' keyword is used,"
                              " no other values can be specified within '"
                              "host-reservation-identifiers' list");
                }
                // Iterate over all identifier types and for those supported
                // in a given context (DHCPv4 or DHCPv6) add the identifier type
                // to the configuration.
                for (unsigned int i = 0;
                     i <= static_cast<unsigned int>(Host::LAST_IDENTIFIER_TYPE);
                     ++i) {
                    std::string supported_id_name =
                        Host::getIdentifierName(static_cast<Host::IdentifierType>(i));
                    if (isSupportedIdentifier(supported_id_name)) {
                        staging_cfg_->addIdentifierType(supported_id_name);
                    }
                }
            }

        } catch (const std::exception& ex) {
            // Append line number where the error occurred.
            isc_throw(DhcpConfigError, ex.what() << " ("
                      << element->getPosition() << ")");
        }
    }

    // The parsed list must not be empty.
    if (staging_cfg_->getIdentifierTypes().empty()) {
        isc_throw(DhcpConfigError, "'host-reservation-identifiers' list must not"
                  " be empty (" << ids_list->getPosition() << ")");
    }

}

HostReservationIdsParser4::HostReservationIdsParser4()
    : HostReservationIdsParser() {
418
    staging_cfg_ = CfgMgr::instance().getStagingCfg()->getCfgHostOperations4();
419
420
421
422
423
424
425
426
427
}

bool
HostReservationIdsParser4::isSupportedIdentifier(const std::string& id_name) const {
    return (getSupportedParams4(true).count(id_name) > 0);
}

HostReservationIdsParser6::HostReservationIdsParser6()
    : HostReservationIdsParser() {
428
    staging_cfg_ = CfgMgr::instance().getStagingCfg()->getCfgHostOperations6();
429
430
431
432
433
434
435
}

bool
HostReservationIdsParser6::isSupportedIdentifier(const std::string& id_name) const {
    return (getSupportedParams6(true).count(id_name) > 0);
}

436
437
} // end of namespace isc::dhcp
} // end of namespace isc