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
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
// Copyright (C) 2018-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/.

#include <config.h>

#include <command_creator.h><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <cc/command_interpreter.h>
#include <exceptions/exceptions.h>
#include <boost/pointer_cast.hpp><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.

using namespace isc::data;
using namespace isc::dhcp;
using namespace std;

namespace isc {
namespace ha {

unordered_set<string> CommandCreator::ha_commands4_ = {
    "list-commands", "status-get",
    "dhcp-disable", "dhcp-enable",
    "ha-reset", "ha-heartbeat",
    "lease4-update", "lease4-del",
    "lease4-get-all", "lease4-get-page",
    "ha-maintenance-notify", "ha-sync-complete-notify"
};

unordered_set<string> CommandCreator::ha_commands6_ = {
    "list-commands", "status-get",
    "dhcp-disable", "dhcp-enable",
    "ha-reset", "ha-heartbeat",
    "lease6-bulk-apply",
    "lease6-update", "lease6-del",
    "lease6-get-all", "lease6-get-page",
    "ha-maintenance-notify", "ha-sync-complete-notify"
};

ConstElementPtr
CommandCreator::createDHCPDisable(const unsigned int origin_id,
                                  const unsigned int max_period,
                                  const HAServerType& server_type) {
    ElementPtr args;
    args = Element::createMap();
    args->set("origin-id", Element::create(origin_id));
    // Add for backward compatibility with Kea 2.4.0 and earlier.
    args->set("origin", Element::create("ha-partner"));
    // max-period is optional. A value of 0 means that it is not specified.
    if (max_period > 0) {
        args->set("max-period", Element::create(static_cast<long int>(max_period)));
    }
    ConstElementPtr command = config::createCommand("dhcp-disable", args);
    insertService(command, server_type);
    return (command);
}

ConstElementPtr
CommandCreator::createDHCPEnable(const unsigned int origin_id,
                                 const HAServerType& server_type) {
    ElementPtr args;
    args = Element::createMap();
    args->set("origin-id", Element::create(origin_id));
    // Add for backward compatibility with Kea 2.4.0 and earlier.
    args->set("origin", Element::create("ha-partner"));
    ConstElementPtr command = config::createCommand("dhcp-enable", args);
    insertService(command, server_type);
    return (command);
}

ConstElementPtr
CommandCreator::createHAReset(const std::string& server_name,
                              const HAServerType& server_type) {
    auto args = Element::createMap();
    args->set("server-name", Element::create(server_name));
    ConstElementPtr command = config::createCommand("ha-reset", args);
    insertService(command, server_type);
    return (command);
}

ConstElementPtr
CommandCreator::createHeartbeat(const std::string& server_name,
                                const HAServerType& server_type) {
    auto args = Element::createMap();
    args->set("server-name", Element::create(server_name));
    ConstElementPtr command = config::createCommand("ha-heartbeat", args);
    insertService(command, server_type);
    return (command);
}

ConstElementPtr
CommandCreator::createLease4Update(const Lease4& lease4) {
    ElementPtr lease_as_json = lease4.toElement();
    insertLeaseExpireTime(lease_as_json);
    lease_as_json->set("force-create", Element::create(true));
    lease_as_json->set("origin", Element::create("ha-partner"));
    ConstElementPtr command = config::createCommand("lease4-update", lease_as_json);
    insertService(command, HAServerType::DHCPv4);
    return (command);
}

ConstElementPtr
CommandCreator::createLease4Delete(const Lease4& lease4) {
    ElementPtr lease_as_json = lease4.toElement();
    insertLeaseExpireTime(lease_as_json);
    lease_as_json->set("origin", Element::create("ha-partner"));
    ConstElementPtr command = config::createCommand("lease4-del", lease_as_json);
    insertService(command, HAServerType::DHCPv4);
    return (command);
}

ConstElementPtr
CommandCreator::createLease4GetAll() {
    ConstElementPtr command = config::createCommand("lease4-get-all");
    insertService(command, HAServerType::DHCPv4);
    return (command);
}

ConstElementPtr
CommandCreator::createLease4GetPage(const Lease4Ptr& last_lease4,
                                    const uint32_t limit) {
    // Zero value is not allowed.
    if (limit == 0) {
        isc_throw(BadValue, "limit value for lease4-get-page command must not be 0");
    }

    // Get the last lease returned on the previous page. A null pointer means that
    // we're fetching first page. In that case a keyword "start" is used to indicate
    // that first page should be returned.
    ElementPtr from_element = Element::create(last_lease4 ? last_lease4->addr_.toText() : "start");
    // Set the maximum size of the page.
    ElementPtr limit_element = Element::create(static_cast<long long int>(limit));
    // Put both parameters into arguments map.
    ElementPtr args = Element::createMap();
    args->set("from", from_element);
    args->set("limit", limit_element);

    // Create the command.
    ConstElementPtr command = config::createCommand("lease4-get-page", args);
    insertService(command, HAServerType::DHCPv4);
    return (command);
}

ConstElementPtr
CommandCreator::createLease6BulkApply(const Lease6CollectionPtr& leases,
                                      const Lease6CollectionPtr& deleted_leases) {
    ElementPtr leases_list = Element::createList();
    ElementPtr deleted_leases_list = Element::createList();
    for (auto const& lease : *deleted_leases) {
        ElementPtr lease_as_json = lease->toElement();
        insertLeaseExpireTime(lease_as_json);
        // If the deleted lease is in the released state it means that it
        // should be preserved in the database. It is treated as released
        // but can be re-allocated to the same client later (lease affinity).
        // Such a lease should be updated by the partner rather than deleted.
        if (lease->state_ == Lease6::STATE_RELEASED) {
            leases_list->add(lease_as_json);
        } else {
            deleted_leases_list->add(lease_as_json);
        }
    }

    for (auto const& lease : *leases) {
        ElementPtr lease_as_json = lease->toElement();
        insertLeaseExpireTime(lease_as_json);
        leases_list->add(lease_as_json);
    }

    ElementPtr args = Element::createMap();
    args->set("deleted-leases", deleted_leases_list);
    args->set("leases", leases_list);
    args->set("origin", Element::create("ha-partner"));

    ConstElementPtr command = config::createCommand("lease6-bulk-apply", args);
    insertService(command, HAServerType::DHCPv6);
    return (command);
}

ConstElementPtr
CommandCreator::createLease6BulkApply(LeaseUpdateBacklog& leases) {
    ElementPtr deleted_leases_list = Element::createList();
    ElementPtr leases_list = Element::createList();

    LeaseUpdateBacklog::OpType op_type;
    Lease6Ptr lease;
    while ((lease = boost::dynamic_pointer_cast<Lease6>(leases.pop(op_type)))) {
        ElementPtr lease_as_json = lease->toElement();
        insertLeaseExpireTime(lease_as_json);
        if (op_type == LeaseUpdateBacklog::DELETE) {
            deleted_leases_list->add(lease_as_json);
        } else {
            leases_list->add(lease_as_json);
        }
    }

    ElementPtr args = Element::createMap();
    args->set("deleted-leases", deleted_leases_list);
    args->set("leases", leases_list);
    args->set("origin", Element::create("ha-partner"));

    ConstElementPtr command = config::createCommand("lease6-bulk-apply", args);
    insertService(command, HAServerType::DHCPv6);
    return (command);
}

ConstElementPtr
CommandCreator::createLease6Update(const Lease6& lease6) {
    ElementPtr lease_as_json = lease6.toElement();
    insertLeaseExpireTime(lease_as_json);
    lease_as_json->set("force-create", Element::create(true));
    lease_as_json->set("origin", Element::create("ha-partner"));
    ConstElementPtr command = config::createCommand("lease6-update", lease_as_json);
    insertService(command, HAServerType::DHCPv6);
    return (command);
}

ConstElementPtr
CommandCreator::createLease6Delete(const Lease6& lease6) {
    ElementPtr lease_as_json = lease6.toElement();
    insertLeaseExpireTime(lease_as_json);
    lease_as_json->set("origin", Element::create("ha-partner"));
    ConstElementPtr command = config::createCommand("lease6-del", lease_as_json);
    insertService(command, HAServerType::DHCPv6);
    return (command);
}

ConstElementPtr
CommandCreator::createLease6GetAll() {
    ConstElementPtr command = config::createCommand("lease6-get-all");
    insertService(command, HAServerType::DHCPv6);
    return (command);
}

ConstElementPtr
CommandCreator::createLease6GetPage(const Lease6Ptr& last_lease6,
                                    const uint32_t limit) {
    // Zero value is not allowed.
    if (limit == 0) {
        isc_throw(BadValue, "limit value for lease6-get-page command must not be 0");
    }

    // Get the last lease returned on the previous page. A null pointer means that
    // we're fetching first page. In that case a keyword "start" is used to indicate
    // that first page should be returned.
    ElementPtr from_element = Element::create(last_lease6 ? last_lease6->addr_.toText() : "start");
    // Set the maximum size of the page.
    ElementPtr limit_element = Element::create(static_cast<long long int>(limit));
    // Put both parameters into arguments map.
    ElementPtr args = Element::createMap();
    args->set("from", from_element);
    args->set("limit", limit_element);

    // Create the command.
    ConstElementPtr command = config::createCommand("lease6-get-page", args);
    insertService(command, HAServerType::DHCPv6);
    return (command);
}

ConstElementPtr
CommandCreator::createMaintenanceNotify(const std::string& server_name,
                                        const bool cancel,
                                        const HAServerType& server_type) {
    auto args = Element::createMap();
    args->set("server-name", Element::create(server_name));
    args->set("cancel", Element::create(cancel));
    auto command = config::createCommand("ha-maintenance-notify", args);
    insertService(command, server_type);
    return (command);
}

ConstElementPtr
CommandCreator::createSyncCompleteNotify(const unsigned int origin_id,
                                         const std::string& server_name,
                                         const HAServerType& server_type) {
    auto args = Element::createMap();
    args->set("server-name", Element::create(server_name));
    args->set("origin-id", Element::create(origin_id));
    // Add for backward compatibility with Kea 2.5.5 to Kea 2.5.7.
    // The origin parameter was introduced for this command in Kea 2.5.5 but
    // renamed to origin-id to be consistent with the origin-id used in
    // dhcp-enable and dhcp-disable commands starting from Kea 2.5.8.
    args->set("origin", Element::create(origin_id));
    auto command = config::createCommand("ha-sync-complete-notify", args);
    insertService(command, server_type);
    return (command);
}

void
CommandCreator::insertLeaseExpireTime(ElementPtr& lease) {
    if ((lease->getType() != Element::map) ||
        (!lease->contains("cltt") || (lease->get("cltt")->getType() != Element::integer) ||
         (!lease->contains("valid-lft") ||
          (lease->get("valid-lft")->getType() != Element::integer)))) {
        isc_throw(Unexpected, "invalid lease format");
    }

    int64_t cltt = lease->get("cltt")->intValue();
    int64_t valid_lifetime = lease->get("valid-lft")->intValue();
    int64_t expire = cltt + valid_lifetime;
    lease->set("expire", Element::create(expire));
    lease->remove("cltt");
}

void
CommandCreator::insertService(ConstElementPtr& command,
                              const HAServerType& server_type) {
    ElementPtr service = Element::createList();
    const string service_name = (server_type == HAServerType::DHCPv4 ? "dhcp4" : "dhcp6");
    service->add(Element::create(service_name));

    // We have no better way of setting a new element here than
    // doing const pointer cast. That's another reason why this
    // functionality could be moved to the core code. We don't
    // do it however, because we want to minimize concurrent
    // code changes in the premium and core Kea repos.
    (boost::const_pointer_cast<Element>(command))->set("service", service);
}

} // end of namespace ha
} // end of namespace isc