+/* SPDX-License-Identifier: LGPL-2.1+ */
/***
- This file is part of systemd.
-
- Copyright (C) 2013 Intel Corporation. All rights reserved.
-
- systemd 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.
-
- 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/>.
+ Copyright © 2013 Intel Corporation. All rights reserved.
***/
#include <errno.h>
#include "random-util.h"
#include "string-util.h"
#include "util.h"
+#include "strv.h"
#define MAX_CLIENT_ID_LEN (sizeof(uint32_t) + MAX_DUID_LEN) /* Arbitrary limit */
#define MAX_MAC_ADDR_LEN CONST_MAX(INFINIBAND_ALEN, ETH_ALEN)
size_t client_id_len;
char *hostname;
char *vendor_class_identifier;
+ char **user_class;
uint32_t mtu;
uint32_t xid;
usec_t start_time;
* without further modification. Otherwise, if duid_type is supported, DUID
* is set based on that type. Otherwise, an error is returned.
*/
-int sd_dhcp_client_set_iaid_duid(
+static int dhcp_client_set_iaid_duid_internal(
sd_dhcp_client *client,
uint32_t iaid,
+ bool append_iaid,
uint16_t duid_type,
const void *duid,
- size_t duid_len) {
+ size_t duid_len,
+ usec_t llt_time) {
DHCP_CLIENT_DONT_DESTROY(client);
int r;
zero(client->client_id);
client->client_id.type = 255;
- /* If IAID is not configured, generate it. */
- if (iaid == 0) {
- r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr,
- client->mac_addr_len,
- &client->client_id.ns.iaid);
- if (r < 0)
- return r;
- } else
- client->client_id.ns.iaid = htobe32(iaid);
+ if (append_iaid) {
+ /* If IAID is not configured, generate it. */
+ if (iaid == 0) {
+ r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr,
+ client->mac_addr_len,
+ &client->client_id.ns.iaid);
+ if (r < 0)
+ return r;
+ } else
+ client->client_id.ns.iaid = htobe32(iaid);
+ }
if (duid != NULL) {
client->client_id.ns.duid.type = htobe16(duid_type);
memcpy(&client->client_id.ns.duid.raw.data, duid, duid_len);
len = sizeof(client->client_id.ns.duid.type) + duid_len;
- } else if (duid_type == DUID_TYPE_EN) {
- r = dhcp_identifier_set_duid_en(&client->client_id.ns.duid, &len);
- if (r < 0)
- return r;
} else
- return -EOPNOTSUPP;
+ switch (duid_type) {
+ case DUID_TYPE_LLT:
+ if (!client->mac_addr || client->mac_addr_len == 0)
+ return -EOPNOTSUPP;
+
+ r = dhcp_identifier_set_duid_llt(&client->client_id.ns.duid, llt_time, client->mac_addr, client->mac_addr_len, client->arp_type, &len);
+ if (r < 0)
+ return r;
+ break;
+ case DUID_TYPE_EN:
+ r = dhcp_identifier_set_duid_en(&client->client_id.ns.duid, &len);
+ if (r < 0)
+ return r;
+ break;
+ case DUID_TYPE_LL:
+ if (!client->mac_addr || client->mac_addr_len == 0)
+ return -EOPNOTSUPP;
+
+ r = dhcp_identifier_set_duid_ll(&client->client_id.ns.duid, client->mac_addr, client->mac_addr_len, client->arp_type, &len);
+ if (r < 0)
+ return r;
+ break;
+ case DUID_TYPE_UUID:
+ r = dhcp_identifier_set_duid_uuid(&client->client_id.ns.duid, &len);
+ if (r < 0)
+ return r;
+ break;
+ default:
+ return -EINVAL;
+ }
client->client_id_len = sizeof(client->client_id.type) + len +
- sizeof(client->client_id.ns.iaid);
+ (append_iaid ? sizeof(client->client_id.ns.iaid) : 0);
if (!IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED)) {
- log_dhcp_client(client, "Configured IAID+DUID, restarting.");
+ log_dhcp_client(client, "Configured %sDUID, restarting.", append_iaid ? "IAID+" : "");
client_stop(client, SD_DHCP_CLIENT_EVENT_STOP);
sd_dhcp_client_start(client);
}
return 0;
}
+int sd_dhcp_client_set_iaid_duid(
+ sd_dhcp_client *client,
+ uint32_t iaid,
+ uint16_t duid_type,
+ const void *duid,
+ size_t duid_len) {
+ return dhcp_client_set_iaid_duid_internal(client, iaid, true, duid_type, duid, duid_len, 0);
+}
+
+int sd_dhcp_client_set_iaid_duid_llt(
+ sd_dhcp_client *client,
+ uint32_t iaid,
+ usec_t llt_time) {
+ return dhcp_client_set_iaid_duid_internal(client, iaid, true, DUID_TYPE_LLT, NULL, 0, llt_time);
+}
+
+int sd_dhcp_client_set_duid(
+ sd_dhcp_client *client,
+ uint16_t duid_type,
+ const void *duid,
+ size_t duid_len) {
+ return dhcp_client_set_iaid_duid_internal(client, 0, false, duid_type, duid, duid_len, 0);
+}
+
+int sd_dhcp_client_set_duid_llt(
+ sd_dhcp_client *client,
+ usec_t llt_time) {
+ return dhcp_client_set_iaid_duid_internal(client, 0, false, DUID_TYPE_LLT, NULL, 0, llt_time);
+}
+
int sd_dhcp_client_set_hostname(
sd_dhcp_client *client,
const char *hostname) {
assert_return(client, -EINVAL);
- /* Refuse hostnames that neither qualify as DNS nor as Linux hosntames */
+ /* Make sure hostnames qualify as DNS and as Linux hostnames */
if (hostname &&
- !(hostname_is_valid(hostname, false) || dns_name_is_valid(hostname) > 0))
+ !(hostname_is_valid(hostname, false) && dns_name_is_valid(hostname) > 0))
return -EINVAL;
return free_and_strdup(&client->hostname, hostname);
return free_and_strdup(&client->vendor_class_identifier, vci);
}
+int sd_dhcp_client_set_user_class(
+ sd_dhcp_client *client,
+ const char* const* user_class) {
+
+ _cleanup_strv_free_ char **s = NULL;
+ char **p;
+
+ STRV_FOREACH(p, (char **) user_class)
+ if (strlen(*p) > 255)
+ return -ENAMETOOLONG;
+
+ s = strv_copy((char **) user_class);
+ if (!s)
+ return -ENOMEM;
+
+ client->user_class = TAKE_PTR(s);
+
+ return 0;
+}
+
int sd_dhcp_client_set_client_port(
sd_dhcp_client *client,
uint16_t port) {
int sd_dhcp_client_get_lease(sd_dhcp_client *client, sd_dhcp_lease **ret) {
assert_return(client, -EINVAL);
- if (client->state != DHCP_STATE_BOUND &&
- client->state != DHCP_STATE_RENEWING &&
- client->state != DHCP_STATE_REBINDING)
+ if (!IN_SET(client->state, DHCP_STATE_BOUND, DHCP_STATE_RENEWING, DHCP_STATE_REBINDING))
return -EADDRNOTAVAIL;
if (ret)
assert(ret);
assert(_optlen);
assert(_optoffset);
- assert(type == DHCP_DISCOVER || type == DHCP_REQUEST);
+ assert(IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST));
optlen = DHCP_MIN_OPTIONS_SIZE;
size = sizeof(DHCPPacket) + optlen;
it MUST include that list in any subsequent DHCPREQUEST
messages.
*/
- r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0,
- SD_DHCP_OPTION_PARAMETER_REQUEST_LIST,
- client->req_opts_size, client->req_opts);
- if (r < 0)
- return r;
+
+ /* RFC7844 section 3:
+ MAY contain the Parameter Request List option. */
+ /* NOTE: in case that there would be an option to do not send
+ * any PRL at all, the size should be checked before sending */
+ if (client->req_opts_size > 0) {
+ r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0,
+ SD_DHCP_OPTION_PARAMETER_REQUEST_LIST,
+ client->req_opts_size, client->req_opts);
+ if (r < 0)
+ return r;
+ }
/* RFC2131 section 3.5:
The client SHOULD include the ’maximum DHCP message size’ option to
Maximum DHCP Message Size option is the total maximum packet size,
including IP and UDP headers.)
*/
- max_size = htobe16(size);
- r = dhcp_option_append(&packet->dhcp, client->mtu, &optoffset, 0,
- SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE,
- 2, &max_size);
- if (r < 0)
- return r;
+ /* RFC7844 section 3:
+ SHOULD NOT contain any other option. */
+ if (!client->anonymize) {
+ max_size = htobe16(size);
+ r = dhcp_option_append(&packet->dhcp, client->mtu, &optoffset, 0,
+ SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE,
+ 2, &max_size);
+ if (r < 0)
+ return r;
+ }
*_optlen = optlen;
*_optoffset = optoffset;
- *ret = packet;
- packet = NULL;
+ *ret = TAKE_PTR(packet);
return 0;
}
int r;
assert(client);
- assert(client->state == DHCP_STATE_INIT ||
- client->state == DHCP_STATE_SELECTING);
+ assert(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_SELECTING));
r = client_message_init(client, &discover, DHCP_DISCOVER,
&optlen, &optoffset);
return r;
}
+ if (client->user_class) {
+ r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
+ SD_DHCP_OPTION_USER_CLASS,
+ strv_length(client->user_class),
+ client->user_class);
+ if (r < 0)
+ return r;
+ }
+
r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
SD_DHCP_OPTION_END, 0, NULL);
if (r < 0)
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->fd = r;
- if (client->state == DHCP_STATE_INIT || client->state == DHCP_STATE_INIT_REBOOT)
+ if (IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_INIT_REBOOT))
client->start_time = now(clock_boottime_or_monotonic());
return client_initialize_events(client, client_receive_message_raw);
if (!lease->have_subnet_mask) {
r = dhcp_lease_set_default_subnet_mask(lease);
if (r < 0) {
- log_dhcp_client(client, "received lease lacks subnet "
- "mask, and a fallback one can not be "
- "generated, ignoring");
+ log_dhcp_client(client,
+ "received lease lacks subnet mask, "
+ "and a fallback one cannot be generated, ignoring");
return -ENOMSG;
}
}
sd_dhcp_lease_unref(client->lease);
- client->lease = lease;
- lease = NULL;
+ client->lease = TAKE_PTR(lease);
log_dhcp_client(client, "OFFER");
if (lease->subnet_mask == INADDR_ANY) {
r = dhcp_lease_set_default_subnet_mask(lease);
if (r < 0) {
- log_dhcp_client(client, "received lease lacks subnet "
- "mask, and a fallback one can not be "
- "generated, ignoring");
+ log_dhcp_client(client,
+ "received lease lacks subnet mask, "
+ "and a fallback one cannot be generated, ignoring");
return -ENOMSG;
}
}
client->lease = sd_dhcp_lease_unref(client->lease);
}
- client->lease = lease;
- lease = NULL;
+ client->lease = TAKE_PTR(lease);
log_dhcp_client(client, "ACK");
len = recv(fd, message, buflen, 0);
if (len < 0) {
- if (errno == EAGAIN || errno == EINTR)
+ if (IN_SET(errno, EAGAIN, EINTR))
return 0;
return log_dhcp_client_errno(client, errno,
len = recvmsg(fd, &msg, 0);
if (len < 0) {
- if (errno == EAGAIN || errno == EINTR)
+ if (IN_SET(errno, EAGAIN, EINTR))
return 0;
return log_dhcp_client_errno(client, errno,
if (r < 0)
return r;
- if (client->last_addr)
+ /* RFC7844 section 3.3:
+ SHOULD perform a complete four-way handshake, starting with a
+ DHCPDISCOVER, to obtain a new address lease. If the client can
+ ascertain that this is exactly the same network to which it was
+ previously connected, and if the link-layer address did not change,
+ the client MAY issue a DHCPREQUEST to try to reclaim the current
+ address. */
+ if (client->last_addr && !client->anonymize)
client->state = DHCP_STATE_INIT_REBOOT;
r = client_start(client);
return client->event;
}
-sd_dhcp_client *sd_dhcp_client_ref(sd_dhcp_client *client) {
-
- if (!client)
- return NULL;
-
- assert(client->n_ref >= 1);
- client->n_ref++;
-
- return client;
-}
-
-sd_dhcp_client *sd_dhcp_client_unref(sd_dhcp_client *client) {
-
- if (!client)
- return NULL;
-
- assert(client->n_ref >= 1);
- client->n_ref--;
-
- if (client->n_ref > 0)
- return NULL;
+static sd_dhcp_client *dhcp_client_free(sd_dhcp_client *client) {
+ assert(client);
log_dhcp_client(client, "FREE");
free(client->req_opts);
free(client->hostname);
free(client->vendor_class_identifier);
+ client->user_class = strv_free(client->user_class);
return mfree(client);
}
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_client, sd_dhcp_client, dhcp_client_free);
+
int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize) {
_cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL;
if (!client->req_opts)
return -ENOMEM;
- *ret = client;
- client = NULL;
+ *ret = TAKE_PTR(client);
return 0;
}