chiark / gitweb /
sd-dhcp-client: don't pass around 'secs'
[elogind.git] / src / libsystemd-network / sd-dhcp-client.c
index 07b27d53939db5e60ed4188c166f86179e989526..5d8efbbd3f7c16c33b93fdc1873ecb0ccf4633df 100644 (file)
@@ -46,7 +46,10 @@ struct sd_dhcp_client {
         size_t req_opts_allocated;
         size_t req_opts_size;
         be32_t last_addr;
-        struct ether_addr mac_addr;
+        struct {
+                uint8_t type;
+                struct ether_addr mac_addr;
+        } _packed_ client_id;
         uint32_t xid;
         usec_t start_time;
         uint16_t secs;
@@ -152,7 +155,8 @@ int sd_dhcp_client_set_mac(sd_dhcp_client *client,
                         addr->ether_addr_octet[4],
                         addr->ether_addr_octet[5]);
 
-        memcpy(&client->mac_addr, addr, ETH_ALEN);
+        memcpy(&client->client_id.mac_addr, addr, ETH_ALEN);
+        client->client_id.type = 0x01;
 
         return 0;
 }
@@ -178,15 +182,13 @@ static int client_notify(sd_dhcp_client *client, int event) {
         return 0;
 }
 
-static int client_stop(sd_dhcp_client *client, int error) {
+static int client_initialize(sd_dhcp_client *client) {
         assert_return(client, -EINVAL);
 
         client->receive_message =
                 sd_event_source_unref(client->receive_message);
 
-        if (client->fd >= 0)
-                close(client->fd);
-        client->fd = -1;
+        client->fd = safe_close(client->fd);
 
         client->timeout_resend = sd_event_source_unref(client->timeout_resend);
 
@@ -196,26 +198,38 @@ static int client_stop(sd_dhcp_client *client, int error) {
 
         client->attempt = 1;
 
-        client_notify(client, error);
-
         client->start_time = 0;
         client->secs = 0;
         client->state = DHCP_STATE_INIT;
+        client->xid = 0;
 
         if (client->lease)
                 client->lease = sd_dhcp_lease_unref(client->lease);
 
+        return 0;
+}
+
+static int client_stop(sd_dhcp_client *client, int error) {
+        assert_return(client, -EINVAL);
+
+        client_notify(client, error);
+
+        client_initialize(client);
+
         log_dhcp_client(client, "STOPPED");
 
         return 0;
 }
 
 static int client_message_init(sd_dhcp_client *client, DHCPMessage *message,
-                               uint8_t type, uint16_t secs, uint8_t **opt,
-                               size_t *optlen) {
+                               uint8_t type, uint8_t **opt, size_t *optlen) {
         int r;
 
-        assert(secs);
+        assert(client);
+        assert(client->secs);
+        assert(message);
+        assert(opt);
+        assert(optlen);
 
         r = dhcp_message_init(message, BOOTREQUEST, client->xid, type, opt,
                               optlen);
@@ -224,9 +238,9 @@ static int client_message_init(sd_dhcp_client *client, DHCPMessage *message,
 
         /* Although 'secs' field is a SHOULD in RFC 2131, certain DHCP servers
            refuse to issue an DHCP lease if 'secs' is set to zero */
-        message->secs = htobe16(secs);
+        message->secs = htobe16(client->secs);
 
-        memcpy(&message->chaddr, &client->mac_addr, ETH_ALEN);
+        memcpy(&message->chaddr, &client->client_id.mac_addr, ETH_ALEN);
 
         if (client->state == DHCP_STATE_RENEWING ||
             client->state == DHCP_STATE_REBINDING)
@@ -235,7 +249,7 @@ static int client_message_init(sd_dhcp_client *client, DHCPMessage *message,
         /* Some DHCP servers will refuse to issue an DHCP lease if the Client
            Identifier option is not set */
         r = dhcp_option_append(opt, optlen, DHCP_OPTION_CLIENT_IDENTIFIER,
-                               ETH_ALEN, &client->mac_addr);
+                               sizeof(client->client_id), &client->client_id);
         if (r < 0)
                 return r;
 
@@ -264,52 +278,71 @@ static int client_message_init(sd_dhcp_client *client, DHCPMessage *message,
         return 0;
 }
 
-static int client_send_discover(sd_dhcp_client *client, uint16_t secs) {
-        int err = 0;
+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,
+                                      INADDR_BROADCAST, DHCP_PORT_SERVER, len);
+
+        return dhcp_network_send_raw_socket(client->fd, &client->link,
+                                            packet, len);
+}
+
+static int client_send_discover(sd_dhcp_client *client) {
         _cleanup_free_ DHCPPacket *discover;
         size_t optlen, len;
         uint8_t *opt;
+        usec_t time_now;
+        int r;
+
+        assert(client);
+
+        r = sd_event_get_now_monotonic(client->event, &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 */
+        client->secs = ((time_now - client->start_time) / USEC_PER_SEC) ? : 1;
 
         optlen = DHCP_MIN_OPTIONS_SIZE;
         len = sizeof(DHCPPacket) + optlen;
 
         discover = malloc0(len);
-
         if (!discover)
                 return -ENOMEM;
 
-        err = client_message_init(client, &discover->dhcp, DHCP_DISCOVER,
-                                  secs, &opt, &optlen);
-        if (err < 0)
-                return err;
+        r = client_message_init(client, &discover->dhcp, DHCP_DISCOVER,
+                                &opt, &optlen);
+        if (r < 0)
+                return r;
 
         if (client->last_addr != INADDR_ANY) {
-                err = dhcp_option_append(&opt, &optlen,
+                r = dhcp_option_append(&opt, &optlen,
                                          DHCP_OPTION_REQUESTED_IP_ADDRESS,
                                          4, &client->last_addr);
-                if (err < 0)
-                        return err;
+                if (r < 0)
+                        return r;
         }
 
-        err = dhcp_option_append(&opt, &optlen, DHCP_OPTION_END, 0, NULL);
-        if (err < 0)
-                return err;
-
-        dhcp_packet_append_ip_headers(discover, len);
+        r = dhcp_option_append(&opt, &optlen, DHCP_OPTION_END, 0, NULL);
+        if (r < 0)
+                return r;
 
-        err = dhcp_network_send_raw_socket(client->fd, &client->link,
-                                           discover, len);
+        r = dhcp_client_send_raw(client, discover, len - optlen);
+        if (r < 0)
+                return r;
 
         log_dhcp_client(client, "DISCOVER");
 
-        return err;
+        return 0;
 }
 
-static int client_send_request(sd_dhcp_client *client, uint16_t secs) {
+static int client_send_request(sd_dhcp_client *client) {
         _cleanup_free_ DHCPPacket *request;
         size_t optlen, len;
-        int err;
         uint8_t *opt;
+        int r;
 
         optlen = DHCP_MIN_OPTIONS_SIZE;
         len = sizeof(DHCPPacket) + optlen;
@@ -318,65 +351,82 @@ static int client_send_request(sd_dhcp_client *client, uint16_t secs) {
         if (!request)
                 return -ENOMEM;
 
-        err = client_message_init(client, &request->dhcp, DHCP_REQUEST, secs,
-                                  &opt, &optlen);
-        if (err < 0)
-                return err;
+        r = client_message_init(client, &request->dhcp, DHCP_REQUEST, &opt,
+                                &optlen);
+        if (r < 0)
+                return r;
+
+        switch (client->state) {
 
-        if (client->state == DHCP_STATE_REQUESTING) {
-                err = dhcp_option_append(&opt, &optlen,
+        case DHCP_STATE_INIT_REBOOT:
+                r = dhcp_option_append(&opt, &optlen,
                                          DHCP_OPTION_REQUESTED_IP_ADDRESS,
-                                         4, &client->lease->address);
-                if (err < 0)
-                        return err;
-
-                err = dhcp_option_append(&opt, &optlen,
-                                         DHCP_OPTION_SERVER_IDENTIFIER,
-                                         4, &client->lease->server_address);
-                if (err < 0)
-                        return err;
+                                         4, &client->last_addr);
+                if (r < 0)
+                        return r;
+                break;
+
+        case DHCP_STATE_REQUESTING:
+                r = dhcp_option_append(&opt, &optlen,
+                                       DHCP_OPTION_REQUESTED_IP_ADDRESS,
+                                       4, &client->lease->address);
+                if (r < 0)
+                        return r;
+
+                r = dhcp_option_append(&opt, &optlen,
+                                       DHCP_OPTION_SERVER_IDENTIFIER,
+                                       4, &client->lease->server_address);
+                if (r < 0)
+                        return r;
+                break;
+
+        case DHCP_STATE_INIT:
+        case DHCP_STATE_SELECTING:
+        case DHCP_STATE_REBOOTING:
+        case DHCP_STATE_BOUND:
+        case DHCP_STATE_RENEWING:
+        case DHCP_STATE_REBINDING:
+
+                break;
         }
 
-        err = dhcp_option_append(&opt, &optlen, DHCP_OPTION_END, 0, NULL);
-        if (err < 0)
-                return err;
+        r = dhcp_option_append(&opt, &optlen, DHCP_OPTION_END, 0, NULL);
+        if (r < 0)
+                return r;
 
         if (client->state == DHCP_STATE_RENEWING) {
-                err = dhcp_network_send_udp_socket(client->fd,
-                                                   client->lease->server_address,
-                                                   DHCP_PORT_SERVER,
-                                                   &request->dhcp,
-                                                   len - DHCP_IP_UDP_SIZE);
+                r = dhcp_network_send_udp_socket(client->fd,
+                                                 client->lease->server_address,
+                                                 DHCP_PORT_SERVER,
+                                                 &request->dhcp,
+                                                 len - optlen - DHCP_IP_UDP_SIZE);
         } else {
-                dhcp_packet_append_ip_headers(request, len);
-
-                err = dhcp_network_send_raw_socket(client->fd, &client->link,
-                                                   request, len);
+                r = dhcp_client_send_raw(client, request, len - optlen);
         }
+        if (r < 0)
+                return r;
 
         log_dhcp_client(client, "REQUEST");
 
-        return err;
-}
-
-static uint16_t client_update_secs(sd_dhcp_client *client, usec_t time_now)
-{
-        client->secs = ((time_now - client->start_time) / USEC_PER_SEC) ? : 1;
-
-        return client->secs;
+        return 0;
 }
 
 static int client_timeout_resend(sd_event_source *s, uint64_t usec,
                                  void *userdata) {
         sd_dhcp_client *client = userdata;
         usec_t next_timeout = 0;
+        uint64_t time_now;
         uint32_t time_left;
-        int r = 0;
+        int r;
 
         assert(s);
         assert(client);
         assert(client->event);
 
+        r = sd_event_get_now_monotonic(client->event, &time_now);
+        if (r < 0)
+                goto error;
+
         switch (client->state) {
         case DHCP_STATE_RENEWING:
 
@@ -384,7 +434,7 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec,
                 if (time_left < 60)
                         time_left = 60;
 
-                next_timeout = usec + time_left * USEC_PER_SEC;
+                next_timeout = time_now + time_left * USEC_PER_SEC;
 
                 break;
 
@@ -394,12 +444,18 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec,
                 if (time_left < 60)
                         time_left = 60;
 
-                next_timeout = usec + time_left * USEC_PER_SEC;
+                next_timeout = time_now + time_left * USEC_PER_SEC;
                 break;
 
+        case DHCP_STATE_REBOOTING:
+                /* start over as we did not receive a timely ack or nak */
+                client->state = DHCP_STATE_INIT;
+                client->attempt = 1;
+                client->xid = random_u32();
+
+                /* fall through */
         case DHCP_STATE_INIT:
         case DHCP_STATE_INIT_REBOOT:
-        case DHCP_STATE_REBOOTING:
         case DHCP_STATE_SELECTING:
         case DHCP_STATE_REQUESTING:
         case DHCP_STATE_BOUND:
@@ -407,7 +463,7 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec,
                 if (client->attempt < 64)
                         client->attempt *= 2;
 
-                next_timeout = usec + (client->attempt - 1) * USEC_PER_SEC;
+                next_timeout = time_now + (client->attempt - 1) * USEC_PER_SEC;
 
                 break;
         }
@@ -431,10 +487,7 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec,
 
         switch (client->state) {
         case DHCP_STATE_INIT:
-
-                client_update_secs(client, usec);
-
-                r = client_send_discover(client, client->secs);
+                r = client_send_discover(client);
                 if (r >= 0) {
                         client->state = DHCP_STATE_SELECTING;
                         client->attempt = 1;
@@ -446,26 +499,27 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec,
                 break;
 
         case DHCP_STATE_SELECTING:
-                client_update_secs(client, usec);
-
-                r = client_send_discover(client, client->secs);
+                r = client_send_discover(client);
                 if (r < 0 && client->attempt >= 64)
                         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, client->secs);
+                r = client_send_request(client);
                 if (r < 0 && client->attempt >= 64)
                          goto error;
 
-                client->request_sent = usec;
+                if (client->state == DHCP_STATE_INIT_REBOOT)
+                        client->state = DHCP_STATE_REBOOTING;
+
+                client->request_sent = time_now;
 
                 break;
 
-        case DHCP_STATE_INIT_REBOOT:
         case DHCP_STATE_REBOOTING:
         case DHCP_STATE_BOUND:
 
@@ -483,8 +537,7 @@ error:
 }
 
 static int client_initialize_events(sd_dhcp_client *client,
-                                    sd_event_io_handler_t io_callback,
-                                    usec_t usec) {
+                                    sd_event_io_handler_t io_callback) {
         int r;
 
         assert(client);
@@ -504,8 +557,7 @@ static int client_initialize_events(sd_dhcp_client *client,
         client->timeout_resend = sd_event_source_unref(client->timeout_resend);
 
         r = sd_event_add_monotonic(client->event,
-                                   &client->timeout_resend,
-                                   usec, 0,
+                                   &client->timeout_resend, 0, 0,
                                    client_timeout_resend, client);
         if (r < 0)
                 goto error;
@@ -521,13 +573,46 @@ error:
 
 }
 
+static int client_start(sd_dhcp_client *client) {
+        int r;
+
+        assert_return(client, -EINVAL);
+        assert_return(client->event, -EINVAL);
+        assert_return(client->index > 0, -EINVAL);
+        assert_return(client->fd < 0, -EBUSY);
+        assert_return(client->xid == 0, -EINVAL);
+        assert_return(client->state == DHCP_STATE_INIT ||
+                      client->state == DHCP_STATE_INIT_REBOOT, -EBUSY);
+
+        client->xid = random_u32();
+
+        r = dhcp_network_bind_raw_socket(client->index, &client->link);
+
+        if (r < 0) {
+                client_stop(client, r);
+                return r;
+        }
+
+        client->fd = r;
+        client->start_time = now(CLOCK_MONOTONIC);
+        client->secs = 0;
+
+        log_dhcp_client(client, "STARTED");
+
+        return client_initialize_events(client, client_receive_message_raw);
+}
+
 static int client_timeout_expire(sd_event_source *s, uint64_t usec,
                                  void *userdata) {
         sd_dhcp_client *client = userdata;
 
         log_dhcp_client(client, "EXPIRED");
 
-        client_stop(client, DHCP_EVENT_EXPIRED);
+        client_notify(client, DHCP_EVENT_EXPIRED);
+
+        /* start over as the lease was lost */
+        client_initialize(client);
+        client_start(client);
 
         return 0;
 }
@@ -536,12 +621,8 @@ static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata)
         sd_dhcp_client *client = userdata;
         int r;
 
-        if (client->fd >= 0) {
-                client->receive_message =
-                        sd_event_source_unref(client->receive_message);
-                close(client->fd);
-                client->fd = -1;
-        }
+        client->receive_message = sd_event_source_unref(client->receive_message);
+        client->fd = safe_close(client->fd);
 
         client->state = DHCP_STATE_REBINDING;
         client->attempt = 1;
@@ -556,8 +637,7 @@ static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata)
 
         log_dhcp_client(client, "TIMEOUT T2");
 
-        return client_initialize_events(client, client_receive_message_raw,
-                                        usec);
+        return client_initialize_events(client, client_receive_message_raw);
 }
 
 static int client_timeout_t1(sd_event_source *s, uint64_t usec,
@@ -580,7 +660,7 @@ static int client_timeout_t1(sd_event_source *s, uint64_t usec,
 
         log_dhcp_client(client, "TIMEOUT T1");
 
-        return client_initialize_events(client, client_receive_message_udp, usec);
+        return client_initialize_events(client, client_receive_message_udp);
 }
 
 static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *offer,
@@ -673,6 +753,10 @@ static int client_set_lease_timeouts(sd_dhcp_client *client, uint64_t usec) {
         assert(client);
         assert(client->event);
 
+        /* don't set timers for infinite leases */
+        if (client->lease->lifetime == 0xffffffff)
+                return 0;
+
         if (client->lease->lifetime < 10)
                 return -EINVAL;
 
@@ -689,10 +773,10 @@ static int client_set_lease_timeouts(sd_dhcp_client *client, uint64_t usec) {
                 return -EINVAL;
 
         r = sd_event_add_monotonic(client->event,
-                                     &client->timeout_t1,
-                                     next_timeout,
-                                     10 * USEC_PER_MSEC,
-                                     client_timeout_t1, client);
+                                   &client->timeout_t1,
+                                   next_timeout,
+                                   10 * USEC_PER_MSEC,
+                                   client_timeout_t1, client);
         if (r < 0)
                 return r;
 
@@ -716,10 +800,10 @@ static int client_set_lease_timeouts(sd_dhcp_client *client, uint64_t usec) {
                 return -EINVAL;
 
         r = sd_event_add_monotonic(client->event,
-                                     &client->timeout_t2,
-                                     next_timeout,
-                                     10 * USEC_PER_MSEC,
-                                     client_timeout_t2, client);
+                                   &client->timeout_t2,
+                                   next_timeout,
+                                   10 * USEC_PER_MSEC,
+                                   client_timeout_t2, client);
         if (r < 0)
                 return r;
 
@@ -734,9 +818,9 @@ static int client_set_lease_timeouts(sd_dhcp_client *client, uint64_t usec) {
                 return -EINVAL;
 
         r = sd_event_add_monotonic(client->event,
-                                     &client->timeout_expire, next_timeout,
-                                     10 * USEC_PER_MSEC,
-                                     client_timeout_expire, client);
+                                   &client->timeout_expire, next_timeout,
+                                   10 * USEC_PER_MSEC,
+                                   client_timeout_expire, client);
         if (r < 0)
                 return r;
 
@@ -774,8 +858,8 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message,
                 return 0;
         }
 
-        if (memcmp(&message->chaddr[0], &client->mac_addr.ether_addr_octet,
-                   ETHER_ADDR_LEN)) {
+        if (memcmp(&message->chaddr[0], &client->client_id.mac_addr,
+                   ETH_ALEN)) {
                 log_dhcp_client(client, "received chaddr does not match "
                                 "expected: ignoring");
                 return 0;
@@ -794,9 +878,8 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message,
                         client->attempt = 1;
 
                         r = sd_event_add_monotonic(client->event,
-                                                   &client->timeout_resend,
-                                                   time_now, 0,
-                                                   client_timeout_resend,
+                                                   &client->timeout_resend, 0,
+                                                   0, client_timeout_resend,
                                                    client);
                         if (r < 0)
                                 goto error;
@@ -809,20 +892,37 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message,
 
                 break;
 
+        case DHCP_STATE_REBOOTING:
         case DHCP_STATE_REQUESTING:
         case DHCP_STATE_RENEWING:
         case DHCP_STATE_REBINDING:
 
                 r = client_handle_ack(client, message, len);
 
-                if (r == DHCP_EVENT_NO_LEASE)
+                if (r == DHCP_EVENT_NO_LEASE) {
+
+                        client->timeout_resend =
+                                sd_event_source_unref(client->timeout_resend);
+
+                        if (client->state == DHCP_STATE_REBOOTING) {
+                                r = client_initialize(client);
+                                if (r < 0)
+                                        goto error;
+
+                                r = client_start(client);
+                                if (r < 0)
+                                        goto error;
+                        }
+
                         goto error;
+                }
 
                 if (r >= 0) {
                         client->timeout_resend =
                                 sd_event_source_unref(client->timeout_resend);
 
-                        if (client->state == DHCP_STATE_REQUESTING)
+                        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 = r;
@@ -841,8 +941,7 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message,
 
                         client->receive_message =
                                 sd_event_source_unref(client->receive_message);
-                        close(client->fd);
-                        client->fd = -1;
+                        client->fd = safe_close(client->fd);
                 }
 
                 r = 0;
@@ -851,7 +950,6 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message,
 
         case DHCP_STATE_INIT:
         case DHCP_STATE_INIT_REBOOT:
-        case DHCP_STATE_REBOOTING:
         case DHCP_STATE_BOUND:
 
                 break;
@@ -960,28 +1058,15 @@ int sd_dhcp_client_start(sd_dhcp_client *client) {
         int r;
 
         assert_return(client, -EINVAL);
-        assert_return(client->event, -EINVAL);
-        assert_return(client->index > 0, -EINVAL);
-        assert_return(client->state == DHCP_STATE_INIT ||
-                      client->state == DHCP_STATE_INIT_REBOOT, -EBUSY);
-
-        client->xid = random_u32();
-
-        r = dhcp_network_bind_raw_socket(client->index, &client->link);
 
-        if (r < 0) {
-                client_stop(client, r);
+        r = client_initialize(client);
+        if (r < 0)
                 return r;
-        }
 
-        client->fd = r;
-        client->start_time = now(CLOCK_MONOTONIC);
-        client->secs = 0;
-
-        log_dhcp_client(client, "STARTED");
+        if (client->last_addr)
+                client->state = DHCP_STATE_INIT_REBOOT;
 
-        return client_initialize_events(client, client_receive_message_raw,
-                                        client->start_time);
+        return client_start(client);
 }
 
 int sd_dhcp_client_stop(sd_dhcp_client *client) {