+ client_append_ip_headers(discover, len);
+
+ err = dhcp_network_send_raw_socket(client->fd, &client->link,
+ discover, len);
+
+ return err;
+}
+
+static int client_send_request(sd_dhcp_client *client, uint16_t secs)
+{
+ _cleanup_free_ DHCPPacket *request;
+ size_t optlen, len;
+ int err;
+ uint8_t *opt;
+
+ optlen = DHCP_CLIENT_MIN_OPTIONS_SIZE;
+ len = DHCP_MESSAGE_SIZE + optlen;
+
+ request = malloc0(len);
+ if (!request)
+ return -ENOMEM;
+
+ err = client_packet_init(client, DHCP_REQUEST, &request->dhcp, secs,
+ &opt, &optlen);
+ if (err < 0)
+ return err;
+
+ if (client->state == DHCP_STATE_REQUESTING) {
+ err = 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;
+ }
+
+ err = dhcp_option_append(&opt, &optlen, DHCP_OPTION_END, 0, NULL);
+ if (err < 0)
+ return err;
+
+ if (client->state == DHCP_STATE_RENEWING) {
+ err = dhcp_network_send_udp_socket(client->fd,
+ client->lease->server_address,
+ &request->dhcp,
+ len - DHCP_IP_UDP_SIZE);
+ } else {
+ client_append_ip_headers(request, len);
+
+ err = dhcp_network_send_raw_socket(client->fd, &client->link,
+ request, len);
+ }
+
+ return err;
+}
+
+static int client_timeout_resend(sd_event_source *s, uint64_t usec,
+ void *userdata)
+{
+ sd_dhcp_client *client = userdata;
+ usec_t next_timeout = 0;
+ uint32_t time_left;
+ uint16_t secs;
+ int err = 0;
+
+ switch (client->state) {
+ case DHCP_STATE_RENEWING:
+
+ time_left = (client->lease->t2 - client->lease->t1)/2;
+ if (time_left < 60)
+ time_left = 60;
+
+ next_timeout = usec + time_left * USEC_PER_SEC;
+
+ break;
+
+ case DHCP_STATE_REBINDING:
+
+ time_left = (client->lease->lifetime - client->lease->t2)/2;
+ if (time_left < 60)
+ time_left = 60;
+
+ next_timeout = usec + time_left * USEC_PER_SEC;
+ break;
+
+ 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:
+
+ if (client->attempt < 64)
+ client->attempt *= 2;
+
+ next_timeout = usec + (client->attempt - 1) * USEC_PER_SEC;
+
+ break;
+ }
+
+ next_timeout += (random_u32() & 0x1fffff);
+
+ err = sd_event_add_monotonic(client->event, next_timeout,
+ 10 * USEC_PER_MSEC,
+ client_timeout_resend, client,
+ &client->timeout_resend);
+ if (err < 0)
+ goto error;
+
+ secs = (usec - client->start_time) / USEC_PER_SEC;
+
+ switch (client->state) {
+ case DHCP_STATE_INIT:
+ err = client_send_discover(client, secs);
+ if (err >= 0) {
+ client->state = DHCP_STATE_SELECTING;
+ client->attempt = 1;
+ } else {
+ if (client->attempt >= 64)
+ goto error;
+ }
+
+ break;
+
+ case DHCP_STATE_SELECTING:
+ err = client_send_discover(client, secs);
+ if (err < 0 && client->attempt >= 64)
+ goto error;
+
+ break;
+
+ case DHCP_STATE_REQUESTING:
+ case DHCP_STATE_RENEWING:
+ case DHCP_STATE_REBINDING:
+ err = client_send_request(client, secs);
+ if (err < 0 && client->attempt >= 64)
+ goto error;
+
+ client->request_sent = usec;
+
+ break;
+
+ case DHCP_STATE_INIT_REBOOT:
+ case DHCP_STATE_REBOOTING:
+ case DHCP_STATE_BOUND:
+
+ break;
+ }
+
+ return 0;
+
+error:
+ client_stop(client, err);
+
+ /* Errors were dealt with when stopping the client, don't spill
+ errors into the event loop handler */
+ return 0;
+}
+
+static int client_initialize_events(sd_dhcp_client *client, usec_t usec)
+{
+ int r;
+
+ r = sd_event_add_io(client->event, client->fd, EPOLLIN,
+ client_receive_message, client,
+ &client->receive_message);
+ if (r < 0)
+ goto error;
+
+ r = sd_event_add_monotonic(client->event, usec, 0,
+ client_timeout_resend, client,
+ &client->timeout_resend);
+
+error:
+ if (r < 0)
+ client_stop(client, r);
+
+ return 0;
+
+}
+
+static int client_timeout_expire(sd_event_source *s, uint64_t usec,
+ void *userdata)
+{
+ sd_dhcp_client *client = userdata;
+
+ client_stop(client, DHCP_EVENT_EXPIRED);
+
+ return 0;
+}
+
+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->state = DHCP_STATE_REBINDING;
+ client->attempt = 1;
+
+ r = dhcp_network_bind_raw_socket(client->index, &client->link);
+ if (r < 0) {
+ client_stop(client, r);
+ return 0;
+ }
+
+ client->fd = r;
+
+ return client_initialize_events(client, usec);
+}
+
+static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata)
+{
+ sd_dhcp_client *client = userdata;
+ int r;
+
+ client->state = DHCP_STATE_RENEWING;
+ client->attempt = 1;
+
+ r = dhcp_network_bind_udp_socket(client->index,
+ client->lease->address);
+ if (r < 0) {
+ client_stop(client, r);
+ return 0;
+ }
+
+ client->fd = r;
+
+ return client_initialize_events(client, usec);
+}
+
+static int client_parse_offer(uint8_t code, uint8_t len, const uint8_t *option,
+ void *user_data)
+{
+ DHCPLease *lease = user_data;
+ be32_t val;
+
+ switch(code) {
+
+ case DHCP_OPTION_IP_ADDRESS_LEASE_TIME:
+ if (len == 4) {
+ memcpy(&val, option, 4);
+ lease->lifetime = be32toh(val);
+ }
+
+ break;
+
+ case DHCP_OPTION_SERVER_IDENTIFIER:
+ if (len >= 4)
+ memcpy(&lease->server_address, option, 4);
+
+ break;
+
+ case DHCP_OPTION_SUBNET_MASK:
+ if (len >= 4)
+ memcpy(&lease->subnet_mask, option, 4);
+
+ break;
+
+ case DHCP_OPTION_ROUTER:
+ if (len >= 4)
+ memcpy(&lease->router, option, 4);
+
+ break;
+
+ case DHCP_OPTION_DOMAIN_NAME_SERVER:
+ if (len >= 4)
+ memcpy(&lease->dns, option, 4);
+
+ break;
+
+ case DHCP_OPTION_RENEWAL_T1_TIME:
+ if (len == 4) {
+ memcpy(&val, option, 4);
+ lease->t1 = be32toh(val);
+ }
+
+ break;
+
+ case DHCP_OPTION_REBINDING_T2_TIME:
+ if (len == 4) {
+ memcpy(&val, option, 4);
+ lease->t2 = be32toh(val);
+ }
+
+ break;
+ }
+
+ return 0;
+}
+
+static int client_verify_headers(sd_dhcp_client *client, DHCPPacket *message,
+ size_t len)
+{
+ size_t hdrlen;
+
+ if (len < (DHCP_IP_UDP_SIZE + DHCP_MESSAGE_SIZE))
+ return -EINVAL;
+
+ hdrlen = message->ip.ihl * 4;
+ if (hdrlen < 20 || hdrlen > len || client_checksum(&message->ip,
+ hdrlen))
+ return -EINVAL;
+
+ message->ip.check = message->udp.len;
+ message->ip.ttl = 0;
+
+ if (hdrlen + be16toh(message->udp.len) > len ||
+ client_checksum(&message->ip.ttl, be16toh(message->udp.len) + 12))
+ return -EINVAL;
+
+ if (be16toh(message->udp.source) != DHCP_PORT_SERVER ||
+ be16toh(message->udp.dest) != DHCP_PORT_CLIENT)
+ return -EINVAL;
+
+ if (message->dhcp.op != BOOTREPLY)
+ return -EINVAL;
+
+ if (be32toh(message->dhcp.xid) != client->xid)
+ return -EINVAL;
+
+ if (memcmp(&message->dhcp.chaddr[0], &client->mac_addr.ether_addr_octet,
+ ETHER_ADDR_LEN))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int client_receive_offer(sd_dhcp_client *client, DHCPPacket *offer,
+ size_t len)
+{
+ int err;
+ DHCPLease *lease;
+
+ err = client_verify_headers(client, offer, len);
+ if (err < 0)
+ return err;
+
+ lease = new0(DHCPLease, 1);
+ if (!lease)
+ return -ENOMEM;