X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=gdhcp%2Fclient.c;h=3508b1e8f98c875313316ff7530cd0108618f850;hb=98d17b07f1609dd12d14a41f9734b5eae74cc6d0;hp=45723d7300252b11e925951070a7947ca951ea3a;hpb=0c96e020cd279cba67274d20d7895939e52d1593;p=framework%2Fconnectivity%2Fconnman.git diff --git a/gdhcp/client.c b/gdhcp/client.c index 45723d7..3508b1e 100644 --- a/gdhcp/client.c +++ b/gdhcp/client.c @@ -32,8 +32,8 @@ #include #include +#include #include -#include #include #include @@ -42,17 +42,19 @@ #include "gdhcp.h" #include "common.h" +#include "ipv4ll.h" #define DISCOVER_TIMEOUT 3 -#define DISCOVER_RETRIES 3 +#define DISCOVER_RETRIES 10 #define REQUEST_TIMEOUT 3 -#define REQUEST_RETRIES 3 +#define REQUEST_RETRIES 5 typedef enum _listen_mode { L_NONE, L2, L3, + L_ARP, } ListenMode; typedef enum _dhcp_client_state { @@ -62,6 +64,10 @@ typedef enum _dhcp_client_state { RENEWING, REBINDING, RELEASED, + IPV4LL_PROBE, + IPV4LL_ANNOUNCE, + IPV4LL_MONITOR, + IPV4LL_DEFEND, } ClientState; struct _GDHCPClient { @@ -80,6 +86,7 @@ struct _GDHCPClient { int listener_sockfd; uint8_t retry_times; uint8_t ack_retry_times; + uint8_t conflicts; guint timeout; guint listener_watch; GIOChannel *listener_channel; @@ -89,18 +96,20 @@ struct _GDHCPClient { GHashTable *send_value_hash; GDHCPClientEventFunc lease_available_cb; gpointer lease_available_data; + GDHCPClientEventFunc ipv4ll_available_cb; + gpointer ipv4ll_available_data; GDHCPClientEventFunc no_lease_cb; gpointer no_lease_data; GDHCPClientEventFunc lease_lost_cb; gpointer lease_lost_data; + GDHCPClientEventFunc ipv4ll_lost_cb; + gpointer ipv4ll_lost_data; GDHCPClientEventFunc address_conflict_cb; gpointer address_conflict_data; GDHCPDebugFunc debug_func; gpointer debug_data; }; -static GTimer *timer = NULL; - static inline void debug(GDHCPClient *client, const char *format, ...) { char str[256]; @@ -270,69 +279,82 @@ static int send_release(GDHCPClient *dhcp_client, server, SERVER_PORT); } -static gboolean interface_is_up(int index) +static gboolean ipv4ll_probe_timeout(gpointer dhcp_data); +static int switch_listening_mode(GDHCPClient *dhcp_client, + ListenMode listen_mode); + +static gboolean send_probe_packet(gpointer dhcp_data) { - int sk, err; - struct ifreq ifr; - gboolean ret = FALSE; + GDHCPClient *dhcp_client; + guint timeout; - sk = socket(PF_INET, SOCK_DGRAM, 0); - if (sk < 0) { - perror("Open socket error"); - return FALSE; + dhcp_client = dhcp_data; + /* if requested_ip is not valid, pick a new address*/ + if (dhcp_client->requested_ip == 0) { + debug(dhcp_client, "pick a new random address"); + dhcp_client->requested_ip = ipv4ll_random_ip(0); } - memset(&ifr, 0, sizeof(ifr)); - ifr.ifr_ifindex = index; + debug(dhcp_client, "sending IPV4LL probe request"); - err = ioctl(sk, SIOCGIFNAME, &ifr); - if (err < 0) { - perror("Get interface name error"); - goto done; + if (dhcp_client->retry_times == 1) { + dhcp_client->state = IPV4LL_PROBE; + switch_listening_mode(dhcp_client, L_ARP); } - - err = ioctl(sk, SIOCGIFFLAGS, &ifr); - if (err < 0) { - perror("Get interface flags error"); - goto done; - } - - if (ifr.ifr_flags & IFF_UP) - ret = TRUE; - -done: - close(sk); - - return ret; + ipv4ll_send_arp_packet(dhcp_client->mac_address, 0, + dhcp_client->requested_ip, dhcp_client->ifindex); + + if (dhcp_client->retry_times < PROBE_NUM) { + /*add a random timeout in range of PROBE_MIN to PROBE_MAX*/ + timeout = ipv4ll_random_delay_ms(PROBE_MAX-PROBE_MIN); + timeout += PROBE_MIN*1000; + } else + timeout = (ANNOUNCE_WAIT * 1000); + + dhcp_client->timeout = g_timeout_add_full(G_PRIORITY_HIGH, + timeout, + ipv4ll_probe_timeout, + dhcp_client, + NULL); + return FALSE; } -static char *get_interface_name(int index) +static gboolean ipv4ll_announce_timeout(gpointer dhcp_data); +static gboolean ipv4ll_defend_timeout(gpointer dhcp_data); + +static gboolean send_announce_packet(gpointer dhcp_data) { - struct ifreq ifr; - int sk, err; + GDHCPClient *dhcp_client; - if (index < 0) - return NULL; + dhcp_client = dhcp_data; - sk = socket(PF_INET, SOCK_DGRAM, 0); - if (sk < 0) { - perror("Open socket error"); - return NULL; - } + debug(dhcp_client, "sending IPV4LL announce request"); - memset(&ifr, 0, sizeof(ifr)); - ifr.ifr_ifindex = index; + ipv4ll_send_arp_packet(dhcp_client->mac_address, + dhcp_client->requested_ip, + dhcp_client->requested_ip, + dhcp_client->ifindex); - err = ioctl(sk, SIOCGIFNAME, &ifr); - if (err < 0) { - perror("Get interface name error"); - close(sk); - return NULL; - } - - close(sk); + if (dhcp_client->timeout > 0) + g_source_remove(dhcp_client->timeout); + dhcp_client->timeout = 0; - return g_strdup(ifr.ifr_name); + if (dhcp_client->state == IPV4LL_DEFEND) { + dhcp_client->timeout = + g_timeout_add_seconds_full(G_PRIORITY_HIGH, + DEFEND_INTERVAL, + ipv4ll_defend_timeout, + dhcp_client, + NULL); + return TRUE; + } else + dhcp_client->timeout = + g_timeout_add_seconds_full(G_PRIORITY_HIGH, + ANNOUNCE_INTERVAL, + ipv4ll_announce_timeout, + dhcp_client, + NULL); + return TRUE; } static void get_interface_mac_address(int index, uint8_t *mac_address) @@ -416,8 +438,10 @@ GDHCPClient *g_dhcp_client_new(GDHCPType type, dhcp_client->type = type; dhcp_client->ifindex = ifindex; dhcp_client->lease_available_cb = NULL; + dhcp_client->ipv4ll_available_cb = NULL; dhcp_client->no_lease_cb = NULL; dhcp_client->lease_lost_cb = NULL; + dhcp_client->ipv4ll_lost_cb = NULL; dhcp_client->address_conflict_cb = NULL; dhcp_client->listener_watch = 0; dhcp_client->retry_times = 0; @@ -501,6 +525,7 @@ static int dhcp_l2_socket(int ifindex) setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter_prog, sizeof(filter_prog)); + memset(&sock, 0, sizeof(sock)); sock.sll_family = AF_PACKET; sock.sll_protocol = htons(ETH_P_IP); sock.sll_ifindex = ifindex; @@ -581,6 +606,131 @@ static int dhcp_recv_l2_packet(struct dhcp_packet *dhcp_pkt, int fd) return bytes - (sizeof(packet.ip) + sizeof(packet.udp)); } +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; + } + + switch_listening_mode(dhcp_client, L_NONE); + dhcp_client->type = G_DHCP_IPV4LL; + dhcp_client->retry_times = 0; + dhcp_client->requested_ip = 0; + + /*try to start with a based mac address ip*/ + seed = (dhcp_client->mac_address[4] << 8 | dhcp_client->mac_address[4]); + dhcp_client->requested_ip = ipv4ll_random_ip(seed); + + /*first wait a random delay to avoid storm of arp request on boot*/ + timeout = ipv4ll_random_delay_ms(PROBE_WAIT); + + dhcp_client->retry_times++; + dhcp_client->timeout = g_timeout_add_full(G_PRIORITY_HIGH, + timeout, + send_probe_packet, + dhcp_client, + NULL); +} + +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); + + if (dhcp_client->listener_watch > 0) { + g_source_remove(dhcp_client->listener_watch); + dhcp_client->listener_watch = 0; + } + + dhcp_client->state = IPV4LL_PROBE; + dhcp_client->retry_times = 0; + dhcp_client->requested_ip = 0; + + g_free(dhcp_client->assigned_ip); + dhcp_client->assigned_ip = NULL; +} + +static int ipv4ll_recv_arp_packet(GDHCPClient *dhcp_client) +{ + int bytes; + struct ether_arp arp; + uint32_t ip_requested; + int source_conflict; + int target_conflict; + + memset(&arp, 0, sizeof(arp)); + bytes = 0; + bytes = read(dhcp_client->listener_sockfd, &arp, sizeof(arp)); + if (bytes < 0) + return bytes; + + if (arp.arp_op != htons(ARPOP_REPLY) && + arp.arp_op != htons(ARPOP_REQUEST)) + return -EINVAL; + + ip_requested = ntohl(dhcp_client->requested_ip); + source_conflict = !memcmp(arp.arp_spa, &ip_requested, + sizeof(ip_requested)); + + target_conflict = !memcmp(arp.arp_tpa, &ip_requested, + sizeof(ip_requested)); + + if (!source_conflict && !target_conflict) + return 0; + + dhcp_client->conflicts++; + + debug(dhcp_client, "IPV4LL conflict detected"); + + if (dhcp_client->state == IPV4LL_MONITOR) { + if (!source_conflict) + return 0; + dhcp_client->state = IPV4LL_DEFEND; + debug(dhcp_client, "DEFEND mode conflicts : %d", + dhcp_client->conflicts); + /*Try to defend with a single announce*/ + send_announce_packet(dhcp_client); + return 0; + } + + if (dhcp_client->state == IPV4LL_DEFEND) { + if (!source_conflict) + return 0; + else if (dhcp_client->ipv4ll_lost_cb != NULL) + dhcp_client->ipv4ll_lost_cb(dhcp_client, + dhcp_client->ipv4ll_lost_data); + } + + ipv4ll_stop(dhcp_client); + + if (dhcp_client->conflicts < MAX_CONFLICTS) { + /*restart whole state machine*/ + dhcp_client->retry_times++; + dhcp_client->timeout = + g_timeout_add_full(G_PRIORITY_HIGH, + ipv4ll_random_delay_ms(PROBE_WAIT), + send_probe_packet, + dhcp_client, + NULL); + } + /* Here we got a lot of conflicts, RFC3927 states that we have + * to wait RATE_LIMIT_INTERVAL before retrying, + * but we just report failure. + */ + else if (dhcp_client->no_lease_cb != NULL) + dhcp_client->no_lease_cb(dhcp_client, + dhcp_client->no_lease_data); + + return 0; +} + static gboolean check_package_owner(GDHCPClient *dhcp_client, struct dhcp_packet *packet) { @@ -602,6 +752,9 @@ static gboolean request_timeout(gpointer user_data) { GDHCPClient *dhcp_client = user_data; + debug(dhcp_client, "request timeout (retries %d)", + dhcp_client->retry_times); + dhcp_client->retry_times++; start_request(dhcp_client); @@ -618,6 +771,9 @@ static int switch_listening_mode(GDHCPClient *dhcp_client, GIOChannel *listener_channel; int listener_sockfd; + debug(dhcp_client, "switch listening mode (%d ==> %d)", + dhcp_client->listen_mode, listen_mode); + if (dhcp_client->listen_mode == listen_mode) return 0; @@ -637,6 +793,8 @@ static int switch_listening_mode(GDHCPClient *dhcp_client, else if (listen_mode == L3) listener_sockfd = dhcp_l3_socket(CLIENT_PORT, dhcp_client->interface); + else if (listen_mode == L_ARP) + listener_sockfd = ipv4ll_arp_socket(dhcp_client->ifindex); else return -EIO; @@ -656,8 +814,8 @@ static int switch_listening_mode(GDHCPClient *dhcp_client, g_io_channel_set_close_on_unref(listener_channel, TRUE); dhcp_client->listener_watch = - g_io_add_watch_full(listener_channel, - G_PRIORITY_HIGH, G_IO_IN, + g_io_add_watch_full(listener_channel, G_PRIORITY_HIGH, + 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); @@ -667,12 +825,12 @@ static int switch_listening_mode(GDHCPClient *dhcp_client, static void start_request(GDHCPClient *dhcp_client) { + debug(dhcp_client, "start request (retries %d)", + dhcp_client->retry_times); + if (dhcp_client->retry_times == REQUEST_RETRIES) { dhcp_client->state = INIT_SELECTING; - - if (dhcp_client->no_lease_cb != NULL) - dhcp_client->no_lease_cb(dhcp_client, - dhcp_client->no_lease_data); + ipv4ll_start(dhcp_client); return; } @@ -712,6 +870,8 @@ static uint32_t get_lease(struct dhcp_packet *packet) 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; @@ -728,6 +888,8 @@ static gboolean start_rebound_timeout(gpointer user_data) { GDHCPClient *dhcp_client = user_data; + debug(dhcp_client, "start rebound timeout"); + switch_listening_mode(dhcp_client, L2); dhcp_client->lease_seconds >>= 1; @@ -757,6 +919,8 @@ static gboolean start_rebound_timeout(gpointer user_data) static void start_rebound(GDHCPClient *dhcp_client) { + debug(dhcp_client, "start rebound"); + dhcp_client->state = REBINDING; dhcp_client->timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH, @@ -769,12 +933,8 @@ static void start_rebound(GDHCPClient *dhcp_client) static gboolean start_renew_timeout(gpointer user_data) { GDHCPClient *dhcp_client = user_data; - gdouble elapse; - gulong microseconds; - elapse = g_timer_elapsed(timer, µseconds); - - g_timer_start(timer); + debug(dhcp_client, "start renew timeout"); dhcp_client->state = RENEWING; @@ -799,13 +959,11 @@ static gboolean start_renew_timeout(gpointer user_data) static void start_bound(GDHCPClient *dhcp_client) { - dhcp_client->state = BOUND; + debug(dhcp_client, "start bound"); - if (timer == NULL) - timer = g_timer_new(); + dhcp_client->state = BOUND; - dhcp_client->timeout = - g_timeout_add_seconds_full(G_PRIORITY_HIGH, + dhcp_client->timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH, dhcp_client->lease_seconds >> 1, start_renew_timeout, dhcp_client, NULL); @@ -815,6 +973,8 @@ static gboolean restart_dhcp_timeout(gpointer user_data) { GDHCPClient *dhcp_client = user_data; + debug(dhcp_client, "restart DHCP timeout"); + dhcp_client->ack_retry_times++; restart_dhcp(dhcp_client, dhcp_client->ack_retry_times); @@ -899,20 +1059,26 @@ static char *malloc_option_value_string(uint8_t *option, GDHCPOptionType type) return ret; } -static GList *get_option_value_list(char *value) +static GList *get_option_value_list(char *value, GDHCPOptionType type) { char *pos = value; GList *list = NULL; + if (pos == NULL) + return NULL; + + if (type == OPTION_STRING) + return g_list_append(list, g_strdup(value)); + while ((pos = strchr(pos, ' ')) != NULL) { *pos = '\0'; - list = g_list_append(list, g_strdup(value)); + list = g_list_append(list, g_strdup(value)); value = ++pos; } - list = g_list_append(list, g_strdup(value)); + list = g_list_append(list, g_strdup(value)); return list; } @@ -942,7 +1108,7 @@ static void get_request(GDHCPClient *dhcp_client, struct dhcp_packet *packet) g_hash_table_remove(dhcp_client->code_value_hash, GINT_TO_POINTER((int) code)); - value_list = get_option_value_list(option_value); + value_list = get_option_value_list(option_value, type); g_free(option_value); @@ -975,6 +1141,10 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition, re = dhcp_recv_l2_packet(&packet, dhcp_client->listener_sockfd); else if (dhcp_client->listen_mode == L3) re = dhcp_recv_l3_packet(&packet, dhcp_client->listener_sockfd); + else if (dhcp_client->listen_mode == L_ARP) { + re = ipv4ll_recv_arp_packet(dhcp_client); + return TRUE; + } else re = -EIO; @@ -986,9 +1156,12 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition, message_type = dhcp_get_option(&packet, DHCP_MESSAGE_TYPE); if (message_type == NULL) - /* No message type option, ignore pakcage */ + /* No message type option, ignore package */ return TRUE; + debug(dhcp_client, "received DHCP packet (current state %d)", + dhcp_client->state); + switch (dhcp_client->state) { case INIT_SELECTING: if (*message_type != DHCPOFFER) @@ -1051,6 +1224,9 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition, break; } + debug(dhcp_client, "processed DHCP packet (new state %d)", + dhcp_client->state); + return TRUE; } @@ -1065,15 +1241,73 @@ static gboolean discover_timeout(gpointer user_data) return FALSE; } +static gboolean ipv4ll_defend_timeout(gpointer dhcp_data) +{ + GDHCPClient *dhcp_client = dhcp_data; + + debug(dhcp_client, "back to MONITOR mode"); + + dhcp_client->conflicts = 0; + dhcp_client->state = IPV4LL_MONITOR; + + return FALSE; +} + +static gboolean ipv4ll_announce_timeout(gpointer dhcp_data) +{ + GDHCPClient *dhcp_client = dhcp_data; + uint32_t ip; + + debug(dhcp_client, "request timeout (retries %d)", + dhcp_client->retry_times); + + if (dhcp_client->retry_times != ANNOUNCE_NUM){ + dhcp_client->retry_times++; + send_announce_packet(dhcp_client); + return FALSE; + } + + ip = htonl(dhcp_client->requested_ip); + debug(dhcp_client, "switching to monitor mode"); + dhcp_client->state = IPV4LL_MONITOR; + dhcp_client->assigned_ip = get_ip(ip); + + if (dhcp_client->ipv4ll_available_cb != NULL) + dhcp_client->ipv4ll_available_cb(dhcp_client, + dhcp_client->ipv4ll_available_data); + dhcp_client->conflicts = 0; + + return FALSE; +} + +static gboolean ipv4ll_probe_timeout(gpointer dhcp_data) +{ + + GDHCPClient *dhcp_client = dhcp_data; + + debug(dhcp_client, "IPV4LL probe timeout (retries %d)", + dhcp_client->retry_times); + + if (dhcp_client->retry_times == PROBE_NUM) { + dhcp_client->state = IPV4LL_ANNOUNCE; + dhcp_client->retry_times = 0; + + dhcp_client->retry_times++; + send_announce_packet(dhcp_client); + return FALSE; + } + dhcp_client->retry_times++; + send_probe_packet(dhcp_client); + + return FALSE; +} + int g_dhcp_client_start(GDHCPClient *dhcp_client) { int re; if (dhcp_client->retry_times == DISCOVER_RETRIES) { - if (dhcp_client->no_lease_cb != NULL) - dhcp_client->no_lease_cb(dhcp_client, - dhcp_client->no_lease_data); - + ipv4ll_start(dhcp_client); return 0; } @@ -1146,6 +1380,10 @@ void g_dhcp_client_register_event(GDHCPClient *dhcp_client, dhcp_client->lease_available_cb = func; dhcp_client->lease_available_data = data; return; + case G_DHCP_CLIENT_EVENT_IPV4LL_AVAILABLE: + dhcp_client->ipv4ll_available_cb = func; + dhcp_client->ipv4ll_available_data = data; + return; case G_DHCP_CLIENT_EVENT_NO_LEASE: dhcp_client->no_lease_cb = func; dhcp_client->no_lease_data = data; @@ -1154,6 +1392,10 @@ void g_dhcp_client_register_event(GDHCPClient *dhcp_client, dhcp_client->lease_lost_cb = func; dhcp_client->lease_lost_data = data; return; + case G_DHCP_CLIENT_EVENT_IPV4LL_LOST: + dhcp_client->ipv4ll_lost_cb = func; + dhcp_client->ipv4ll_lost_data = data; + return; case G_DHCP_CLIENT_EVENT_ADDRESS_CONFLICT: dhcp_client->address_conflict_cb = func; dhcp_client->address_conflict_data = data; @@ -1196,32 +1438,15 @@ static uint8_t *alloc_dhcp_option(int code, const char *str, int extra) return storage; } -static const char *get_hostname(const char *host) -{ - char local_host_name[HOST_NAME_MAX + 1]; - - if (g_strcmp0("", host) != 0) - return g_strdup(host); - - if (gethostname(local_host_name, HOST_NAME_MAX) != 0) - return NULL; - - local_host_name[HOST_NAME_MAX] = 0; - - return g_strdup(local_host_name); -} - /* Now only support send hostname */ GDHCPClientError g_dhcp_client_set_send(GDHCPClient *dhcp_client, unsigned char option_code, const char *option_value) { uint8_t *binary_option; - const char *hostname; - - if (option_code == G_DHCP_HOST_NAME) { - hostname = get_hostname(option_value); - binary_option = alloc_dhcp_option(option_code, hostname, 0); + if (option_code == G_DHCP_HOST_NAME && option_value != NULL) { + binary_option = alloc_dhcp_option(option_code, + option_value, 0); g_hash_table_insert(dhcp_client->send_value_hash, GINT_TO_POINTER((int) option_code), binary_option);