diff --git a/src/dhcp/client.c b/src/dhcp/client.c index 30ad0e7..3d04cd1 100644 --- a/src/dhcp/client.c +++ b/src/dhcp/client.c @@ -2,7 +2,7 @@ * * DHCP client library with GLib integration * - * Copyright (C) 2009-2012 Intel Corporation. All rights reserved. + * Copyright (C) 2009-2014 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -46,11 +46,11 @@ #include "common.h" #include "ipv4ll.h" -#define DISCOVER_TIMEOUT 3 -#define DISCOVER_RETRIES 10 +#define DISCOVER_TIMEOUT 5 +#define DISCOVER_RETRIES 6 -#define REQUEST_TIMEOUT 3 -#define REQUEST_RETRIES 5 +#define REQUEST_TIMEOUT 5 +#define REQUEST_RETRIES 3 typedef enum _listen_mode { L_NONE, @@ -61,6 +61,7 @@ typedef enum _listen_mode { typedef enum _dhcp_client_state { INIT_SELECTING, + REBOOTING, REQUESTING, BOUND, RENEWING, @@ -99,8 +100,10 @@ struct _GDHCPClient { uint8_t ack_retry_times; uint8_t conflicts; guint timeout; + guint t1_timeout; + guint t2_timeout; + guint lease_timeout; guint listener_watch; - GIOChannel *listener_channel; GList *require_list; GList *request_list; GHashTable *code_value_hash; @@ -270,6 +273,25 @@ static int32_t get_time_diff(struct timeval *tv) return hsec; } +static void remove_timeouts(GDHCPClient *dhcp_client) +{ + + if (dhcp_client->timeout > 0) + g_source_remove(dhcp_client->timeout); + if (dhcp_client->t1_timeout > 0) + g_source_remove(dhcp_client->t1_timeout); + if (dhcp_client->t2_timeout > 0) + g_source_remove(dhcp_client->t2_timeout); + if (dhcp_client->lease_timeout > 0) + g_source_remove(dhcp_client->lease_timeout); + + dhcp_client->timeout = 0; + dhcp_client->t1_timeout = 0; + dhcp_client->t2_timeout = 0; + dhcp_client->lease_timeout = 0; + +} + static void add_dhcpv6_request_options(GDHCPClient *dhcp_client, struct dhcpv6_packet *packet, unsigned char *buf, int max_buf, @@ -437,63 +459,37 @@ static int send_discover(GDHCPClient *dhcp_client, uint32_t requested) MAC_BCAST_ADDR, dhcp_client->ifindex); } -static int send_select(GDHCPClient *dhcp_client) +static int send_request(GDHCPClient *dhcp_client) { struct dhcp_packet packet; - - debug(dhcp_client, "sending DHCP select request"); + debug(dhcp_client, "sending DHCP request"); init_packet(dhcp_client, &packet, DHCPREQUEST); packet.xid = dhcp_client->xid; packet.secs = dhcp_attempt_secs(dhcp_client); - dhcp_add_option_uint32(&packet, DHCP_REQUESTED_IP, - dhcp_client->requested_ip); - dhcp_add_option_uint32(&packet, DHCP_SERVER_ID, - dhcp_client->server_ip); + if (dhcp_client->state == REQUESTING || dhcp_client->state == REBOOTING) + dhcp_add_option_uint32(&packet, DHCP_REQUESTED_IP, + dhcp_client->requested_ip); + + if (dhcp_client->state == REQUESTING) + dhcp_add_option_uint32(&packet, DHCP_SERVER_ID, + dhcp_client->server_ip); + + dhcp_add_option_uint16(&packet, DHCP_MAX_SIZE, 576); add_request_options(dhcp_client, &packet); add_send_options(dhcp_client, &packet); - return dhcp_send_raw_packet(&packet, INADDR_ANY, CLIENT_PORT, - INADDR_BROADCAST, SERVER_PORT, - MAC_BCAST_ADDR, dhcp_client->ifindex); -} + if (dhcp_client->state == RENEWING || dhcp_client->state == REBINDING) + packet.ciaddr = htonl(dhcp_client->requested_ip); -static int send_renew(GDHCPClient *dhcp_client) -{ - struct dhcp_packet packet; - - debug(dhcp_client, "sending DHCP renew request"); - - init_packet(dhcp_client , &packet, DHCPREQUEST); - packet.xid = dhcp_client->xid; - packet.ciaddr = htonl(dhcp_client->requested_ip); - - add_request_options(dhcp_client, &packet); - - add_send_options(dhcp_client, &packet); - - return dhcp_send_kernel_packet(&packet, - dhcp_client->requested_ip, CLIENT_PORT, - dhcp_client->server_ip, SERVER_PORT); -} - -static int send_rebound(GDHCPClient *dhcp_client) -{ - struct dhcp_packet packet; - - debug(dhcp_client, "sending DHCP rebound request"); - - init_packet(dhcp_client , &packet, DHCPREQUEST); - packet.xid = dhcp_client->xid; - packet.ciaddr = htonl(dhcp_client->requested_ip); - - add_request_options(dhcp_client, &packet); - - add_send_options(dhcp_client, &packet); + if (dhcp_client->state == RENEWING) + return dhcp_send_kernel_packet(&packet, + dhcp_client->requested_ip, CLIENT_PORT, + dhcp_client->server_ip, SERVER_PORT); return dhcp_send_raw_packet(&packet, INADDR_ANY, CLIENT_PORT, INADDR_BROADCAST, SERVER_PORT, @@ -573,9 +569,7 @@ static gboolean send_announce_packet(gpointer dhcp_data) dhcp_client->requested_ip, dhcp_client->ifindex); - if (dhcp_client->timeout > 0) - g_source_remove(dhcp_client->timeout); - dhcp_client->timeout = 0; + remove_timeouts(dhcp_client); if (dhcp_client->state == IPV4LL_DEFEND) { dhcp_client->timeout = @@ -1167,7 +1161,6 @@ GDHCPClient *g_dhcp_client_new(GDHCPType type, get_interface_mac_address(ifindex, dhcp_client->mac_address); dhcp_client->listener_sockfd = -1; - dhcp_client->listener_channel = NULL; dhcp_client->listen_mode = L_NONE; dhcp_client->ref_count = 1; dhcp_client->type = type; @@ -1257,7 +1250,7 @@ static int dhcp_l2_socket(int ifindex) fd = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_IP)); if (fd < 0) - return fd; + return -errno; if (SERVER_PORT == 67 && CLIENT_PORT == 68) /* Use only if standard ports are in use */ @@ -1270,8 +1263,9 @@ static int dhcp_l2_socket(int ifindex) sock.sll_ifindex = ifindex; if (bind(fd, (struct sockaddr *) &sock, sizeof(sock)) != 0) { + int err = -errno; close(fd); - return -errno; + return err; } return fd; @@ -1350,10 +1344,7 @@ static void ipv4ll_start(GDHCPClient *dhcp_client) guint timeout; int seed; - if (dhcp_client->timeout > 0) { - g_source_remove(dhcp_client->timeout); - dhcp_client->timeout = 0; - } + remove_timeouts(dhcp_client); switch_listening_mode(dhcp_client, L_NONE); dhcp_client->retry_times = 0; @@ -1379,8 +1370,7 @@ static void ipv4ll_stop(GDHCPClient *dhcp_client) switch_listening_mode(dhcp_client, L_NONE); - if (dhcp_client->timeout > 0) - g_source_remove(dhcp_client->timeout); + remove_timeouts(dhcp_client); if (dhcp_client->listener_watch > 0) { g_source_remove(dhcp_client->listener_watch); @@ -1533,7 +1523,6 @@ static int switch_listening_mode(GDHCPClient *dhcp_client, if (dhcp_client->listen_mode != L_NONE) { if (dhcp_client->listener_watch > 0) g_source_remove(dhcp_client->listener_watch); - dhcp_client->listener_channel = NULL; dhcp_client->listen_mode = L_NONE; dhcp_client->listener_sockfd = -1; dhcp_client->listener_watch = 0; @@ -1570,7 +1559,6 @@ static int switch_listening_mode(GDHCPClient *dhcp_client, dhcp_client->listen_mode = listen_mode; dhcp_client->listener_sockfd = listener_sockfd; - dhcp_client->listener_channel = listener_channel; g_io_channel_set_close_on_unref(listener_channel, TRUE); dhcp_client->listener_watch = @@ -1578,7 +1566,7 @@ static int switch_listening_mode(GDHCPClient *dhcp_client, G_IO_IN | G_IO_NVAL | G_IO_ERR | G_IO_HUP, listener_event, dhcp_client, NULL); - g_io_channel_unref(dhcp_client->listener_channel); + g_io_channel_unref(listener_channel); return 0; } @@ -1600,7 +1588,7 @@ static void start_request(GDHCPClient *dhcp_client) switch_listening_mode(dhcp_client, L2); } - send_select(dhcp_client); + send_request(dhcp_client); dhcp_client->timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH, REQUEST_TIMEOUT, @@ -1631,10 +1619,7 @@ static void restart_dhcp(GDHCPClient *dhcp_client, int retry_times) { debug(dhcp_client, "restart DHCP (retries %d)", retry_times); - if (dhcp_client->timeout > 0) { - g_source_remove(dhcp_client->timeout); - dhcp_client->timeout = 0; - } + remove_timeouts(dhcp_client); dhcp_client->retry_times = retry_times; dhcp_client->requested_ip = 0; @@ -1644,94 +1629,106 @@ static void restart_dhcp(GDHCPClient *dhcp_client, int retry_times) g_dhcp_client_start(dhcp_client, dhcp_client->last_address); } -static gboolean start_rebound_timeout(gpointer user_data) +static gboolean start_expire(gpointer user_data) { GDHCPClient *dhcp_client = user_data; - debug(dhcp_client, "start rebound timeout"); + debug(dhcp_client, "lease expired"); + + /*remove all timeouts if they are set*/ + remove_timeouts(dhcp_client); + + restart_dhcp(dhcp_client, 0); + + /* ip need to be cleared */ + if (dhcp_client->lease_lost_cb) + dhcp_client->lease_lost_cb(dhcp_client, + dhcp_client->lease_lost_data); + + return false; +} + +static gboolean continue_rebound(gpointer user_data) +{ + GDHCPClient *dhcp_client = user_data; switch_listening_mode(dhcp_client, L2); + send_request(dhcp_client); - dhcp_client->lease_seconds >>= 1; + if (dhcp_client->t2_timeout> 0) + g_source_remove(dhcp_client->t2_timeout); - /* We need to have enough time to receive ACK package*/ - if (dhcp_client->lease_seconds <= 6) { - - /* ip need to be cleared */ - if (dhcp_client->lease_lost_cb) - dhcp_client->lease_lost_cb(dhcp_client, - dhcp_client->lease_lost_data); - - restart_dhcp(dhcp_client, 0); - } else { - send_rebound(dhcp_client); - - dhcp_client->timeout = - g_timeout_add_seconds_full(G_PRIORITY_HIGH, - dhcp_client->lease_seconds >> 1, - start_rebound_timeout, - dhcp_client, - NULL); + /*recalculate remaining rebind time*/ + dhcp_client->T2 >>= 1; + if (dhcp_client->T2 > 60) { + dhcp_client->t2_timeout = + g_timeout_add_full(G_PRIORITY_HIGH, + dhcp_client->T2 * 1000 + (rand() % 2000) - 1000, + continue_rebound, + dhcp_client, + NULL); } return FALSE; } -static void start_rebound(GDHCPClient *dhcp_client) +static gboolean start_rebound(gpointer user_data) { - debug(dhcp_client, "start rebound"); + GDHCPClient *dhcp_client = user_data; + /*remove renew timer*/ + if (dhcp_client->t1_timeout > 0) + g_source_remove(dhcp_client->t1_timeout); + + debug(dhcp_client, "start rebound"); dhcp_client->state = REBINDING; - dhcp_client->timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH, - dhcp_client->lease_seconds >> 1, - start_rebound_timeout, - dhcp_client, - NULL); + /*calculate total rebind time*/ + dhcp_client->T2 = dhcp_client->expire - dhcp_client->T2; + + /*send the first rebound and reschedule*/ + continue_rebound(user_data); + + return FALSE; } -static gboolean start_renew_request_timeout(gpointer user_data) +static gboolean continue_renew (gpointer user_data) { GDHCPClient *dhcp_client = user_data; - debug(dhcp_client, "renew request timeout"); - - if (dhcp_client->no_lease_cb) - dhcp_client->no_lease_cb(dhcp_client, - dhcp_client->no_lease_data); - - return false; -} - -static gboolean start_renew_timeout(gpointer user_data) -{ - GDHCPClient *dhcp_client = user_data; - - debug(dhcp_client, "start renew timeout"); - - dhcp_client->state = RENEWING; - - dhcp_client->lease_seconds >>= 1; - switch_listening_mode(dhcp_client, L3); - if (dhcp_client->lease_seconds <= 60) - start_rebound(dhcp_client); - else { - send_renew(dhcp_client); + send_request(dhcp_client); - if (dhcp_client->timeout > 0) - g_source_remove(dhcp_client->timeout); + if (dhcp_client->t1_timeout > 0) + g_source_remove(dhcp_client->t1_timeout); - dhcp_client->timeout = - g_timeout_add_seconds_full(G_PRIORITY_HIGH, - REQUEST_TIMEOUT, - start_renew_request_timeout, - dhcp_client, - NULL); + dhcp_client->T1 >>= 1; + + if (dhcp_client->T1 > 60) { + dhcp_client->t1_timeout = g_timeout_add_full(G_PRIORITY_HIGH, + dhcp_client->T1 * 1000 + (rand() % 2000) - 1000, + continue_renew, + dhcp_client, + NULL); } return FALSE; } +static gboolean start_renew(gpointer user_data) +{ + GDHCPClient *dhcp_client = user_data; + + debug(dhcp_client, "start renew"); + dhcp_client->state = RENEWING; + + /*calculate total renew period*/ + dhcp_client->T1 = dhcp_client->T2 - dhcp_client->T1; + + /*send first renew and reschedule for half the remaining time.*/ + continue_renew(user_data); + + return FALSE; +} static void start_bound(GDHCPClient *dhcp_client) { @@ -1739,12 +1736,30 @@ static void start_bound(GDHCPClient *dhcp_client) dhcp_client->state = BOUND; - if (dhcp_client->timeout > 0) - g_source_remove(dhcp_client->timeout); + remove_timeouts(dhcp_client); - dhcp_client->timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH, - dhcp_client->lease_seconds >> 1, - start_renew_timeout, dhcp_client, + /* + *TODO: T1 and T2 should be set through options instead of + * defaults as they are here. + */ + + dhcp_client->T1 = dhcp_client->lease_seconds >> 1; + dhcp_client->T2 = dhcp_client->lease_seconds * 0.875; + dhcp_client->expire = dhcp_client->lease_seconds; + + dhcp_client->t1_timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH, + dhcp_client->T1, + start_renew, dhcp_client, + NULL); + + dhcp_client->t2_timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH, + dhcp_client->T2, + start_rebound, dhcp_client, + NULL); + + dhcp_client->lease_timeout= g_timeout_add_seconds_full(G_PRIORITY_HIGH, + dhcp_client->expire, + start_expire, dhcp_client, NULL); } @@ -1754,10 +1769,14 @@ static gboolean restart_dhcp_timeout(gpointer user_data) debug(dhcp_client, "restart DHCP timeout"); - dhcp_client->ack_retry_times++; - - restart_dhcp(dhcp_client, dhcp_client->ack_retry_times); - + if (dhcp_client->state == REBOOTING) { + g_free(dhcp_client->last_address); + dhcp_client->last_address = NULL; + restart_dhcp(dhcp_client, 0); + } else { + dhcp_client->ack_retry_times++; + restart_dhcp(dhcp_client, dhcp_client->ack_retry_times); + } return FALSE; } @@ -2315,7 +2334,7 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition, if (*message_type != DHCPOFFER) return TRUE; - g_source_remove(dhcp_client->timeout); + remove_timeouts(dhcp_client); dhcp_client->timeout = 0; dhcp_client->retry_times = 0; @@ -2328,15 +2347,14 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition, start_request(dhcp_client); return TRUE; + case REBOOTING: case REQUESTING: case RENEWING: case REBINDING: if (*message_type == DHCPACK) { dhcp_client->retry_times = 0; - if (dhcp_client->timeout > 0) - g_source_remove(dhcp_client->timeout); - dhcp_client->timeout = 0; + remove_timeouts(dhcp_client); dhcp_client->lease_seconds = get_lease(&packet); @@ -2347,6 +2365,12 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition, g_free(dhcp_client->assigned_ip); dhcp_client->assigned_ip = get_ip(packet.yiaddr); + if (dhcp_client->state == REBOOTING) { + option = dhcp_get_option(&packet, + DHCP_SERVER_ID); + dhcp_client->server_ip = get_be32(option); + } + /* Address should be set up here */ if (dhcp_client->lease_available_cb) dhcp_client->lease_available_cb(dhcp_client, @@ -2356,8 +2380,7 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition, } else if (*message_type == DHCPNAK) { dhcp_client->retry_times = 0; - if (dhcp_client->timeout > 0) - g_source_remove(dhcp_client->timeout); + remove_timeouts(dhcp_client); dhcp_client->timeout = g_timeout_add_seconds_full( G_PRIORITY_HIGH, 3, @@ -2563,6 +2586,22 @@ static gboolean discover_timeout(gpointer user_data) return FALSE; } +static gboolean reboot_timeout(gpointer user_data) +{ + GDHCPClient *dhcp_client = user_data; + dhcp_client->retry_times = 0; + dhcp_client->requested_ip = 0; + dhcp_client->state = INIT_SELECTING; + /* + * We do not send the REQUESTED IP option because the server didn't + * respond when we send DHCPREQUEST with the REQUESTED IP option in + * init-reboot state + */ + g_dhcp_client_start(dhcp_client, NULL); + + return FALSE; +} + static gboolean ipv4ll_defend_timeout(gpointer dhcp_data) { GDHCPClient *dhcp_client = dhcp_data; @@ -2751,6 +2790,21 @@ int g_dhcp_client_start(GDHCPClient *dhcp_client, const char *last_address) dhcp_client->last_address = g_strdup(last_address); } } + + if ((addr != 0) && (dhcp_client->type != G_DHCP_IPV4LL)) { + debug(dhcp_client, "DHCP client start with state init_reboot"); + dhcp_client->requested_ip = addr; + dhcp_client->state = REBOOTING; + send_request(dhcp_client); + + dhcp_client->timeout = g_timeout_add_seconds_full( + G_PRIORITY_HIGH, + REQUEST_TIMEOUT, + reboot_timeout, + dhcp_client, + NULL); + return 0; + } send_discover(dhcp_client, addr); dhcp_client->timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH, @@ -2771,18 +2825,13 @@ void g_dhcp_client_stop(GDHCPClient *dhcp_client) send_release(dhcp_client, dhcp_client->server_ip, dhcp_client->requested_ip); - if (dhcp_client->timeout > 0) { - g_source_remove(dhcp_client->timeout); - dhcp_client->timeout = 0; - } + remove_timeouts(dhcp_client); if (dhcp_client->listener_watch > 0) { g_source_remove(dhcp_client->listener_watch); dhcp_client->listener_watch = 0; } - dhcp_client->listener_channel = NULL; - dhcp_client->retry_times = 0; dhcp_client->ack_retry_times = 0; @@ -2917,6 +2966,7 @@ char *g_dhcp_client_get_netmask(GDHCPClient *dhcp_client) if (option) return g_strdup(option->data); case INIT_SELECTING: + case REBOOTING: case REQUESTING: case RELEASED: case IPV4LL_PROBE: diff --git a/src/dhcp/common.c b/src/dhcp/common.c index 91897b1..969bf32 100644 --- a/src/dhcp/common.c +++ b/src/dhcp/common.c @@ -1,7 +1,7 @@ /* * DHCP library with GLib integration * - * Copyright (C) 2007-2012 Intel Corporation. All rights reserved. + * Copyright (C) 2007-2013 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -34,8 +34,8 @@ #include #include #include -#include #include +#include #include "gdhcp.h" #include "common.h" @@ -173,10 +173,7 @@ uint8_t *dhcpv6_get_option(struct dhcpv6_packet *packet, uint16_t pkt_len, if (opt_code == code) { if (option_len) *option_len = opt_len; - if (rem < 0) - goto bad_packet; - else - found = optionptr + 2 + 2; + found = optionptr + 2 + 2; count++; } @@ -372,34 +369,6 @@ void dhcpv6_init_header(struct dhcpv6_packet *packet, uint8_t type) packet->transaction_id[2] = id & 0xff; } -static bool check_vendor(uint8_t *option_vendor, const char *vendor) -{ - uint8_t vendor_length = sizeof(vendor) - 1; - - if (option_vendor[OPT_LEN - OPT_DATA] != vendor_length) - return false; - - if (memcmp(option_vendor, vendor, vendor_length) != 0) - return false; - - return true; -} - -static void check_broken_vendor(struct dhcp_packet *packet) -{ - uint8_t *vendor; - - if (packet->op != BOOTREQUEST) - return; - - vendor = dhcp_get_option(packet, DHCP_VENDOR); - if (!vendor) - return; - - if (check_vendor(vendor, "MSFT 98")) - packet->flags |= htons(BROADCAST_FLAG); -} - int dhcp_recv_l3_packet(struct dhcp_packet *packet, int fd) { int n; @@ -413,8 +382,6 @@ int dhcp_recv_l3_packet(struct dhcp_packet *packet, int fd) if (packet->cookie != htonl(DHCP_MAGIC)) return -EPROTO; - check_broken_vendor(packet); - return n; } @@ -557,6 +524,8 @@ int dhcp_send_raw_packet(struct dhcp_packet *dhcp_pkt, if (fd < 0) return -errno; + dhcp_pkt->flags |= htons(BROADCAST_FLAG); + memset(&dest, 0, sizeof(dest)); memset(&packet, 0, sizeof(packet)); packet.data = *dhcp_pkt; diff --git a/src/dhcp/gdhcp.h b/src/dhcp/gdhcp.h index 404ad33..bed4b6a 100644 --- a/src/dhcp/gdhcp.h +++ b/src/dhcp/gdhcp.h @@ -2,7 +2,7 @@ * * DHCP library with GLib integration * - * Copyright (C) 2009-2012 Intel Corporation. All rights reserved. + * Copyright (C) 2009-2013 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as diff --git a/src/dhcp/ipv4ll.c b/src/dhcp/ipv4ll.c index 033ef81..9bf52b0 100644 --- a/src/dhcp/ipv4ll.c +++ b/src/dhcp/ipv4ll.c @@ -122,10 +122,13 @@ int ipv4ll_arp_socket(int ifindex) { int fd; struct sockaddr_ll sock; + fd = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_ARP)); if (fd < 0) return fd; + memset(&sock, 0, sizeof(sock)); + sock.sll_family = AF_PACKET; sock.sll_protocol = htons(ETH_P_ARP); sock.sll_ifindex = ifindex;