+ r = dhcp6_lease_get_preference(client->lease, &pref_lease);
+
+ if (r < 0 || pref_advertise > pref_lease) {
+ sd_dhcp6_lease_unref(client->lease);
+ client->lease = lease;
+ lease = NULL;
+ 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;
+
+ assert(s);
+ assert(client);
+ assert(client->event);
+
+ r = ioctl(fd, FIONREAD, &buflen);
+ if (r < 0 || buflen <= 0)
+ buflen = DHCP6_MIN_OPTIONS_SIZE;
+
+ message = malloc0(buflen);
+ if (!message)
+ return -ENOMEM;
+
+ len = read(fd, message, buflen);
+ if ((size_t)len < sizeof(DHCP6Message)) {
+ log_dhcp6_client(client, "could not receive message from UDP socket: %m");
+ return 0;
+ }
+
+ switch(message->type) {
+ case DHCP6_SOLICIT:
+ case DHCP6_REQUEST:
+ case DHCP6_CONFIRM:
+ case DHCP6_RENEW:
+ case DHCP6_REBIND:
+ case DHCP6_RELEASE:
+ case DHCP6_DECLINE:
+ case DHCP6_INFORMATION_REQUEST:
+ case DHCP6_RELAY_FORW:
+ case DHCP6_RELAY_REPL:
+ return 0;
+
+ case DHCP6_ADVERTISE:
+ case DHCP6_REPLY:
+ case DHCP6_RECONFIGURE:
+ break;
+
+ default:
+ log_dhcp6_client(client, "unknown message type %d",
+ message->type);
+ return 0;
+ }
+
+ if (client->transaction_id != (message->transaction_id &
+ htobe32(0x00ffffff)))
+ return 0;
+
+ switch (client->state) {
+ case DHCP6_STATE_INFORMATION_REQUEST:
+ r = client_receive_reply(client, message, len);
+ if (r < 0)
+ return 0;
+
+ client_notify(client, DHCP6_EVENT_INFORMATION_REQUEST);
+
+ client_start(client, DHCP6_STATE_STOPPED);
+
+ break;
+
+ case DHCP6_STATE_SOLICITATION:
+ r = client_receive_advertise(client, message, len);
+
+ if (r == DHCP6_STATE_REQUEST) {
+ client_start(client, r);
+
+ 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;
+
+ 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:
+ return 0;
+ }
+
+ if (r >= 0) {
+ log_dhcp6_client(client, "Recv %s",
+ dhcp6_message_type_to_string(message->type));
+ }
+
+ return 0;
+}
+
+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);
+ assert_return(client->index > 0, -EINVAL);
+ assert_return(client->state != state, -EINVAL);
+
+ log_dhcp6_client(client, "client state %d new state %d", client->state, state);
+ client->timeout_resend_expire =
+ sd_event_source_unref(client->timeout_resend_expire);
+ client->timeout_resend = sd_event_source_unref(client->timeout_resend);
+ client->retransmit_time = 0;
+ client->retransmit_count = 0;
+
+ if (client->state == DHCP6_STATE_STOPPED) {
+ time_now = now(clock_boottime_or_monotonic());
+ } else {
+ r = sd_event_now(client->event, clock_boottime_or_monotonic(),
+ &time_now);
+ if (r < 0)
+ return r;
+ }
+
+ switch (state) {
+ case DHCP6_STATE_STOPPED:
+ if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
+ client->state = DHCP6_STATE_STOPPED;
+
+ return 0;
+ }
+
+ /* fall through */
+ case DHCP6_STATE_SOLICITATION:
+ client->state = DHCP6_STATE_SOLICITATION;
+
+ break;
+
+ case DHCP6_STATE_INFORMATION_REQUEST:
+ case DHCP6_STATE_REQUEST:
+ case DHCP6_STATE_RENEW:
+ case DHCP6_STATE_REBIND:
+
+ client->state = state;
+
+ break;
+
+ case DHCP6_STATE_BOUND:
+
+ 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_boottime_or_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;
+
+ r = sd_event_source_set_description(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",
+ format_timespan(time_string,
+ FORMAT_TIMESPAN_MAX,
+ timeout, 0));
+
+ r = sd_event_add_time(client->event,
+ &client->lease->ia.timeout_t2,
+ clock_boottime_or_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;
+
+ r = sd_event_source_set_description(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);
+ client->transaction_start = time_now;