/* * DHCP4 Client Leases * * This implements the public API wrapping DHCP4 client leases. A lease object * conists 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 #include #include #include #include #include #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 argumnet 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 cotained 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 cotained 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_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_DCHP4_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. */ _c_public_ int n_dhcp4_client_lease_select(NDhcp4ClientLease *lease) { NDhcp4ClientLease *l, *t_l; NDhcp4ClientProbe *probe; int r; /* XXX error handling, this must be an OFFER */ 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. */ _c_public_ int n_dhcp4_client_lease_accept(NDhcp4ClientLease *lease) { int r; /* XXX error handling, this must be an ACK */ 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. */ _c_public_ int n_dhcp4_client_lease_decline(NDhcp4ClientLease *lease, const char *error) { int r; /* XXX: error handling, this must be an ACK */ 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; }