]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/libsystemd-network/sd-dhcp-client.c
Merge pull request #2029 from teg/network-fixes
[thirdparty/systemd.git] / src / libsystemd-network / sd-dhcp-client.c
index 29c67f23be7fcfd95d68ff08490886dd17189ed3..7deb00af9c27d805a781f258d3b7e1d79e4ed9f8 100644 (file)
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
-#include <stdlib.h>
 #include <errno.h>
-#include <string.h>
-#include <stdio.h>
 #include <net/ethernet.h>
 #include <net/if_arp.h>
-#include <linux/if_infiniband.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
 #include <sys/ioctl.h>
+#include <linux/if_infiniband.h>
 
-#include "util.h"
-#include "random-util.h"
-#include "async.h"
+#include "sd-dhcp-client.h"
 
-#include "dhcp-protocol.h"
+#include "alloc-util.h"
+#include "async.h"
+#include "dhcp-identifier.h"
 #include "dhcp-internal.h"
 #include "dhcp-lease-internal.h"
-#include "dhcp-identifier.h"
-#include "sd-dhcp-client.h"
+#include "dhcp-protocol.h"
+#include "dns-domain.h"
+#include "hostname-util.h"
+#include "random-util.h"
+#include "string-util.h"
+#include "util.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)
@@ -105,7 +109,6 @@ static const uint8_t default_req_opts[] = {
         DHCP_OPTION_HOST_NAME,
         DHCP_OPTION_DOMAIN_NAME,
         DHCP_OPTION_DOMAIN_NAME_SERVER,
-        DHCP_OPTION_NTP_SERVER,
 };
 
 static int client_receive_message_raw(sd_event_source *s, int fd,
@@ -214,7 +217,7 @@ int sd_dhcp_client_set_mac(sd_dhcp_client *client, const uint8_t *addr,
                 log_dhcp_client(client, "Changing MAC address on running DHCP "
                                 "client, restarting");
                 need_restart = true;
-                client_stop(client, DHCP_EVENT_STOP);
+                client_stop(client, SD_DHCP_CLIENT_EVENT_STOP);
         }
 
         memcpy(&client->mac_addr, addr, addr_len);
@@ -278,7 +281,7 @@ int sd_dhcp_client_set_client_id(sd_dhcp_client *client, uint8_t type,
                 log_dhcp_client(client, "Changing client ID on running DHCP "
                                 "client, restarting");
                 need_restart = true;
-                client_stop(client, DHCP_EVENT_STOP);
+                client_stop(client, SD_DHCP_CLIENT_EVENT_STOP);
         }
 
         client->client_id.type = type;
@@ -297,6 +300,9 @@ int sd_dhcp_client_set_hostname(sd_dhcp_client *client,
 
         assert_return(client, -EINVAL);
 
+        if (!hostname_is_valid(hostname, false) && !dns_name_is_valid(hostname))
+                return -EINVAL;
+
         if (streq_ptr(client->hostname, hostname))
                 return 0;
 
@@ -386,7 +392,7 @@ static void client_stop(sd_dhcp_client *client, int error) {
 
         if (error < 0)
                 log_dhcp_client(client, "STOPPED: %s", strerror(-error));
-        else if (error == DHCP_EVENT_STOP)
+        else if (error == SD_DHCP_CLIENT_EVENT_STOP)
                 log_dhcp_client(client, "STOPPED");
         else
                 log_dhcp_client(client, "STOPPED: Unknown event");
@@ -538,6 +544,24 @@ static int client_message_init(sd_dhcp_client *client, DHCPPacket **ret,
         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);
+        if (r > 0)
+                r = dhcp_option_append(message, optlen, optoffset, 0,
+                                       DHCP_OPTION_FQDN, 3 + r, buffer);
+
+        return r;
+}
+
 static int dhcp_client_send_raw(sd_dhcp_client *client, DHCPPacket *packet,
                                 size_t len) {
         dhcp_packet_append_ip_headers(packet, INADDR_ANY, DHCP_PORT_CLIENT,
@@ -575,13 +599,21 @@ static int client_send_discover(sd_dhcp_client *client) {
                         return r;
         }
 
-        /* it is unclear from RFC 2131 if client should send hostname in
-           DHCPDISCOVER but dhclient does and so we do as well
-        */
         if (client->hostname) {
-                r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
-                                       DHCP_OPTION_HOST_NAME,
-                                       strlen(client->hostname), 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(&discover->dhcp, optlen, &optoffset, 0,
+                                               DHCP_OPTION_HOST_NAME,
+                                               strlen(client->hostname), client->hostname);
+                } else
+                        r = client_append_fqdn_option(&discover->dhcp, optlen, &optoffset,
+                                                      client->hostname);
                 if (r < 0)
                         return r;
         }
@@ -687,9 +719,13 @@ static int client_send_request(sd_dhcp_client *client) {
         }
 
         if (client->hostname) {
-                r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0,
-                                       DHCP_OPTION_HOST_NAME,
-                                       strlen(client->hostname), client->hostname);
+                if (dns_name_is_single_label(client->hostname))
+                        r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0,
+                                               DHCP_OPTION_HOST_NAME,
+                                               strlen(client->hostname), client->hostname);
+                else
+                        r = client_append_fqdn_option(&request->dhcp, optlen, &optoffset,
+                                                      client->hostname);
                 if (r < 0)
                         return r;
         }
@@ -984,7 +1020,7 @@ static int client_timeout_expire(sd_event_source *s, uint64_t usec,
 
         log_dhcp_client(client, "EXPIRED");
 
-        client_notify(client, DHCP_EVENT_EXPIRED);
+        client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED);
 
         /* lease was lost, start over if not freed or stopped in callback */
         if (client->state != DHCP_STATE_STOPPED) {
@@ -1046,7 +1082,7 @@ static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *offer,
                         return r;
         }
 
-        r = dhcp_option_parse(offer, len, dhcp_lease_parse_options, lease);
+        r = dhcp_option_parse(offer, len, dhcp_lease_parse_options, lease, NULL);
         if (r != DHCP_OFFER) {
                 log_dhcp_client(client, "received message was not an OFFER, ignoring");
                 return -ENOMSG;
@@ -1085,7 +1121,7 @@ static int client_handle_forcerenew(sd_dhcp_client *client, DHCPMessage *force,
                                     size_t len) {
         int r;
 
-        r = dhcp_option_parse(force, len, NULL, NULL);
+        r = dhcp_option_parse(force, len, NULL, NULL, NULL);
         if (r != DHCP_FORCERENEW)
                 return -ENOMSG;
 
@@ -1097,6 +1133,7 @@ static int client_handle_forcerenew(sd_dhcp_client *client, DHCPMessage *force,
 static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack,
                              size_t len) {
         _cleanup_dhcp_lease_unref_ sd_dhcp_lease *lease = NULL;
+        _cleanup_free_ char *error_message = NULL;
         int r;
 
         r = dhcp_lease_new(&lease);
@@ -1111,9 +1148,9 @@ static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack,
                         return r;
         }
 
-        r = dhcp_option_parse(ack, len, dhcp_lease_parse_options, lease);
+        r = dhcp_option_parse(ack, len, dhcp_lease_parse_options, lease, &error_message);
         if (r == DHCP_NAK) {
-                log_dhcp_client(client, "NAK");
+                log_dhcp_client(client, "NAK: %s", strna(error_message));
                 return -EADDRNOTAVAIL;
         }
 
@@ -1144,14 +1181,14 @@ static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack,
                 }
         }
 
-        r = DHCP_EVENT_IP_ACQUIRE;
+        r = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE;
         if (client->lease) {
                 if (client->lease->address != lease->address ||
                     client->lease->subnet_mask != lease->subnet_mask ||
                     client->lease->router != lease->router) {
-                        r = DHCP_EVENT_IP_CHANGE;
+                        r = SD_DHCP_CLIENT_EVENT_IP_CHANGE;
                 } else
-                        r = DHCP_EVENT_RENEW;
+                        r = SD_DHCP_CLIENT_EVENT_RENEW;
 
                 client->lease = sd_dhcp_lease_unref(client->lease);
         }
@@ -1164,13 +1201,17 @@ static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack,
         return r;
 }
 
-static uint64_t client_compute_timeout(sd_dhcp_client *client,
-                                       uint32_t lifetime, double factor) {
+static uint64_t client_compute_timeout(sd_dhcp_client *client, uint32_t lifetime, double factor) {
         assert(client);
         assert(client->request_sent);
-        assert(lifetime);
+        assert(lifetime > 0);
+
+        if (lifetime > 3)
+                lifetime -= 3;
+        else
+                lifetime = 0;
 
-        return client->request_sent + ((lifetime - 3) * USEC_PER_SEC * factor) +
+        return client->request_sent + (lifetime * USEC_PER_SEC * factor) +
                 + (random_u32() & 0x1fffff);
 }
 
@@ -1202,7 +1243,7 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) {
 
         /* convert the various timeouts from relative (secs) to absolute (usecs) */
         lifetime_timeout = client_compute_timeout(client, client->lease->lifetime, 1);
-        if (client->lease->t1 && client->lease->t2) {
+        if (client->lease->t1 > 0 && client->lease->t2 > 0) {
                 /* both T1 and T2 are given */
                 if (client->lease->t1 < client->lease->t2 &&
                     client->lease->t2 < client->lease->lifetime) {
@@ -1216,7 +1257,7 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) {
                         t1_timeout = client_compute_timeout(client, client->lease->lifetime, 0.5);
                         client->lease->t1 = client->lease->lifetime / 2;
                 }
-        } else if (client->lease->t2 && client->lease->t2 < client->lease->lifetime) {
+        } else if (client->lease->t2 > 0 && client->lease->t2 < client->lease->lifetime) {
                 /* only T2 is given, and it is valid */
                 t2_timeout = client_compute_timeout(client, client->lease->t2, 1);
                 t1_timeout = client_compute_timeout(client, client->lease->lifetime, 0.5);
@@ -1226,7 +1267,7 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) {
                         t2_timeout = client_compute_timeout(client, client->lease->lifetime, 7.0 / 8.0);
                         client->lease->t2 = (client->lease->lifetime * 7) / 8;
                 }
-        } else if (client->lease->t1 && client->lease->t1 < client->lease->lifetime) {
+        } else if (client->lease->t1 > 0 && client->lease->t1 < client->lease->lifetime) {
                 /* only T1 is given, and it is valid */
                 t1_timeout = client_compute_timeout(client, client->lease->t1, 1);
                 t2_timeout = client_compute_timeout(client, client->lease->lifetime, 7.0 / 8.0);
@@ -1262,8 +1303,7 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) {
                 return r;
 
         log_dhcp_client(client, "lease expires in %s",
-                        format_timespan(time_string, FORMAT_TIMESPAN_MAX,
-                        lifetime_timeout - time_now, 0));
+                        format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime_timeout - time_now, USEC_PER_SEC));
 
         /* don't arm earlier timeouts if this has already expired */
         if (lifetime_timeout <= time_now)
@@ -1289,8 +1329,7 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) {
                 return r;
 
         log_dhcp_client(client, "T2 expires in %s",
-                        format_timespan(time_string, FORMAT_TIMESPAN_MAX,
-                        t2_timeout - time_now, 0));
+                        format_timespan(time_string, FORMAT_TIMESPAN_MAX, t2_timeout - time_now, USEC_PER_SEC));
 
         /* don't arm earlier timeout if this has already expired */
         if (t2_timeout <= time_now)
@@ -1315,8 +1354,7 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) {
                 return r;
 
         log_dhcp_client(client, "T1 expires in %s",
-                        format_timespan(time_string, FORMAT_TIMESPAN_MAX,
-                        t1_timeout - time_now, 0));
+                        format_timespan(time_string, FORMAT_TIMESPAN_MAX, t1_timeout - time_now, USEC_PER_SEC));
 
         return 0;
 }
@@ -1379,8 +1417,8 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message,
 
                         if (IN_SET(client->state, DHCP_STATE_REQUESTING,
                                    DHCP_STATE_REBOOTING))
-                                notify_event = DHCP_EVENT_IP_ACQUIRE;
-                        else if (r != DHCP_EVENT_IP_ACQUIRE)
+                                notify_event = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE;
+                        else if (r != SD_DHCP_CLIENT_EVENT_IP_ACQUIRE)
                                 notify_event = r;
 
                         client->state = DHCP_STATE_BOUND;
@@ -1476,9 +1514,8 @@ static int client_receive_message_udp(sd_event_source *s, int fd,
 
         r = ioctl(fd, FIONREAD, &buflen);
         if (r < 0)
-                return r;
-
-        if (buflen < 0)
+                return -errno;
+        else if (buflen < 0)
                 /* this can't be right */
                 return -EIO;
 
@@ -1488,26 +1525,28 @@ static int client_receive_message_udp(sd_event_source *s, int fd,
 
         len = read(fd, message, buflen);
         if (len < 0) {
-                log_dhcp_client(client, "could not receive message from UDP "
-                                "socket: %m");
-                return 0;
+                if (errno == EAGAIN || errno == EINTR)
+                        return 0;
+
+                log_dhcp_client(client, "Could not receive message from UDP socket: %m");
+                return -errno;
         } else if ((size_t)len < sizeof(DHCPMessage)) {
-                log_dhcp_client(client, "too small to be a DHCP message: ignoring");
+                log_dhcp_client(client, "Too small to be a DHCP message: ignoring");
                 return 0;
         }
 
         if (be32toh(message->magic) != DHCP_MAGIC_COOKIE) {
-                log_dhcp_client(client, "not a DHCP message: ignoring");
+                log_dhcp_client(client, "Not a DHCP message: ignoring");
                 return 0;
         }
 
         if (message->op != BOOTREPLY) {
-                log_dhcp_client(client, "not a BOOTREPLY message: ignoring");
+                log_dhcp_client(client, "Not a BOOTREPLY message: ignoring");
                 return 0;
         }
 
         if (message->htype != client->arp_type) {
-                log_dhcp_client(client, "packet type does not match client type");
+                log_dhcp_client(client, "Packet type does not match client type");
                 return 0;
         }
 
@@ -1515,19 +1554,18 @@ static int client_receive_message_udp(sd_event_source *s, int fd,
                 expected_hlen = ETH_ALEN;
                 expected_chaddr = (const struct ether_addr *) &client->mac_addr;
         } else {
-               /* Non-ethernet links expect zero chaddr */
+               /* Non-Ethernet links expect zero chaddr */
                expected_hlen = 0;
                expected_chaddr = &zero_mac;
         }
 
         if (message->hlen != expected_hlen) {
-                log_dhcp_client(client, "unexpected packet hlen %d", message->hlen);
+                log_dhcp_client(client, "Unexpected packet hlen %d", message->hlen);
                 return 0;
         }
 
         if (memcmp(&message->chaddr[0], expected_chaddr, ETH_ALEN)) {
-                log_dhcp_client(client, "received chaddr does not match "
-                                "expected: ignoring");
+                log_dhcp_client(client, "Received chaddr does not match expected: ignoring");
                 return 0;
         }
 
@@ -1535,8 +1573,7 @@ static int client_receive_message_udp(sd_event_source *s, int fd,
             be32toh(message->xid) != client->xid) {
                 /* in BOUND state, we may receive FORCERENEW with xid set by server,
                    so ignore the xid in this case */
-                log_dhcp_client(client, "received xid (%u) does not match "
-                                "expected (%u): ignoring",
+                log_dhcp_client(client, "Received xid (%u) does not match expected (%u): ignoring",
                                 be32toh(message->xid), client->xid);
                 return 0;
         }
@@ -1565,9 +1602,8 @@ static int client_receive_message_raw(sd_event_source *s, int fd,
 
         r = ioctl(fd, FIONREAD, &buflen);
         if (r < 0)
-                return r;
-
-        if (buflen < 0)
+                return -errno;
+        else if (buflen < 0)
                 /* this can't be right */
                 return -EIO;
 
@@ -1580,9 +1616,12 @@ static int client_receive_message_raw(sd_event_source *s, int fd,
 
         len = recvmsg(fd, &msg, 0);
         if (len < 0) {
-                log_dhcp_client(client, "could not receive message from raw "
-                                "socket: %m");
-                return 0;
+                if (errno == EAGAIN || errno == EINTR)
+                        return 0;
+
+                log_dhcp_client(client, "Could not receive message from raw socket: %m");
+
+                return -errno;
         } else if ((size_t)len < sizeof(DHCPPacket))
                 return 0;
 
@@ -1630,7 +1669,7 @@ int sd_dhcp_client_stop(sd_dhcp_client *client) {
 
         assert_return(client, -EINVAL);
 
-        client_stop(client, DHCP_EVENT_STOP);
+        client_stop(client, SD_DHCP_CLIENT_EVENT_STOP);
         client->state = DHCP_STATE_STOPPED;
 
         return 0;