Coverage Report

Created: 2026-05-16 07:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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