summaryrefslogtreecommitdiff
path: root/src/n-dhcp4/src/n-dhcp4-c-lease.c
blob: a30b45535637ea8b25594ec31292b5ba487af34c (plain)
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
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
/*
 * DHCP4 Client Leases
 *
 * This implements the public API wrapping DHCP4 client leases. A lease object
 * consists of the information given to us from the server, together with the
 * timestamp recording the start of the validity of the lease.
 *
 * A probe may yield many OFFERS, each of which contains a lease object. One of
 * these offers may be SELECTED, which implicitly rejects all the others.
 * The server may then ACK or NAK the lease which tells us whether or not we
 * are permitted to start using it. Once an ACK has been received, we can
 * configure the address, and only then can we SELECT the lease. If we
 * determine that the offered lease was not appropriate after all we
 * may DECLINE it instead.
 */

#include <assert.h>
#include <c-list.h>
#include <c-stdaux.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include "n-dhcp4.h"
#include "n-dhcp4-private.h"

/*
 * Compute the absolute timeouts from an incoming message. A message contains relative timeouts and the userdata
 * of the incoming message is set to the offset we must apply to get the absolute values.
 *
 * The special value UINT64_MAX is returned to indicate no or infinite timeouts. In case the given timeouts
 * are invalid relative to each other, we recompute T1 and/or T2 to take their default values. Later timeouts
 * take predecende above earlier ones (T1 is adjusted if it conflicts with T2, etc).
 */
static int n_dhcp4_incoming_get_timeouts(NDhcp4Incoming *message, uint64_t *t1p, uint64_t *t2p, uint64_t *lifetimep) {
        uint64_t lifetime, t2, t1;
        uint32_t u32;
        int r;

        r = n_dhcp4_incoming_query_lifetime(message, &u32);
        if (r == N_DHCP4_E_UNSET) {
                lifetime = UINT64_MAX;
        } else if (r) {
                return r;
        } else if (u32 == UINT32_MAX) {
                lifetime = UINT64_MAX;
        } else {
                lifetime = u32 * (1000000000ULL);
        }

        r = n_dhcp4_incoming_query_t2(message, &u32);
        if (r == N_DHCP4_E_UNSET) {
                if (lifetime == UINT64_MAX)
                        t2 = UINT64_MAX;
                else
                        t2 = (lifetime * 7) / 8;
        } else if (r) {
                return r;
        } else {
                if (u32 == UINT32_MAX)
                        t2 = UINT64_MAX;
                else
                        t2 = u32 * (1000000000ULL);

                if (t2 > lifetime)
                        t2 = (lifetime * 7) / 8;
        }

        r = n_dhcp4_incoming_query_t1(message, &u32);
        if (r == N_DHCP4_E_UNSET) {
                if (t2 == UINT64_MAX)
                        t1 = UINT64_MAX;
                else
                        t1 = (t2 * 4) / 7;
        } else if (r) {
                return r;
        } else {
                if (u32 == UINT32_MAX)
                        t1 = UINT64_MAX;
                else
                        t1 = u32 * (1000000000ULL);

                if (t1 > t2)
                        t1 = (t2 * 4) / 7;
        }

        if (lifetime != UINT64_MAX)
                lifetime += message->userdata.base_time;
        if (t2 != UINT64_MAX)
                t2 += message->userdata.base_time;
        if (t1 != UINT64_MAX)
                t1 += message->userdata.base_time;

        *lifetimep = lifetime;
        *t2p = t2;
        *t1p = t1;
        return 0;
}

/**
 * n_dhcp4_client_lease_new() - allocate new client lease object
 * @leasep:                     output argument for new client lease object
 * @message:                    incoming message representing the lease
 *
 * This creates a new client lease object. Client lease objects are simple
 * wrappers around an incoming message representing a lease.
 *
 * Return: 0 on success, negative error code on failure.
 */
int n_dhcp4_client_lease_new(NDhcp4ClientLease **leasep, NDhcp4Incoming *message) {
        _c_cleanup_(n_dhcp4_client_lease_unrefp) NDhcp4ClientLease *lease = NULL;
        int r;

        c_assert(leasep);

        lease = malloc(sizeof(*lease));
        if (!lease)
                return -ENOMEM;

        *lease = (NDhcp4ClientLease)N_DHCP4_CLIENT_LEASE_NULL(*lease);

        r = n_dhcp4_incoming_get_timeouts(message, &lease->t1, &lease->t2, &lease->lifetime);
        if (r)
                return r;

        lease->message = message;
        *leasep = lease;
        lease = NULL;
        return 0;
}

static void n_dhcp4_client_lease_free(NDhcp4ClientLease *lease) {
        n_dhcp4_client_lease_unlink(lease);
        n_dhcp4_incoming_free(lease->message);
        free(lease);
}

/**
 * n_dhcp4_client_lease_ref() - reference client lease
 * @lease:                      the client lease object to reference
 *
 * Take a new reference to a client lease.
 *
 * Return: the lease.
 */
_c_public_ NDhcp4ClientLease *n_dhcp4_client_lease_ref(NDhcp4ClientLease *lease) {
        if (lease)
                ++lease->n_refs;
        return lease;
}

/**
 * n_dhcp4_client_lease_unref() - dereference client lease
 * @lease:                      the client lease object to dereference
 *
 * Relase a reference to a client lease.
 *
 * Return: NULL.
 */
_c_public_ NDhcp4ClientLease *n_dhcp4_client_lease_unref(NDhcp4ClientLease *lease) {
        if (lease && !--lease->n_refs)
                n_dhcp4_client_lease_free(lease);
        return NULL;
}

/**
 * n_dhcp4_client_lease_link() - link lease into probe
 * @lease:                      the lease to operate on
 * @probe:                      the probe to link the lease into
 *
 * Associate a lease with a probe. The lease may not already be linked.
 */
void n_dhcp4_client_lease_link(NDhcp4ClientLease *lease, NDhcp4ClientProbe *probe) {
        c_assert(!lease->probe);
        c_assert(!c_list_is_linked(&lease->probe_link));

        lease->probe = probe;
        c_list_link_tail(&probe->lease_list, &lease->probe_link);
}

/**
 * n_dhcp4_client_lease_unlink() - unlinke lease from its probe
 * @lease:                      the lease to operate on
 *
 * Dissassociate a lease from a probe if it is associated with one. Otherwise,
 * this is a noop.
 */
void n_dhcp4_client_lease_unlink(NDhcp4ClientLease *lease) {
        lease->probe = NULL;
        c_list_unlink(&lease->probe_link);
}

/**
 * n_dhcp4_client_lease_get_yiaddr() - get the IP address
 * @lease:                      the lease to operate on
 * @yiaddr:                     return argument for the IP address
 *
 * Gets the IP address contained in the lease. Or INADDR_ANY if the lease
 * does not contain an IP address.
 */
_c_public_ void n_dhcp4_client_lease_get_yiaddr(NDhcp4ClientLease *lease, struct in_addr *yiaddr) {
        NDhcp4Header *header = n_dhcp4_incoming_get_header(lease->message);

        yiaddr->s_addr = header->yiaddr;
}

/**
 * n_dhcp4_client_lease_get_siaddr() - get the server IP address
 * @lease:                      the lease to operate on
 * @siaddr:                     return argument for the IP address
 *
 * Gets the server IP address contained in the lease. Or INADDR_ANY if the
 * lease does not contain an IP address.
 */
_c_public_ void n_dhcp4_client_lease_get_siaddr(NDhcp4ClientLease *lease, struct in_addr *siaddr) {
        NDhcp4Header *header = n_dhcp4_incoming_get_header(lease->message);

        siaddr->s_addr = header->siaddr;
}

/**
 * n_dhcp4_client_lease_get_basetime() - get the timestamp when the lease was received.
 * @lease:                      the lease to operate on
 * @ns_basetimep:               return argument for the base time in nano seconds
 *
 * Gets the timestamp when the lease was received in CLOCK_BOOTTIME. This
 * is also the base timestamp for the expiration of the lifetime and t1/t2.
 */
_c_public_ void n_dhcp4_client_lease_get_basetime(NDhcp4ClientLease *lease, uint64_t *ns_basetimep) {
        *ns_basetimep = lease->message->userdata.base_time;
}

/**
 * n_dhcp4_client_lease_get_lifetime() - get the lifetime
 * @lease:                      the lease to operate on
 * @ns_lifetimep:               return argument for the lifetime in nano seconds
 *
 * Gets the end of the lease's lifetime in nanoseconds according to CLOCK_BOOTTIME,
 * or (uint64_t)-1 for permanent leases.
 */
_c_public_ void n_dhcp4_client_lease_get_lifetime(NDhcp4ClientLease *lease, uint64_t *ns_lifetimep) {
        *ns_lifetimep = lease->lifetime;
}

/**
 * n_dhcp4_client_lease_get_server_identifier() - get the server identifier
 * @lease:                      the lease to operate on
 * @addr:                       return argument for the server identifier
 *
 * Gets the address contained in the server-identifier DHCP option, in network
 * byte order.
 *
 * Return: 0 on success,
 *         N_DHCP4_E_UNSET if the lease doesn't contain a server-identifier, or
 *         N_DHCP4_E_INTERNAL if the server-identifier is not valid.
 */
_c_public_ int n_dhcp4_client_lease_get_server_identifier(NDhcp4ClientLease *lease, struct in_addr *addr) {
        uint8_t *data;
        size_t n_data;
        int r;

        r = n_dhcp4_incoming_query(lease->message, N_DHCP4_OPTION_SERVER_IDENTIFIER, &data, &n_data);
        if (r)
                return r;
        if (n_data < sizeof(struct in_addr))
                return N_DHCP4_E_INTERNAL;

        memcpy(addr, data, sizeof(struct in_addr));

        return 0;
}

/**
 * n_dhcp4_client_lease_get_file() - query the lease for the boot file name
 * @lease:                      the lease to operate on
 * @file:                       return argument for the file name
 *
 * Query the lease for the boot file name from the DHCP header. The file name
 * is returned as a NULL-terminated string.
 *
 * Return: 0 on success,
 *         N_DHCP4_E_UNSET if the lease does not contain a file name, or
 *         N_DHCP4_E_INTERNAL if the file name is invalid.
 */
_c_public_ int n_dhcp4_client_lease_get_file(NDhcp4ClientLease *lease, const char **file) {
        NDhcp4Message *message;

        if (lease->message->options[N_DHCP4_OPTION_OVERLOAD].size > 0
            && ((*lease->message->options[N_DHCP4_OPTION_OVERLOAD].value) & N_DHCP4_OVERLOAD_FILE)) {
                /* The field is overloaded to contain other options */
                return N_DHCP4_E_UNSET;
        }

        message = &lease->message->message;

        if (message->file[0] == '\0')
                return N_DHCP4_E_UNSET;

        if (!memchr(message->file, '\0', sizeof(message->file))) {
                /* The field is NULL-terminated (RFC 2131 section 2) */
                return N_DHCP4_E_INTERNAL;
        }

        *file = (const char *) message->file;

        return 0;
}

/**
 * n_dhcp4_client_lease_query() - query the lease for an option
 * @lease:                      the lease to operate on
 * @option:                     the DHCP4 option code
 * @datap:                      return argument of the data pointer
 * @n_datap:                    return argument of data length in bytes
 *
 * Query the lease for a given option. Options internal to the DHCP protocol cannot
 * be queried, and only options that were explicitly requested can be queried.
 *
 * Return: 0 on success,
 *         N_DHCP4_E_INTERNAL if an invalid option is queried,
 *         N_DHCP4_E_UNSET if the lease did not contain the option, or
 *         a negative error code on failure.
 */
_c_public_ int n_dhcp4_client_lease_query(NDhcp4ClientLease *lease, uint8_t option, uint8_t **datap, size_t *n_datap) {
        switch (option) {
        case N_DHCP4_OPTION_PAD:
        case N_DHCP4_OPTION_REQUESTED_IP_ADDRESS:
        case N_DHCP4_OPTION_IP_ADDRESS_LEASE_TIME:
        case N_DHCP4_OPTION_OVERLOAD:
        case N_DHCP4_OPTION_MESSAGE_TYPE:
        case N_DHCP4_OPTION_SERVER_IDENTIFIER:
        case N_DHCP4_OPTION_PARAMETER_REQUEST_LIST:
        case N_DHCP4_OPTION_ERROR_MESSAGE:
        case N_DHCP4_OPTION_MAXIMUM_MESSAGE_SIZE:
        case N_DHCP4_OPTION_RENEWAL_T1_TIME:
        case N_DHCP4_OPTION_REBINDING_T2_TIME:
        case N_DHCP4_OPTION_END:
                return N_DHCP4_E_INTERNAL;
        }

        /* XXX: refuse to return options that were not requested */

        return n_dhcp4_incoming_query(lease->message, option, datap, n_datap);
}

/**
 * n_dhcp4_client_lease_select() - select an offered lease
 * @lease:                      lease to operate on
 *
 * Select a lease. This must be a lease that was offered, once
 * one of the leases that were offered in response to a probe was
 * selected none of the others can be.
 *
 * Return: 0 on success, or a negative error code on failure.
 *   Returns -ENOTRECOVERABLE when called in an unexpected state.
 */
_c_public_ int n_dhcp4_client_lease_select(NDhcp4ClientLease *lease) {
        NDhcp4ClientLease *l, *t_l;
        NDhcp4ClientProbe *probe;
        int r;

        if (!lease->probe)
                return -ENOTRECOVERABLE;
        if (lease->probe->current_lease)
                return -ENOTRECOVERABLE;

        r = n_dhcp4_client_probe_transition_select(lease->probe, lease->message, n_dhcp4_gettime(CLOCK_BOOTTIME));
        if (r)
                return r;

        /*
         * Only one of the offered leases can be selected, so flush the list.
         * All offered lease, including this one are now dead.
         */
        probe = lease->probe;
        c_list_for_each_entry_safe(l, t_l, &probe->lease_list, probe_link)
                n_dhcp4_client_lease_unlink(l);

        return 0;
}

/**
 * n_dhcp4_client_lease_accept() - accept an ack'ed lease
 * @lease:                      lease to operate on
 *
 * Accept a lease. This must be a lease that was ack'ed by the
 * server.
 *
 * The offered IP address must be fully configured before the lease
 * can be accepted.
 *
 * Return: 0 on success, or a negative error code on failure.
 *   Returns -ENOTRECOVERABLE when called in an unexpected state.
 */
_c_public_ int n_dhcp4_client_lease_accept(NDhcp4ClientLease *lease) {
        int r;

        if (!lease->probe)
                return -ENOTRECOVERABLE;
        if (lease->probe->current_lease != lease)
                return -ENOTRECOVERABLE;

        r = n_dhcp4_client_probe_transition_accept(lease->probe, lease->message);
        if (r)
                return r;

        n_dhcp4_client_lease_unlink(lease);

        return 0;
}

/**
 * n_dhcp4_client_lease_decline() - decline an ack'ed lease
 * @lease:                      lease to operate on
 *
 * Decline a lease. This must be a lease that was ack'ed by the
 * server.
 *
 * The offered IP address must not be used once the lease has been
 * decline.
 *
 * Return: 0 on success, or a negative error code on failure.
 *   Returns -ENOTRECOVERABLE when called in an unexpected state.
 */
_c_public_ int n_dhcp4_client_lease_decline(NDhcp4ClientLease *lease, const char *error) {
        int r;

        if (!lease->probe)
                return -ENOTRECOVERABLE;
        if (lease->probe->current_lease != lease)
                return -ENOTRECOVERABLE;

        r = n_dhcp4_client_probe_transition_decline(lease->probe, lease->message, error, n_dhcp4_gettime(CLOCK_BOOTTIME));
        if (r)
                return r;

        lease->probe->current_lease = n_dhcp4_client_lease_unref(lease->probe->current_lease);
        n_dhcp4_client_lease_unlink(lease);

        return 0;
}