blob: bc3994e354ba988592e8abe0126a566feb971f05 [file] [log] [blame]
/*
*
* Embedded Linux library
*
* Copyright (C) 2018 Intel Corporation. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <netinet/ip.h>
#include <net/ethernet.h>
#include <linux/types.h>
#include <net/if_arp.h>
#include <errno.h>
#include "private.h"
#include "random.h"
#include "time.h"
#include "net.h"
#include "timeout.h"
#include "dhcp.h"
#include "dhcp-private.h"
#define CLIENT_DEBUG(fmt, args...) \
l_util_debug(client->debug_handler, client->debug_data, \
"%s:%i " fmt, __func__, __LINE__, ## args)
#define CLIENT_ENTER_STATE(s) \
l_util_debug(client->debug_handler, client->debug_data, \
"%s:%i Entering state: " #s, \
__func__, __LINE__); \
client->state = (s)
#define BITS_PER_LONG (sizeof(unsigned long) * 8)
#define DHCP_OPTION_PAD 0 /* RFC 2132, Section 3.1 */
#define DHCP_OPTION_END 255 /* RFC 2132, Section 3.2 */
/* RFC 2132, Section 9.3. Option Overload */
#define DHCP_OPTION_OVERLOAD 52
enum dhcp_option_overload {
DHCP_OVERLOAD_FILE = 1,
DHCP_OVERLOAD_SNAME = 2,
DHCP_OVERLOAD_BOTH = 3,
};
/* RFC 2132, Section 9.6. DHCP Message Type */
#define DHCP_OPTION_MESSAGE_TYPE 53
enum dhcp_message_type {
DHCP_MESSAGE_TYPE_DISCOVER = 1,
DHCP_MESSAGE_TYPE_OFFER = 2,
DHCP_MESSAGE_TYPE_REQUEST = 3,
DHCP_MESSAGE_TYPE_DECLINE = 4,
DHCP_MESSAGE_TYPE_ACK = 5,
DHCP_MESSAGE_TYPE_NAK = 6,
DHCP_MESSAGE_TYPE_RELEASE = 7,
DHCP_MESSAGE_TYPE_INFORM = 8,
};
#define DHCP_OPTION_PARAMETER_REQUEST_LIST 55 /* Section 9.8 */
#define DHCP_OPTION_MAXIMUM_MESSAGE_SIZE 57 /* Section 9.10 */
#define DHCP_OPTION_CLIENT_IDENTIFIER 61 /* Section 9.14 */
enum dhcp_state {
DHCP_STATE_INIT,
DHCP_STATE_SELECTING,
DHCP_STATE_INIT_REBOOT,
DHCP_STATE_REBOOTING,
DHCP_STATE_REQUESTING,
DHCP_STATE_BOUND,
DHCP_STATE_RENEWING,
DHCP_STATE_REBINDING,
};
const char *_dhcp_message_type_to_string(uint8_t type)
{
switch(type) {
case DHCP_MESSAGE_TYPE_DISCOVER:
return "DHCPDISCOVER";
case DHCP_MESSAGE_TYPE_OFFER:
return "DHCPOFFER";
case DHCP_MESSAGE_TYPE_REQUEST:
return "DHCPREQUEST";
case DHCP_MESSAGE_TYPE_DECLINE:
return "DHCPDECLINE";
case DHCP_MESSAGE_TYPE_ACK:
return "DHCPACK";
case DHCP_MESSAGE_TYPE_NAK:
return "DHCPNAK";
case DHCP_MESSAGE_TYPE_RELEASE:
return "DHCPRELEASE";
default:
return "unknown";
}
}
const char *_dhcp_option_to_string(uint8_t option)
{
switch (option) {
case DHCP_OPTION_PAD:
return "Pad";
case L_DHCP_OPTION_SUBNET_MASK:
return "Subnet Mask";
case L_DHCP_OPTION_ROUTER:
return "Router";
case L_DHCP_OPTION_DOMAIN_NAME_SERVER:
return "Domain Name Server";
case L_DHCP_OPTION_HOST_NAME:
return "Host Name";
case L_DHCP_OPTION_DOMAIN_NAME:
return "Domain Name";
case L_DHCP_OPTION_BROADCAST_ADDRESS:
return "Broadcast Address";
case L_DHCP_OPTION_NTP_SERVERS:
return "NTP Servers";
case L_DHCP_OPTION_REQUESTED_IP_ADDRESS:
return "IP Address";
case L_DHCP_OPTION_IP_ADDRESS_LEASE_TIME:
return "IP Address Lease Time";
case DHCP_OPTION_OVERLOAD:
return "Overload";
case DHCP_OPTION_MESSAGE_TYPE:
return "DHCP Message Type";
case L_DHCP_OPTION_SERVER_IDENTIFIER:
return "Server Identifier";
case DHCP_OPTION_PARAMETER_REQUEST_LIST:
return "Parameter Request List";
case DHCP_OPTION_MAXIMUM_MESSAGE_SIZE:
return "Maximum Message Size";
case L_DHCP_OPTION_RENEWAL_T1_TIME:
return "Renewal Time";
case L_DHCP_OPTION_REBINDING_T2_TIME:
return "Rebinding Time";
case DHCP_OPTION_CLIENT_IDENTIFIER:
return "Client Identifier";
case DHCP_OPTION_END:
return "End";
default:
return "unknown";
}
}
bool _dhcp_message_iter_init(struct dhcp_message_iter *iter,
const struct dhcp_message *message, size_t len)
{
if (!message)
return false;
if (len < sizeof(struct dhcp_message))
return false;
if (L_BE32_TO_CPU(message->magic) != DHCP_MAGIC)
return false;
memset(iter, 0, sizeof(*iter));
iter->message = message;
iter->message_len = len;
iter->max = len - sizeof(struct dhcp_message);
iter->options = message->options;
iter->can_overload = true;
return true;
}
static bool next_option(struct dhcp_message_iter *iter,
uint8_t *t, uint8_t *l, const void **v)
{
uint8_t type;
uint8_t len;
while (iter->pos < iter->max) {
type = iter->options[iter->pos];
switch (type) {
case DHCP_OPTION_PAD:
iter->pos += 1;
continue;
case DHCP_OPTION_END:
return false;
default:
break;
}
if (iter->pos + 2 >= iter->max)
return false;
len = iter->options[iter->pos + 1];
if (iter->pos + 2 + len > iter->max)
return false;
*t = type;
*l = len;
*v = &iter->options[iter->pos + 2];
iter->pos += 2 + len;
return true;
}
return false;
}
bool _dhcp_message_iter_next(struct dhcp_message_iter *iter, uint8_t *type,
uint8_t *len, const void **data)
{
bool r;
uint8_t t, l;
const void *v;
do {
r = next_option(iter, &t, &l, &v);
if (!r) {
iter->can_overload = false;
if (iter->overload_file) {
iter->options = iter->message->file;
iter->pos = 0;
iter->max = sizeof(iter->message->file);
iter->overload_file = false;
continue;
}
if (iter->overload_sname) {
iter->options = iter->message->sname;
iter->pos = 0;
iter->max = sizeof(iter->message->sname);
iter->overload_sname = false;
continue;
}
return r;
}
switch (t) {
case DHCP_OPTION_OVERLOAD:
if (l != 1)
continue;
if (!iter->can_overload)
continue;
if (l_get_u8(v) & DHCP_OVERLOAD_FILE)
iter->overload_file = true;
if (l_get_u8(v) & DHCP_OVERLOAD_SNAME)
iter->overload_sname = true;
continue;
default:
if (type)
*type = t;
if (len)
*len = l;
if (data)
*data = v;
return r;
}
} while (true);
return false;
}
int _dhcp_option_append(uint8_t **buf, size_t *buflen, uint8_t code,
size_t optlen, const void *optval)
{
if (!buf || !buflen)
return -EINVAL;
switch (code) {
case DHCP_OPTION_PAD:
case DHCP_OPTION_END:
if (*buflen < 1)
return -ENOBUFS;
(*buf)[0] = code;
*buf += 1;
*buflen -= 1;
break;
default:
if (*buflen < optlen + 2)
return -ENOBUFS;
if (!optval)
return -EINVAL;
(*buf)[0] = code;
(*buf)[1] = optlen;
memcpy(&(*buf)[2], optval, optlen);
*buf += optlen + 2;
*buflen -= (optlen + 2);
break;
}
return 0;
}
static int dhcp_append_prl(const unsigned long *reqopts,
uint8_t **buf, size_t *buflen)
{
uint8_t optlen = 0;
unsigned int i;
unsigned int j;
if (!buf || !buflen)
return -EINVAL;
for (i = 0; i < 256 / BITS_PER_LONG; i++)
optlen += __builtin_popcountl(reqopts[i]);
/*
* This function assumes that there's enough space to put the PRL
* into the buffer without resorting to file or sname overloading
*/
if (*buflen < optlen + 2U)
return -ENOBUFS;
i = 0;
(*buf)[i++] = DHCP_OPTION_PARAMETER_REQUEST_LIST;
(*buf)[i++] = optlen;
for (j = 0; j < 256; j++) {
if (reqopts[j / BITS_PER_LONG] & 1UL << (j % BITS_PER_LONG))
(*buf)[i++] = j;
}
*buf += optlen + 2;
*buflen -= (optlen + 2);
return 0;
}
static int dhcp_message_init(struct dhcp_message *message,
enum dhcp_op_code op,
uint8_t type, uint32_t xid,
uint8_t **opt, size_t *optlen)
{
int err;
message->op = op;
message->xid = L_CPU_TO_BE32(xid);
message->magic = L_CPU_TO_BE32(DHCP_MAGIC);
*opt = (uint8_t *)(message + 1);
err = _dhcp_option_append(opt, optlen,
DHCP_OPTION_MESSAGE_TYPE, 1, &type);
if (err < 0)
return err;
return 0;
}
static void dhcp_message_set_address_type(struct dhcp_message *message,
uint8_t addr_type,
uint8_t addr_len)
{
message->htype = addr_type;
switch (addr_type) {
case ARPHRD_ETHER:
message->hlen = addr_len;
break;
default:
message->hlen = 0;
}
}
static inline int dhcp_message_optimize(struct dhcp_message *message,
const uint8_t *end)
{
/*
* Don't bother sending a full sized dhcp_message as it is most likely
* mostly zeros. Instead truncate it at DHCP_OPTION_END and align to
* the nearest 4 byte boundary. Many implementations expect a packet
* of a certain size or it is filtered, so we cap the length in
* accordance to RFC 1542:
* "The IP Total Length and UDP Length must be large enough to contain
* the minimal BOOTP header of 300 octets"
*/
size_t len = align_len(end - (uint8_t *) message, 4);
if (len < 300)
len = 300;
return len;
}
#define DHCP_MIN_OPTIONS_SIZE 312
struct l_dhcp_client {
enum dhcp_state state;
unsigned long request_options[256 / BITS_PER_LONG];
uint32_t ifindex;
char *ifname;
uint8_t addr[6];
uint8_t addr_len;
uint8_t addr_type;
char *hostname;
uint32_t xid;
struct dhcp_transport *transport;
uint64_t start_t;
struct l_timeout *timeout_resend;
struct l_timeout *timeout_lease;
struct l_dhcp_lease *lease;
uint8_t attempt;
l_dhcp_client_event_cb_t event_handler;
void *event_data;
l_dhcp_destroy_cb_t event_destroy;
l_dhcp_debug_cb_t debug_handler;
l_dhcp_destroy_cb_t debug_destroy;
void *debug_data;
bool have_addr : 1;
bool override_xid : 1;
};
static inline void dhcp_enable_option(struct l_dhcp_client *client,
uint8_t option)
{
client->request_options[option / BITS_PER_LONG] |=
1UL << (option % BITS_PER_LONG);
}
static uint16_t dhcp_attempt_secs(uint64_t start)
{
uint64_t now = l_time_now();
uint64_t elapsed = l_time_to_secs(now - start);
if (elapsed == 0)
return 1;
if (elapsed > UINT16_MAX)
return UINT16_MAX;
return elapsed;
}
/*
* Takes a time in seconds and produces a fuzzed value that can be directly
* used by l_timeout_modify_ms
*/
static uint64_t dhcp_fuzz_secs(uint32_t secs)
{
uint64_t ms = secs * 1000ULL;
uint32_t r = l_getrandom_uint32();
/*
* RFC2132, Section 4.1:
* DHCP clients are responsible for all message retransmission. The
* client MUST adopt a retransmission strategy that incorporates a
* randomized exponential backoff algorithm to determine the delay
* between retransmissions.
*
* and later in the same paragraph:
* For example, in a 10Mb/sec Ethernet internetwork, the delay before
* the first retransmission SHOULD be 4 seconds randomized by the
* value of a uniform random number chosen from the range -1 to +1.
* Clients with clocks that provide resolution granularity of less than
* one second may choose a non-integer randomization value.
*/
if (r & 0x80000000)
ms += r & 0x3ff;
else
ms -= r & 0x3ff;
return ms;
}
/*
* Takes a time in milliseconds and produces a fuzzed value that can be directly
* used by l_timeout_modify_ms. The fluctuation of the random noise added is
* from -63 to 63 milliseconds.
*/
static uint64_t dhcp_fuzz_msecs(uint64_t ms)
{
uint32_t r = l_getrandom_uint32();
if (r & 0x80000000)
ms += r & 0x3f;
else
ms -= r & 0x3f;
return ms;
}
static uint32_t dhcp_rebind_renew_retry_time(uint64_t start_t, uint32_t expiry)
{
uint64_t now = l_time_now();
uint32_t relative_now;
uint32_t retry_time;
/*
* RFC 2131, Section 4.4.5:
* " In both RENEWING and REBINDING states, if the client receives no
* response to its DHCPREQUEST message, the client SHOULD wait one-half
* of the remaining time until T2 (in RENEWING state) and one-half of
* the remaining lease time (in REBINDING state), down to a minimum of
* 60 seconds, before retransmitting the DHCPREQUEST message.
*/
relative_now = l_time_to_secs(now - start_t);
retry_time = (expiry - relative_now) / 2;
if (retry_time < 60)
retry_time = 60;
return retry_time;
}
static int client_message_init(struct l_dhcp_client *client,
struct dhcp_message *message,
uint8_t type,
uint8_t **opt, size_t *optlen)
{
int err;
uint16_t max_size;
err = dhcp_message_init(message, DHCP_OP_CODE_BOOTREQUEST,
type, client->xid, opt, optlen);
if (err < 0)
return err;
dhcp_message_set_address_type(message, client->addr_type,
client->addr_len);
/*
* RFC2132 section 4.1.1:
* The client MUST include its hardware address in the ’chaddr’ field,
* if necessary for delivery of DHCP reply messages. Non-Ethernet
* interfaces will leave 'chaddr' empty and use the client identifier
* instead
*/
if (client->addr_type == ARPHRD_ETHER)
memcpy(message->chaddr, &client->addr, client->addr_len);
/*
* Althrough RFC 2131 says that secs should be initialized to 0,
* some servers refuse to give us a lease unless we set this to a
* non-zero value
*/
message->secs = L_CPU_TO_BE16(dhcp_attempt_secs(client->start_t));
err = dhcp_append_prl(client->request_options, opt, optlen);
if (err < 0)
return err;
/*
* Set the maximum DHCP message size to the minimum legal value. This
* helps some buggy DHCP servers to not send bigger packets
*/
max_size = L_CPU_TO_BE16(576);
err = _dhcp_option_append(opt, optlen,
DHCP_OPTION_MAXIMUM_MESSAGE_SIZE,
2, &max_size);
if (err < 0)
return err;
return 0;
}
static void dhcp_client_event_notify(struct l_dhcp_client *client,
enum l_dhcp_client_event event)
{
if (client->event_handler)
client->event_handler(client, event, client->event_data);
}
static int dhcp_client_send_discover(struct l_dhcp_client *client)
{
uint8_t *opt;
size_t optlen = DHCP_MIN_OPTIONS_SIZE;
size_t len = sizeof(struct dhcp_message) + optlen;
L_AUTO_FREE_VAR(struct dhcp_message *, discover);
int err;
CLIENT_DEBUG("");
discover = (struct dhcp_message *) l_new(uint8_t, len);
err = client_message_init(client, discover,
DHCP_MESSAGE_TYPE_DISCOVER,
&opt, &optlen);
if (err < 0)
return err;
if (client->hostname) {
err = _dhcp_option_append(&opt, &optlen,
L_DHCP_OPTION_HOST_NAME,
strlen(client->hostname),
client->hostname);
if (err < 0)
return err;
}
err = _dhcp_option_append(&opt, &optlen, DHCP_OPTION_END, 0, NULL);
if (err < 0)
return err;
len = dhcp_message_optimize(discover, opt);
return client->transport->broadcast(client->transport,
INADDR_ANY, DHCP_PORT_CLIENT,
INADDR_BROADCAST, DHCP_PORT_SERVER,
discover, len);
}
static int dhcp_client_send_request(struct l_dhcp_client *client)
{
uint8_t *opt;
size_t optlen = DHCP_MIN_OPTIONS_SIZE;
size_t len = sizeof(struct dhcp_message) + optlen;
L_AUTO_FREE_VAR(struct dhcp_message *, request);
int err;
CLIENT_DEBUG("");
request = (struct dhcp_message *) l_new(uint8_t, len);
err = client_message_init(client, request,
DHCP_MESSAGE_TYPE_REQUEST,
&opt, &optlen);
if (err < 0)
return err;
switch (client->state) {
case DHCP_STATE_REQUESTING:
/*
* RFC 2131, Section 4.3.2:
* "If the DHCPREQUEST message contains a 'server identifier'
* option, the message is in response to a DHCPOFFER message."
*
* and
*
* "DHCPREQUEST generated during SELECTING state:
* Client inserts the address of the selected server in
* 'server identifier', 'ciaddr' MUST be zero, 'requested IP
* address' MUST be filled in with the yiaddr value from the
* chosen DHCPOFFER."
*
* NOTE: 'SELECTING' is meant to be 'REQUESTING' in the RFC
*/
err = _dhcp_option_append(&opt, &optlen,
L_DHCP_OPTION_SERVER_IDENTIFIER,
4, &client->lease->server_address);
if (err < 0)
return err;
err = _dhcp_option_append(&opt, &optlen,
L_DHCP_OPTION_REQUESTED_IP_ADDRESS,
4, &client->lease->address);
if (err < 0)
return err;
break;
case DHCP_STATE_RENEWING:
case DHCP_STATE_REBINDING:
request->ciaddr = client->lease->address;
break;
case DHCP_STATE_INIT:
case DHCP_STATE_SELECTING:
case DHCP_STATE_INIT_REBOOT:
case DHCP_STATE_REBOOTING:
case DHCP_STATE_BOUND:
return -EINVAL;
}
if (client->hostname) {
err = _dhcp_option_append(&opt, &optlen,
L_DHCP_OPTION_HOST_NAME,
strlen(client->hostname),
client->hostname);
if (err < 0)
return err;
}
err = _dhcp_option_append(&opt, &optlen, DHCP_OPTION_END, 0, NULL);
if (err < 0)
return err;
len = dhcp_message_optimize(request, opt);
/*
* RFC2131, Section 4.1:
* "DHCP clients MUST use the IP address provided in the
* 'server identifier' option for any unicast requests to the DHCP
* server.
*/
if (client->state == DHCP_STATE_RENEWING) {
struct sockaddr_in si;
memset(&si, 0, sizeof(si));
si.sin_family = AF_INET;
si.sin_port = L_CPU_TO_BE16(DHCP_PORT_SERVER);
si.sin_addr.s_addr = client->lease->server_address;
return client->transport->send(client->transport,
&si, request, len);
}
return client->transport->broadcast(client->transport,
INADDR_ANY, DHCP_PORT_CLIENT,
INADDR_BROADCAST, DHCP_PORT_SERVER,
request, len);
}
static void dhcp_client_timeout_resend(struct l_timeout *timeout,
void *user_data)
{
struct l_dhcp_client *client = user_data;
unsigned int next_timeout = 0;
CLIENT_DEBUG("");
switch (client->state) {
case DHCP_STATE_SELECTING:
if (dhcp_client_send_discover(client) < 0)
goto error;
break;
case DHCP_STATE_RENEWING:
case DHCP_STATE_REQUESTING:
case DHCP_STATE_REBINDING:
if (dhcp_client_send_request(client) < 0)
goto error;
break;
case DHCP_STATE_INIT:
case DHCP_STATE_INIT_REBOOT:
case DHCP_STATE_REBOOTING:
case DHCP_STATE_BOUND:
break;
}
switch (client->state) {
case DHCP_STATE_RENEWING:
next_timeout = dhcp_rebind_renew_retry_time(client->start_t,
client->lease->t2);
break;
case DHCP_STATE_REBINDING:
next_timeout = dhcp_rebind_renew_retry_time(client->start_t,
client->lease->lifetime);
break;
case DHCP_STATE_REQUESTING:
case DHCP_STATE_SELECTING:
/*
* RFC 2131 Section 4.1:
* "The retransmission delay SHOULD be doubled with subsequent
* retransmissions up to a maximum of 64 seconds.
*/
client->attempt += 1;
next_timeout = minsize(2 << client->attempt, 64);
break;
case DHCP_STATE_INIT:
case DHCP_STATE_INIT_REBOOT:
case DHCP_STATE_REBOOTING:
case DHCP_STATE_BOUND:
break;
}
if (next_timeout)
l_timeout_modify_ms(timeout, dhcp_fuzz_secs(next_timeout));
return;
error:
l_dhcp_client_stop(client);
}
static void dhcp_client_lease_expired(struct l_timeout *timeout,
void *user_data)
{
struct l_dhcp_client *client = user_data;
CLIENT_DEBUG("");
l_dhcp_client_stop(client);
dhcp_client_event_notify(client, L_DHCP_CLIENT_EVENT_NO_LEASE);
}
static void dhcp_client_t2_expired(struct l_timeout *timeout, void *user_data)
{
struct l_dhcp_client *client = user_data;
uint32_t next_timeout = client->lease->lifetime - client->lease->t2;
CLIENT_DEBUG("");
/*
* If we got here, then resend_timeout is active, with a timeout
* set originally for ~60 seconds. So we simply set the new state
* and wait for the timer to fire
*/
CLIENT_ENTER_STATE(DHCP_STATE_REBINDING);
l_timeout_modify_ms(client->timeout_lease,
dhcp_fuzz_secs(next_timeout));
l_timeout_set_callback(client->timeout_lease,
dhcp_client_lease_expired, client, NULL);
}
static void dhcp_client_t1_expired(struct l_timeout *timeout, void *user_data)
{
struct l_dhcp_client *client = user_data;
uint32_t next_timeout;
CLIENT_DEBUG("");
CLIENT_ENTER_STATE(DHCP_STATE_RENEWING);
client->attempt = 1;
if (dhcp_client_send_request(client) < 0)
goto error;
next_timeout = client->lease->t2 - client->lease->t1;
l_timeout_modify_ms(client->timeout_lease,
dhcp_fuzz_secs(next_timeout));
l_timeout_set_callback(client->timeout_lease, dhcp_client_t2_expired,
client, NULL);
next_timeout = dhcp_rebind_renew_retry_time(client->start_t,
client->lease->t2);
client->timeout_resend =
l_timeout_create_ms(dhcp_fuzz_secs(next_timeout),
dhcp_client_timeout_resend,
client, NULL);
return;
error:
l_dhcp_client_stop(client);
}
static int dhcp_client_receive_ack(struct l_dhcp_client *client,
const struct dhcp_message *ack,
size_t len)
{
struct dhcp_message_iter iter;
struct l_dhcp_lease *lease;
int r;
CLIENT_DEBUG("");
if (ack->yiaddr == 0)
return -ENOMSG;
if (!_dhcp_message_iter_init(&iter, ack, len))
return -EINVAL;
lease = _dhcp_lease_parse_options(&iter);
if (!lease) {
CLIENT_DEBUG("Failed to parse DHCP options.");
return -ENOMSG;
}
lease->address = ack->yiaddr;
r = L_DHCP_CLIENT_EVENT_LEASE_RENEWED;
if (client->lease) {
if (client->lease->subnet_mask != lease->subnet_mask ||
client->lease->address != lease->address ||
client->lease->router != lease->router)
r = L_DHCP_CLIENT_EVENT_IP_CHANGED;
_dhcp_lease_free(client->lease);
}
client->lease = lease;
/* In case this is an initial request, override to LEASE_OBTAINED */
if (client->state == DHCP_STATE_REQUESTING ||
client->state == DHCP_STATE_REBOOTING)
r = L_DHCP_CLIENT_EVENT_LEASE_OBTAINED;
return r;
}
static int dhcp_client_receive_offer(struct l_dhcp_client *client,
const struct dhcp_message *offer,
size_t len)
{
struct dhcp_message_iter iter;
CLIENT_DEBUG("");
if (offer->yiaddr == 0)
return -ENOMSG;
if (!_dhcp_message_iter_init(&iter, offer, len))
return -EINVAL;
client->lease = _dhcp_lease_parse_options(&iter);
if (!client->lease)
return -ENOMSG;
client->lease->address = offer->yiaddr;
return 0;
}
static void dhcp_client_rx_message(const void *data, size_t len, void *userdata)
{
struct l_dhcp_client *client = userdata;
const struct dhcp_message *message = data;
struct dhcp_message_iter iter;
uint8_t msg_type = 0;
uint8_t t, l;
const void *v;
int r;
CLIENT_DEBUG("");
if (len < sizeof(struct dhcp_message))
return;
if (message->op != DHCP_OP_CODE_BOOTREPLY)
return;
if (L_BE32_TO_CPU(message->xid) != client->xid)
return;
if (memcmp(message->chaddr, client->addr, client->addr_len))
return;
if (!_dhcp_message_iter_init(&iter, message, len))
return;
while (_dhcp_message_iter_next(&iter, &t, &l, &v) && !msg_type) {
switch (t) {
case DHCP_OPTION_MESSAGE_TYPE:
if (l == 1)
msg_type = l_get_u8(v);
break;
}
}
switch (client->state) {
case DHCP_STATE_INIT:
return;
case DHCP_STATE_SELECTING:
if (msg_type != DHCP_MESSAGE_TYPE_OFFER)
return;
if (dhcp_client_receive_offer(client, message, len) < 0)
return;
CLIENT_ENTER_STATE(DHCP_STATE_REQUESTING);
client->attempt = 1;
if (dhcp_client_send_request(client) < 0) {
l_dhcp_client_stop(client);
return;
}
l_timeout_modify_ms(client->timeout_resend, dhcp_fuzz_secs(4));
break;
case DHCP_STATE_REQUESTING:
case DHCP_STATE_RENEWING:
case DHCP_STATE_REBINDING:
if (msg_type == DHCP_MESSAGE_TYPE_NAK) {
l_dhcp_client_stop(client);
dhcp_client_event_notify(client,
L_DHCP_CLIENT_EVENT_NO_LEASE);
return;
}
if (msg_type != DHCP_MESSAGE_TYPE_ACK)
return;
r = dhcp_client_receive_ack(client, message, len);
if (r < 0)
return;
CLIENT_ENTER_STATE(DHCP_STATE_BOUND);
l_timeout_remove(client->timeout_resend);
client->timeout_resend = NULL;
if (client->transport->bind)
client->transport->bind(client->transport,
client->lease->address);
dhcp_client_event_notify(client, r);
/*
* Start T1, once it expires we will start the T2 timer. If
* we renew the lease, we will end up back here.
*
* RFC2131, Section 4.4.5 states:
* "Times T1 and T2 SHOULD be chosen with some random "fuzz"
* around a fixed value, to avoid synchronization of client
* reacquisition."
*/
l_timeout_remove(client->timeout_lease);
/* Infinite lease, no need to start t1 */
if (client->lease->lifetime != 0xffffffffu) {
uint32_t next_timeout =
dhcp_fuzz_secs(client->lease->t1);
client->timeout_lease =
l_timeout_create_ms(next_timeout,
dhcp_client_t1_expired,
client, NULL);
}
break;
case DHCP_STATE_INIT_REBOOT:
case DHCP_STATE_REBOOTING:
case DHCP_STATE_BOUND:
break;
}
}
LIB_EXPORT struct l_dhcp_client *l_dhcp_client_new(uint32_t ifindex)
{
struct l_dhcp_client *client;
client = l_new(struct l_dhcp_client, 1);
client->state = DHCP_STATE_INIT;
client->ifindex = ifindex;
/* Enable these options by default */
dhcp_enable_option(client, L_DHCP_OPTION_SUBNET_MASK);
dhcp_enable_option(client, L_DHCP_OPTION_ROUTER);
dhcp_enable_option(client, L_DHCP_OPTION_HOST_NAME);
dhcp_enable_option(client, L_DHCP_OPTION_DOMAIN_NAME);
dhcp_enable_option(client, L_DHCP_OPTION_DOMAIN_NAME_SERVER);
dhcp_enable_option(client, L_DHCP_OPTION_NTP_SERVERS);
return client;
}
LIB_EXPORT void l_dhcp_client_destroy(struct l_dhcp_client *client)
{
if (unlikely(!client))
return;
l_dhcp_client_stop(client);
if (client->event_destroy)
client->event_destroy(client->event_data);
_dhcp_transport_free(client->transport);
l_free(client->ifname);
l_free(client->hostname);
l_free(client);
}
LIB_EXPORT bool l_dhcp_client_add_request_option(struct l_dhcp_client *client,
uint8_t option)
{
if (unlikely(!client))
return false;
if (unlikely(client->state != DHCP_STATE_INIT))
return false;
switch (option) {
case DHCP_OPTION_PAD:
case DHCP_OPTION_END:
case DHCP_OPTION_OVERLOAD:
case DHCP_OPTION_MESSAGE_TYPE:
case DHCP_OPTION_PARAMETER_REQUEST_LIST:
return false;
}
dhcp_enable_option(client, option);
return true;
}
LIB_EXPORT bool l_dhcp_client_set_address(struct l_dhcp_client *client,
uint8_t type,
const uint8_t *addr,
size_t addr_len)
{
if (unlikely(!client))
return false;
switch (type) {
case ARPHRD_ETHER:
if (addr_len != ETH_ALEN)
return false;
break;
default:
return false;
}
client->addr_len = addr_len;
memcpy(client->addr, addr, addr_len);
client->addr_type = type;
client->have_addr = true;
return true;
}
LIB_EXPORT bool l_dhcp_client_set_interface_name(struct l_dhcp_client *client,
const char *ifname)
{
if (unlikely(!client))
return false;
if (unlikely(client->state != DHCP_STATE_INIT))
return false;
l_free(client->ifname);
client->ifname = l_strdup(ifname);
return true;
}
LIB_EXPORT bool l_dhcp_client_set_hostname(struct l_dhcp_client *client,
const char *hostname)
{
if (unlikely(!client))
return false;
if (unlikely(client->state != DHCP_STATE_INIT))
return false;
if (!hostname)
goto done;
if (client->hostname && !strcmp(client->hostname, hostname))
return true;
done:
l_free(client->hostname);
client->hostname = l_strdup(hostname);
return true;
}
bool _dhcp_client_set_transport(struct l_dhcp_client *client,
struct dhcp_transport *transport)
{
if (unlikely(!client))
return false;
if (unlikely(client->state != DHCP_STATE_INIT))
return false;
if (client->transport)
_dhcp_transport_free(client->transport);
client->transport = transport;
return true;
}
void _dhcp_client_override_xid(struct l_dhcp_client *client, uint32_t xid)
{
client->override_xid = true;
client->xid = xid;
}
LIB_EXPORT const struct l_dhcp_lease *l_dhcp_client_get_lease(
const struct l_dhcp_client *client)
{
if (unlikely(!client))
return NULL;
return client->lease;
}
LIB_EXPORT bool l_dhcp_client_start(struct l_dhcp_client *client)
{
int err;
if (unlikely(!client))
return false;
if (unlikely(client->state != DHCP_STATE_INIT))
return false;
if (!client->have_addr) {
uint8_t mac[6];
if (!l_net_get_mac_address(client->ifindex, mac))
return false;
l_dhcp_client_set_address(client, ARPHRD_ETHER, mac, 6);
}
if (!client->ifname) {
client->ifname = l_net_get_name(client->ifindex);
if (!client->ifname)
return false;
}
if (!client->transport) {
client->transport =
_dhcp_default_transport_new(client->ifindex,
client->ifname,
DHCP_PORT_CLIENT);
if (!client->transport)
return false;
}
if (!client->override_xid)
l_getrandom(&client->xid, sizeof(client->xid));
if (client->transport->open)
if (client->transport->open(client->transport,
client->xid) < 0)
return false;
_dhcp_transport_set_rx_callback(client->transport,
dhcp_client_rx_message,
client);
client->start_t = l_time_now();
err = dhcp_client_send_discover(client);
if (err < 0)
return false;
client->timeout_resend = l_timeout_create_ms(dhcp_fuzz_msecs(600),
dhcp_client_timeout_resend,
client, NULL);
CLIENT_ENTER_STATE(DHCP_STATE_SELECTING);
client->attempt = 1;
return true;
}
LIB_EXPORT bool l_dhcp_client_stop(struct l_dhcp_client *client)
{
if (unlikely(!client))
return false;
l_timeout_remove(client->timeout_resend);
client->timeout_resend = NULL;
l_timeout_remove(client->timeout_lease);
client->timeout_lease = NULL;
if (client->transport && client->transport->close)
client->transport->close(client->transport);
client->start_t = 0;
CLIENT_ENTER_STATE(DHCP_STATE_INIT);
_dhcp_lease_free(client->lease);
client->lease = NULL;
return true;
}
LIB_EXPORT bool l_dhcp_client_set_event_handler(struct l_dhcp_client *client,
l_dhcp_client_event_cb_t handler,
void *userdata,
l_dhcp_destroy_cb_t destroy)
{
if (unlikely(!client))
return false;
if (client->event_destroy)
client->event_destroy(client->event_data);
client->event_handler = handler;
client->event_data = userdata;
client->event_destroy = destroy;
return true;
}
LIB_EXPORT bool l_dhcp_client_set_debug(struct l_dhcp_client *client,
l_dhcp_debug_cb_t function,
void *user_data,
l_dhcp_destroy_cb_t destroy)
{
if (unlikely(!client))
return false;
if (client->debug_destroy)
client->debug_destroy(client->debug_data);
client->debug_handler = function;
client->debug_destroy = destroy;
client->debug_data = user_data;
return true;
}