1 // Copyright (C) 2015-2019 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 
7 #ifndef LEASE_FILE_LOADER_H
8 #define LEASE_FILE_LOADER_H
9 
10 #include <dhcpsrv/dhcpsrv_log.h>
11 #include <dhcpsrv/memfile_lease_storage.h>
12 #include <util/versioned_csv_file.h>
13 #include <dhcpsrv/sanity_checker.h>
14 
15 #include <boost/scoped_ptr.hpp>
16 #include <boost/shared_ptr.hpp>
17 
18 namespace isc {
19 namespace dhcp {
20 
21 /// @brief Utility class to manage bulk of leases in the lease files.
22 ///
23 /// This class exposes methods which allow for bulk loading leases from
24 /// the lease file and dumping the leases held in memory into the
25 /// lease file. There are two major use cases for this class:
26 /// - load leases by the DHCP server when the server starts up or
27 ///   reloads configuration,
28 /// - an application performing a lease file cleanup rewrites the whole
29 ///   lease file to remove the redundant lease entries.
30 ///
31 /// In the former case, this class is used by the @c MemFile_LeaseMgr.
32 /// In the latter case, this class is used by the standalone application
33 /// which reads the whole lease file into memory (storage) and then
34 /// dumps the leases held in the storage to another file.
35 ///
36 /// The methods in this class are templated so as they can be used both
37 /// with the @c Lease4Storage and @c Lease6Storage to process the DHCPv4
38 /// and DHCPv6 leases respectively.
39 ///
40 class LeaseFileLoader {
41 public:
42 
43     /// @brief Load leases from the lease file into the specified storage.
44     ///
45     /// This method iterates over the entries in the lease file in the
46     /// CSV format, creates @c Lease4 or @c Lease6 objects and inserts
47     /// them into the storage to which reference is specified as an
48     /// argument. If there are multiple entries for the particular lease
49     /// in the lease file the entries further in the lease file override
50     /// the previous entries.
51     ///
52     /// If the method finds the entry with the valid lifetime of 0 it
53     /// means that the particular lease was released and the method
54     /// removes an existing lease from the container.
55     ///
56     /// @param lease_file A reference to the @c CSVLeaseFile4 or
57     /// @c CSVLeaseFile6 object representing the lease file. The file
58     /// doesn't need to be open because the method re-opens the file.
59     /// @param storage A reference to the container to which leases
60     /// should be inserted.
61     /// @param max_errors Maximum number of corrupted leases in the
62     /// lease file. The method will skip corrupted leases but after
63     /// exceeding the specified number of errors it will throw an
64     /// exception. A value of 0 (default) disables the limit check.
65     /// @param close_file_on_exit A boolean flag which indicates if
66     /// the file should be closed after it has been successfully parsed.
67     /// One case when the file is not opened is when the server starts
68     /// up, reads the leases in the file and then leaves the file open
69     /// for writing future lease updates.
70     /// @tparam LeaseObjectType A @c Lease4 or @c Lease6.
71     /// @tparam LeaseFileType A @c CSVLeaseFile4 or @c CSVLeaseFile6.
72     /// @tparam StorageType A @c Lease4Storage or @c Lease6Storage.
73     ///
74     /// @throw isc::util::CSVFileError when the maximum number of errors
75     /// has been exceeded.
76     template<typename LeaseObjectType, typename LeaseFileType,
77              typename StorageType>
78     static void load(LeaseFileType& lease_file, StorageType& storage,
79                      const uint32_t max_errors = 0,
80                      const bool close_file_on_exit = true) {
81 
82         LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_LEASE_FILE_LOAD)
83             .arg(lease_file.getFilename());
84 
85         // Reopen the file, as we don't know whether the file is open
86         // and we also don't know its current state.
87         lease_file.close();
88         lease_file.open();
89 
90         // Create lease sanity checker if checking is enabled.
91         boost::scoped_ptr<SanityChecker> lease_checker;
92         if (SanityChecker::leaseCheckingEnabled(false)) {
93             // Since lease file is loaded during the configuration,
94             // we have to use staging config, rather than current
95             // config for this (false = staging).
96             lease_checker.reset(new SanityChecker());
97         }
98 
99         boost::shared_ptr<LeaseObjectType> lease;
100         // Track the number of corrupted leases.
101         uint32_t errcnt = 0;
102         while (true) {
103             // Unable to parse the lease.
104             if (!lease_file.next(lease)) {
105                 LOG_ERROR(dhcpsrv_logger, DHCPSRV_MEMFILE_LEASE_LOAD_ROW_ERROR)
106                             .arg(lease_file.getReads())
107                             .arg(lease_file.getReadMsg());
108 
109                 // A value of 0 indicates that we don't return
110                 // until the whole file is parsed, even if errors occur.
111                 // Otherwise, check if we have exceeded the maximum number
112                 // of errors and throw an exception if we have.
113                 if (max_errors && (++errcnt > max_errors)) {
114                     // If we break parsing the CSV file because of too many
115                     // errors, it doesn't make sense to keep the file open.
116                     // This is because the caller wouldn't know where we
117                     // stopped parsing and where the internal file pointer
118                     // is. So, there are probably no cases when the caller
119                     // would continue to use the open file.
120                     lease_file.close();
121                     isc_throw(util::CSVFileError, "exceeded maximum number of"
122                               " failures " << max_errors << " to read a lease"
123                               " from the lease file "
124                               << lease_file.getFilename());
125                 }
126                 // Skip the corrupted lease.
127                 continue;
128             }
129 
130             // Lease was found and we successfully parsed it.
131             if (lease) {
132                 LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL_DATA,
133                           DHCPSRV_MEMFILE_LEASE_LOAD)
134                     .arg(lease->toText());
135 
136                 if (lease_checker)  {
137                     // If the lease is insane the checker will reset the lease pointer.
138                     // As lease file is loaded during the configuration, we have
139                     // to use staging config, rather than current config for this
140                     // (false = staging).
141                     lease_checker->checkLease(lease, false);
142                     if (!lease) {
143                         continue;
144                     }
145                 }
146 
147                 // Check if this lease exists.
148                 typename StorageType::iterator lease_it =
149                     storage.find(lease->addr_);
150                 // The lease doesn't exist yet. Insert the lease if
151                 // it has a positive valid lifetime.
152                 if (lease_it == storage.end()) {
153                     if (lease->valid_lft_ > 0) {
154                         storage.insert(lease);
155                     }
156                 } else {
157                     // The lease exists. If the new entry has a valid
158                     // lifetime of 0 it is an indication to remove the
159                     // existing entry. Otherwise, we update the lease.
160                     if (lease->valid_lft_ == 0) {
161                         storage.erase(lease_it);
162 
163                     } else {
164                         // Use replace to re-index leases on update.
165                         storage.replace(lease_it, lease);
166                     }
167                 }
168 
169             } else {
170                 // Being here means that we hit the end of file.
171                 break;
172 
173             }
174         }
175 
176         if (lease_file.needsConversion()) {
177             LOG_WARN(dhcpsrv_logger,
178                      (lease_file.getInputSchemaState()
179                       == util::VersionedCSVFile::NEEDS_UPGRADE
180                       ?  DHCPSRV_MEMFILE_NEEDS_UPGRADING
181                       : DHCPSRV_MEMFILE_NEEDS_DOWNGRADING))
182                      .arg(lease_file.getFilename())
183                      .arg(lease_file.getSchemaVersion());
184         }
185 
186         if (close_file_on_exit) {
187             lease_file.close();
188         }
189     }
190 
191     /// @brief Write leases from the storage into a lease file
192     ///
193     /// This method iterates over the @c Lease4 or @c Lease6 object in the
194     /// storage specified in the arguments and writes them to the file
195     /// specified in the arguments.
196     ///
197     /// This method writes all entries in the storage to the file, it does
198     /// not perform any checks for expiration or duplication.
199     ///
200     /// The order in which the entries will be written to the file depends
201     /// on the first index in the multi-index container.  Currently that
202     /// is the v4 or v6 IP address and they are written from lowest to highest.
203     ///
204     /// Before writing the method will close the file if it is open
205     /// and reopen it for writing.  After completion it will close
206     /// the file.
207     ///
208     /// @param lease_file A reference to the @c CSVLeaseFile4 or
209     /// @c CSVLeaseFile6 object representing the lease file. The file
210     /// doesn't need to be open because the method re-opens the file.
211     /// @param storage A reference to the container from which leases
212     /// should be written.
213     ///
214     /// @tparam LeaseObjectType A @c Lease4 or @c Lease6.
215     /// @tparam LeaseFileType A @c CSVLeaseFile4 or @c CSVLeaseFile6.
216     /// @tparam StorageType A @c Lease4Storage or @c Lease6Storage.
217     template<typename LeaseObjectType, typename LeaseFileType,
218              typename StorageType>
write(LeaseFileType & lease_file,const StorageType & storage)219     static void write(LeaseFileType& lease_file, const StorageType& storage) {
220         // Reopen the file, as we don't know whether the file is open
221         // and we also don't know its current state.
222         lease_file.close();
223         lease_file.open();
224 
225         // Iterate over the storage area writing out the leases
226         for (typename StorageType::const_iterator lease = storage.begin();
227              lease != storage.end();
228              ++lease) {
229             try {
230                 lease_file.append(**lease);
231             } catch (const isc::Exception&) {
232                 // Close the file
233                 lease_file.close();
234                 throw;
235             }
236         }
237 
238         // Close the file
239         lease_file.close();
240     }
241 };
242 
243 }  // namespace dhcp
244 }  // namespace isc
245 
246 #endif // LEASE_FILE_LOADER_H
247