diff --git a/src/libsystemd-network/dhcp-client-send.c b/src/libsystemd-network/dhcp-client-send.c index d1f20fef46..e8b91a3e7c 100644 --- a/src/libsystemd-network/dhcp-client-send.c +++ b/src/libsystemd-network/dhcp-client-send.c @@ -1,15 +1,17 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + #include "sd-event.h" #include "dhcp-client-internal.h" #include "dhcp-client-send.h" #include "dhcp-lease-internal.h" /* IWYU pragma: keep */ +#include "dhcp-message.h" #include "dhcp-network.h" -#include "dhcp-packet.h" #include "fd-util.h" -#include "iovec-util.h" #include "iovec-wrapper.h" +#include "ip-util.h" #include "socket-util.h" static int client_get_socket(sd_dhcp_client *client, int domain) { @@ -70,17 +72,16 @@ static int client_setup_io_event( return 0; } -int dhcp_client_send_raw( +static int client_send_raw( sd_dhcp_client *client, - bool expect_reply, - DHCPPacket *packet, - size_t optoffset) { + sd_dhcp_message *message, + bool expect_reply) { _cleanup_close_ int fd_close = -EBADF; int r, fd; assert(client); - assert(packet); + assert(message); fd = client_get_socket(client, AF_PACKET); if (fd < 0) { @@ -100,24 +101,39 @@ int dhcp_client_send_raw( fd_close = fd; } - r = dhcp_packet_append_ip_headers( - packet, + _cleanup_(iovw_done_free) struct iovec_wrapper payload = {}; + r = dhcp_message_build(message, &payload); + if (r < 0) + return r; + + struct iphdr ip; + struct udphdr udp; + r = udp_packet_build( INADDR_ANY, client->port, INADDR_BROADCAST, client->server_port, - sizeof(DHCPPacket) + optoffset, - client->ip_service_type); + client->ip_service_type, + &payload, + &ip, + &udp); if (r < 0) return r; - r = dhcp_network_send_raw_socket( - fd, - &client->link, - &(struct iovec_wrapper) { - .iovec = &IOVEC_MAKE(packet, sizeof(DHCPPacket) + optoffset), - .count = 1, - }); + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + r = iovw_put(&iovw, &ip, sizeof(struct iphdr)); + if (r < 0) + return r; + + r = iovw_put(&iovw, &udp, sizeof(struct udphdr)); + if (r < 0) + return r; + + r = iovw_put_iovw(&iovw, &payload); + if (r < 0) + return r; + + r = dhcp_network_send_raw_socket(fd, &client->link, &iovw); if (r < 0) return r; @@ -138,17 +154,16 @@ int dhcp_client_send_raw( return 0; } -int dhcp_client_send_udp( +static int client_send_udp( sd_dhcp_client *client, - bool expect_reply, - DHCPPacket *packet, - size_t optoffset) { + sd_dhcp_message *message, + bool expect_reply) { _cleanup_close_ int fd_close = -EBADF; int r, fd; assert(client); - assert(packet); + assert(message); if (!client->lease || client->lease->address == 0) return -EADDRNOTAVAIL; @@ -166,14 +181,16 @@ int dhcp_client_send_udp( fd_close = fd; } + _cleanup_(iovw_done_free) struct iovec_wrapper payload = {}; + r = dhcp_message_build(message, &payload); + if (r < 0) + return r; + r = dhcp_network_send_udp_socket( fd, client->lease->server_address, client->server_port, - &(struct iovec_wrapper) { - .iovec = &IOVEC_MAKE(&packet->dhcp, sizeof(DHCPMessage) + optoffset), - .count = 1, - }); + &payload); if (r < 0) return r; @@ -193,3 +210,323 @@ int dhcp_client_send_udp( TAKE_FD(fd_close); return 0; } + +static int client_new_message(sd_dhcp_client *client, uint8_t type, sd_dhcp_message **ret) { + int r; + + assert(client); + assert(IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST, DHCP_RELEASE, DHCP_DECLINE)); + assert(ret); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = dhcp_message_new(&message); + if (r < 0) + return r; + + r = dhcp_message_init_header( + message, + BOOTREQUEST, + client->xid, + client->arp_type, + &client->hw_addr); + if (r < 0) + return r; + + /* Although 'secs' field is a SHOULD in RFC 2131, certain DHCP servers refuse to issue a DHCP lease + * if 'secs' is set to zero. */ + usec_t time_now; + r = sd_event_now(client->event, CLOCK_BOOTTIME, &time_now); + if (r < 0) + return r; + assert(time_now >= client->start_time); + + /* Seconds between sending first and last DISCOVER must always be strictly positive to deal with + * broken servers. */ + message->header.secs = usec_to_be16_sec(usec_sub_unsigned(time_now, client->start_time) ?: 1 * USEC_PER_SEC); + + /* RFC2131 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 need this to be disabled as + * broadcasts are filtered, so this needs to be configurable. */ + dhcp_message_set_broadcast_flag(message, client->request_broadcast || client->arp_type != ARPHRD_ETHER); + + /* We append no vendor options on BOOTP mode. */ + if (client->bootp) { + *ret = TAKE_PTR(message); + return 0; + } + + /* DHCP Message Type (53): Mandatory. */ + r = dhcp_message_append_option_u8(message, SD_DHCP_OPTION_MESSAGE_TYPE, type); + if (r < 0) + return r; + + /* Server Identifier (54): mandatory in DHCPREQUEST on REQUESTING state. It is also mandatory when + * DHCPRELEASE and DHCPDECLINE. */ + if ((type == DHCP_REQUEST && client->state == DHCP_STATE_REQUESTING) || + IN_SET(type, DHCP_RELEASE, DHCP_DECLINE)) { + r = dhcp_message_append_option_be32( + message, + SD_DHCP_OPTION_SERVER_IDENTIFIER, + ASSERT_PTR(client->lease)->server_address); + if (r < 0) + return r; + } + + /* Client Identifier (61): Not mandatory, but some DHCP servers will reject messages without client + * identifier option. Hence, we always set it. */ + r = dhcp_message_append_option_client_id(message, &client->client_id); + if (r < 0) + return r; + + /* Requested IP Address option (50) or ciaddr + * + * See RFC2131 section 4.3.2 (note that there is a typo in the RFC, SELECTING should be REQUESTING). */ + be32_t addr = INADDR_ANY; + switch (type) { + case DHCP_DISCOVER: + /* 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. + * + * RFC7844 section 3: + * SHOULD NOT contain any other option (when running on anonymize mode). */ + if (!client->anonymize) + addr = client->last_addr; + break; + + case DHCP_REQUEST: + switch (client->state) { + + case DHCP_STATE_REQUESTING: + /* ’ciaddr’ MUST be zero, ’requested IP address’ MUST be filled in with the + * yiaddr value from the chosen DHCPOFFER. */ + addr = ASSERT_PTR(client->lease)->address; + break; + + case DHCP_STATE_REBOOTING: + /* ’requested IP address’ option MUST be filled in with client’s notion of its + * previously assigned address. ’ciaddr’ MUST be zero. */ + addr = client->last_addr; + break; + + case DHCP_STATE_RENEWING: + case DHCP_STATE_REBINDING: + /* ’requested IP address’ option MUST NOT be filled in, ’ciaddr’ MUST be filled + * in with client’s IP address. */ + message->header.ciaddr = ASSERT_PTR(client->lease)->address; + break; + + default: + assert_not_reached(); + } + break; + + case DHCP_RELEASE: + /* The acquired address must be set in ciaddr. */ + message->header.ciaddr = ASSERT_PTR(client->lease)->address; + break; + + case DHCP_DECLINE: + /* The acquired address must be set in Requested IP Address option. */ + addr = ASSERT_PTR(client->lease)->address; + break; + + default: + assert_not_reached(); + } + + if (addr != INADDR_ANY) { + r = dhcp_message_append_option_be32(message, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, addr); + if (r < 0) + return r; + } + + /* DHCPRELEASE and DHCPDECLINE MUST NOT contain any other options. */ + if (IN_SET(type, DHCP_RELEASE, DHCP_DECLINE)) { + *ret = TAKE_PTR(message); + return 0; + } + + /* Parameter Request List (55) + * + * 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. + * + * RFC7844 section 3: + * MAY contain the Parameter Request List option. + * + * RFC7844 section 3.6: + * The client intending to protect its privacy SHOULD only request a minimal number of options in the + * PRL and SHOULD also randomly shuffle the ordering of option codes in the PRL. If this random + * ordering cannot be implemented, the client MAY order the option codes in the PRL by option code + * number (lowest to highest). + * + * NOTE: using PRL options that Windows 10 RFC7844 implementation uses. */ + if (client->anonymize) { + static const uint8_t default_req_opts_anonymize[] = { + SD_DHCP_OPTION_SUBNET_MASK, /* 1 */ + SD_DHCP_OPTION_ROUTER, /* 3 */ + SD_DHCP_OPTION_DOMAIN_NAME_SERVER, /* 6 */ + SD_DHCP_OPTION_DOMAIN_NAME, /* 15 */ + SD_DHCP_OPTION_ROUTER_DISCOVERY, /* 31 */ + SD_DHCP_OPTION_STATIC_ROUTE, /* 33 */ + SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, /* 43 */ + SD_DHCP_OPTION_NETBIOS_NAME_SERVER, /* 44 */ + SD_DHCP_OPTION_NETBIOS_NODE_TYPE, /* 46 */ + SD_DHCP_OPTION_NETBIOS_SCOPE, /* 47 */ + SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, /* 121 */ + SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE, /* 249 */ + SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY, /* 252 */ + }; + + r = dhcp_message_append_option( + message, + SD_DHCP_OPTION_PARAMETER_REQUEST_LIST, + ELEMENTSOF(default_req_opts_anonymize), + default_req_opts_anonymize); + if (r < 0) + return r; + + /* RFC7844 section 3: + * SHOULD NOT contain any other option (when running on anonymize mode). */ + *ret = TAKE_PTR(message); + return 0; + } + + /* When not on anonymized mode, use the default + user requested options. */ + r = dhcp_message_append_option_parameter_request_list(message, client->req_opts); + if (r < 0) + return r; + + /* Maximum Message Size (57) + * + * 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 Message 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 Maximum DHCP Message + * Size [4] option if the DHCP client's TCP/IP 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.) */ + r = dhcp_message_append_option_u16( + message, + SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, + CLAMP(client->mtu, (uint32_t) IPV4_MIN_REASSEMBLY_SIZE, (uint32_t) UINT16_MAX)); + if (r < 0) + return r; + + /* Hostname (12) or FQDN (81) + * + * Note, it is unclear from RFC 2131 if client should send hostname in DHCPDISCOVER but dhclient does + * and so we do as well. */ + r = dhcp_message_append_option_hostname( + message, + DHCP_FQDN_FLAG_S, /* Request server to perform A RR DNS updates */ + /* is_client= */ true, + client->hostname); + if (r < 0) + return r; + + /* Vendor Specific (43) */ + r = dhcp_message_append_option_sub_tlv( + message, + SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, + client->vendor_options); + if (r < 0) + return r; + + /* Vendor Class Identifier (60) */ + r = dhcp_message_append_option_string( + message, + SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, + client->vendor_class_identifier); + if (r < 0) + return r; + + /* User Class (77) */ + r = dhcp_message_append_option_length_prefixed_data( + message, + SD_DHCP_OPTION_USER_CLASS, + /* length_size= */ 1, + &client->user_class); + if (r < 0) + return r; + + /* Rapid Commit (80): only for DHCPDISCOVER */ + if (client->rapid_commit && type == DHCP_DISCOVER) { + r = dhcp_message_append_option_flag(message, SD_DHCP_OPTION_RAPID_COMMIT); + if (r < 0) + return r; + } + + /* MUD URL (161) */ + r = dhcp_message_append_option_string(message, SD_DHCP_OPTION_MUD_URL, client->mudurl); + if (r < 0) + return r; + + r = dhcp_message_append_option_tlv(message, client->extra_options); + if (r < 0) + return r; + + *ret = TAKE_PTR(message); + return 0; +} + +int dhcp_client_send_message(sd_dhcp_client *client, uint8_t type) { + int r; + + assert(client); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = client_new_message(client, type, &message); + if (r < 0) + return r; + + switch (type) { + case DHCP_DISCOVER: + r = client_send_raw(client, message, /* expect_reply= */ true); + break; + case DHCP_REQUEST: + if (client->state == DHCP_STATE_RENEWING) + r = client_send_udp(client, message, /* expect_reply= */ true); + else + r = client_send_raw(client, message, /* expect_reply= */ true); + break; + case DHCP_RELEASE: + r = client_send_udp(client, message, /* expect_reply= */ false); + break; + case DHCP_DECLINE: + r = client_send_raw(client, message, /* expect_reply= */ false); + break; + default: + r = -EINVAL; + } + if (r < 0) + return r; + + if (client->bootp) + log_dhcp_client(client, "BOOTREQUEST"); + else if (type == DHCP_REQUEST) + log_dhcp_client(client, "%s (%s)", + dhcp_message_type_to_string(type), + dhcp_state_to_string(client->state)); + else + log_dhcp_client(client, "%s", dhcp_message_type_to_string(type)); + return 0; +} diff --git a/src/libsystemd-network/dhcp-client-send.h b/src/libsystemd-network/dhcp-client-send.h index 2dbb6a0878..d5d65a31cc 100644 --- a/src/libsystemd-network/dhcp-client-send.h +++ b/src/libsystemd-network/dhcp-client-send.h @@ -3,16 +3,4 @@ #include "sd-forward.h" -#include "dhcp-protocol.h" - -int dhcp_client_send_raw( - sd_dhcp_client *client, - bool expect_reply, - DHCPPacket *packet, - size_t optoffset); - -int dhcp_client_send_udp( - sd_dhcp_client *client, - bool expect_reply, - DHCPPacket *packet, - size_t optoffset); +int dhcp_client_send_message(sd_dhcp_client *client, uint8_t type); diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 463f95ea29..e2513567a2 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -24,7 +24,6 @@ #include "network-common.h" #include "random-util.h" #include "set.h" -#include "sort-util.h" #include "string-table.h" #include "string-util.h" #include "time-util.h" @@ -715,495 +714,6 @@ static usec_t client_compute_reacquisition_timeout(usec_t now_usec, usec_t expir return MAX(usec_sub_unsigned(expire, now_usec) / 2, 60 * USEC_PER_SEC); } -static int cmp_uint8(const uint8_t *a, const uint8_t *b) { - assert(a); - assert(b); - - return CMP(*a, *b); -} - -static int client_message_init( - sd_dhcp_client *client, - uint8_t type, - DHCPPacket **ret_packet, - size_t *ret_optlen, - size_t *ret_optoffset) { - - _cleanup_free_ DHCPPacket *packet = NULL; - size_t optlen, optoffset, size; - usec_t time_now; - uint16_t secs; - int r; - - assert(client); - assert(IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST, DHCP_RELEASE, DHCP_DECLINE)); - assert(ret_packet); - assert(ret_optlen); - assert(ret_optoffset); - - optlen = DHCP_MIN_OPTIONS_SIZE; - size = sizeof(DHCPPacket) + optlen; - - packet = malloc0(size); - if (!packet) - return -ENOMEM; - if (client->bootp) { - /* BOOTP supports options, but only DHCP_OPTION_END is used. The rest of the 64-byte buffer - * is set to zero, per RFC1542. Allow for this by initialaizing optoffset to 0. */ - optoffset = 0; - r = bootp_message_init( - &packet->dhcp, BOOTREQUEST, client->xid, client->arp_type, - client->hw_addr.length, client->hw_addr.bytes); - } else - r = dhcp_message_init( - &packet->dhcp, BOOTREQUEST, client->xid, client->arp_type, - client->hw_addr.length, client->hw_addr.bytes, - type, optlen, &optoffset); - 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 */ - r = sd_event_now(client->event, CLOCK_BOOTTIME, &time_now); - if (r < 0) - return r; - assert(time_now >= client->start_time); - - /* seconds between sending first and last DISCOVER - * must always be strictly positive to deal with broken servers */ - secs = ((time_now - client->start_time) / USEC_PER_SEC) ?: 1; - packet->dhcp.secs = htobe16(secs); - - /* RFC2131 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 */ - if (client->request_broadcast || client->arp_type != ARPHRD_ETHER) - packet->dhcp.flags = htobe16(0x8000); - - if (client->bootp) { - *ret_optlen = optlen; - *ret_optoffset = optoffset; - *ret_packet = TAKE_PTR(packet); - return 0; - } - - /* Some DHCP servers will refuse to issue an DHCP lease if the Client - Identifier option is not set */ - r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_CLIENT_IDENTIFIER, - client->client_id.size, - client->client_id.raw); - 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. - */ - - /* 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 (!set_isempty(client->req_opts) && IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST)) { - _cleanup_free_ uint8_t *opts = NULL; - size_t n_opts, i = 0; - void *val; - - n_opts = set_size(client->req_opts); - opts = new(uint8_t, n_opts); - if (!opts) - return -ENOMEM; - - SET_FOREACH(val, client->req_opts) - opts[i++] = PTR_TO_UINT8(val); - assert(i == n_opts); - - /* For anonymizing the request, let's sort the options. */ - typesafe_qsort(opts, n_opts, cmp_uint8); - - r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_PARAMETER_REQUEST_LIST, - n_opts, opts); - 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 Message 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 - Maximum DHCP Message Size [4] option if the DHCP client's TCP/IP - 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.) - */ - /* RFC7844 section 3: - SHOULD NOT contain any other option. */ - if (!client->anonymize && IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST)) { - be16_t max_size = htobe16(MIN(client->mtu - DHCP_IP_UDP_SIZE, (uint32_t) UINT16_MAX)); - r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, - 2, &max_size); - if (r < 0) - return r; - } - - *ret_optlen = optlen; - *ret_optoffset = optoffset; - *ret_packet = TAKE_PTR(packet); - - return 0; -} - -static int client_append_fqdn_option( - DHCPMessage *message, - size_t optlen, - size_t *optoffset, - const char *fqdn) { - - uint8_t buffer[3 + DHCP_MAX_FQDN_LENGTH]; - int r; - - buffer[0] = DHCP_FQDN_FLAG_S | /* Request server to perform A RR DNS updates */ - DHCP_FQDN_FLAG_E; /* Canonical wire format */ - buffer[1] = 0; /* RCODE1 (deprecated) */ - buffer[2] = 0; /* RCODE2 (deprecated) */ - - r = dns_name_to_wire_format(fqdn, buffer + 3, sizeof(buffer) - 3, false); - if (r > 0) - r = dhcp_option_append(message, optlen, optoffset, 0, - SD_DHCP_OPTION_FQDN, 3 + r, buffer); - - return r; -} - -static int client_append_common_discover_request_options(sd_dhcp_client *client, DHCPPacket *packet, size_t *optoffset, size_t optlen) { - int r; - - assert(client); - - if (client->hostname) { - /* 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. - */ - if (dns_name_is_single_label(client->hostname)) { - /* it is unclear from RFC 2131 if client should send hostname in - DHCPDISCOVER but dhclient does and so we do as well - */ - r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, - SD_DHCP_OPTION_HOST_NAME, - strlen(client->hostname), client->hostname); - } else - r = client_append_fqdn_option(&packet->dhcp, optlen, optoffset, - client->hostname); - if (r < 0) - return r; - } - - if (client->vendor_class_identifier) { - r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, - SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, - strlen(client->vendor_class_identifier), - client->vendor_class_identifier); - if (r < 0) - return r; - } - - if (client->mudurl) { - r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, - SD_DHCP_OPTION_MUD_URL, - strlen(client->mudurl), - client->mudurl); - if (r < 0) - return r; - } - - if (!iovw_isempty(&client->user_class)) { - size_t sz = iovw_size(&client->user_class) + client->user_class.count; - if (sz <= UINT8_MAX) { - _cleanup_free_ uint8_t *buf = new(uint8_t, sz); - if (!buf) - return -ENOMEM; - - uint8_t *p = buf; - FOREACH_ARRAY(iovec, client->user_class.iovec, client->user_class.count) { - assert(iovec->iov_len > 0 && iovec->iov_len <= UINT8_MAX); - *p++ = iovec->iov_len; - p = mempcpy(p, iovec->iov_base, iovec->iov_len); - } - - r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, - SD_DHCP_OPTION_USER_CLASS, - sz, buf); - if (r < 0) - return r; - } - } - - if (client->extra_options) { - void *key; - struct iovec_wrapper *iovw; - HASHMAP_FOREACH_KEY(iovw, key, client->extra_options->entries) { - uint32_t tag = PTR_TO_UINT32(key); - - FOREACH_ARRAY(iov, iovw->iovec, iovw->count) { - r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, - tag, iov->iov_len, iov->iov_base); - if (r < 0) - return r; - } - } - } - - if (!tlv_isempty(client->vendor_options)) { - _cleanup_(iovec_done) struct iovec iov = {}; - r = tlv_build(client->vendor_options, &iov); - if (r < 0) - return r; - - r = dhcp_option_append( - &packet->dhcp, optlen, optoffset, 0, - SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, - iov.iov_len, iov.iov_base); - if (r < 0) - return r; - } - - return 0; -} - -static int client_send_dhcp_discover(sd_dhcp_client *client) { - _cleanup_free_ DHCPPacket *discover = NULL; - size_t optoffset, optlen; - int r; - - assert(client); - - r = client_message_init(client, DHCP_DISCOVER, &discover, &optlen, &optoffset); - 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. - */ - /* RFC7844 section 3: - SHOULD NOT contain any other option. */ - if (!client->anonymize && client->last_addr != INADDR_ANY) { - r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, - 4, &client->last_addr); - if (r < 0) - return r; - } - - if (client->rapid_commit) { - r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_RAPID_COMMIT, 0, NULL); - if (r < 0) - return r; - } - - r = client_append_common_discover_request_options(client, discover, &optoffset, optlen); - if (r < 0) - return r; - - r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_END, 0, NULL); - if (r < 0) - return r; - - r = dhcp_client_send_raw(client, /* expect_reply= */ true, discover, optoffset); - if (r < 0) - return r; - - log_dhcp_client(client, "DISCOVER"); - return 0; -} - -static int client_send_bootp_discover(sd_dhcp_client *client) { - _cleanup_free_ DHCPPacket *discover = NULL; - size_t optoffset, optlen; - int r; - - assert(client); - - r = client_message_init(client, DHCP_DISCOVER, &discover, &optlen, &optoffset); - if (r < 0) - return r; - - r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, SD_DHCP_OPTION_END, 0, NULL); - if (r < 0) - return r; - - /* RFC1542 section 3.5: - * if the client has no information to communicate to the server, the octet immediately following the - * magic cookie SHOULD be set to the "End" tag (255) and the remaining octets of the 'vend' field - * SHOULD be set to zero. - * - * Use this RFC, along with the fact that some BOOTP servers require a 64-byte vend field, to suggest - * that we always zero and send 64 bytes in the options field. The first four bites are the "magic" - * field, so this only needs to add 60 bytes. */ - if (optoffset < 60 && optlen >= 60) { - memzero(&discover->dhcp.options[optoffset], optlen - optoffset); - optoffset = 60; - } - - r = dhcp_client_send_raw(client, /* expect_reply= */ true, discover, optoffset); - if (r < 0) - return r; - - log_dhcp_client(client, "DISCOVER"); - return 0; -} - -static int client_send_discover(sd_dhcp_client *client) { - assert(client); - assert(client->state == DHCP_STATE_SELECTING); - - return client->bootp ? - client_send_bootp_discover(client) : - client_send_dhcp_discover(client); -} - -static int client_send_request(sd_dhcp_client *client) { - _cleanup_free_ DHCPPacket *request = NULL; - size_t optoffset, optlen; - int r; - - assert(client); - assert(!client->bootp); - - r = client_message_init(client, DHCP_REQUEST, &request, &optlen, &optoffset); - if (r < 0) - return r; - - switch (client->state) { - /* 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. - */ - - r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_SERVER_IDENTIFIER, - 4, &client->lease->server_address); - if (r < 0) - return r; - - r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, - 4, &client->lease->address); - if (r < 0) - return r; - break; - - case DHCP_STATE_REBOOTING: - /* ’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. - */ - r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, - 4, &client->last_addr); - 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. - */ - - 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. - */ - request->dhcp.ciaddr = client->lease->address; - break; - - default: - assert_not_reached(); - } - - r = client_append_common_discover_request_options(client, request, &optoffset, optlen); - if (r < 0) - return r; - - r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_END, 0, NULL); - if (r < 0) - return r; - - if (client->state == DHCP_STATE_RENEWING) - r = dhcp_client_send_udp(client, /* expect_reply= */ true, request, optoffset); - else - r = dhcp_client_send_raw(client, /* expect_reply= */ true, request, optoffset); - if (r < 0) - return r; - - switch (client->state) { - - case DHCP_STATE_REQUESTING: - log_dhcp_client(client, "REQUEST (requesting)"); - break; - - case DHCP_STATE_REBOOTING: - log_dhcp_client(client, "REQUEST (rebooting)"); - break; - - case DHCP_STATE_RENEWING: - log_dhcp_client(client, "REQUEST (renewing)"); - break; - - case DHCP_STATE_REBINDING: - log_dhcp_client(client, "REQUEST (rebinding)"); - break; - - default: - assert_not_reached(); - } - - return 0; -} - static int client_timeout_resend( sd_event_source *s, uint64_t usec, @@ -1282,7 +792,7 @@ static int client_timeout_resend( switch (client->state) { case DHCP_STATE_SELECTING: - r = client_send_discover(client); + r = dhcp_client_send_message(client, DHCP_DISCOVER); if (r < 0 && client->discover_attempt >= client->max_discover_attempts) goto error; @@ -1291,7 +801,7 @@ static int client_timeout_resend( break; case DHCP_STATE_REBOOTING: - r = client_send_request(client); + r = dhcp_client_send_message(client, DHCP_REQUEST); if (r < 0 && client->request_attempt >= MAX_REQUEST_ATTEMPTS_ON_REBOOTING) goto restart; break; @@ -1299,7 +809,7 @@ static int client_timeout_resend( case DHCP_STATE_REQUESTING: case DHCP_STATE_RENEWING: case DHCP_STATE_REBINDING: - r = client_send_request(client); + r = dhcp_client_send_message(client, DHCP_REQUEST); if (r < 0 && client->request_attempt >= MAX_REQUEST_ATTEMPTS) goto restart; break; @@ -2109,99 +1619,31 @@ int sd_dhcp_client_start(sd_dhcp_client *client) { return r; } -static int client_send_release_or_decline(sd_dhcp_client *client, uint8_t type) { - int r; - - assert(IN_SET(type, DHCP_RELEASE, DHCP_DECLINE)); - - if (!sd_dhcp_client_is_running(client) || !client->lease || client->bootp) - return 0; /* there is nothing to release or decline */ - - const char *name = type == DHCP_RELEASE ? "RELEASE" : "DECLINE"; - - _cleanup_free_ DHCPPacket *packet = NULL; - size_t optoffset, optlen; - r = client_message_init(client, type, &packet, &optlen, &optoffset); - if (r < 0) - return log_dhcp_client_errno(client, r, "Failed to initialize DHCP %s message: %m", name); - - /* See RFC 2131, Table 5 */ - switch (type) { - case DHCP_RELEASE: - /* On release, the acquired address must be set in ciaddr. */ - packet->dhcp.ciaddr = client->lease->address; - break; - - case DHCP_DECLINE: - /* On decline, the acquired address must be set in Requested IP Address option. */ - r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, /* overload= */ 0, - SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, - 4, &client->lease->address); - if (r < 0) - return log_dhcp_client_errno( - client, r, - "Failed to append Requested IP Address option to DHCP %s message: %m", - name); - break; - - default: - assert_not_reached(); - } - - /* In both cases, the server identifier must be set. */ - r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, /* overload= */ 0, - SD_DHCP_OPTION_SERVER_IDENTIFIER, - 4, &client->lease->server_address); - if (r < 0) - return log_dhcp_client_errno( - client, r, - "Failed to append Server Identifier option to DHCP %s message: %m", - name); - - r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, /* overload= */ 0, - SD_DHCP_OPTION_END, /* optlen= */ 0, /* optval= */ NULL); - if (r < 0) - return log_dhcp_client_errno( - client, r, - "Failed to finalize DHCP %s message: %m", - name); - - switch (type) { - case DHCP_RELEASE: - r = dhcp_client_send_udp(client, /* expect_reply= */ false, packet, optoffset); - break; - case DHCP_DECLINE: - r = dhcp_client_send_raw(client, /* expect_reply= */ false, packet, optoffset); - break; - default: - assert_not_reached(); - } - if (r < 0) - return log_dhcp_client_errno( - client, r, - "Failed to send DHCP %s message: %m", - name); - - log_dhcp_client(client, "%s", name); - return 1; /* sent */ -} - int sd_dhcp_client_send_decline(sd_dhcp_client *client) { int r; - r = client_send_release_or_decline(client, DHCP_DECLINE); - if (r <= 0) - return r; + if (!sd_dhcp_client_is_running(client) || !client->lease || client->bootp) + return 0; /* there is nothing to decline */ - log_dhcp_client(client, "DECLINE"); - - /* This function is mostly called when the acquired address conflicts with another host. - * Restarting the daemon to acquire another address. */ - r = client_restart(client); + r = dhcp_client_send_message(client, DHCP_DECLINE); if (r < 0) return r; - return 1; /* sent and restarted. */ + /* This function is mostly called when the acquired address conflicts with another host. + * Restarting the daemon to acquire another address. */ + return client_restart(client); +} + +static int client_send_release(sd_dhcp_client *client) { + assert(client); + + if (!client->send_release) + return 0; + + if (!sd_dhcp_client_is_running(client) || !client->lease || client->bootp) + return 0; /* there is nothing to release */ + + return dhcp_client_send_message(client, DHCP_RELEASE); } int sd_dhcp_client_stop(sd_dhcp_client *client) { @@ -2210,8 +1652,7 @@ int sd_dhcp_client_stop(sd_dhcp_client *client) { DHCP_CLIENT_DONT_DESTROY(client); - if (client->send_release) - (void) client_send_release_or_decline(client, DHCP_RELEASE); + (void) client_send_release(client); client_stop(client, SD_DHCP_CLIENT_EVENT_STOP); return 0; diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index d7dd01cc81..c62f46222a 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -352,7 +352,8 @@ static void test_addr_acq_recv_request(size_t size, DHCPMessage *request) { ASSERT_OK_EQ(dhcp_option_parse(request, size, check_options, NULL, NULL), DHCP_REQUEST); ASSERT_EQ(request->xid, xid); - ASSERT_EQ(msg_bytes[size - 1], SD_DHCP_OPTION_END); + uint8_t *end = ASSERT_NOT_NULL(memrchr(msg_bytes, SD_DHCP_OPTION_END, size)); + ASSERT_TRUE(memeqzero(end + 1, msg_bytes + size - end - 1)); log_info(" recv DHCP Request 0x%08x", be32toh(xid)); @@ -374,7 +375,8 @@ static void test_addr_acq_recv_discover(size_t size, DHCPMessage *discover) { ASSERT_OK_EQ(dhcp_option_parse(discover, size, check_options, NULL, NULL), DHCP_DISCOVER); - ASSERT_EQ(msg_bytes[size - 1], SD_DHCP_OPTION_END); + uint8_t *end = ASSERT_NOT_NULL(memrchr(msg_bytes, SD_DHCP_OPTION_END, size)); + ASSERT_TRUE(memeqzero(end + 1, msg_bytes + size - end - 1)); xid = discover->xid;