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
// Copyright (C) 2015-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/.

#ifndef LEASE_FILE_LOADER_H
#define LEASE_FILE_LOADER_H

#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/memfile_lease_storage.h>
#include <util/versioned_csv_file.h>
#include <dhcpsrv/sanity_checker.h>

#include <boost/scoped_ptr.hpp><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <boost/shared_ptr.hpp><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.

namespace isc {
namespace dhcp {

/// @brief Utility class to manage bulk of leases in the lease files.
///
/// This class exposes methods which allow for bulk loading leases from
/// the lease file and dumping the leases held in memory into the
/// lease file. There are two major use cases for this class:
/// - load leases by the DHCP server when the server starts up or
///   reloads configuration,
/// - an application performing a lease file cleanup rewrites the whole
///   lease file to remove the redundant lease entries.
///
/// In the former case, this class is used by the @c MemFile_LeaseMgr.
/// In the latter case, this class is used by the standalone application
/// which reads the whole lease file into memory (storage) and then
/// dumps the leases held in the storage to another file.
///
/// The methods in this class are templated so as they can be used both
/// with the @c Lease4Storage and @c Lease6Storage to process the DHCPv4
/// and DHCPv6 leases respectively.
///
class LeaseFileLoader {
public:

    /// @brief Load leases from the lease file into the specified storage.
    ///
    /// This method iterates over the entries in the lease file in the
    /// CSV format, creates @c Lease4 or @c Lease6 objects and inserts
    /// them into the storage to which reference is specified as an
    /// argument. If there are multiple entries for the particular lease
    /// in the lease file the entries further in the lease file override
    /// the previous entries.
    ///
    /// If the method finds the entry with the valid lifetime of 0 it
    /// means that the particular lease was released and the method
    /// removes an existing lease from the container.
    ///
    /// @param lease_file A reference to the @c CSVLeaseFile4 or
    /// @c CSVLeaseFile6 object representing the lease file. The file
    /// doesn't need to be open because the method re-opens the file.
    /// @param storage A reference to the container to which leases
    /// should be inserted.
    /// @param max_errors Maximum number of corrupted leases in the
    /// lease file. The method will skip corrupted leases but after
    /// exceeding the specified number of errors it will throw an
    /// exception. A value of 0 (default) disables the limit check.
    /// @param close_file_on_exit A boolean flag which indicates if
    /// the file should be closed after it has been successfully parsed.
    /// One case when the file is not opened is when the server starts
    /// up, reads the leases in the file and then leaves the file open
    /// for writing future lease updates.
    /// @tparam LeaseObjectType A @c Lease4 or @c Lease6.
    /// @tparam LeaseFileType A @c CSVLeaseFile4 or @c CSVLeaseFile6.
    /// @tparam StorageType A @c Lease4Storage or @c Lease6Storage.
    ///
    /// @throw isc::util::CSVFileError when the maximum number of errors
    /// has been exceeded.
    template<typename LeaseObjectType, typename LeaseFileType,
             typename StorageType>
    static void load(LeaseFileType& lease_file, StorageType& storage,
                     const uint32_t max_errors = 0,
                     const bool close_file_on_exit = true) {

        LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_LEASE_FILE_LOAD)
            .arg(lease_file.getFilename());

        // Reopen the file, as we don't know whether the file is open
        // and we also don't know its current state.
        lease_file.close();
        lease_file.open();

        // Create lease sanity checker if checking is enabled.
        boost::scoped_ptr<SanityChecker> lease_checker;
        if (SanityChecker::leaseCheckingEnabled(false)) {
            // Since lease file is loaded during the configuration,
            // we have to use staging config, rather than current
            // config for this (false = staging).
            lease_checker.reset(new SanityChecker());
        }

        boost::shared_ptr<LeaseObjectType> lease;
        // Track the number of corrupted leases.
        uint32_t errcnt = 0;
        while (true) {
            // Unable to parse the lease.
            if (!lease_file.next(lease)) {
                LOG_ERROR(dhcpsrv_logger, DHCPSRV_MEMFILE_LEASE_LOAD_ROW_ERROR)
                            .arg(lease_file.getReads())
                            .arg(lease_file.getReadMsg());

                // A value of 0 indicates that we don't return
                // until the whole file is parsed, even if errors occur.
                // Otherwise, check if we have exceeded the maximum number
                // of errors and throw an exception if we have.
                if (max_errors && (++errcnt > max_errors)) {
                    // If we break parsing the CSV file because of too many
                    // errors, it doesn't make sense to keep the file open.
                    // This is because the caller wouldn't know where we
                    // stopped parsing and where the internal file pointer
                    // is. So, there are probably no cases when the caller
                    // would continue to use the open file.
                    lease_file.close();
                    isc_throw(util::CSVFileError, "exceeded maximum number of"
                              " failures " << max_errors << " to read a lease"
                              " from the lease file "
                              << lease_file.getFilename());
                }
                // Skip the corrupted lease.
                continue;
            }

            // Lease was found and we successfully parsed it.
            if (lease) {
                LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL_DATA,
                          DHCPSRV_MEMFILE_LEASE_LOAD)
                    .arg(lease->toText());

                if (lease_checker)  {
                    // If the lease is insane the checker will reset the lease pointer.
                    // As lease file is loaded during the configuration, we have
                    // to use staging config, rather than current config for this
                    // (false = staging).
                    lease_checker->checkLease(lease, false);
                    if (!lease) {
                        continue;
                    }
                }

                // Check if this lease exists.
                typename StorageType::iterator lease_it =
                    storage.find(lease->addr_);
                // The lease doesn't exist yet. Insert the lease if
                // it has a positive valid lifetime.
                if (lease_it == storage.end()) {
                    if (lease->valid_lft_ > 0) {
                        storage.insert(lease);
                    }
                } else {
                    // The lease exists. If the new entry has a valid
                    // lifetime of 0 it is an indication to remove the
                    // existing entry. Otherwise, we update the lease.
                    if (lease->valid_lft_ == 0) {
                        storage.erase(lease_it);

                    } else {
                        // Use replace to re-index leases on update.
                        storage.replace(lease_it, lease);
                    }
                }

            } else {
                // Being here means that we hit the end of file.
                break;

            }
        }

        if (lease_file.needsConversion()) {
            LOG_WARN(dhcpsrv_logger,
                     (lease_file.getInputSchemaState()
                      == util::VersionedCSVFile::NEEDS_UPGRADE
                      ?  DHCPSRV_MEMFILE_NEEDS_UPGRADING
                      : DHCPSRV_MEMFILE_NEEDS_DOWNGRADING))
                     .arg(lease_file.getFilename())
                     .arg(lease_file.getSchemaVersion());
        }

        if (close_file_on_exit) {
            lease_file.close();
        }
    }

    /// @brief Write leases from the storage into a lease file
    ///
    /// This method iterates over the @c Lease4 or @c Lease6 object in the
    /// storage specified in the arguments and writes them to the file
    /// specified in the arguments.
    ///
    /// This method writes all entries in the storage to the file, it does
    /// not perform any checks for expiration or duplication.
    ///
    /// The order in which the entries will be written to the file depends
    /// on the first index in the multi-index container.  Currently that
    /// is the v4 or v6 IP address and they are written from lowest to highest.
    ///
    /// Before writing the method will close the file if it is open
    /// and reopen it for writing.  After completion it will close
    /// the file.
    ///
    /// @param lease_file A reference to the @c CSVLeaseFile4 or
    /// @c CSVLeaseFile6 object representing the lease file. The file
    /// doesn't need to be open because the method re-opens the file.
    /// @param storage A reference to the container from which leases
    /// should be written.
    ///
    /// @tparam LeaseObjectType A @c Lease4 or @c Lease6.
    /// @tparam LeaseFileType A @c CSVLeaseFile4 or @c CSVLeaseFile6.
    /// @tparam StorageType A @c Lease4Storage or @c Lease6Storage.
    template<typename LeaseObjectType, typename LeaseFileType,
             typename StorageType>
    static void write(LeaseFileType& lease_file, const StorageType& storage) {
        // Reopen the file, as we don't know whether the file is open
        // and we also don't know its current state.
        lease_file.close();
        lease_file.open();

        // Iterate over the storage area writing out the leases
        for (auto const& lease : storage) {
            try {
                lease_file.append(*lease);
            } catch (const isc::Exception&) {
                // Close the file
                lease_file.close();
                throw;
            }
        }

        // Close the file
        lease_file.close();
    }
};

}  // namespace dhcp
}  // namespace isc

#endif // LEASE_FILE_LOADER_H