/src/kea/src/lib/dhcpsrv/csv_lease_file6.cc
Line | Count | Source |
1 | | // Copyright (C) 2014-2025 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 | | #include <config.h> |
8 | | |
9 | | #include <dhcpsrv/dhcpsrv_log.h> |
10 | | #include <dhcpsrv/csv_lease_file6.h> |
11 | | |
12 | | #include <ctime> |
13 | | |
14 | | using namespace isc::asiolink; |
15 | | using namespace isc::data; |
16 | | using namespace isc::util; |
17 | | |
18 | | namespace isc { |
19 | | namespace dhcp { |
20 | | |
21 | | CSVLeaseFile6::CSVLeaseFile6(const std::string& filename) |
22 | 22.8k | : VersionedCSVFile(filename) { |
23 | 22.8k | initColumns(); |
24 | 22.8k | } |
25 | | |
26 | | void |
27 | 6.18k | CSVLeaseFile6::open(const bool seek_to_end) { |
28 | | // Call the base class to open the file |
29 | 6.18k | VersionedCSVFile::open(seek_to_end); |
30 | | |
31 | | // and clear any statistics we may have |
32 | 6.18k | clearStatistics(); |
33 | 6.18k | } |
34 | | |
35 | | void |
36 | 0 | CSVLeaseFile6::append(const Lease6& lease) { |
37 | | // Bump the number of write attempts |
38 | 0 | ++writes_; |
39 | |
|
40 | 0 | if (((!(lease.duid_)) || (*(lease.duid_) == DUID::EMPTY())) && |
41 | 0 | (lease.state_ != Lease::STATE_DECLINED)) { |
42 | 0 | ++write_errs_; |
43 | 0 | isc_throw(BadValue, "Lease6: " << lease.addr_.toText() << ", state: " |
44 | 0 | << Lease::basicStatesToText(lease.state_) << ", has no DUID"); |
45 | 0 | } |
46 | | |
47 | 0 | CSVRow row(getColumnCount()); |
48 | 0 | row.writeAt(getColumnIndex("address"), lease.addr_.toText()); |
49 | 0 | row.writeAt(getColumnIndex("duid"), lease.duid_->toText()); |
50 | 0 | row.writeAt(getColumnIndex("valid_lifetime"), lease.valid_lft_); |
51 | 0 | row.writeAt(getColumnIndex("expire"), static_cast<uint64_t>(lease.cltt_) + lease.valid_lft_); |
52 | 0 | row.writeAt(getColumnIndex("subnet_id"), lease.subnet_id_); |
53 | 0 | row.writeAt(getColumnIndex("pref_lifetime"), lease.preferred_lft_); |
54 | 0 | row.writeAt(getColumnIndex("lease_type"), lease.type_); |
55 | 0 | row.writeAt(getColumnIndex("iaid"), lease.iaid_); |
56 | 0 | row.writeAt(getColumnIndex("prefix_len"), |
57 | 0 | static_cast<int>(lease.prefixlen_)); |
58 | 0 | row.writeAt(getColumnIndex("fqdn_fwd"), lease.fqdn_fwd_); |
59 | 0 | row.writeAt(getColumnIndex("fqdn_rev"), lease.fqdn_rev_); |
60 | 0 | row.writeAtEscaped(getColumnIndex("hostname"), lease.hostname_); |
61 | | // We may not have hardware information. |
62 | 0 | if (lease.hwaddr_) { |
63 | 0 | row.writeAt(getColumnIndex("hwaddr"), lease.hwaddr_->toText(false)); |
64 | 0 | row.writeAt(getColumnIndex("hwtype"), lease.hwaddr_->htype_); |
65 | 0 | row.writeAt(getColumnIndex("hwaddr_source"), lease.hwaddr_->source_); |
66 | 0 | } |
67 | 0 | row.writeAt(getColumnIndex("state"), lease.state_); |
68 | | // User context is optional. |
69 | 0 | if (lease.getContext()) { |
70 | 0 | row.writeAtEscaped(getColumnIndex("user_context"), lease.getContext()->str()); |
71 | 0 | } |
72 | 0 | row.writeAt(getColumnIndex("pool_id"), lease.pool_id_); |
73 | 0 | try { |
74 | 0 | VersionedCSVFile::append(row); |
75 | 0 | } catch (const std::exception&) { |
76 | | // Catch any errors so we can bump the error counter than rethrow it |
77 | 0 | ++write_errs_; |
78 | 0 | throw; |
79 | 0 | } |
80 | | |
81 | | // Bump the number of leases written |
82 | 0 | ++write_leases_; |
83 | 0 | } |
84 | | |
85 | | bool |
86 | 25.6k | CSVLeaseFile6::next(Lease6Ptr& lease) { |
87 | | // Bump the number of read attempts |
88 | 25.6k | ++reads_; |
89 | | |
90 | | // Read the CSV row and try to create a lease from the values read. |
91 | | // This may easily result in exception. We don't want this function |
92 | | // to throw exceptions, so we catch them all and rather return the |
93 | | // false value. |
94 | 25.6k | try { |
95 | | // Get the row of CSV values. |
96 | 25.6k | CSVRow row; |
97 | 25.6k | VersionedCSVFile::next(row); |
98 | | // The empty row signals EOF. |
99 | 25.6k | if (row == CSVFile::EMPTY_ROW()) { |
100 | 5.47k | lease.reset(); |
101 | 5.47k | return (true); |
102 | 5.47k | } |
103 | | |
104 | 20.1k | Lease::Type type = readType(row); |
105 | 20.1k | uint8_t prefixlen = 128; |
106 | 20.1k | if (type == Lease::TYPE_PD) { |
107 | 0 | prefixlen = readPrefixLen(row); |
108 | 0 | } |
109 | | |
110 | 20.1k | lease.reset(new Lease6(type, readAddress(row), readDUID(row), |
111 | 20.1k | readIAID(row), readPreferred(row), |
112 | 20.1k | readValid(row), |
113 | 20.1k | readSubnetID(row), |
114 | 20.1k | readHWAddr(row), |
115 | 20.1k | prefixlen)); |
116 | | |
117 | 20.1k | lease->cltt_ = readCltt(row); |
118 | 20.1k | lease->fqdn_fwd_ = readFqdnFwd(row); |
119 | 20.1k | lease->fqdn_rev_ = readFqdnRev(row); |
120 | 20.1k | lease->hostname_ = readHostname(row); |
121 | 20.1k | lease->state_ = readState(row); |
122 | | |
123 | 20.1k | if ((*lease->duid_ == DUID::EMPTY()) |
124 | 0 | && lease->state_ != Lease::STATE_DECLINED) { |
125 | 0 | isc_throw(isc::BadValue, |
126 | 0 | "The Empty DUID is only valid for declined leases"); |
127 | 0 | } |
128 | | |
129 | 20.1k | ConstElementPtr ctx = readContext(row); |
130 | 20.1k | if (ctx) { |
131 | 15.0k | lease->setContext(ctx); |
132 | 15.0k | } |
133 | | |
134 | 20.1k | lease->pool_id_ = readPoolID(row); |
135 | 20.1k | } catch (const std::exception& ex) { |
136 | | // bump the read error count |
137 | 0 | ++read_errs_; |
138 | | |
139 | | // The lease might have been created, so let's set it back to NULL to |
140 | | // signal that lease hasn't been parsed. |
141 | 0 | lease.reset(); |
142 | 0 | setReadMsg(ex.what()); |
143 | 0 | return (false); |
144 | 0 | } |
145 | | |
146 | | // bump the number of leases read |
147 | 20.1k | ++read_leases_; |
148 | | |
149 | 20.1k | return (true); |
150 | 25.6k | } |
151 | | |
152 | | void |
153 | 22.8k | CSVLeaseFile6::initColumns() { |
154 | 22.8k | addColumn("address", "1.0"); |
155 | 22.8k | addColumn("duid", "1.0"); |
156 | 22.8k | addColumn("valid_lifetime", "1.0"); |
157 | 22.8k | addColumn("expire", "1.0"); |
158 | 22.8k | addColumn("subnet_id", "1.0"); |
159 | 22.8k | addColumn("pref_lifetime", "1.0"); |
160 | 22.8k | addColumn("lease_type", "1.0"); |
161 | 22.8k | addColumn("iaid", "1.0"); |
162 | 22.8k | addColumn("prefix_len", "1.0"); |
163 | 22.8k | addColumn("fqdn_fwd", "1.0"); |
164 | 22.8k | addColumn("fqdn_rev", "1.0"); |
165 | 22.8k | addColumn("hostname", "1.0"); |
166 | 22.8k | addColumn("hwaddr", "2.0"); |
167 | 22.8k | addColumn("state", "3.0", "0" /* == STATE_DEFAULT */); |
168 | 22.8k | addColumn("user_context", "3.1"); |
169 | | // Default not added for hwtype and hwaddr_source, because they depend on |
170 | | // hwaddr having value. When a CSV lease having a hwaddr is upgraded to 4.0, |
171 | | // hwtype will have value "1" meaning HTYPE_ETHER and |
172 | | // hwaddr_source will have value "0" meaning HWADDR_SOURCE_UNKNOWN. |
173 | 22.8k | addColumn("hwtype", "4.0"); |
174 | 22.8k | addColumn("hwaddr_source", "4.0"); |
175 | 22.8k | addColumn("pool_id", "5.0", "0"); |
176 | | |
177 | | // Any file with less than hostname is invalid |
178 | 22.8k | setMinimumValidColumns("hostname"); |
179 | 22.8k | } |
180 | | |
181 | | Lease::Type |
182 | 20.1k | CSVLeaseFile6::readType(const CSVRow& row) { |
183 | 20.1k | return (static_cast<Lease::Type> |
184 | 20.1k | (row.readAndConvertAt<int>(getColumnIndex("lease_type")))); |
185 | 20.1k | } |
186 | | |
187 | | IOAddress |
188 | 20.1k | CSVLeaseFile6::readAddress(const CSVRow& row) { |
189 | 20.1k | IOAddress address(row.readAt(getColumnIndex("address"))); |
190 | 20.1k | return (address); |
191 | 20.1k | } |
192 | | |
193 | | DuidPtr |
194 | 20.1k | CSVLeaseFile6::readDUID(const util::CSVRow& row) { |
195 | 20.1k | DuidPtr duid(new DUID(DUID::fromText(row.readAt(getColumnIndex("duid"))))); |
196 | 20.1k | return (duid); |
197 | 20.1k | } |
198 | | |
199 | | uint32_t |
200 | 20.1k | CSVLeaseFile6::readIAID(const CSVRow& row) { |
201 | 20.1k | uint32_t iaid = row.readAndConvertAt<uint32_t>(getColumnIndex("iaid")); |
202 | 20.1k | return (iaid); |
203 | 20.1k | } |
204 | | |
205 | | uint32_t |
206 | 20.1k | CSVLeaseFile6::readPreferred(const CSVRow& row) { |
207 | 20.1k | uint32_t pref = |
208 | 20.1k | row.readAndConvertAt<uint32_t>(getColumnIndex("pref_lifetime")); |
209 | 20.1k | return (pref); |
210 | 20.1k | } |
211 | | |
212 | | uint32_t |
213 | 40.2k | CSVLeaseFile6::readValid(const CSVRow& row) { |
214 | 40.2k | uint32_t valid = |
215 | 40.2k | row.readAndConvertAt<uint32_t>(getColumnIndex("valid_lifetime")); |
216 | 40.2k | return (valid); |
217 | 40.2k | } |
218 | | |
219 | | uint32_t |
220 | 20.1k | CSVLeaseFile6::readCltt(const CSVRow& row) { |
221 | 20.1k | uint32_t cltt = |
222 | 20.1k | static_cast<uint32_t>(row.readAndConvertAt<uint64_t>(getColumnIndex("expire")) |
223 | 20.1k | - readValid(row)); |
224 | 20.1k | return (cltt); |
225 | 20.1k | } |
226 | | |
227 | | SubnetID |
228 | 20.1k | CSVLeaseFile6::readSubnetID(const CSVRow& row) { |
229 | 20.1k | SubnetID subnet_id = |
230 | 20.1k | row.readAndConvertAt<SubnetID>(getColumnIndex("subnet_id")); |
231 | 20.1k | return (subnet_id); |
232 | 20.1k | } |
233 | | |
234 | | uint32_t |
235 | 20.1k | CSVLeaseFile6::readPoolID(const CSVRow& row) { |
236 | 20.1k | uint32_t pool_id = |
237 | 20.1k | row.readAndConvertAt<uint32_t>(getColumnIndex("pool_id")); |
238 | 20.1k | return (pool_id); |
239 | 20.1k | } |
240 | | |
241 | | uint8_t |
242 | 0 | CSVLeaseFile6::readPrefixLen(const CSVRow& row) { |
243 | 0 | int prefixlen = row.readAndConvertAt<int>(getColumnIndex("prefix_len")); |
244 | 0 | return (static_cast<uint8_t>(prefixlen)); |
245 | 0 | } |
246 | | |
247 | | bool |
248 | 20.1k | CSVLeaseFile6::readFqdnFwd(const CSVRow& row) { |
249 | 20.1k | bool fqdn_fwd = row.readAndConvertAt<bool>(getColumnIndex("fqdn_fwd")); |
250 | 20.1k | return (fqdn_fwd); |
251 | 20.1k | } |
252 | | |
253 | | bool |
254 | 20.1k | CSVLeaseFile6::readFqdnRev(const CSVRow& row) { |
255 | 20.1k | bool fqdn_rev = row.readAndConvertAt<bool>(getColumnIndex("fqdn_rev")); |
256 | 20.1k | return (fqdn_rev); |
257 | 20.1k | } |
258 | | |
259 | | std::string |
260 | 20.1k | CSVLeaseFile6::readHostname(const CSVRow& row) { |
261 | 20.1k | std::string hostname = row.readAtEscaped(getColumnIndex("hostname")); |
262 | 20.1k | return (hostname); |
263 | 20.1k | } |
264 | | |
265 | | HWAddrPtr |
266 | 20.1k | CSVLeaseFile6::readHWAddr(const CSVRow& row) { |
267 | | |
268 | 20.1k | try { |
269 | 20.1k | uint16_t const hwtype(readHWType(row).valueOr(HTYPE_ETHER)); |
270 | 20.1k | HWAddr hwaddr( |
271 | 20.1k | HWAddr::fromText(row.readAt(getColumnIndex("hwaddr")), hwtype)); |
272 | 20.1k | if (hwaddr.hwaddr_.empty()) { |
273 | 0 | return (HWAddrPtr()); |
274 | 0 | } |
275 | 20.1k | hwaddr.source_ = |
276 | 20.1k | readHWAddrSource(row).valueOr(HWAddr::HWADDR_SOURCE_UNKNOWN); |
277 | | |
278 | | /// @todo: HWAddr returns an object, not a pointer. Without HWAddr |
279 | | /// refactoring, at least one copy is unavoidable. |
280 | | |
281 | | // Let's return a pointer to new freshly created copy. |
282 | 20.1k | return (HWAddrPtr(new HWAddr(hwaddr))); |
283 | | |
284 | 20.1k | } catch (const std::exception& ex) { |
285 | | // That's worse. There was something in the file, but its conversion |
286 | | // to HWAddr failed. Let's log it on warning and carry on. |
287 | 0 | LOG_WARN(dhcpsrv_logger, DHCPSRV_MEMFILE_READ_HWADDR_FAIL) |
288 | 0 | .arg(ex.what()); |
289 | |
|
290 | 0 | return (HWAddrPtr()); |
291 | 0 | } |
292 | 20.1k | } |
293 | | |
294 | | uint32_t |
295 | 20.1k | CSVLeaseFile6::readState(const util::CSVRow& row) { |
296 | 20.1k | uint32_t state = row.readAndConvertAt<uint32_t>(getColumnIndex("state")); |
297 | 20.1k | return (state); |
298 | 20.1k | } |
299 | | |
300 | | ConstElementPtr |
301 | 20.1k | CSVLeaseFile6::readContext(const util::CSVRow& row) { |
302 | 20.1k | std::string user_context = row.readAtEscaped(getColumnIndex("user_context")); |
303 | 20.1k | if (user_context.empty()) { |
304 | 5.03k | return (ConstElementPtr()); |
305 | 5.03k | } |
306 | 15.0k | ConstElementPtr ctx = Element::fromJSON(user_context); |
307 | 15.0k | if (!ctx || (ctx->getType() != Element::map)) { |
308 | 0 | isc_throw(isc::BadValue, "user context '" << user_context |
309 | 0 | << "' is not a JSON map"); |
310 | 0 | } |
311 | 15.0k | return (ctx); |
312 | 15.0k | } |
313 | | |
314 | | Optional<uint16_t> |
315 | 20.1k | CSVLeaseFile6::readHWType(const CSVRow& row) { |
316 | 20.1k | size_t const index(getColumnIndex("hwtype")); |
317 | 20.1k | if (row.readAt(index).empty()) { |
318 | 0 | return Optional<uint16_t>(); |
319 | 0 | } |
320 | 20.1k | return row.readAndConvertAt<uint16_t>(index); |
321 | 20.1k | } |
322 | | |
323 | | Optional<uint32_t> |
324 | 20.1k | CSVLeaseFile6::readHWAddrSource(const CSVRow& row) { |
325 | 20.1k | size_t const index(getColumnIndex("hwaddr_source")); |
326 | 20.1k | if (row.readAt(index).empty()) { |
327 | 0 | return Optional<uint16_t>(); |
328 | 0 | } |
329 | 20.1k | return row.readAndConvertAt<uint32_t>(index); |
330 | 20.1k | } |
331 | | |
332 | | } // end of namespace isc::dhcp |
333 | | } // end of namespace isc |