chiark / gitweb /
sd-dhcp6-client: Add Option Request Option support
[elogind.git] / src / libsystemd-network / sd-dhcp6-client.c
index 1a59cc286179d8007efd3b622212d30ca426608d..5d65603dc229dc3331ab887695c03d0e8be89b0b 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",
@@ -95,6 +104,12 @@ const char * dhcp6_message_status_table[_DHCP6_STATUS_MAX] = {
 
 DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_status, int);
 
+DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp6_client*, sd_dhcp6_client_unref);
+#define _cleanup_dhcp6_client_unref_ _cleanup_(sd_dhcp6_client_unrefp)
+
+#define DHCP6_CLIENT_DONT_DESTROY(client) \
+        _cleanup_dhcp6_client_unref_ _unused_ sd_dhcp6_client *_dont_destroy_##client = sd_dhcp6_client_ref(client)
+
 static int client_start(sd_dhcp6_client *client, enum DHCP6State state);
 
 int sd_dhcp6_client_set_callback(sd_dhcp6_client *client,
@@ -131,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);
@@ -143,25 +189,18 @@ int sd_dhcp6_client_get_lease(sd_dhcp6_client *client, sd_dhcp6_lease **ret) {
         return 0;
 }
 
-static sd_dhcp6_client *client_notify(sd_dhcp6_client *client, int event) {
-        if (client->cb) {
-                client = sd_dhcp6_client_ref(client);
+static void client_notify(sd_dhcp6_client *client, int event) {
+        if (client->cb)
                 client->cb(client, event, client->userdata);
-                client = sd_dhcp6_client_unref(client);
-        }
-
-        return client;
 }
 
-static int client_initialize(sd_dhcp6_client *client)
-{
+static int client_reset(sd_dhcp6_client *client) {
         assert_return(client, -EINVAL);
 
         client->receive_message =
                 sd_event_source_unref(client->receive_message);
 
-        if (client->fd > 0)
-                client->fd = safe_close(client->fd);
+        client->fd = safe_close(client->fd);
 
         client->transaction_id = 0;
 
@@ -181,14 +220,14 @@ static int client_initialize(sd_dhcp6_client *client)
         return 0;
 }
 
-static sd_dhcp6_client *client_stop(sd_dhcp6_client *client, int error) {
-        assert_return(client, NULL);
+static void client_stop(sd_dhcp6_client *client, int error) {
+        DHCP6_CLIENT_DONT_DESTROY(client);
 
-        client = client_notify(client, error);
-        if (client)
-                client_initialize(client);
+        assert(client);
 
-        return client;
+        client_notify(client, error);
+
+        client_reset(client);
 }
 
 static int client_send_message(sd_dhcp6_client *client) {
@@ -213,12 +252,22 @@ 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_CLIENTID,
-                                        sizeof(client->duid), &client->duid);
+                r = dhcp6_option_append_ia(&opt, &optlen, &client->ia_na);
                 if (r < 0)
                         return r;
 
-                r = dhcp6_option_append_ia(&opt, &optlen, &client->ia_na);
+                break;
+
+        case DHCP6_STATE_REQUEST:
+                message->type = DHCP6_REQUEST;
+
+                r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_SERVERID,
+                                        client->lease->serverid_len,
+                                        client->lease->serverid);
+                if (r < 0)
+                        return r;
+
+                r = dhcp6_option_append_ia(&opt, &optlen, &client->lease->ia);
                 if (r < 0)
                         return r;
 
@@ -226,9 +275,21 @@ static int client_send_message(sd_dhcp6_client *client) {
 
         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)
+                return r;
+
         r = dhcp6_network_send_udp_socket(client->fd, &all_servers, message,
                                           len - optlen);
         if (r < 0)
@@ -240,6 +301,38 @@ static int client_send_message(sd_dhcp6_client *client) {
         return 0;
 }
 
+static int client_timeout_t2(sd_event_source *s, uint64_t usec,
+                             void *userdata) {
+        sd_dhcp6_client *client = userdata;
+
+        assert_return(s, -EINVAL);
+        assert_return(client, -EINVAL);
+        assert_return(client->lease, -EINVAL);
+
+        client->lease->ia.timeout_t2 =
+                sd_event_source_unref(client->lease->ia.timeout_t2);
+
+        log_dhcp6_client(client, "Timeout T2");
+
+        return 0;
+}
+
+static int client_timeout_t1(sd_event_source *s, uint64_t usec,
+                             void *userdata) {
+        sd_dhcp6_client *client = userdata;
+
+        assert_return(s, -EINVAL);
+        assert_return(client, -EINVAL);
+        assert_return(client->lease, -EINVAL);
+
+        client->lease->ia.timeout_t1 =
+                sd_event_source_unref(client->lease->ia.timeout_t1);
+
+        log_dhcp6_client(client, "Timeout T1");
+
+        return 0;
+}
+
 static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec,
                                         void *userdata) {
         sd_dhcp6_client *client = userdata;
@@ -264,7 +357,7 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec,
         sd_dhcp6_client *client = userdata;
         usec_t time_now, init_retransmit_time, max_retransmit_time;
         usec_t max_retransmit_duration;
-        uint8_t max_retransmit_count;
+        uint8_t max_retransmit_count = 0;
         char time_string[FORMAT_TIMESPAN_MAX];
 
         assert(s);
@@ -275,6 +368,12 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec,
 
         switch (client->state) {
         case DHCP6_STATE_SOLICITATION:
+
+                if (client->retransmit_count && client->lease) {
+                        client_start(client, DHCP6_STATE_REQUEST);
+                        return 0;
+                }
+
                 init_retransmit_time = DHCP6_SOL_TIMEOUT;
                 max_retransmit_time = DHCP6_SOL_MAX_RT;
                 max_retransmit_count = 0;
@@ -282,8 +381,17 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec,
 
                 break;
 
+        case DHCP6_STATE_REQUEST:
+                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_STOPPED:
         case DHCP6_STATE_RS:
+        case DHCP6_STATE_BOUND:
                 return 0;
         }
 
@@ -373,7 +481,7 @@ 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;
+                _cleanup_udev_device_unref_ struct udev_device *device = NULL;
                 char ifindex_str[2 + DECIMAL_STR_MAX(int)];
 
                 udev = udev_new();
@@ -508,6 +616,32 @@ static int client_parse_message(sd_dhcp6_client *client,
         return r;
 }
 
+static int client_receive_reply(sd_dhcp6_client *client, DHCP6Message *reply,
+                                size_t len)
+{
+        int r;
+        _cleanup_dhcp6_lease_free_ sd_dhcp6_lease *lease = NULL;
+
+        if (reply->type != DHCP6_REPLY)
+                return -EINVAL;
+
+        r = dhcp6_lease_new(&lease);
+        if (r < 0)
+                return -ENOMEM;
+
+        r = client_parse_message(client, reply, len, lease);
+        if (r < 0)
+                return r;
+
+        dhcp6_lease_clear_timers(&client->lease->ia);
+
+        client->lease = sd_dhcp6_lease_unref(client->lease);
+        client->lease = lease;
+        lease = NULL;
+
+        return DHCP6_STATE_BOUND;
+}
+
 static int client_receive_advertise(sd_dhcp6_client *client,
                                     DHCP6Message *advertise, size_t len) {
         int r;
@@ -537,12 +671,16 @@ static int client_receive_advertise(sd_dhcp6_client *client,
                 r = 0;
         }
 
+        if (pref_advertise == 255 || client->retransmit_count > 1)
+                r = DHCP6_STATE_REQUEST;
+
         return r;
 }
 
 static int client_receive_message(sd_event_source *s, int fd, uint32_t revents,
                                   void *userdata) {
         sd_dhcp6_client *client = userdata;
+        DHCP6_CLIENT_DONT_DESTROY(client);
         _cleanup_free_ DHCP6Message *message;
         int r, buflen, len;
 
@@ -560,7 +698,7 @@ static int client_receive_message(sd_event_source *s, int fd, uint32_t revents,
 
         len = read(fd, message, buflen);
         if ((size_t)len < sizeof(DHCP6Message)) {
-                log_dhcp6_client(client, "could not receive message from UDP socket: %s", strerror(errno));
+                log_dhcp6_client(client, "could not receive message from UDP socket: %m");
                 return 0;
         }
 
@@ -596,6 +734,31 @@ 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)
+                        client_start(client, r);
+
+                break;
+
+        case DHCP6_STATE_REQUEST:
+                r = client_receive_reply(client, message, len);
+                if (r < 0)
+                        return 0;
+
+                if (r == DHCP6_STATE_BOUND) {
+
+                        r = client_start(client, DHCP6_STATE_BOUND);
+                        if (r < 0) {
+                                client_stop(client, r);
+                                return 0;
+                        }
+
+                        client_notify(client, DHCP6_EVENT_IP_ACQUIRE);
+                }
+
+                break;
+
+        case DHCP6_STATE_BOUND:
+
                 break;
 
         case DHCP6_STATE_STOPPED:
@@ -614,6 +777,8 @@ static int client_receive_message(sd_event_source *s, int fd, uint32_t revents,
 static int client_start(sd_dhcp6_client *client, enum DHCP6State state)
 {
         int r;
+        usec_t timeout, time_now;
+        char time_string[FORMAT_TIMESPAN_MAX];
 
         assert_return(client, -EINVAL);
         assert_return(client->event, -EINVAL);
@@ -655,6 +820,70 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state)
                 client->state = DHCP6_STATE_SOLICITATION;
 
                 break;
+
+        case DHCP6_STATE_REQUEST:
+
+                client->state = state;
+
+                break;
+
+        case DHCP6_STATE_BOUND:
+
+                r = sd_event_now(client->event, CLOCK_MONOTONIC, &time_now);
+                if (r < 0)
+                        return r;
+
+                if (client->lease->ia.lifetime_t1 == 0xffffffff ||
+                    client->lease->ia.lifetime_t2 == 0xffffffff) {
+
+                        log_dhcp6_client(client, "infinite T1 0x%08x or T2 0x%08x",
+                                         be32toh(client->lease->ia.lifetime_t1),
+                                         be32toh(client->lease->ia.lifetime_t2));
+
+                        return 0;
+                }
+
+                timeout = client_timeout_compute_random(be32toh(client->lease->ia.lifetime_t1) * USEC_PER_SEC);
+
+                log_dhcp6_client(client, "T1 expires in %s",
+                                 format_timespan(time_string,
+                                                 FORMAT_TIMESPAN_MAX,
+                                                 timeout, 0));
+
+                r = sd_event_add_time(client->event,
+                                      &client->lease->ia.timeout_t1,
+                                      CLOCK_MONOTONIC, time_now + timeout,
+                                      10 * USEC_PER_SEC, client_timeout_t1,
+                                      client);
+                if (r < 0)
+                        return r;
+
+                r = sd_event_source_set_priority(client->lease->ia.timeout_t1,
+                                                 client->event_priority);
+                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",
+                                 format_timespan(time_string,
+                                                 FORMAT_TIMESPAN_MAX,
+                                                 timeout, 0));
+
+                r = sd_event_add_time(client->event,
+                                      &client->lease->ia.timeout_t2,
+                                      CLOCK_MONOTONIC, time_now + timeout,
+                                      10 * USEC_PER_SEC, client_timeout_t2,
+                                      client);
+                if (r < 0)
+                        return r;
+
+                r = sd_event_source_set_priority(client->lease->ia.timeout_t2,
+                                                 client->event_priority);
+                if (r < 0)
+                        return r;
+
+                return 0;
         }
 
         client->transaction_id = random_u32() & htobe32(0x00ffffff);
@@ -688,7 +917,7 @@ int sd_dhcp6_client_start(sd_dhcp6_client *client)
         assert_return(client->event, -EINVAL);
         assert_return(client->index > 0, -EINVAL);
 
-        r = client_initialize(client);
+        r = client_reset(client);
         if (r < 0)
                 return r;
 
@@ -740,10 +969,11 @@ sd_dhcp6_client *sd_dhcp6_client_ref(sd_dhcp6_client *client) {
 
 sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client) {
         if (client && REFCNT_DEC(client->n_ref) <= 0) {
-                client_initialize(client);
+                client_reset(client);
 
                 sd_dhcp6_client_detach_event(client);
 
+                free(client->req_opts);
                 free(client);
 
                 return NULL;
@@ -752,14 +982,12 @@ sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client) {
         return client;
 }
 
-DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp6_client*, sd_dhcp6_client_unref);
-#define _cleanup_dhcp6_client_free_ _cleanup_(sd_dhcp6_client_unrefp)
-
 int sd_dhcp6_client_new(sd_dhcp6_client **ret)
 {
-        _cleanup_dhcp6_client_free_ sd_dhcp6_client *client = NULL;
+        _cleanup_dhcp6_client_unref_ sd_dhcp6_client *client = NULL;
         sd_id128_t machine_id;
         int r;
+        size_t t;
 
         assert_return(ret, -EINVAL);
 
@@ -773,6 +1001,8 @@ int sd_dhcp6_client_new(sd_dhcp6_client **ret)
 
         client->index = -1;
 
+        client->fd = -1;
+
         /* initialize DUID */
         client->duid.type = htobe16(DHCP6_DUID_EN);
         client->duid.pen = htobe32(SYSTEMD_PEN);
@@ -786,6 +1016,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;