X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=gdhcp%2Fclient.c;h=3508b1e8f98c875313316ff7530cd0108618f850;hb=98d17b07f1609dd12d14a41f9734b5eae74cc6d0;hp=81fa443783741ed1bd54a785acebdb38e1d22a19;hpb=0029c2ecea7899800a9238ab90b561bc54d83f02;p=framework%2Fconnectivity%2Fconnman.git diff --git a/gdhcp/client.c b/gdhcp/client.c index 81fa443..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,10 +96,14 @@ 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; @@ -268,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; - - err = ioctl(sk, SIOCGIFNAME, &ifr); - if (err < 0) { - perror("Get interface name error"); - goto done; - } + debug(dhcp_client, "sending IPV4LL probe request"); - err = ioctl(sk, SIOCGIFFLAGS, &ifr); - if (err < 0) { - perror("Get interface flags error"); - goto done; + if (dhcp_client->retry_times == 1) { + dhcp_client->state = IPV4LL_PROBE; + switch_listening_mode(dhcp_client, L_ARP); } - - 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) @@ -414,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; @@ -499,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; @@ -579,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) { @@ -600,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); @@ -616,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; @@ -635,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; @@ -654,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); @@ -665,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; } @@ -710,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; @@ -726,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; @@ -755,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, @@ -768,6 +934,8 @@ 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; @@ -791,6 +959,8 @@ static gboolean start_renew_timeout(gpointer user_data) static void start_bound(GDHCPClient *dhcp_client) { + debug(dhcp_client, "start bound"); + dhcp_client->state = BOUND; dhcp_client->timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH, @@ -803,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); @@ -887,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; } @@ -930,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); @@ -963,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; @@ -974,7 +1156,7 @@ 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)", @@ -1059,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; } @@ -1140,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; @@ -1148,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;