From f13255cfe3c98ce4e7598f9bda03b4dc0ac837a1 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Thu, 5 Jan 2012 11:42:07 +0200 Subject: [PATCH] gdhcp: Generic stateless DHCPv6 support. The patch adds support for information-request DHCPv6 message. --- gdhcp/client.c | 503 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- gdhcp/common.c | 202 ++++++++++++++++++++++- gdhcp/common.h | 43 ++++- gdhcp/gdhcp.h | 24 ++- gdhcp/server.c | 2 +- 5 files changed, 734 insertions(+), 40 deletions(-) diff --git a/gdhcp/client.c b/gdhcp/client.c index 6df3281..be8905c 100644 --- a/gdhcp/client.c +++ b/gdhcp/client.c @@ -69,6 +69,7 @@ typedef enum _dhcp_client_state { IPV4LL_ANNOUNCE, IPV4LL_MONITOR, IPV4LL_DEFEND, + INFORMATION_REQ, } ClientState; struct _GDHCPClient { @@ -109,7 +110,12 @@ struct _GDHCPClient { gpointer address_conflict_data; GDHCPDebugFunc debug_func; gpointer debug_data; + GDHCPClientEventFunc information_req_cb; + gpointer information_req_data; char *last_address; + unsigned char *duid; + int duid_len; + uint16_t status_code; }; static inline void debug(GDHCPClient *client, const char *format, ...) @@ -129,12 +135,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, @@ -159,6 +169,97 @@ 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; + + default: + break; + } + } +} + static void add_binary_option(gpointer key, gpointer value, gpointer user_data) { uint8_t *option = value; @@ -388,6 +489,122 @@ 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; +} + +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_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; @@ -451,6 +668,8 @@ 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; *error = G_DHCP_CLIENT_ERROR_NONE; @@ -730,17 +949,33 @@ static int ipv4ll_recv_arp_packet(GDHCPClient *dhcp_client) return 0; } -static gboolean check_package_owner(GDHCPClient *dhcp_client, - struct dhcp_packet *packet) +static gboolean check_package_owner(GDHCPClient *dhcp_client, gpointer pkt) { - if (packet->xid != dhcp_client->xid) - return FALSE; + if (dhcp_client->type == G_DHCP_IPV6) { + struct dhcpv6_packet *packet6 = pkt; + uint32_t xid; - if (packet->hlen != 6) - return FALSE; + if (packet6 == NULL) + return FALSE; - if (memcmp(packet->chaddr, dhcp_client->mac_address, 6)) - 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 (memcmp(packet->chaddr, dhcp_client->mac_address, 6)) + return FALSE; + } return TRUE; } @@ -770,12 +1005,12 @@ 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) { if (dhcp_client->listener_watch > 0) g_source_remove(dhcp_client->listener_watch); @@ -790,10 +1025,16 @@ 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 == L_ARP) + 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; @@ -1089,6 +1330,55 @@ static GList *get_option_value_list(char *value, GDHCPOptionType type) return list; } +static GList *get_dhcpv6_option_value_list(GDHCPClient *dhcp_client, + int code, int len, + unsigned char *value) +{ + GList *list = NULL; + + switch (code) { + default: + break; + } + + return list; +} + +static void get_dhcpv6_request(GDHCPClient *dhcp_client, + struct dhcpv6_packet *packet, + uint16_t pkt_len) +{ + 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); + + 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; @@ -1132,7 +1422,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; + 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)) { @@ -1143,11 +1440,21 @@ 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); - else if (dhcp_client->listen_mode == L_ARP) { + 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; } @@ -1157,12 +1464,43 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition, 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 package */ + 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) { + gchar *txt = g_strndup((gchar *)&option_u8[2], + option_len - 2); + debug(dhcp_client, "error code %d: %s", + status, 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)", @@ -1226,6 +1564,38 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition, } break; + case INFORMATION_REQ: + 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) { + /* 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); + get_dhcpv6_request(dhcp_client, packet6, pkt_len); + + 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; + } + break; default: break; } @@ -1318,6 +1688,20 @@ 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); + } + return 0; + } + if (dhcp_client->retry_times == DISCOVER_RETRIES) { ipv4ll_start(dhcp_client); return 0; @@ -1404,6 +1788,8 @@ void g_dhcp_client_register_event(GDHCPClient *dhcp_client, 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; @@ -1416,6 +1802,8 @@ void g_dhcp_client_register_event(GDHCPClient *dhcp_client, 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; @@ -1423,6 +1811,12 @@ void g_dhcp_client_register_event(GDHCPClient *dhcp_client, 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; } } @@ -1440,6 +1834,9 @@ 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: @@ -1455,13 +1852,14 @@ char *g_dhcp_client_get_netmask(GDHCPClient *dhcp_client) case RELEASED: case IPV4LL_PROBE: case IPV4LL_ANNOUNCE: + case INFORMATION_REQ: 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) @@ -1502,6 +1900,52 @@ 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); + } +} + +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) @@ -1525,6 +1969,7 @@ void g_dhcp_client_unref(GDHCPClient *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_list_free(dhcp_client->request_list); g_list_free(dhcp_client->require_list); diff --git a/gdhcp/common.c b/gdhcp/common.c index 0a2b51b..46cf5f6 100644 --- a/gdhcp/common.c +++ b/gdhcp/common.c @@ -23,6 +23,7 @@ #endif #include +#include #include #include #include @@ -33,6 +34,9 @@ #include #include #include +#include + +#include #include "gdhcp.h" #include "common.h" @@ -142,6 +146,48 @@ int dhcp_end_option(uint8_t *optionptr) return i; } +uint8_t *dhcpv6_get_option(struct dhcpv6_packet *packet, uint16_t pkt_len, + int code, uint16_t *option_len, int *option_count) +{ + int rem, count = 0; + uint8_t *optionptr, *found = NULL; + uint16_t opt_code, opt_len, len; + + optionptr = packet->options; + rem = pkt_len - 1 - 3; + + if (rem <= 0) + /* Bad packet */ + return NULL; + + while (1) { + opt_code = optionptr[0] << 8 | optionptr[1]; + opt_len = len = optionptr[2] << 8 | optionptr[3]; + len += 2 + 2; /* skip code and len */ + + rem -= len; + if (rem < 0) + break; + + if (opt_code == code) { + if (option_len != NULL) + *option_len = opt_len; + found = optionptr + 2 + 2; + count++; + } + + if (rem == 0) + break; + + optionptr += len; + } + + if (option_count != NULL) + *option_count = count; + + return found; +} + /* * Add an option (supplied in binary form) to the options. * Option format: [code][len][data1][data2]..[dataLEN] @@ -164,6 +210,27 @@ void dhcp_add_binary_option(struct dhcp_packet *packet, uint8_t *addopt) optionptr[end + len] = DHCP_END; } +/* + * Add an option (supplied in binary form) to the options. + * Option format: [code][len][data1][data2]..[dataLEN] + */ +void dhcpv6_add_binary_option(struct dhcpv6_packet *packet, uint16_t max_len, + uint16_t *pkt_len, uint8_t *addopt) +{ + unsigned len; + uint8_t *optionptr = packet->options; + + len = 2 + 2 + (addopt[2] << 8 | addopt[3]); + + /* end position + (option code/length + addopt length) */ + if (*pkt_len + len >= max_len) + /* option did not fit into the packet */ + return; + + memcpy(optionptr + *pkt_len, addopt, len); + *pkt_len += len; +} + void dhcp_add_simple_option(struct dhcp_packet *packet, uint8_t code, uint32_t data) { @@ -209,6 +276,21 @@ void dhcp_init_header(struct dhcp_packet *packet, char type) dhcp_add_simple_option(packet, DHCP_MESSAGE_TYPE, type); } +void dhcpv6_init_header(struct dhcpv6_packet *packet, uint8_t type) +{ + int id; + + memset(packet, 0, sizeof(*packet)); + + packet->message = type; + + id = random(); + + packet->transaction_id[0] = (id >> 16) & 0xff; + packet->transaction_id[1] = (id >> 8) & 0xff; + packet->transaction_id[2] = id & 0xff; +} + static gboolean check_vendor(uint8_t *option_vendor, const char *vendor) { uint8_t vendor_length = sizeof(vendor) - 1; @@ -255,6 +337,20 @@ int dhcp_recv_l3_packet(struct dhcp_packet *packet, int fd) return n; } +int dhcpv6_recv_l3_packet(struct dhcpv6_packet **packet, unsigned char *buf, + int buf_len, int fd) +{ + int n; + + n = read(fd, buf, buf_len); + if (n < 0) + return -errno; + + *packet = (struct dhcpv6_packet *)buf; + + return n; +} + /* TODO: Use glib checksum */ uint16_t dhcp_checksum(void *addr, int count) { @@ -286,6 +382,78 @@ uint16_t dhcp_checksum(void *addr, int count) return ~sum; } +#define IN6ADDR_ALL_DHCP_RELAY_AGENTS_AND_SERVERS_MC_INIT \ + { { { 0xff,0x02,0,0,0,0,0,0,0,0,0,0,0,0x1,0,0x2 } } } /* ff02::1:2 */ +static const struct in6_addr in6addr_all_dhcp_relay_agents_and_servers_mc = + IN6ADDR_ALL_DHCP_RELAY_AGENTS_AND_SERVERS_MC_INIT; + +/* from netinet/in.h */ +struct in6_pktinfo { + struct in6_addr ipi6_addr; /* src/dst IPv6 address */ + unsigned int ipi6_ifindex; /* send/recv interface index */ +}; + +int dhcpv6_send_packet(int index, struct dhcpv6_packet *dhcp_pkt, int len) +{ + struct msghdr m; + struct iovec v; + struct in6_pktinfo *pktinfo; + struct cmsghdr *cmsg; + int fd, ret; + struct sockaddr_in6 dst; + void *control_buf; + size_t control_buf_len; + + fd = socket(PF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP); + if (fd < 0) + return -errno; + + memset(&dst, 0, sizeof(dst)); + dst.sin6_family = AF_INET6; + dst.sin6_port = htons(DHCPV6_SERVER_PORT); + + dst.sin6_addr = in6addr_all_dhcp_relay_agents_and_servers_mc; + + control_buf_len = CMSG_SPACE(sizeof(struct in6_pktinfo)); + control_buf = g_try_malloc0(control_buf_len); + if (control_buf == NULL) { + close(fd); + return -ENOMEM; + } + + memset(&m, 0, sizeof(m)); + memset(&v, 0, sizeof(v)); + + m.msg_name = &dst; + m.msg_namelen = sizeof(dst); + + v.iov_base = (char *)dhcp_pkt; + v.iov_len = len; + m.msg_iov = &v; + m.msg_iovlen = 1; + + m.msg_control = control_buf; + m.msg_controllen = control_buf_len; + cmsg = CMSG_FIRSTHDR(&m); + cmsg->cmsg_level = IPPROTO_IPV6; + cmsg->cmsg_type = IPV6_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(*pktinfo)); + + pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg); + memset(pktinfo, 0, sizeof(*pktinfo)); + pktinfo->ipi6_ifindex = index; + m.msg_controllen = cmsg->cmsg_len; + + ret = sendmsg(fd, &m, 0); + if (ret < 0) + perror("DHCPv6 msg send failed"); + + g_free(control_buf); + close(fd); + + return ret; +} + int dhcp_send_raw_packet(struct dhcp_packet *dhcp_pkt, uint32_t source_ip, int source_port, uint32_t dest_ip, int dest_port, const uint8_t *dest_arp, int ifindex) @@ -397,12 +565,16 @@ int dhcp_send_kernel_packet(struct dhcp_packet *dhcp_pkt, return n; } -int dhcp_l3_socket(int port, const char *interface) +int dhcp_l3_socket(int port, const char *interface, int family) { - int fd, opt = 1; - struct sockaddr_in addr; + int fd, opt = 1, len; + struct sockaddr_in addr4; + struct sockaddr_in6 addr6; + struct sockaddr *addr; - fd = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP); + fd = socket(family, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP); + if (fd < 0) + return -errno; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); @@ -412,10 +584,24 @@ int dhcp_l3_socket(int port, const char *interface) return -1; } - memset(&addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) { + if (family == AF_INET) { + memset(&addr4, 0, sizeof(addr4)); + addr4.sin_family = family; + addr4.sin_port = htons(port); + addr = (struct sockaddr *)&addr4; + len = sizeof(addr4); + } else if (family == AF_INET6) { + memset(&addr6, 0, sizeof(addr6)); + addr6.sin6_family = family; + addr6.sin6_port = htons(port); + addr = (struct sockaddr *)&addr6; + len = sizeof(addr6); + } else { + close(fd); + return -EINVAL; + } + + if (bind(fd, addr, len) != 0) { close(fd); return -1; } diff --git a/gdhcp/common.h b/gdhcp/common.h index 3aab3d2..ae4029c 100644 --- a/gdhcp/common.h +++ b/gdhcp/common.h @@ -45,6 +45,10 @@ do { \ #define CLIENT_PORT 68 #define SERVER_PORT 67 +#define DHCPV6_CLIENT_PORT 546 +#define DHCPV6_SERVER_PORT 547 +#define MAX_DHCPV6_PKT_SIZE 1500 + #define EXTEND_FOR_BUGGY_SERVERS 80 static const uint8_t MAC_BCAST_ADDR[ETH_ALEN] __attribute__((aligned(2))) = { @@ -89,6 +93,14 @@ struct ip_udp_dhcp_packet { struct dhcp_packet data; } __attribute__((packed)); +/* See RFC 3315 */ +struct dhcpv6_packet { + uint8_t message; + uint8_t transaction_id[3]; + uint8_t options[]; +} __attribute__((packed)); + + /* See RFC 2132 */ #define DHCP_PADDING 0x00 #define DHCP_SUBNET 0x01 @@ -130,6 +142,24 @@ struct ip_udp_dhcp_packet { #define DHCP_MINTYPE DHCPDISCOVER #define DHCP_MAXTYPE DHCPINFORM +/* Message types for DHCPv6, RFC 3315 sec 5.3 */ +#define DHCPV6_SOLICIT 1 +#define DHCPV6_ADVERTISE 2 +#define DHCPV6_REQUEST 3 +#define DHCPV6_CONFIRM 4 +#define DHCPV6_RENEW 5 +#define DHCPV6_REBIND 6 +#define DHCPV6_REPLY 7 +#define DHCPV6_RELEASE 8 +#define DHCPV6_DECLINE 9 +#define DHCPV6_RECONFIGURE 10 +#define DHCPV6_INFORMATION_REQ 11 + +/* + * DUID time starts 2000-01-01. + */ +#define DUID_TIME_EPOCH 946684800 + typedef enum { OPTION_UNKNOWN, OPTION_IP, @@ -156,24 +186,35 @@ static const uint8_t dhcp_option_lengths[] = { }; uint8_t *dhcp_get_option(struct dhcp_packet *packet, int code); +uint8_t *dhcpv6_get_option(struct dhcpv6_packet *packet, uint16_t pkt_len, + int code, uint16_t *option_len, int *option_count); int dhcp_end_option(uint8_t *optionptr); void dhcp_add_binary_option(struct dhcp_packet *packet, uint8_t *addopt); +void dhcpv6_add_binary_option(struct dhcpv6_packet *packet, uint16_t max_len, + uint16_t *pkt_len, uint8_t *addopt); void dhcp_add_simple_option(struct dhcp_packet *packet, uint8_t code, uint32_t data); GDHCPOptionType dhcp_get_code_type(uint8_t code); +GDHCPOptionType dhcpv6_get_code_type(uint16_t code); uint16_t dhcp_checksum(void *addr, int count); void dhcp_init_header(struct dhcp_packet *packet, char type); +void dhcpv6_init_header(struct dhcpv6_packet *packet, uint8_t type); int dhcp_send_raw_packet(struct dhcp_packet *dhcp_pkt, uint32_t source_ip, int source_port, uint32_t dest_ip, int dest_port, const uint8_t *dest_arp, int ifindex); +int dhcpv6_send_packet(int index, struct dhcpv6_packet *dhcp_pkt, int len); int dhcp_send_kernel_packet(struct dhcp_packet *dhcp_pkt, uint32_t source_ip, int source_port, uint32_t dest_ip, int dest_port); -int dhcp_l3_socket(int port, const char *interface); +int dhcp_l3_socket(int port, const char *interface, int family); int dhcp_recv_l3_packet(struct dhcp_packet *packet, int fd); +int dhcpv6_recv_l3_packet(struct dhcpv6_packet **packet, unsigned char *buf, + int buf_len, int fd); +int dhcp_l3_socket_send(int index, int port, int family); + char *get_interface_name(int index); gboolean interface_is_up(int index); diff --git a/gdhcp/gdhcp.h b/gdhcp/gdhcp.h index d89446e..5c0e2ee 100644 --- a/gdhcp/gdhcp.h +++ b/gdhcp/gdhcp.h @@ -22,6 +22,8 @@ #ifndef __G_DHCP_H #define __G_DHCP_H +#include + #include #ifdef __cplusplus @@ -50,6 +52,7 @@ typedef enum { G_DHCP_CLIENT_EVENT_LEASE_LOST, G_DHCP_CLIENT_EVENT_IPV4LL_LOST, G_DHCP_CLIENT_EVENT_ADDRESS_CONFLICT, + G_DHCP_CLIENT_EVENT_INFORMATION_REQ, } GDHCPClientEvent; typedef enum { @@ -66,6 +69,17 @@ typedef enum { #define G_DHCP_HOST_NAME 0x0c #define G_DHCP_NTP_SERVER 0x2a +#define G_DHCPV6_CLIENTID 1 +#define G_DHCPV6_SERVERID 2 +#define G_DHCPV6_ORO 6 +#define G_DHCPV6_STATUS_CODE 13 + +typedef enum { + G_DHCPV6_DUID_LLT = 1, + G_DHCPV6_DUID_EN = 2, + G_DHCPV6_DUID_LL = 3, +} GDHCPDuidType; + typedef void (*GDHCPClientEventFunc) (GDHCPClient *client, gpointer user_data); typedef void (*GDHCPDebugFunc)(const char *str, gpointer user_data); @@ -85,7 +99,7 @@ void g_dhcp_client_register_event(GDHCPClient *client, gpointer user_data); GDHCPClientError g_dhcp_client_set_request(GDHCPClient *client, - unsigned char option_code); + unsigned int option_code); GDHCPClientError g_dhcp_client_set_send(GDHCPClient *client, unsigned char option_code, const char *option_value); @@ -98,6 +112,14 @@ int g_dhcp_client_get_index(GDHCPClient *client); void g_dhcp_client_set_debug(GDHCPClient *client, GDHCPDebugFunc func, gpointer user_data); +int g_dhcpv6_create_duid(GDHCPDuidType duid_type, int index, int type, + unsigned char **duid, int *duid_len); +int g_dhcpv6_client_set_duid(GDHCPClient *dhcp_client, unsigned char *duid, + int duid_len); +void g_dhcpv6_client_set_send(GDHCPClient *dhcp_client, uint16_t option_code, + uint8_t *option_value, uint16_t option_len); +uint16_t g_dhcpv6_client_get_status(GDHCPClient *dhcp_client); +int g_dhcpv6_client_set_oro(GDHCPClient *dhcp_client, int args, ...); /* DHCP Server */ typedef enum { diff --git a/gdhcp/server.c b/gdhcp/server.c index 4f0b5b7..34ec21e 100644 --- a/gdhcp/server.c +++ b/gdhcp/server.c @@ -754,7 +754,7 @@ int g_dhcp_server_start(GDHCPServer *dhcp_server) return 0; listener_sockfd = dhcp_l3_socket(SERVER_PORT, - dhcp_server->interface); + dhcp_server->interface, AF_INET); if (listener_sockfd < 0) return -EIO; -- 2.7.4