X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=gdhcp%2Fclient.c;h=99f9951011f5da1cdd8a1b206c516245ab93880b;hb=53f38ce0bc6505c8b8a7342b0243df442b7db5a4;hp=430ecc863878b3a56b04ad53438a089c89777902;hpb=4b819888f4556ed4b4633f7f14a9265a2f478896;p=platform%2Fupstream%2Fconnman.git diff --git a/gdhcp/client.c b/gdhcp/client.c index 430ecc8..99f9951 100644 --- a/gdhcp/client.c +++ b/gdhcp/client.c @@ -2,7 +2,7 @@ * * DHCP client library with GLib integration * - * Copyright (C) 2009-2010 Intel Corporation. All rights reserved. + * Copyright (C) 2009-2011 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 @@ -23,6 +23,7 @@ #include #endif +#define _GNU_SOURCE #include #include #include @@ -32,8 +33,8 @@ #include #include +#include #include -#include #include #include @@ -42,9 +43,10 @@ #include "gdhcp.h" #include "common.h" +#include "ipv4ll.h" #define DISCOVER_TIMEOUT 3 -#define DISCOVER_RETRIES 5 +#define DISCOVER_RETRIES 10 #define REQUEST_TIMEOUT 3 #define REQUEST_RETRIES 5 @@ -53,6 +55,7 @@ typedef enum _listen_mode { L_NONE, L2, L3, + L_ARP, } ListenMode; typedef enum _dhcp_client_state { @@ -62,10 +65,20 @@ typedef enum _dhcp_client_state { RENEWING, REBINDING, RELEASED, + IPV4LL_PROBE, + IPV4LL_ANNOUNCE, + IPV4LL_MONITOR, + IPV4LL_DEFEND, + INFORMATION_REQ, + SOLICITATION, + REQUEST, + RENEW, + REBIND, + RELEASE, } ClientState; struct _GDHCPClient { - gint ref_count; + int ref_count; GDHCPType type; ClientState state; int ifindex; @@ -80,6 +93,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,14 +103,45 @@ 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; + GDHCPClientEventFunc information_req_cb; + gpointer information_req_data; + GDHCPClientEventFunc solicitation_cb; + gpointer solicitation_data; + GDHCPClientEventFunc advertise_cb; + gpointer advertise_data; + GDHCPClientEventFunc request_cb; + gpointer request_data; + GDHCPClientEventFunc renew_cb; + gpointer renew_data; + GDHCPClientEventFunc rebind_cb; + gpointer rebind_data; + GDHCPClientEventFunc release_cb; + gpointer release_data; + char *last_address; + unsigned char *duid; + int duid_len; + unsigned char *server_duid; + int server_duid_len; + uint16_t status_code; + uint32_t iaid; + uint32_t T1, T2; + struct in6_addr ia_na; + struct in6_addr ia_ta; + time_t last_renew; + time_t last_rebind; + time_t expire; }; static inline void debug(GDHCPClient *client, const char *format, ...) @@ -116,12 +161,16 @@ static inline void debug(GDHCPClient *client, const char *format, ...) } /* Initialize the packet with the proper defaults */ -static void init_packet(GDHCPClient *dhcp_client, - struct dhcp_packet *packet, char type) +static void init_packet(GDHCPClient *dhcp_client, gpointer pkt, char type) { - dhcp_init_header(packet, type); + if (dhcp_client->type == G_DHCP_IPV6) + dhcpv6_init_header(pkt, type); + else { + struct dhcp_packet *packet = pkt; - memcpy(packet->chaddr, dhcp_client->mac_address, 6); + dhcp_init_header(packet, type); + memcpy(packet->chaddr, dhcp_client->mac_address, 6); + } } static void add_request_options(GDHCPClient *dhcp_client, @@ -146,6 +195,135 @@ static void add_request_options(GDHCPClient *dhcp_client, } } +struct hash_params { + unsigned char *buf; + int max_buf; + unsigned char **ptr_buf; +}; + +static void add_dhcpv6_binary_option(gpointer key, gpointer value, + gpointer user_data) +{ + uint8_t *option = value; + uint16_t len; + struct hash_params *params = user_data; + + /* option[0][1] contains option code */ + len = option[2] << 8 | option[3]; + + if ((*params->ptr_buf + len + 2 + 2) > (params->buf + params->max_buf)) + return; + + memcpy(*params->ptr_buf, option, len + 2 + 2); + (*params->ptr_buf) += len + 2 + 2; +} + +static void add_dhcpv6_send_options(GDHCPClient *dhcp_client, + unsigned char *buf, int max_buf, + unsigned char **ptr_buf) +{ + struct hash_params params = { + .buf = buf, + .max_buf = max_buf, + .ptr_buf = ptr_buf + }; + + if (dhcp_client->type == G_DHCP_IPV4) + return; + + g_hash_table_foreach(dhcp_client->send_value_hash, + add_dhcpv6_binary_option, ¶ms); + + *ptr_buf = *params.ptr_buf; +} + +static void copy_option(uint8_t *buf, uint16_t code, uint16_t len, + uint8_t *msg) +{ + buf[0] = code >> 8; + buf[1] = code & 0xff; + buf[2] = len >> 8; + buf[3] = len & 0xff; + if (len > 0 && msg != NULL) + memcpy(&buf[4], msg, len); +} + +static void add_dhcpv6_request_options(GDHCPClient *dhcp_client, + struct dhcpv6_packet *packet, + unsigned char *buf, int max_buf, + unsigned char **ptr_buf) +{ + GList *list; + uint16_t code; + int len; + + if (dhcp_client->type == G_DHCP_IPV4) + return; + + for (list = dhcp_client->request_list; list; list = list->next) { + code = (uint16_t) GPOINTER_TO_INT(list->data); + + switch (code) { + case G_DHCPV6_CLIENTID: + if (dhcp_client->duid == NULL) + return; + + len = 2 + 2 + dhcp_client->duid_len; + if ((*ptr_buf + len) > (buf + max_buf)) { + debug(dhcp_client, "Too long dhcpv6 message " + "when writing client id option"); + return; + } + + copy_option(*ptr_buf, G_DHCPV6_CLIENTID, + dhcp_client->duid_len, dhcp_client->duid); + (*ptr_buf) += len; + break; + + case G_DHCPV6_SERVERID: + if (dhcp_client->server_duid == NULL) + return; + + len = 2 + 2 + dhcp_client->server_duid_len; + if ((*ptr_buf + len) > (buf + max_buf)) { + debug(dhcp_client, "Too long dhcpv6 message " + "when writing server id option"); + return; + } + + copy_option(*ptr_buf, G_DHCPV6_SERVERID, + dhcp_client->server_duid_len, + dhcp_client->server_duid); + (*ptr_buf) += len; + break; + + case G_DHCPV6_RAPID_COMMIT: + len = 2 + 2; + if ((*ptr_buf + len) > (buf + max_buf)) { + debug(dhcp_client, "Too long dhcpv6 message " + "when writing rapid commit option"); + return; + } + + copy_option(*ptr_buf, G_DHCPV6_RAPID_COMMIT, 0, 0); + (*ptr_buf) += len; + break; + + case G_DHCPV6_ORO: + break; + + case G_DHCPV6_DNS_SERVERS: + break; + + case G_DHCPV6_SNTP_SERVERS: + break; + + default: + break; + } + } +} + static void add_binary_option(gpointer key, gpointer value, gpointer user_data) { uint8_t *option = value; @@ -190,7 +368,6 @@ static int send_discover(GDHCPClient *dhcp_client, uint32_t requested) static int send_select(GDHCPClient *dhcp_client) { struct dhcp_packet packet; - struct in_addr addr; debug(dhcp_client, "sending DHCP select request"); @@ -206,8 +383,6 @@ static int send_select(GDHCPClient *dhcp_client) add_send_options(dhcp_client, &packet); - addr.s_addr = dhcp_client->requested_ip; - return dhcp_send_raw_packet(&packet, INADDR_ANY, CLIENT_PORT, INADDR_BROADCAST, SERVER_PORT, MAC_BCAST_ADDR, dhcp_client->ifindex); @@ -268,69 +443,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) -{ - struct ifreq ifr; - int sk, err; +static gboolean ipv4ll_announce_timeout(gpointer dhcp_data); +static gboolean ipv4ll_defend_timeout(gpointer dhcp_data); - if (index < 0) - return NULL; +static gboolean send_announce_packet(gpointer dhcp_data) +{ + GDHCPClient *dhcp_client; - sk = socket(PF_INET, SOCK_DGRAM, 0); - if (sk < 0) { - perror("Open socket error"); - return NULL; - } + dhcp_client = dhcp_data; - memset(&ifr, 0, sizeof(ifr)); - ifr.ifr_ifindex = index; + debug(dhcp_client, "sending IPV4LL announce request"); - err = ioctl(sk, SIOCGIFNAME, &ifr); - if (err < 0) { - perror("Get interface name error"); - close(sk); - return NULL; - } + ipv4ll_send_arp_packet(dhcp_client->mac_address, + dhcp_client->requested_ip, + dhcp_client->requested_ip, + dhcp_client->ifindex); - 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) @@ -338,7 +526,7 @@ static void get_interface_mac_address(int index, uint8_t *mac_address) struct ifreq ifr; int sk, err; - sk = socket(PF_INET, SOCK_DGRAM, 0); + sk = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); if (sk < 0) { perror("Open socket error"); return; @@ -365,6 +553,284 @@ done: close(sk); } +int g_dhcpv6_create_duid(GDHCPDuidType duid_type, int index, int type, + unsigned char **duid, int *duid_len) +{ + time_t duid_time; + + switch (duid_type) { + case G_DHCPV6_DUID_LLT: + *duid_len = 2 + 2 + 4 + ETH_ALEN; + *duid = g_try_malloc(*duid_len); + if (*duid == NULL) + return -ENOMEM; + + (*duid)[0] = 0; + (*duid)[1] = 1; + get_interface_mac_address(index, &(*duid)[2 + 2 + 4]); + (*duid)[2] = 0; + (*duid)[3] = type; + duid_time = time(0) - DUID_TIME_EPOCH; + (*duid)[4] = duid_time >> 24; + (*duid)[5] = duid_time >> 16; + (*duid)[6] = duid_time >> 8; + (*duid)[7] = duid_time & 0xff; + break; + case G_DHCPV6_DUID_EN: + return -EINVAL; + case G_DHCPV6_DUID_LL: + *duid_len = 2 + 2 + ETH_ALEN; + *duid = g_try_malloc(*duid_len); + if (*duid == NULL) + return -ENOMEM; + + (*duid)[0] = 0; + (*duid)[1] = 3; + get_interface_mac_address(index, &(*duid)[2 + 2]); + (*duid)[2] = 0; + (*duid)[3] = type; + break; + } + + return 0; +} + +int g_dhcpv6_client_set_duid(GDHCPClient *dhcp_client, unsigned char *duid, + int duid_len) +{ + if (dhcp_client == NULL || dhcp_client->type == G_DHCP_IPV4) + return -EINVAL; + + g_free(dhcp_client->duid); + + dhcp_client->duid = duid; + dhcp_client->duid_len = duid_len; + + return 0; +} + +uint32_t g_dhcpv6_client_get_iaid(GDHCPClient *dhcp_client) +{ + if (dhcp_client == NULL || dhcp_client->type == G_DHCP_IPV4) + return 0; + + return dhcp_client->iaid; +} + +void g_dhcpv6_client_create_iaid(GDHCPClient *dhcp_client, int index, + unsigned char *iaid) +{ + uint8_t buf[6]; + + get_interface_mac_address(index, buf); + + memcpy(iaid, &buf[2], 4); + dhcp_client->iaid = iaid[0] << 24 | + iaid[1] << 16 | iaid[2] << 8 | iaid[3]; +} + +int g_dhcpv6_client_get_timeouts(GDHCPClient *dhcp_client, + uint32_t *T1, uint32_t *T2, + time_t *last_renew, time_t *last_rebind, + time_t *expire) +{ + if (dhcp_client == NULL || dhcp_client->type == G_DHCP_IPV4) + return -EINVAL; + + if (T1 != NULL) + *T1 = dhcp_client->T1; + + if (T2 != NULL) + *T2 = dhcp_client->T2; + + if (last_renew != NULL) + *last_renew = dhcp_client->last_renew; + + if (last_rebind != NULL) + *last_rebind = dhcp_client->last_rebind; + + if (expire != NULL) + *expire = dhcp_client->expire; + + return 0; +} + +static uint8_t *create_iaaddr(GDHCPClient *dhcp_client, uint8_t *buf, + uint16_t len) +{ + buf[0] = 0; + buf[1] = G_DHCPV6_IAADDR; + buf[2] = 0; + buf[3] = len; + memcpy(&buf[4], &dhcp_client->ia_na, 16); + memset(&buf[20], 0, 4); /* preferred */ + memset(&buf[24], 0, 4); /* valid */ + return buf; +} + +static void put_iaid(GDHCPClient *dhcp_client, int index, uint8_t *buf) +{ + uint32_t iaid; + + iaid = g_dhcpv6_client_get_iaid(dhcp_client); + if (iaid == 0) { + g_dhcpv6_client_create_iaid(dhcp_client, index, buf); + return; + } + + buf[0] = iaid >> 24; + buf[1] = iaid >> 16; + buf[2] = iaid >> 8; + buf[3] = iaid; +} + +int g_dhcpv6_client_set_ia(GDHCPClient *dhcp_client, int index, + int code, uint32_t *T1, uint32_t *T2, + gboolean add_iaaddr) +{ + if (code == G_DHCPV6_IA_TA) { + uint8_t ia_options[4]; + + put_iaid(dhcp_client, index, ia_options); + + g_dhcp_client_set_request(dhcp_client, G_DHCPV6_IA_TA); + g_dhcpv6_client_set_send(dhcp_client, G_DHCPV6_IA_TA, + ia_options, sizeof(ia_options)); + + } else if (code == G_DHCPV6_IA_NA) { + + g_dhcp_client_set_request(dhcp_client, G_DHCPV6_IA_NA); + + if (add_iaaddr == TRUE) { +#define IAADDR_LEN (16+4+4) + uint8_t ia_options[4+4+4+2+2+IAADDR_LEN]; + + put_iaid(dhcp_client, index, ia_options); + + if (T1 != NULL) { + ia_options[4] = *T1 >> 24; + ia_options[5] = *T1 >> 16; + ia_options[6] = *T1 >> 8; + ia_options[7] = *T1; + } else + memset(&ia_options[4], 0x00, 4); + + if (T2 != NULL) { + ia_options[8] = *T2 >> 24; + ia_options[9] = *T2 >> 16; + ia_options[10] = *T2 >> 8; + ia_options[11] = *T2; + } else + memset(&ia_options[8], 0x00, 4); + + create_iaaddr(dhcp_client, &ia_options[12], + IAADDR_LEN); + + g_dhcpv6_client_set_send(dhcp_client, G_DHCPV6_IA_NA, + ia_options, sizeof(ia_options)); + } else { + uint8_t ia_options[4+4+4]; + + put_iaid(dhcp_client, index, ia_options); + + memset(&ia_options[4], 0x00, 4); /* T1 (4 bytes) */ + memset(&ia_options[8], 0x00, 4); /* T2 (4 bytes) */ + + g_dhcpv6_client_set_send(dhcp_client, G_DHCPV6_IA_NA, + ia_options, sizeof(ia_options)); + } + + } else + return -EINVAL; + + return 0; +} + +int g_dhcpv6_client_set_oro(GDHCPClient *dhcp_client, int args, ...) +{ + va_list va; + int i, j, len = sizeof(uint16_t) * args; + uint8_t *values; + + values = g_try_malloc(len); + if (values == NULL) + return -ENOMEM; + + va_start(va, args); + for (i = 0, j = 0; i < args; i++) { + uint16_t value = va_arg(va, int); + values[j++] = value >> 8; + values[j++] = value & 0xff; + } + va_end(va); + + g_dhcpv6_client_set_send(dhcp_client, G_DHCPV6_ORO, values, len); + + return 0; +} + +static int send_dhcpv6_msg(GDHCPClient *dhcp_client, int type, char *msg) +{ + struct dhcpv6_packet *packet; + uint8_t buf[MAX_DHCPV6_PKT_SIZE]; + unsigned char *ptr; + int ret, max_buf; + + memset(buf, 0, sizeof(buf)); + packet = (struct dhcpv6_packet *)&buf[0]; + ptr = buf + sizeof(struct dhcpv6_packet); + + debug(dhcp_client, "sending DHCPv6 %s message", msg); + + init_packet(dhcp_client, packet, type); + + dhcp_client->xid = packet->transaction_id[0] << 16 | + packet->transaction_id[1] << 8 | + packet->transaction_id[2]; + + max_buf = MAX_DHCPV6_PKT_SIZE - sizeof(struct dhcpv6_packet); + + add_dhcpv6_request_options(dhcp_client, packet, buf, max_buf, &ptr); + + add_dhcpv6_send_options(dhcp_client, buf, max_buf, &ptr); + + ret = dhcpv6_send_packet(dhcp_client->ifindex, packet, ptr - buf); + + debug(dhcp_client, "sent %d pkt %p len %d", ret, packet, ptr - buf); + return ret; +} + +static int send_solicitation(GDHCPClient *dhcp_client) +{ + return send_dhcpv6_msg(dhcp_client, DHCPV6_SOLICIT, "solicit"); +} + +static int send_dhcpv6_request(GDHCPClient *dhcp_client) +{ + return send_dhcpv6_msg(dhcp_client, DHCPV6_REQUEST, "request"); +} + +static int send_dhcpv6_renew(GDHCPClient *dhcp_client) +{ + return send_dhcpv6_msg(dhcp_client, DHCPV6_RENEW, "renew"); +} + +static int send_dhcpv6_rebind(GDHCPClient *dhcp_client) +{ + return send_dhcpv6_msg(dhcp_client, DHCPV6_REBIND, "rebind"); +} + +static int send_dhcpv6_release(GDHCPClient *dhcp_client) +{ + return send_dhcpv6_msg(dhcp_client, DHCPV6_RELEASE, "release"); +} + +static int send_information_req(GDHCPClient *dhcp_client) +{ + return send_dhcpv6_msg(dhcp_client, DHCPV6_INFORMATION_REQ, + "information-req"); +} + static void remove_value(gpointer data, gpointer user_data) { char *value = data; @@ -414,8 +880,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; @@ -426,6 +894,10 @@ GDHCPClient *g_dhcp_client_new(GDHCPType type, g_direct_equal, NULL, g_free); dhcp_client->request_list = NULL; dhcp_client->require_list = NULL; + dhcp_client->duid = NULL; + dhcp_client->duid_len = 0; + dhcp_client->last_renew = dhcp_client->last_rebind = time(0); + dhcp_client->expire = 0; *error = G_DHCP_CLIENT_ERROR_NONE; @@ -490,7 +962,7 @@ static int dhcp_l2_socket(int ifindex) .filter = (struct sock_filter *) filter_instr, }; - fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP)); + fd = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_IP)); if (fd < 0) return fd; @@ -499,6 +971,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,17 +1052,158 @@ static int dhcp_recv_l2_packet(struct dhcp_packet *dhcp_pkt, int fd) return bytes - (sizeof(packet.ip) + sizeof(packet.udp)); } -static gboolean check_package_owner(GDHCPClient *dhcp_client, - struct dhcp_packet *packet) -{ - if (packet->xid != dhcp_client->xid) - return FALSE; +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, gpointer pkt) +{ + if (dhcp_client->type == G_DHCP_IPV6) { + struct dhcpv6_packet *packet6 = pkt; + uint32_t xid; + + if (packet6 == NULL) + return FALSE; + + xid = packet6->transaction_id[0] << 16 | + packet6->transaction_id[1] << 8 | + packet6->transaction_id[2]; + + if (xid != dhcp_client->xid) + return FALSE; + } else { + struct dhcp_packet *packet = pkt; + + if (packet->xid != dhcp_client->xid) + return FALSE; - if (packet->hlen != 6) - return FALSE; + if (packet->hlen != 6) + return FALSE; - if (memcmp(packet->chaddr, dhcp_client->mac_address, 6)) - return FALSE; + if (memcmp(packet->chaddr, dhcp_client->mac_address, 6)) + return FALSE; + } return TRUE; } @@ -619,14 +1233,15 @@ 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; + debug(dhcp_client, "switch listening mode (%d ==> %d)", + dhcp_client->listen_mode, listen_mode); + if (dhcp_client->listen_mode != L_NONE) { - g_source_remove(dhcp_client->listener_watch); + 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; @@ -638,9 +1253,17 @@ static int switch_listening_mode(GDHCPClient *dhcp_client, if (listen_mode == L2) listener_sockfd = dhcp_l2_socket(dhcp_client->ifindex); - else if (listen_mode == L3) - listener_sockfd = dhcp_l3_socket(CLIENT_PORT, - dhcp_client->interface); + else if (listen_mode == L3) { + if (dhcp_client->type == G_DHCP_IPV6) + listener_sockfd = dhcp_l3_socket(DHCPV6_CLIENT_PORT, + dhcp_client->interface, + AF_INET6); + else + listener_sockfd = dhcp_l3_socket(CLIENT_PORT, + dhcp_client->interface, + AF_INET); + } else if (listen_mode == L_ARP) + listener_sockfd = ipv4ll_arp_socket(dhcp_client->ifindex); else return -EIO; @@ -660,8 +1283,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); @@ -676,10 +1299,7 @@ static void start_request(GDHCPClient *dhcp_client) 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; } @@ -730,7 +1350,7 @@ static void restart_dhcp(GDHCPClient *dhcp_client, int retry_times) dhcp_client->requested_ip = 0; switch_listening_mode(dhcp_client, L2); - g_dhcp_client_start(dhcp_client); + g_dhcp_client_start(dhcp_client, dhcp_client->last_address); } static gboolean start_rebound_timeout(gpointer user_data) @@ -795,6 +1415,9 @@ static gboolean start_renew_timeout(gpointer user_data) else { send_renew(dhcp_client); + if (dhcp_client->timeout > 0) + g_source_remove(dhcp_client->timeout); + dhcp_client->timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH, dhcp_client->lease_seconds >> 1, @@ -812,6 +1435,9 @@ static void start_bound(GDHCPClient *dhcp_client) dhcp_client->state = BOUND; + if (dhcp_client->timeout > 0) + g_source_remove(dhcp_client->timeout); + dhcp_client->timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH, dhcp_client->lease_seconds >> 1, start_renew_timeout, dhcp_client, @@ -908,24 +1534,229 @@ 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; +} + +static inline uint32_t get_uint32(unsigned char *value) +{ + return value[0] << 24 | value[1] << 16 | + value[2] << 8 | value[3]; +} + +static inline uint16_t get_uint16(unsigned char *value) +{ + return value[0] << 8 | value[1]; +} + +static GList *get_addresses(GDHCPClient *dhcp_client, + int code, int len, + unsigned char *value, + uint16_t *status) +{ + GList *list = NULL; + struct in6_addr addr; + uint32_t iaid, T1 = 0, T2 = 0, preferred = 0, valid = 0; + uint16_t option_len, option_code, st = 0, max_len; + int addr_count = 0, i, pos; + uint8_t *option; + char *str; + + iaid = get_uint32(&value[0]); + if (dhcp_client->iaid != iaid) + return NULL; + + if (code == G_DHCPV6_IA_NA) { + T1 = get_uint32(&value[4]); + T2 = get_uint32(&value[8]); + + if (T1 > T2) + /* RFC 3315, 22.4 */ + return NULL; + + pos = 12; + } else + pos = 4; + + if (len <= pos) + return NULL; + + max_len = len - pos; + + /* We have more sub-options in this packet. */ + do { + option = dhcpv6_get_sub_option(&value[pos], max_len, + &option_code, &option_len); + + debug(dhcp_client, "pos %d option %p code %d len %d", + pos, option, option_code, option_len); + + if (option == NULL) + break; + + if (pos >= max_len) + break; + + switch (option_code) { + case G_DHCPV6_IAADDR: + i = 0; + memcpy(&addr, &option[0], sizeof(addr)); + i += sizeof(addr); + preferred = get_uint32(&option[i]); + i += 4; + valid = get_uint32(&option[i]); + + addr_count++; + break; + + case G_DHCPV6_STATUS_CODE: + st = get_uint16(&option[0]); + debug(dhcp_client, "error code %d", st); + if (option_len > 2) { + str = g_strndup((gchar *)&option[2], + option_len - 2); + debug(dhcp_client, "error text: %s", str); + g_free(str); + } + + *status = st; + break; + } + + pos += 2 + 2 + option_len; + + } while (option != NULL); + + if (addr_count > 0 && st == 0) { + /* We only support one address atm */ + char str[INET6_ADDRSTRLEN + 1]; + + if (preferred > valid) + /* RFC 3315, 22.6 */ + return NULL; + + dhcp_client->T1 = T1; + dhcp_client->T2 = T2; + + inet_ntop(AF_INET6, &addr, str, INET6_ADDRSTRLEN); + debug(dhcp_client, "count %d addr %s T1 %u T2 %u", + addr_count, str, T1, T2); + + list = g_list_append(list, g_strdup(str)); + + if (code == G_DHCPV6_IA_NA) + memcpy(&dhcp_client->ia_na, &addr, + sizeof(struct in6_addr)); + else + memcpy(&dhcp_client->ia_ta, &addr, + sizeof(struct in6_addr)); + + g_dhcpv6_client_set_expire(dhcp_client, valid); + } + + return list; +} + +static GList *get_dhcpv6_option_value_list(GDHCPClient *dhcp_client, + int code, int len, + unsigned char *value, + uint16_t *status) +{ + GList *list = NULL; + char *str; + int i; + + switch (code) { + case G_DHCPV6_DNS_SERVERS: /* RFC 3646, chapter 3 */ + case G_DHCPV6_SNTP_SERVERS: /* RFC 4075, chapter 4 */ + if (len % 16) { + debug(dhcp_client, + "%s server list length (%d) is invalid", + code == G_DHCPV6_DNS_SERVERS ? "DNS" : "SNTP", + len); + return NULL; + } + for (i = 0; i < len; i += 16) { + + str = g_try_malloc0(INET6_ADDRSTRLEN+1); + if (str == NULL) + return list; + + if (inet_ntop(AF_INET6, &value[i], str, + INET6_ADDRSTRLEN) == NULL) + g_free(str); + else + list = g_list_append(list, str); + } + break; + + case G_DHCPV6_IA_NA: /* RFC 3315, chapter 22.4 */ + case G_DHCPV6_IA_TA: /* RFC 3315, chapter 22.5 */ + list = get_addresses(dhcp_client, code, len, value, status); + break; + + default: + break; + } return list; } +static void get_dhcpv6_request(GDHCPClient *dhcp_client, + struct dhcpv6_packet *packet, + uint16_t pkt_len, uint16_t *status) +{ + GList *list, *value_list; + uint8_t *option; + uint16_t code; + uint16_t option_len; + + for (list = dhcp_client->request_list; list; list = list->next) { + code = (uint16_t) GPOINTER_TO_INT(list->data); + + option = dhcpv6_get_option(packet, pkt_len, code, &option_len, + NULL); + if (option == NULL) { + g_hash_table_remove(dhcp_client->code_value_hash, + GINT_TO_POINTER((int) code)); + continue; + } + + value_list = get_dhcpv6_option_value_list(dhcp_client, code, + option_len, option, status); + + debug(dhcp_client, "code %d %p len %d list %p", code, option, + option_len, value_list); + + if (value_list == NULL) + g_hash_table_remove(dhcp_client->code_value_hash, + GINT_TO_POINTER((int) code)); + else + g_hash_table_insert(dhcp_client->code_value_hash, + GINT_TO_POINTER((int) code), value_list); + } +} + static void get_request(GDHCPClient *dhcp_client, struct dhcp_packet *packet) { GDHCPOptionType type; @@ -951,7 +1782,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); @@ -969,7 +1800,14 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition, { GDHCPClient *dhcp_client = user_data; struct dhcp_packet packet; - uint8_t *message_type, *option_u8; + struct dhcpv6_packet *packet6 = NULL; + uint8_t *message_type = NULL, *client_id = NULL, *option_u8, + *server_id = NULL; + uint16_t option_len = 0, status = 0; + gpointer pkt; + unsigned char buf[MAX_DHCPV6_PKT_SIZE]; + uint16_t pkt_len = 0; + int count; int re; if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) { @@ -980,22 +1818,71 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition, if (dhcp_client->listen_mode == L_NONE) return FALSE; + pkt = &packet; + if (dhcp_client->listen_mode == L2) - 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); + re = dhcp_recv_l2_packet(&packet, + dhcp_client->listener_sockfd); + else if (dhcp_client->listen_mode == L3) { + if (dhcp_client->type == G_DHCP_IPV6) { + re = dhcpv6_recv_l3_packet(&packet6, buf, sizeof(buf), + dhcp_client->listener_sockfd); + pkt_len = re; + pkt = packet6; + } else + 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; if (re < 0) return TRUE; - if (check_package_owner(dhcp_client, &packet) == FALSE) + if (check_package_owner(dhcp_client, pkt) == FALSE) return TRUE; - message_type = dhcp_get_option(&packet, DHCP_MESSAGE_TYPE); - if (message_type == NULL) - /* No message type option, ignore pakcage */ + if (dhcp_client->type == G_DHCP_IPV6) { + count = 0; + client_id = dhcpv6_get_option(packet6, pkt_len, + G_DHCPV6_CLIENTID, &option_len, &count); + + if (client_id == NULL || count == 0 || option_len == 0 || + memcmp(dhcp_client->duid, client_id, + dhcp_client->duid_len) != 0) { + debug(dhcp_client, + "client duid error, discarding msg %p/%d/%d", + client_id, option_len, count); + return TRUE; + } + + option_u8 = dhcpv6_get_option(packet6, pkt_len, + G_DHCPV6_STATUS_CODE, &option_len, NULL); + if (option_u8 != 0 && option_len > 0) { + status = option_u8[0]<<8 | option_u8[1]; + if (status != 0) { + debug(dhcp_client, "error code %d", status); + if (option_len > 2) { + gchar *txt = g_strndup( + (gchar *)&option_u8[2], + option_len - 2); + debug(dhcp_client, "error text: %s", + txt); + g_free(txt); + } + } + dhcp_client->status_code = status; + } else + dhcp_client->status_code = 0; + + } else + message_type = dhcp_get_option(&packet, DHCP_MESSAGE_TYPE); + + if (message_type == NULL && client_id == NULL) + /* No message type / client id option, ignore package */ return TRUE; debug(dhcp_client, "received DHCP packet (current state %d)", @@ -1059,6 +1946,128 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition, } break; + case SOLICITATION: + if (dhcp_client->type != G_DHCP_IPV6) + return TRUE; + + if (packet6->message != DHCPV6_REPLY && + packet6->message != DHCPV6_ADVERTISE) + return TRUE; + + count = 0; + server_id = dhcpv6_get_option(packet6, pkt_len, + G_DHCPV6_SERVERID, &option_len, &count); + if (server_id == NULL || count != 1 || option_len == 0) { + /* RFC 3315, 15.10 */ + debug(dhcp_client, + "server duid error, discarding msg %p/%d/%d", + server_id, option_len, count); + return TRUE; + } + dhcp_client->server_duid = g_try_malloc(option_len); + if (dhcp_client->server_duid == NULL) + return TRUE; + memcpy(dhcp_client->server_duid, server_id, option_len); + dhcp_client->server_duid_len = option_len; + + if (packet6->message == DHCPV6_REPLY) { + uint8_t *rapid_commit; + count = 0; + option_len = 0; + rapid_commit = dhcpv6_get_option(packet6, pkt_len, + G_DHCPV6_RAPID_COMMIT, + &option_len, &count); + if (rapid_commit == NULL || option_len == 0 || + count != 1) + /* RFC 3315, 17.1.4 */ + return TRUE; + } + + switch_listening_mode(dhcp_client, L_NONE); + + if (dhcp_client->status_code == 0) + get_dhcpv6_request(dhcp_client, packet6, pkt_len, + &dhcp_client->status_code); + + if (packet6->message == DHCPV6_ADVERTISE) { + if (dhcp_client->advertise_cb != NULL) + dhcp_client->advertise_cb(dhcp_client, + dhcp_client->advertise_data); + return TRUE; + } + + if (dhcp_client->solicitation_cb != NULL) { + /* + * The dhcp_client might not be valid after the + * callback call so just return immediately. + */ + dhcp_client->solicitation_cb(dhcp_client, + dhcp_client->solicitation_data); + return TRUE; + } + break; + case INFORMATION_REQ: + case REQUEST: + case RENEW: + case REBIND: + case RELEASE: + if (dhcp_client->type != G_DHCP_IPV6) + return TRUE; + + if (packet6->message != DHCPV6_REPLY) + return TRUE; + + count = 0; + option_len = 0; + server_id = dhcpv6_get_option(packet6, pkt_len, + G_DHCPV6_SERVERID, &option_len, &count); + if (server_id == NULL || count != 1 || option_len == 0 || + (dhcp_client->server_duid_len > 0 && + memcmp(dhcp_client->server_duid, server_id, + dhcp_client->server_duid_len) != 0)) { + /* RFC 3315, 15.10 */ + debug(dhcp_client, + "server duid error, discarding msg %p/%d/%d", + server_id, option_len, count); + return TRUE; + } + + switch_listening_mode(dhcp_client, L_NONE); + + dhcp_client->status_code = 0; + get_dhcpv6_request(dhcp_client, packet6, pkt_len, + &dhcp_client->status_code); + + if (dhcp_client->information_req_cb != NULL) { + /* + * The dhcp_client might not be valid after the + * callback call so just return immediately. + */ + dhcp_client->information_req_cb(dhcp_client, + dhcp_client->information_req_data); + return TRUE; + } + if (dhcp_client->request_cb != NULL) { + dhcp_client->request_cb(dhcp_client, + dhcp_client->request_data); + return TRUE; + } + if (dhcp_client->renew_cb != NULL) { + dhcp_client->renew_cb(dhcp_client, + dhcp_client->renew_data); + return TRUE; + } + if (dhcp_client->rebind_cb != NULL) { + dhcp_client->rebind_cb(dhcp_client, + dhcp_client->rebind_data); + return TRUE; + } + if (dhcp_client->release_cb != NULL) { + dhcp_client->release_cb(dhcp_client, + dhcp_client->release_data); + return TRUE; + } + break; default: break; } @@ -1075,20 +2084,149 @@ static gboolean discover_timeout(gpointer user_data) dhcp_client->retry_times++; - g_dhcp_client_start(dhcp_client); + /* + * We do not send the REQUESTED IP option if we are retrying because + * if the server is non-authoritative it will ignore the request if the + * option is present. + */ + g_dhcp_client_start(dhcp_client, NULL); + + 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 g_dhcp_client_start(GDHCPClient *dhcp_client, const char *last_address) { int re; + uint32_t addr; + + if (dhcp_client->type == G_DHCP_IPV6) { + if (dhcp_client->information_req_cb) { + dhcp_client->state = INFORMATION_REQ; + re = switch_listening_mode(dhcp_client, L3); + if (re != 0) { + switch_listening_mode(dhcp_client, L_NONE); + dhcp_client->state = 0; + return re; + } + send_information_req(dhcp_client); + + } else if (dhcp_client->solicitation_cb) { + dhcp_client->state = SOLICITATION; + re = switch_listening_mode(dhcp_client, L3); + if (re != 0) { + switch_listening_mode(dhcp_client, L_NONE); + dhcp_client->state = 0; + return re; + } + send_solicitation(dhcp_client); + + } else if (dhcp_client->request_cb) { + dhcp_client->state = REQUEST; + re = switch_listening_mode(dhcp_client, L3); + if (re != 0) { + switch_listening_mode(dhcp_client, L_NONE); + dhcp_client->state = 0; + return re; + } + send_dhcpv6_request(dhcp_client); + + } else if (dhcp_client->renew_cb) { + dhcp_client->state = RENEW; + re = switch_listening_mode(dhcp_client, L3); + if (re != 0) { + switch_listening_mode(dhcp_client, L_NONE); + dhcp_client->state = 0; + return re; + } + send_dhcpv6_renew(dhcp_client); + + } else if (dhcp_client->rebind_cb) { + dhcp_client->state = REBIND; + re = switch_listening_mode(dhcp_client, L3); + if (re != 0) { + switch_listening_mode(dhcp_client, L_NONE); + dhcp_client->state = 0; + return re; + } + send_dhcpv6_rebind(dhcp_client); + + } else if (dhcp_client->release_cb) { + dhcp_client->state = RENEW; + re = switch_listening_mode(dhcp_client, L3); + if (re != 0) { + switch_listening_mode(dhcp_client, L_NONE); + dhcp_client->state = 0; + return re; + } + send_dhcpv6_release(dhcp_client); + } - 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); + return 0; + } + if (dhcp_client->retry_times == DISCOVER_RETRIES) { + ipv4ll_start(dhcp_client); return 0; } @@ -1104,7 +2242,18 @@ int g_dhcp_client_start(GDHCPClient *dhcp_client) dhcp_client->xid = rand(); } - send_discover(dhcp_client, 0); + if (last_address == NULL) { + addr = 0; + } else { + addr = inet_addr(last_address); + if (addr == 0xFFFFFFFF) { + addr = 0; + } else { + g_free(dhcp_client->last_address); + dhcp_client->last_address = g_strdup(last_address); + } + } + send_discover(dhcp_client, addr); dhcp_client->timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH, DISCOVER_TIMEOUT, @@ -1161,6 +2310,12 @@ 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: + if (dhcp_client->type == G_DHCP_IPV6) + return; + 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; @@ -1169,10 +2324,58 @@ 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: + if (dhcp_client->type == G_DHCP_IPV6) + return; + 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; return; + case G_DHCP_CLIENT_EVENT_INFORMATION_REQ: + if (dhcp_client->type == G_DHCP_IPV4) + return; + dhcp_client->information_req_cb = func; + dhcp_client->information_req_data = data; + return; + case G_DHCP_CLIENT_EVENT_SOLICITATION: + if (dhcp_client->type == G_DHCP_IPV4) + return; + dhcp_client->solicitation_cb = func; + dhcp_client->solicitation_data = data; + return; + case G_DHCP_CLIENT_EVENT_ADVERTISE: + if (dhcp_client->type == G_DHCP_IPV4) + return; + dhcp_client->advertise_cb = func; + dhcp_client->advertise_data = data; + return; + case G_DHCP_CLIENT_EVENT_REQUEST: + if (dhcp_client->type == G_DHCP_IPV4) + return; + dhcp_client->request_cb = func; + dhcp_client->request_data = data; + return; + case G_DHCP_CLIENT_EVENT_RENEW: + if (dhcp_client->type == G_DHCP_IPV4) + return; + dhcp_client->renew_cb = func; + dhcp_client->renew_data = data; + return; + case G_DHCP_CLIENT_EVENT_REBIND: + if (dhcp_client->type == G_DHCP_IPV4) + return; + dhcp_client->rebind_cb = func; + dhcp_client->rebind_data = data; + return; + case G_DHCP_CLIENT_EVENT_RELEASE: + if (dhcp_client->type == G_DHCP_IPV4) + return; + dhcp_client->release_cb = func; + dhcp_client->release_data = data; + return; } } @@ -1186,8 +2389,41 @@ char *g_dhcp_client_get_address(GDHCPClient *dhcp_client) return g_strdup(dhcp_client->assigned_ip); } +char *g_dhcp_client_get_netmask(GDHCPClient *dhcp_client) +{ + GList *option = NULL; + + if (dhcp_client->type == G_DHCP_IPV6) + return NULL; + + switch (dhcp_client->state) { + case IPV4LL_DEFEND: + case IPV4LL_MONITOR: + return g_strdup("255.255.0.0"); + case BOUND: + case RENEWING: + case REBINDING: + option = g_dhcp_client_get_option(dhcp_client, G_DHCP_SUBNET); + if (option != NULL) + return g_strdup(option->data); + case INIT_SELECTING: + case REQUESTING: + case RELEASED: + case IPV4LL_PROBE: + case IPV4LL_ANNOUNCE: + case INFORMATION_REQ: + case SOLICITATION: + case REQUEST: + case RENEW: + case REBIND: + case RELEASE: + break; + } + return NULL; +} + GDHCPClientError g_dhcp_client_set_request(GDHCPClient *dhcp_client, - unsigned char option_code) + unsigned int option_code) { if (g_list_find(dhcp_client->request_list, GINT_TO_POINTER((int) option_code)) == NULL) @@ -1198,6 +2434,17 @@ GDHCPClientError g_dhcp_client_set_request(GDHCPClient *dhcp_client, return G_DHCP_CLIENT_ERROR_NONE; } +void g_dhcp_client_clear_requests(GDHCPClient *dhcp_client) +{ + g_list_free(dhcp_client->request_list); + dhcp_client->request_list = NULL; +} + +void g_dhcp_client_clear_values(GDHCPClient *dhcp_client) +{ + g_hash_table_remove_all(dhcp_client->send_value_hash); +} + static uint8_t *alloc_dhcp_option(int code, const char *str, int extra) { uint8_t *storage; @@ -1228,12 +2475,82 @@ GDHCPClientError g_dhcp_client_set_send(GDHCPClient *dhcp_client, return G_DHCP_CLIENT_ERROR_NONE; } +static uint8_t *alloc_dhcpv6_option(uint16_t code, uint8_t *option, + uint16_t len) +{ + uint8_t *storage; + + storage = g_malloc(2 + 2 + len); + if (storage == NULL) + return NULL; + + storage[0] = code >> 8; + storage[1] = code & 0xff; + storage[2] = len >> 8; + storage[3] = len & 0xff; + memcpy(storage + 2 + 2, option, len); + + return storage; +} + +void g_dhcpv6_client_set_send(GDHCPClient *dhcp_client, + uint16_t option_code, + uint8_t *option_value, + uint16_t option_len) +{ + if (option_value != NULL) { + uint8_t *binary_option; + + debug(dhcp_client, "setting option %d to %p len %d", + option_code, option_value, option_len); + + binary_option = alloc_dhcpv6_option(option_code, option_value, + option_len); + if (binary_option != NULL) + g_hash_table_insert(dhcp_client->send_value_hash, + GINT_TO_POINTER((int) option_code), + binary_option); + } +} + +void g_dhcpv6_client_reset_renew(GDHCPClient *dhcp_client) +{ + if (dhcp_client == NULL || dhcp_client->type == G_DHCP_IPV4) + return; + + dhcp_client->last_renew = time(0); +} + +void g_dhcpv6_client_reset_rebind(GDHCPClient *dhcp_client) +{ + if (dhcp_client == NULL || dhcp_client->type == G_DHCP_IPV4) + return; + + dhcp_client->last_rebind = time(0); +} + +void g_dhcpv6_client_set_expire(GDHCPClient *dhcp_client, uint32_t timeout) +{ + if (dhcp_client == NULL || dhcp_client->type == G_DHCP_IPV4) + return; + + dhcp_client->expire = time(0) + timeout; +} + +uint16_t g_dhcpv6_client_get_status(GDHCPClient *dhcp_client) +{ + if (dhcp_client == NULL || dhcp_client->type == G_DHCP_IPV4) + return 0; + + return dhcp_client->status_code; +} + GDHCPClient *g_dhcp_client_ref(GDHCPClient *dhcp_client) { if (dhcp_client == NULL) return NULL; - g_atomic_int_inc(&dhcp_client->ref_count); + __sync_fetch_and_add(&dhcp_client->ref_count, 1); return dhcp_client; } @@ -1243,13 +2560,16 @@ void g_dhcp_client_unref(GDHCPClient *dhcp_client) if (dhcp_client == NULL) return; - if (g_atomic_int_dec_and_test(&dhcp_client->ref_count) == FALSE) + if (__sync_fetch_and_sub(&dhcp_client->ref_count, 1) != 1) return; g_dhcp_client_stop(dhcp_client); g_free(dhcp_client->interface); g_free(dhcp_client->assigned_ip); + g_free(dhcp_client->last_address); + g_free(dhcp_client->duid); + g_free(dhcp_client->server_duid); g_list_free(dhcp_client->request_list); g_list_free(dhcp_client->require_list);