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