/***
This file is part of systemd.
Copyright (C) 2013 Intel Corporation. All rights reserved.
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.
systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <errno.h>
#include <net/ethernet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <linux/if_infiniband.h>
#include "sd-dhcp-client.h"
#include "alloc-util.h"
#include "async.h"
#include "dhcp-identifier.h"
#include "dhcp-internal.h"
#include "dhcp-lease-internal.h"
#include "dhcp-protocol.h"
#include "dns-domain.h"
#include "hostname-util.h"
#include "random-util.h"
#include "string-util.h"
#include "util.h"
struct sd_dhcp_client {
unsigned n_ref;
int event_priority;
int index;
int fd;
bool request_broadcast;
struct {
union {
struct {
/* 0: Generic (non-LL) (RFC 2132) */
struct {
/* 1: Ethernet Link-Layer (RFC 2132) */
struct {
/* 2 - 254: ARP/Link-Layer (RFC 2132) */
struct {
/* 255: Node-specific (RFC 4361) */
struct {
};
char *hostname;
char *vendor_class_identifier;
unsigned int attempt;
void *userdata;
};
};
void *userdata) {
return 0;
}
return 0;
}
size_t i;
DHCP_STATE_STOPPED), -EBUSY);
switch(option) {
case SD_DHCP_OPTION_PAD:
case SD_DHCP_OPTION_OVERLOAD:
case SD_DHCP_OPTION_END:
return -EINVAL;
default:
break;
}
for (i = 0; i < client->req_opts_size; i++)
return -EEXIST;
return -ENOMEM;
return 0;
}
DHCP_STATE_STOPPED), -EBUSY);
if (last_addr)
else
return 0;
}
DHCP_STATE_STOPPED), -EBUSY);
return 0;
}
bool need_restart = false;
if (arp_type == ARPHRD_ETHER)
else if (arp_type == ARPHRD_INFINIBAND)
else
return -EINVAL;
return 0;
"client, restarting");
need_restart = true;
}
return 0;
}
*type = 0;
*data_len = 0;
if (client->client_id_len) {
}
return 0;
}
bool need_restart = false;
switch (type) {
case ARPHRD_ETHER:
return -EINVAL;
break;
case ARPHRD_INFINIBAND:
if (data_len != INFINIBAND_ALEN)
return -EINVAL;
break;
default:
break;
}
return 0;
"client, restarting");
need_restart = true;
}
return 0;
}
const char *hostname) {
return -EINVAL;
return 0;
if (hostname) {
if (!new_hostname)
return -ENOMEM;
}
return 0;
}
const char *vci) {
if (!new_vci)
return -ENOMEM;
return 0;
}
return 0;
}
return -EADDRNOTAVAIL;
return 0;
}
}
return 0;
}
if (error < 0)
else if (error == SD_DHCP_CLIENT_EVENT_STOP)
else
}
int r;
if (!packet)
return -ENOMEM;
if (r < 0)
return r;
/* Although 'secs' field is a SHOULD in RFC 2131, certain DHCP servers
refuse to issue an DHCP lease if 'secs' is set to zero */
if (r < 0)
return r;
/* seconds between sending first and last DISCOVER
* must always be strictly positive to deal with broken servers */
/* RFC2132 section 4.1
A client that cannot receive unicast IP datagrams until its protocol
software has been configured with an IP address SHOULD set the
BROADCAST bit in the 'flags' field to 1 in any DHCPDISCOVER or
DHCPREQUEST messages that client sends. The BROADCAST bit will
provide a hint to the DHCP server and BOOTP relay agent to broadcast
any messages to the client on the client's subnet.
Note: some interfaces needs this to be enabled, but some networks
needs this to be disabled as broadcasts are filteretd, so this
needs to be configurable */
/* 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 (eg, RFC 4390 section 2.1).
*/
/* If no client identifier exists, construct an RFC 4361-compliant one */
if (client->client_id_len == 0) {
r = dhcp_identifier_set_iaid(client->index, client->mac_addr, client->mac_addr_len, &client->client_id.ns.iaid);
if (r < 0)
return r;
if (r < 0)
return r;
client->client_id_len = sizeof(client->client_id.type) + sizeof(client->client_id.ns.iaid) + duid_len;
}
/* Some DHCP servers will refuse to issue an DHCP lease if the Client
Identifier option is not set */
if (client->client_id_len) {
if (r < 0)
return r;
}
/* RFC2131 section 3.5:
in its initial DHCPDISCOVER or DHCPREQUEST message, a
client may provide the server with a list of specific
parameters the client is interested in. If the client
includes a list of parameters in a DHCPDISCOVER message,
it MUST include that list in any subsequent DHCPREQUEST
messages.
*/
if (r < 0)
return r;
/* RFC2131 section 3.5:
The client SHOULD include the ’maximum DHCP message size’ option to
let the server know how large the server may make its DHCP messages.
Note (from ConnMan): Some DHCP servers will send bigger DHCP packets
than the defined default size unless the Maximum Messge Size option
is explicitly set
RFC3442 "Requirements to Avoid Sizing Constraints":
Because a full routing table can be quite large, the standard 576
octet maximum size for a DHCP message may be too short to contain
some legitimate Classless Static Route options. Because of this,
clients implementing the Classless Static Route option SHOULD send a
stack is capable of receiving larger IP datagrams. In this case, the
client SHOULD set the value of this option to at least the MTU of the
interface that the client is configuring. The client MAY set the
value of this option higher, up to the size of the largest UDP packet
it is prepared to accept. (Note that the value specified in the
Maximum DHCP Message Size option is the total maximum packet size,
including IP and UDP headers.)
*/
2, &max_size);
if (r < 0)
return r;
*_optoffset = optoffset;
return 0;
}
const char *fqdn) {
int r;
DHCP_FQDN_FLAG_E; /* Canonical wire format */
if (r > 0)
return r;
}
}
int r;
if (r < 0)
return r;
/* the client may suggest values for the network address
and lease time in the DHCPDISCOVER message. The client may include
the ’requested IP address’ option to suggest that a particular IP
address be assigned, and may include the ’IP address lease time’
option to suggest the lease time it would like.
*/
if (r < 0)
return r;
}
/* According to RFC 4702 "clients that send the Client FQDN option in
their messages MUST NOT also send the Host Name option". Just send
one of the two depending on the hostname type.
*/
/* it is unclear from RFC 2131 if client should send hostname in
DHCPDISCOVER but dhclient does and so we do as well
*/
} else
if (r < 0)
return r;
}
if (client->vendor_class_identifier) {
if (r < 0)
return r;
}
SD_DHCP_OPTION_END, 0, NULL);
if (r < 0)
return r;
/* We currently ignore:
The client SHOULD wait a random time between one and ten seconds to
desynchronize the use of DHCP at startup.
*/
if (r < 0)
return r;
return 0;
}
int r;
if (r < 0)
return r;
/* See RFC2131 section 4.3.2 (note that there is a typo in the RFC,
SELECTING should be REQUESTING)
*/
case DHCP_STATE_REQUESTING:
/* 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.
*/
if (r < 0)
return r;
if (r < 0)
return r;
break;
case DHCP_STATE_INIT_REBOOT:
/* ’server identifier’ MUST NOT be filled in, ’requested IP address’
option MUST be filled in with client’s notion of its previously
assigned address. ’ciaddr’ MUST be zero.
*/
if (r < 0)
return r;
break;
case DHCP_STATE_RENEWING:
/* ’server identifier’ MUST NOT be filled in, ’requested IP address’
option MUST NOT be filled in, ’ciaddr’ MUST be filled in with
client’s IP address.
*/
/* fall through */
case DHCP_STATE_REBINDING:
/* ’server identifier’ MUST NOT be filled in, ’requested IP address’
option MUST NOT be filled in, ’ciaddr’ MUST be filled in with
client’s IP address.
This message MUST be broadcast to the 0xffffffff IP broadcast address.
*/
break;
case DHCP_STATE_INIT:
case DHCP_STATE_SELECTING:
case DHCP_STATE_REBOOTING:
case DHCP_STATE_BOUND:
case DHCP_STATE_STOPPED:
return -EINVAL;
}
else
if (r < 0)
return r;
}
SD_DHCP_OPTION_END, 0, NULL);
if (r < 0)
return r;
sizeof(DHCPMessage) + optoffset);
} else {
}
if (r < 0)
return r;
case DHCP_STATE_REQUESTING:
break;
case DHCP_STATE_INIT_REBOOT:
break;
case DHCP_STATE_RENEWING:
break;
case DHCP_STATE_REBINDING:
break;
default:
break;
}
return 0;
}
void *userdata) {
int r;
assert(s);
if (r < 0)
goto error;
case DHCP_STATE_RENEWING:
if (time_left < 60)
time_left = 60;
break;
case DHCP_STATE_REBINDING:
if (time_left < 60)
time_left = 60;
break;
case DHCP_STATE_REBOOTING:
/* start over as we did not receive a timely ack or nak */
r = client_initialize(client);
if (r < 0)
goto error;
r = client_start(client);
if (r < 0)
goto error;
else {
return 0;
}
case DHCP_STATE_INIT:
case DHCP_STATE_INIT_REBOOT:
case DHCP_STATE_SELECTING:
case DHCP_STATE_REQUESTING:
case DHCP_STATE_BOUND:
break;
case DHCP_STATE_STOPPED:
r = -EINVAL;
goto error;
}
if (r < 0)
goto error;
if (r < 0)
goto error;
if (r < 0)
goto error;
case DHCP_STATE_INIT:
r = client_send_discover(client);
if (r >= 0) {
} else {
goto error;
}
break;
case DHCP_STATE_SELECTING:
r = client_send_discover(client);
goto error;
break;
case DHCP_STATE_INIT_REBOOT:
case DHCP_STATE_REQUESTING:
case DHCP_STATE_RENEWING:
case DHCP_STATE_REBINDING:
r = client_send_request(client);
goto error;
break;
case DHCP_STATE_REBOOTING:
case DHCP_STATE_BOUND:
break;
case DHCP_STATE_STOPPED:
r = -EINVAL;
goto error;
}
return 0;
client_stop(client, r);
/* Errors were dealt with when stopping the client, don't spill
errors into the event loop handler */
return 0;
}
int r;
client);
if (r < 0)
goto error;
if (r < 0)
goto error;
if (r < 0)
goto error;
if (r < 0)
client_stop(client, r);
return 0;
}
int r;
if (client->start_delay) {
}
usec, 0,
if (r < 0)
goto error;
if (r < 0)
goto error;
if (r < 0)
goto error;
if (r < 0)
client_stop(client, r);
return 0;
}
return 0;
}
int r;
if (r < 0) {
client_stop(client, r);
return r;
}
}
client->start_delay = 0;
return client_start_delayed(client);
}
void *userdata) {
/* lease was lost, start over if not freed or stopped in callback */
}
return 0;
}
int r;
if (r < 0) {
client_stop(client, r);
return 0;
}
}
void *userdata) {
return client_initialize_time_events(client);
}
int r;
r = dhcp_lease_new(&lease);
if (r < 0)
return r;
if (client->client_id_len) {
if (r < 0)
return r;
}
if (r != DHCP_OFFER) {
return -ENOMSG;
}
lease->server_address == 0 ||
log_dhcp_client(client, "received lease lacks address, server address or lease lifetime, ignoring");
return -ENOMSG;
}
if (!lease->have_subnet_mask) {
if (r < 0) {
"mask, and a fallback one can not be "
"generated, ignoring");
return -ENOMSG;
}
}
return 0;
}
int r;
if (r != DHCP_FORCERENEW)
return -ENOMSG;
return 0;
}
int r;
r = dhcp_lease_new(&lease);
if (r < 0)
return r;
if (client->client_id_len) {
if (r < 0)
return r;
}
if (r == DHCP_NAK) {
return -EADDRNOTAVAIL;
}
if (r != DHCP_ACK) {
return -ENOMSG;
}
"address or lease lifetime, ignoring");
return -ENOMSG;
}
if (r < 0) {
"mask, and a fallback one can not be "
"generated, ignoring");
return -ENOMSG;
}
}
} else
}
return r;
}
if (lifetime > 3)
lifetime -= 3;
else
lifetime = 0;
+ (random_u32() & 0x1fffff);
}
int r;
/* don't set timers for infinite leases */
return 0;
if (r < 0)
return r;
/* convert the various timeouts from relative (secs) to absolute (usecs) */
/* both T1 and T2 are given */
/* they are both valid */
} else {
/* discard both */
}
/* only T2 is given, and it is valid */
if (t2_timeout <= t1_timeout) {
/* the computed T1 would be invalid, so discard T2 */
}
/* only T1 is given, and it is valid */
if (t2_timeout <= t1_timeout) {
/* the computed T2 would be invalid, so discard T1 */
}
} else {
/* fall back to the default timeouts */
}
/* arm lifetime timeout */
if (r < 0)
return r;
if (r < 0)
return r;
if (r < 0)
return r;
/* don't arm earlier timeouts if this has already expired */
if (lifetime_timeout <= time_now)
return 0;
/* arm T2 timeout */
&client->timeout_t2,
10 * USEC_PER_MSEC,
if (r < 0)
return r;
if (r < 0)
return r;
if (r < 0)
return r;
/* don't arm earlier timeout if this has already expired */
if (t2_timeout <= time_now)
return 0;
/* arm T1 timeout */
&client->timeout_t1,
if (r < 0)
return r;
if (r < 0)
return r;
if (r < 0)
return r;
return 0;
}
int len) {
int r = 0, notify_event = 0;
case DHCP_STATE_SELECTING:
if (r >= 0) {
0, 0,
if (r < 0)
goto error;
if (r < 0)
goto error;
if (r < 0)
goto error;
} else if (r == -ENOMSG)
/* invalid message, let's ignore it */
return 0;
break;
case DHCP_STATE_REBOOTING:
case DHCP_STATE_REQUESTING:
case DHCP_STATE_RENEWING:
case DHCP_STATE_REBINDING:
if (r >= 0) {
client->start_delay = 0;
else if (r != SD_DHCP_CLIENT_EVENT_IP_ACQUIRE)
notify_event = r;
if (r < 0) {
goto error;
}
if (r < 0) {
goto error;
}
if (notify_event) {
return 0;
}
} else if (r == -EADDRNOTAVAIL) {
/* got a NAK, let's restart the client */
r = client_initialize(client);
if (r < 0)
goto error;
r = client_start_delayed(client);
if (r < 0)
goto error;
return 0;
} else if (r == -ENOMSG)
/* invalid message, let's ignore it */
return 0;
break;
case DHCP_STATE_BOUND:
if (r >= 0) {
if (r < 0)
goto error;
} else if (r == -ENOMSG)
/* invalid message, let's ignore it */
return 0;
break;
case DHCP_STATE_INIT:
case DHCP_STATE_INIT_REBOOT:
break;
case DHCP_STATE_STOPPED:
r = -EINVAL;
goto error;
}
if (r < 0)
client_stop(client, r);
return r;
}
assert(s);
if (r < 0)
return -errno;
else if (buflen < 0)
/* this can't be right */
return -EIO;
if (!message)
return -ENOMEM;
if (len < 0) {
return 0;
return -errno;
return 0;
}
return 0;
}
return 0;
}
return 0;
}
} else {
/* Non-Ethernet links expect zero chaddr */
expected_hlen = 0;
}
return 0;
}
return 0;
}
/* in BOUND state, we may receive FORCERENEW with xid set by server,
so ignore the xid in this case */
return 0;
}
}
.msg_iovlen = 1,
.msg_control = cmsgbuf,
.msg_controllen = sizeof(cmsgbuf),
};
bool checksum = true;
assert(s);
if (r < 0)
return -errno;
else if (buflen < 0)
/* this can't be right */
return -EIO;
if (!packet)
return -ENOMEM;
if (len < 0) {
return 0;
return -errno;
return 0;
break;
}
}
if (r < 0)
return 0;
len -= DHCP_IP_UDP_SIZE;
}
int r;
r = client_initialize(client);
if (r < 0)
return r;
r = client_start(client);
if (r >= 0)
return r;
}
return 0;
}
int priority) {
int r;
if (event)
else {
if (r < 0)
return 0;
}
return 0;
}
return 0;
}
if (!client)
return NULL;
}
if (!client)
return NULL;
return client;
}
if (!client)
return NULL;
return NULL;
return NULL;
}
if (!client)
return -ENOMEM;
return -ENOMEM;
return 0;
}