chiark / gitweb /
sd-dhcp6-client: properly calculate buffer size when parsing options
[elogind.git] / src / libsystemd-network / sd-dhcp6-client.c
index 928f562df080f03ea73740033c965a39d37ee461..6860c66858bc09d25a62742fe046c724fd36ad36 100644 (file)
@@ -51,6 +51,9 @@ struct sd_dhcp6_client {
         be32_t transaction_id;
         struct sd_dhcp6_lease *lease;
         int fd;
+        be16_t *req_opts;
+        size_t req_opts_allocated;
+        size_t req_opts_len;
         sd_event_source *receive_message;
         usec_t retransmit_time;
         uint8_t retransmit_count;
@@ -66,6 +69,12 @@ struct sd_dhcp6_client {
         } _packed_ duid;
 };
 
+static const uint16_t default_req_opts[] = {
+        DHCP6_OPTION_DNS_SERVERS,
+        DHCP6_OPTION_DOMAIN_LIST,
+        DHCP6_OPTION_NTP_SERVER,
+};
+
 const char * dhcp6_message_type_table[_DHCP6_MESSAGE_MAX] = {
         [DHCP6_SOLICIT] = "SOLICIT",
         [DHCP6_ADVERTISE] = "ADVERTISE",
@@ -137,6 +146,37 @@ int sd_dhcp6_client_set_mac(sd_dhcp6_client *client,
         return 0;
 }
 
+int sd_dhcp6_client_set_request_option(sd_dhcp6_client *client,
+                                       uint16_t option) {
+        size_t t;
+
+        assert_return(client, -EINVAL);
+        assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY);
+
+        switch(option) {
+        case DHCP6_OPTION_DNS_SERVERS:
+        case DHCP6_OPTION_DOMAIN_LIST:
+        case DHCP6_OPTION_SNTP_SERVERS:
+        case DHCP6_OPTION_NTP_SERVER:
+                break;
+
+        default:
+                return -EINVAL;
+        }
+
+        for (t = 0; t < client->req_opts_len; t++)
+                if (client->req_opts[t] == htobe16(option))
+                        return -EEXIST;
+
+        if (!GREEDY_REALLOC(client->req_opts, client->req_opts_allocated,
+                            client->req_opts_len + 1))
+                return -ENOMEM;
+
+        client->req_opts[client->req_opts_len++] = htobe16(option);
+
+        return 0;
+}
+
 int sd_dhcp6_client_get_lease(sd_dhcp6_client *client, sd_dhcp6_lease **ret) {
         assert_return(client, -EINVAL);
         assert_return(ret, -EINVAL);
@@ -212,6 +252,11 @@ static int client_send_message(sd_dhcp6_client *client) {
         case DHCP6_STATE_SOLICITATION:
                 message->type = DHCP6_SOLICIT;
 
+                r = dhcp6_option_append(&opt, &optlen,
+                                        DHCP6_OPTION_RAPID_COMMIT, 0, NULL);
+                if (r < 0)
+                        return r;
+
                 r = dhcp6_option_append_ia(&opt, &optlen, &client->ia_na);
                 if (r < 0)
                         return r;
@@ -219,7 +264,12 @@ static int client_send_message(sd_dhcp6_client *client) {
                 break;
 
         case DHCP6_STATE_REQUEST:
-                message->type = DHCP6_REQUEST;
+        case DHCP6_STATE_RENEW:
+
+                if (client->state == DHCP6_STATE_REQUEST)
+                        message->type = DHCP6_REQUEST;
+                else
+                        message->type = DHCP6_RENEW;
 
                 r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_SERVERID,
                                         client->lease->serverid_len,
@@ -233,12 +283,26 @@ static int client_send_message(sd_dhcp6_client *client) {
 
                 break;
 
+        case DHCP6_STATE_REBIND:
+                message->type = DHCP6_REBIND;
+
+                r = dhcp6_option_append_ia(&opt, &optlen, &client->lease->ia);
+                if (r < 0)
+                        return r;
+
+                break;
+
         case DHCP6_STATE_STOPPED:
-        case DHCP6_STATE_RS:
         case DHCP6_STATE_BOUND:
                 return -EINVAL;
         }
 
+        r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_ORO,
+                                client->req_opts_len * sizeof(be16_t),
+                                client->req_opts);
+        if (r < 0)
+                return r;
+
         r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_CLIENTID,
                                 sizeof(client->duid), &client->duid);
         if (r < 0)
@@ -268,6 +332,8 @@ static int client_timeout_t2(sd_event_source *s, uint64_t usec,
 
         log_dhcp6_client(client, "Timeout T2");
 
+        client_start(client, DHCP6_STATE_REBIND);
+
         return 0;
 }
 
@@ -284,19 +350,30 @@ static int client_timeout_t1(sd_event_source *s, uint64_t usec,
 
         log_dhcp6_client(client, "Timeout T1");
 
+        client_start(client, DHCP6_STATE_RENEW);
+
         return 0;
 }
 
 static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec,
                                         void *userdata) {
         sd_dhcp6_client *client = userdata;
+        DHCP6_CLIENT_DONT_DESTROY(client);
+        enum DHCP6State state;
 
         assert(s);
         assert(client);
         assert(client->event);
 
+        state = client->state;
+
         client_stop(client, DHCP6_EVENT_RESEND_EXPIRE);
 
+        /* RFC 3315, section 18.1.4., says that "...the client may choose to
+           use a Solicit message to locate a new DHCP server..." */
+        if (state == DHCP6_STATE_REBIND)
+                client_start(client, DHCP6_STATE_SOLICITATION);
+
         return 0;
 }
 
@@ -309,10 +386,11 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec,
                                  void *userdata) {
         int r = 0;
         sd_dhcp6_client *client = userdata;
-        usec_t time_now, init_retransmit_time, max_retransmit_time;
-        usec_t max_retransmit_duration;
+        usec_t time_now, init_retransmit_time = 0, max_retransmit_time = 0;
+        usec_t max_retransmit_duration = 0;
         uint8_t max_retransmit_count = 0;
         char time_string[FORMAT_TIMESPAN_MAX];
+        uint32_t expire = 0;
 
         assert(s);
         assert(client);
@@ -330,8 +408,6 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec,
 
                 init_retransmit_time = DHCP6_SOL_TIMEOUT;
                 max_retransmit_time = DHCP6_SOL_MAX_RT;
-                max_retransmit_count = 0;
-                max_retransmit_duration = 0;
 
                 break;
 
@@ -339,12 +415,36 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec,
                 init_retransmit_time = DHCP6_REQ_TIMEOUT;
                 max_retransmit_time = DHCP6_REQ_MAX_RT;
                 max_retransmit_count = DHCP6_REQ_MAX_RC;
-                max_retransmit_duration = 0;
+
+                break;
+
+        case DHCP6_STATE_RENEW:
+                init_retransmit_time = DHCP6_REN_TIMEOUT;
+                max_retransmit_time = DHCP6_REN_MAX_RT;
+
+                /* RFC 3315, section 18.1.3. says max retransmit duration will
+                   be the remaining time until T2. Instead of setting MRD,
+                   wait for T2 to trigger with the same end result */
+
+                break;
+
+        case DHCP6_STATE_REBIND:
+                init_retransmit_time = DHCP6_REB_TIMEOUT;
+                max_retransmit_time = DHCP6_REB_MAX_RT;
+
+                if (!client->timeout_resend_expire) {
+                        r = dhcp6_lease_ia_rebind_expire(&client->lease->ia,
+                                                         &expire);
+                        if (r < 0) {
+                                client_stop(client, r);
+                                return 0;
+                        }
+                        max_retransmit_duration = expire * USEC_PER_SEC;
+                }
 
                 break;
 
         case DHCP6_STATE_STOPPED:
-        case DHCP6_STATE_RS:
         case DHCP6_STATE_BOUND:
                 return 0;
         }
@@ -360,7 +460,7 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec,
                 client->retransmit_count++;
 
 
-        r = sd_event_now(client->event, CLOCK_MONOTONIC, &time_now);
+        r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now);
         if (r < 0)
                 goto error;
 
@@ -384,7 +484,7 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec,
                                          client->retransmit_time, 0));
 
         r = sd_event_add_time(client->event, &client->timeout_resend,
-                              CLOCK_MONOTONIC,
+                              clock_boottime_or_monotonic(),
                               time_now + client->retransmit_time,
                               10 * USEC_PER_MSEC, client_timeout_resend,
                               client);
@@ -396,6 +496,11 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec,
         if (r < 0)
                 goto error;
 
+        r = sd_event_source_set_name(client->timeout_resend,
+                                     "dhcp6-resend-timer");
+        if (r < 0)
+                goto error;
+
         if (max_retransmit_duration && !client->timeout_resend_expire) {
 
                 log_dhcp6_client(client, "Max retransmission duration %"PRIu64" secs",
@@ -403,7 +508,7 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec,
 
                 r = sd_event_add_time(client->event,
                                       &client->timeout_resend_expire,
-                                      CLOCK_MONOTONIC,
+                                      clock_boottime_or_monotonic(),
                                       time_now + max_retransmit_duration,
                                       USEC_PER_SEC,
                                       client_timeout_resend_expire, client);
@@ -414,6 +519,11 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec,
                                                  client->event_priority);
                 if (r < 0)
                         goto error;
+
+                r = sd_event_source_set_name(client->timeout_resend_expire,
+                                             "dhcp6-resend-expire-timer");
+                if (r < 0)
+                        goto error;
         }
 
 error:
@@ -424,6 +534,9 @@ error:
 }
 
 static int client_ensure_iaid(sd_dhcp6_client *client) {
+        /* name is a pointer to memory in the udev_device struct, so must
+           have the same scope */
+        _cleanup_udev_device_unref_ struct udev_device *device = NULL;
         const char *name = NULL;
         uint64_t id;
 
@@ -435,7 +548,6 @@ static int client_ensure_iaid(sd_dhcp6_client *client) {
         if (detect_container(NULL) <= 0) {
                 /* not in a container, udev will be around */
                 _cleanup_udev_unref_ struct udev *udev;
-                _cleanup_udev_device_unref_ struct udev_device *device = NULL;
                 char ifindex_str[2 + DECIMAL_STR_MAX(int)];
 
                 udev = udev_new();
@@ -471,12 +583,15 @@ static int client_parse_message(sd_dhcp6_client *client,
                                 DHCP6Message *message, size_t len,
                                 sd_dhcp6_lease *lease) {
         int r;
-        uint8_t *optval, *option = (uint8_t *)(message + 1), *id = NULL;
+        uint8_t *optval, *option, *id = NULL;
         uint16_t optcode, status;
         size_t optlen, id_len;
         bool clientid = false;
         be32_t iaid_lease;
 
+        option = (uint8_t *)message + sizeof(DHCP6Message);
+        len -= sizeof(DHCP6Message);
+
         while ((r = dhcp6_option_parse(&option, &len, &optcode, &optlen,
                                        &optval)) >= 0) {
                 switch (optcode) {
@@ -553,6 +668,13 @@ static int client_parse_message(sd_dhcp6_client *client,
                         }
 
                         break;
+
+                case DHCP6_OPTION_RAPID_COMMIT:
+                        r = dhcp6_lease_set_rapid_commit(lease);
+                        if (r < 0)
+                                return r;
+
+                        break;
                 }
         }
 
@@ -575,9 +697,10 @@ static int client_receive_reply(sd_dhcp6_client *client, DHCP6Message *reply,
 {
         int r;
         _cleanup_dhcp6_lease_free_ sd_dhcp6_lease *lease = NULL;
+        bool rapid_commit;
 
         if (reply->type != DHCP6_REPLY)
-                return -EINVAL;
+                return 0;
 
         r = dhcp6_lease_new(&lease);
         if (r < 0)
@@ -587,7 +710,17 @@ static int client_receive_reply(sd_dhcp6_client *client, DHCP6Message *reply,
         if (r < 0)
                 return r;
 
-        dhcp6_lease_clear_timers(&client->lease->ia);
+        if (client->state == DHCP6_STATE_SOLICITATION) {
+                r = dhcp6_lease_get_rapid_commit(lease, &rapid_commit);
+                if (r < 0)
+                        return r;
+
+                if (!rapid_commit)
+                        return 0;
+        }
+
+        if (client->lease)
+                dhcp6_lease_clear_timers(&client->lease->ia);
 
         client->lease = sd_dhcp6_lease_unref(client->lease);
         client->lease = lease;
@@ -603,7 +736,7 @@ static int client_receive_advertise(sd_dhcp6_client *client,
         uint8_t pref_advertise = 0, pref_lease = 0;
 
         if (advertise->type != DHCP6_ADVERTISE)
-                return -EINVAL;
+                return 0;
 
         r = dhcp6_lease_new(&lease);
         if (r < 0)
@@ -688,12 +821,17 @@ static int client_receive_message(sd_event_source *s, int fd, uint32_t revents,
         case DHCP6_STATE_SOLICITATION:
                 r = client_receive_advertise(client, message, len);
 
-                if (r == DHCP6_STATE_REQUEST)
+                if (r == DHCP6_STATE_REQUEST) {
                         client_start(client, r);
 
-                break;
+                        break;
+                }
 
+                /* fall through for Soliciation Rapid Commit option check */
         case DHCP6_STATE_REQUEST:
+        case DHCP6_STATE_RENEW:
+        case DHCP6_STATE_REBIND:
+
                 r = client_receive_reply(client, message, len);
                 if (r < 0)
                         return 0;
@@ -716,7 +854,6 @@ static int client_receive_message(sd_event_source *s, int fd, uint32_t revents,
                 break;
 
         case DHCP6_STATE_STOPPED:
-        case DHCP6_STATE_RS:
                 return 0;
         }
 
@@ -747,7 +884,6 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state)
 
         switch (state) {
         case DHCP6_STATE_STOPPED:
-        case DHCP6_STATE_RS:
         case DHCP6_STATE_SOLICITATION:
 
                 r = client_ensure_iaid(client);
@@ -771,11 +907,18 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state)
                 if (r < 0)
                         return r;
 
+                r = sd_event_source_set_name(client->receive_message,
+                                             "dhcp6-receive-message");
+                if (r < 0)
+                        return r;
+
                 client->state = DHCP6_STATE_SOLICITATION;
 
                 break;
 
         case DHCP6_STATE_REQUEST:
+        case DHCP6_STATE_RENEW:
+        case DHCP6_STATE_REBIND:
 
                 client->state = state;
 
@@ -783,7 +926,7 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state)
 
         case DHCP6_STATE_BOUND:
 
-                r = sd_event_now(client->event, CLOCK_MONOTONIC, &time_now);
+                r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now);
                 if (r < 0)
                         return r;
 
@@ -806,7 +949,7 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state)
 
                 r = sd_event_add_time(client->event,
                                       &client->lease->ia.timeout_t1,
-                                      CLOCK_MONOTONIC, time_now + timeout,
+                                      clock_boottime_or_monotonic(), time_now + timeout,
                                       10 * USEC_PER_SEC, client_timeout_t1,
                                       client);
                 if (r < 0)
@@ -817,6 +960,11 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state)
                 if (r < 0)
                         return r;
 
+                r = sd_event_source_set_name(client->lease->ia.timeout_t1,
+                                             "dhcp6-t1-timeout");
+                if (r < 0)
+                        return r;
+
                 timeout = client_timeout_compute_random(be32toh(client->lease->ia.lifetime_t2) * USEC_PER_SEC);
 
                 log_dhcp6_client(client, "T2 expires in %s",
@@ -826,7 +974,7 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state)
 
                 r = sd_event_add_time(client->event,
                                       &client->lease->ia.timeout_t2,
-                                      CLOCK_MONOTONIC, time_now + timeout,
+                                      clock_boottime_or_monotonic(), time_now + timeout,
                                       10 * USEC_PER_SEC, client_timeout_t2,
                                       client);
                 if (r < 0)
@@ -837,13 +985,20 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state)
                 if (r < 0)
                         return r;
 
+                r = sd_event_source_set_name(client->lease->ia.timeout_t2,
+                                             "dhcp6-t2-timeout");
+                if (r < 0)
+                        return r;
+
+                client->state = state;
+
                 return 0;
         }
 
         client->transaction_id = random_u32() & htobe32(0x00ffffff);
 
         r = sd_event_add_time(client->event, &client->timeout_resend,
-                              CLOCK_MONOTONIC, 0, 0, client_timeout_resend,
+                              clock_boottime_or_monotonic(), 0, 0, client_timeout_resend,
                               client);
         if (r < 0)
                 return r;
@@ -853,6 +1008,11 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state)
         if (r < 0)
                 return r;
 
+        r = sd_event_source_set_name(client->timeout_resend,
+                                     "dhcp6-resend-timeout");
+        if (r < 0)
+                return r;
+
         return 0;
 }
 
@@ -927,6 +1087,7 @@ sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client) {
 
                 sd_dhcp6_client_detach_event(client);
 
+                free(client->req_opts);
                 free(client);
 
                 return NULL;
@@ -940,6 +1101,7 @@ int sd_dhcp6_client_new(sd_dhcp6_client **ret)
         _cleanup_dhcp6_client_unref_ sd_dhcp6_client *client = NULL;
         sd_id128_t machine_id;
         int r;
+        size_t t;
 
         assert_return(ret, -EINVAL);
 
@@ -968,6 +1130,15 @@ int sd_dhcp6_client_new(sd_dhcp6_client **ret)
         siphash24(client->duid.id, &machine_id, sizeof(machine_id),
                   HASH_KEY.bytes);
 
+        client->req_opts_len = ELEMENTSOF(default_req_opts);
+
+        client->req_opts = new0(be16_t, client->req_opts_len);
+        if (!client->req_opts)
+                return -ENOMEM;
+
+        for (t = 0; t < client->req_opts_len; t++)
+                client->req_opts[t] = htobe16(default_req_opts[t]);
+
         *ret = client;
         client = NULL;