Revision: 528 http://vde.svn.sourceforge.net/vde/?rev=528&view=rev Author: danielel Date: 2011-12-30 10:33:54 +0000 (Fri, 30 Dec 2011) Log Message: ----------- Added DHCP client Added arp command to show arp tables
Modified Paths: -------------- branches/vde-router/vde-2/src/vde_router/Makefile.am branches/vde-router/vde-2/src/vde_router/vde_router.c branches/vde-router/vde-2/src/vde_router/vde_router.h branches/vde-router/vde-2/src/vde_router/vder_datalink.c branches/vde-router/vde-2/src/vde_router/vder_packet.c branches/vde-router/vde-2/src/vde_router/vder_packet.h branches/vde-router/vde-2/src/vde_router/vder_queue.c branches/vde-router/vde-2/src/vde_router/vder_udp.c branches/vde-router/vde-2/src/vde_router/vder_udp.h Added Paths: ----------- branches/vde-router/vde-2/src/vde_router/vder_dhcp.c branches/vde-router/vde-2/src/vde_router/vder_dhcp.h Removed Paths: ------------- branches/vde-router/vde-2/src/vde_router/vder_dhcpd.c branches/vde-router/vde-2/src/vde_router/vder_dhcpd.h Modified: branches/vde-router/vde-2/src/vde_router/Makefile.am =================================================================== --- branches/vde-router/vde-2/src/vde_router/Makefile.am 2011-12-27 14:45:35 UTC (rev 527) +++ branches/vde-router/vde-2/src/vde_router/Makefile.am 2011-12-30 10:33:54 UTC (rev 528) @@ -13,6 +13,6 @@ bin_PROGRAMS = vde_router vde_router_SOURCES = rbtree.h vde_headers.h vder_arp.h vder_datalink.h vder_icmp.h \ vde_router.h vder_packet.h vder_queue.h rbtree.c vder_arp.c vder_datalink.c \ - vder_icmp.c vde_router.c vder_packet.c vder_queue.c vder_udp.c vder_dhcpd.c + vder_icmp.c vde_router.c vder_packet.c vder_queue.c vder_udp.c vder_dhcp.c vde_router_LDADD = $(top_builddir)/src/common/libvdecommon.la $(top_builddir)/src/lib/libvdeplug.la -lpthread Modified: branches/vde-router/vde-2/src/vde_router/vde_router.c =================================================================== --- branches/vde-router/vde-2/src/vde_router/vde_router.c 2011-12-27 14:45:35 UTC (rev 527) +++ branches/vde-router/vde-2/src/vde_router/vde_router.c 2011-12-30 10:33:54 UTC (rev 528) @@ -11,7 +11,7 @@ #include "vde_router.h" #include "vder_queue.h" #include "vder_packet.h" -#include "vder_dhcpd.h" +#include "vder_dhcp.h" #include <stdlib.h> #include <unistd.h> #include <stdio.h> @@ -64,6 +64,7 @@ printoutc(fd, "ifconfig show/change interface addresses configuration"); printoutc(fd, "dhcpd start/stop dhcp server on a specific interface"); printoutc(fd, "route show/change routing table"); + printoutc(fd, "arp show neighbors ip/mac associations"); printoutc(fd, "queue show/change outgoing frames queues"); printoutc(fd, "ipfilter show/change ip filtering configuration"); printoutc(fd, "stats print interface statistics"); @@ -87,17 +88,21 @@ } else if (match_input("ifconfig",arg)) { printoutc(fd, "Syntax:"); printoutc(fd, "\tifconfig [<devname> [<action> <address> <netmask>]]"); + printoutc(fd, "--or--"); + printoutc(fd, "\tifconfig <devname> add dhcp"); printoutc(fd, "Show/store IP address configuration. If no <devname> is provided, the default action"); printoutc(fd, "will be to display the current configuration for all the existing ethernet devices."); printoutc(fd, "<action> can be \"add\" or \"del\". If \"add\" is specified, all other arguments are mandatory."); printoutc(fd, "If \"del\" is specified, only <address> will be used to search for an existing entry."); printoutc(fd, "Each virtual ethernet can be associated to more than one IP addresses. A static route for"); printoutc(fd, "the resulting neighborhood will be added."); + printoutc(fd, "Dhcp option allows to ask for a dynamic IP address."); printoutc(fd, ""); printoutc(fd, "Examples:"); printoutc(fd, "ifconfig"); printoutc(fd, "ifconfig eth0"); printoutc(fd, "ifconfig eth1 add 10.0.0.1 255.0.0.0"); + printoutc(fd, "ifconfig eth1 add dhcp"); printoutc(fd, "ifconfig eth1 del 10.0.0.1"); return 0; } else if (match_input("dhcpd",arg)) { @@ -199,6 +204,10 @@ printoutc(fd, "ipfilter add src eth1 tos 2 to 172.16.0.0 255.255.0.0 prio 7"); printoutc(fd, "ipfilter del src eth1 tos 2 to 172.16.0.0 255.255.0.0"); return 0; + } else if (match_input("arp",arg)) { + printoutc(fd, "Syntax:"); + printoutc(fd, "\tarp"); + return 0; } else if (match_input("stats",arg)) { printoutc(fd, "Syntax:"); printoutc(fd, "\tstats"); @@ -247,7 +256,10 @@ char *txt_address, *txt_netmask; txt_address = strdup(vder_ntoa(addr->address)); txt_netmask= strdup(vder_ntoa(addr->netmask)); - printoutc(fd, "\taddress: %s netmask: %s", txt_address, txt_netmask); + if (addr->address == (uint32_t)(-1)) + printoutc(fd, "\tAcquiring one IP address via DHCP..."); + else + printoutc(fd, "\taddress: %s netmask: %s", txt_address, txt_netmask); free(txt_address); free(txt_netmask); addr = addr->next; @@ -358,16 +370,21 @@ not_understood(fd, ""); return EINVAL; } - if (!inet_aton(arg, &temp_address) || !is_unicast(temp_address.s_addr)) { + if (match_input("dhcp", arg)) { + temp_address.s_addr = (uint32_t)(-1); + pthread_create(&selected->dhcpclient, 0, dhcp_client_loop, selected); + } + else if (!inet_aton(arg, &temp_address) || !is_unicast(temp_address.s_addr)) { printoutc(fd, "Invalid address \"%s\"", arg); return EINVAL; } arg = strtok_r(NULL, " ", &nextargs); - if (!arg && (action == ACTION_ADD)) { + if (!arg && (action == ACTION_ADD) && (temp_address.s_addr != (uint32_t)(-1))) { printoutc(fd, "Error: parameter 'netmask' required."); return EINVAL; } - if ((action == ACTION_ADD) && (!inet_aton(arg, &temp_netmask) || !is_netmask(temp_netmask.s_addr))) { + if ((action == ACTION_ADD) && (temp_address.s_addr != (uint32_t)(-1)) && + (!inet_aton(arg, &temp_netmask) || !is_netmask(temp_netmask.s_addr))) { printoutc(fd, "Invalid netmask \"%s\"", arg); return EINVAL; } @@ -974,7 +991,7 @@ return EINVAL; iface = Router.iflist; while(iface) { - printoutc(fd, "eth%d frame sent:%d, frame received:%d", + printoutc(fd, "eth%d frames sent:%d, frames received:%d", iface->interface_id, iface->stats.sent, iface->stats.recvd); printoutc(fd, ""); iface = iface->next; @@ -982,6 +999,42 @@ return 0; } +static int arp(int fd, char *args) +{ + struct vder_iface *iface; + struct rb_node *node; + if (strlen(args) > 0) + return EINVAL; + iface = Router.iflist; + while(iface) { + node = iface->arp_table.rb_node; + while (node) { + struct vder_arp_entry *ae = rb_entry(node, struct vder_arp_entry, rb_node); + char *txt_address = strdup(vder_ntoa(ae->ipaddr)); + printoutc(fd, "%s %02x:%02x:%02x:%02x:%02x:%02x (eth%d)", txt_address, + ae->macaddr[0], ae->macaddr[1], ae->macaddr[2], ae->macaddr[3], ae->macaddr[4], ae->macaddr[5], + iface->interface_id); + free(txt_address); + node = node->rb_left; + } + node = iface->arp_table.rb_node; + if (node) + node = node->rb_right; + while (node) { + struct vder_arp_entry *ae = rb_entry(node, struct vder_arp_entry, rb_node); + char *txt_address = strdup(vder_ntoa(ae->ipaddr)); + printoutc(fd, "%s %02x:%02x:%02x:%02x:%02x:%02x (eth%d)", txt_address, + ae->macaddr[0], ae->macaddr[1], ae->macaddr[2], ae->macaddr[3], ae->macaddr[4], ae->macaddr[5], + iface->interface_id); + free(txt_address); + node = node->rb_right; + } + iface = iface->next; + } + return 0; +} + + #define DEFAULT_LEASE_TIME htonl(0xa8c0) static int dhcpd(int fd,char *s) { @@ -1070,6 +1123,7 @@ } commandlist [] = { {"help", help, WITHFILE}, {"ifconfig", ifconfig, WITHFILE}, + {"arp", arp, WITHFILE}, {"route", route, WITHFILE}, {"connect", doconnect, 0}, {"stats", stats, WITHFILE}, Modified: branches/vde-router/vde-2/src/vde_router/vde_router.h =================================================================== --- branches/vde-router/vde-2/src/vde_router/vde_router.h 2011-12-27 14:45:35 UTC (rev 527) +++ branches/vde-router/vde-2/src/vde_router/vde_router.h 2011-12-30 10:33:54 UTC (rev 528) @@ -164,6 +164,7 @@ pthread_t receiver; pthread_t queue_manager; pthread_t dhcpd; + pthread_t dhcpclient; int dhcpd_started; struct { uint32_t sent; Modified: branches/vde-router/vde-2/src/vde_router/vder_datalink.c =================================================================== --- branches/vde-router/vde-2/src/vde_router/vder_datalink.c 2011-12-27 14:45:35 UTC (rev 527) +++ branches/vde-router/vde-2/src/vde_router/vder_datalink.c 2011-12-30 10:33:54 UTC (rev 528) @@ -372,7 +372,8 @@ pthread_mutex_unlock(&Router.global_config_lock); /* Add static route towards neightbors */ - vder_route_add(address->address, address->netmask, 0U, 1, iface); + if (addr != (uint32_t) (-1)) + vder_route_add(address->address, address->netmask, 0U, 1, iface); return 0; } @@ -469,7 +470,7 @@ while (iface) { struct vder_ip4address *cur = iface->address_list; while(cur) { - if (cur->address == addr) { + if ((cur->address == addr)|| (cur->address == (uint32_t)(-1))) { return 1; } cur = cur->next; Copied: branches/vde-router/vde-2/src/vde_router/vder_dhcp.c (from rev 527, branches/vde-router/vde-2/src/vde_router/vder_dhcpd.c) =================================================================== --- branches/vde-router/vde-2/src/vde_router/vder_dhcp.c (rev 0) +++ branches/vde-router/vde-2/src/vde_router/vder_dhcp.c 2011-12-30 10:33:54 UTC (rev 528) @@ -0,0 +1,468 @@ +#include "vder_udp.h" +#include "vder_arp.h" +#include "vder_dhcp.h" +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/time.h> + +static struct vder_dhcp_negotiation *Negotiation_list; +static struct vder_udp_socket *udpsock; +static struct vder_dhcpd_settings Settings; + +static struct vder_dhcp_negotiation * +get_negotiation_by_xid(uint32_t xid) +{ + struct vder_dhcp_negotiation *cur = Negotiation_list; + while (cur) { + if (cur->xid == xid) + return cur; + cur = cur->next; + } + return NULL; +} + +static uint8_t dhcp_get_next_option(uint8_t *begin, uint8_t *data, int *len, uint8_t **nextopt) +{ + uint8_t *p; + uint8_t type; + uint8_t opt_len; + + if (!begin) + p = *nextopt; + else + p = begin; + + type = *p; + *nextopt = ++p; + if ((type == DHCPOPT_END) || (type == DHCPOPT_PAD)) { + memset(data, 0, *len); + len = 0; + return type; + } + opt_len = *p; + p++; + if (*len > opt_len) + *len = opt_len; + memcpy(data, p, *len); + *nextopt = p + opt_len; + return type; +} + +static int is_options_valid(uint8_t *opt_buffer, int len) +{ + uint8_t *p = opt_buffer; + while (len > 0) { + if (*p == DHCPOPT_END) + return 1; + else if (*p == DHCPOPT_PAD) { + p++; + len--; + } else { + uint8_t opt_len; + p++; + len--; + opt_len = *p; + p += opt_len + 1; + len -= opt_len; + } + } + return 0; +} + +#define DHCP_DATAGRAM_SIZE 308 +#define OPENDNS (htonl(0xd043dede)) + +static void dhcpd_make_reply(struct vder_dhcp_negotiation *dn, uint8_t reply_type) +{ + + uint8_t buf_out[DHCP_DATAGRAM_SIZE] = {0}; + struct dhcphdr *dh_out = (struct dhcphdr *) buf_out; + uint32_t server_address = vder_get_right_localip(Settings.iface, Settings.pool_next); + uint32_t netmask = vder_get_netmask(Settings.iface, server_address); + uint32_t bcast = vder_get_broadcast(server_address, netmask); + uint32_t dns_server = OPENDNS; + + int sent = 0; + + + memcpy(dh_out->hwaddr, dn->hwaddr, HLEN_ETHER); + dh_out->op = DHCP_OP_REPLY; + dh_out->htype = HTYPE_ETHER; + dh_out->hlen = HLEN_ETHER; + dh_out->xid = dn->xid; + dh_out->yiaddr = dn->arp->ipaddr; + dh_out->siaddr = server_address; + dh_out->dhcp_magic = DHCPD_MAGIC_COOKIE; + + /* Option: msg type, len 1 */ + dh_out->options[0] = DHCPOPT_MSGTYPE; + dh_out->options[1] = 1; + dh_out->options[2] = reply_type; + + /* Option: server id, len 4 */ + dh_out->options[3] = DHCPOPT_SERVERID; + dh_out->options[4] = 4; + memcpy(dh_out->options + 5, &server_address, 4); + + /* Option: Lease time, len 4 */ + dh_out->options[9] = DHCPOPT_LEASETIME; + dh_out->options[10] = 4; + memcpy(dh_out->options + 11, &Settings.lease_time, 4); + + /* Option: Netmask, len 4 */ + dh_out->options[15] = DHCPOPT_NETMASK; + dh_out->options[16] = 4; + memcpy(dh_out->options + 17, &netmask, 4); + + /* Option: Router, len 4 */ + dh_out->options[21] = DHCPOPT_ROUTER; + dh_out->options[22] = 4; + memcpy(dh_out->options + 23, &server_address, 4); + + /* Option: Broadcast, len 4 */ + dh_out->options[27] = DHCPOPT_BCAST; + dh_out->options[28] = 4; + memcpy(dh_out->options + 29, &bcast, 4); + + /* Option: DNS, len 4 */ + dh_out->options[33] = DHCPOPT_DNS; + dh_out->options[34] = 4; + memcpy(dh_out->options + 35, &dns_server, 4); + + dh_out->options[40] = DHCPOPT_END; + + sent = vder_udpsocket_sendto(udpsock, buf_out, DHCP_DATAGRAM_SIZE, dh_out->yiaddr, DHCP_CLIENT_PORT); + if (sent < 0) { + perror("udp sendto"); + } +} + +#define dhcpd_make_offer(x) dhcpd_make_reply(x, DHCP_MSG_OFFER) +#define dhcpd_make_ack(x) dhcpd_make_reply(x, DHCP_MSG_ACK) + +#define ip_inrange(x) ((ntohl(x) >= ntohl(Settings.pool_start)) && (ntohl(x) <= ntohl(Settings.pool_end))) + +static void dhcp_recv(uint8_t *buffer, int len) +{ + struct dhcphdr *dhdr = (struct dhcphdr *) buffer; + struct vder_dhcp_negotiation *dn = get_negotiation_by_xid(dhdr->xid); + uint8_t *nextopt, opt_data[20], opt_type; + int opt_len = 20; + + + if (!is_options_valid(dhdr->options, len - sizeof(struct dhcphdr))) + return; + + + + if (!dn) { + dn = malloc(sizeof(struct vder_dhcp_negotiation)); + memset(dn, 0, sizeof(struct vder_dhcp_negotiation)); + dn->xid = dhdr->xid; + dn->state = DHCPSTATE_DISCOVER; + memcpy(dn->hwaddr, dhdr->hwaddr, HLEN_ETHER); + dn->next = Negotiation_list; + Negotiation_list = dn; + dn->arp = vder_arp_get_record_by_macaddr(Settings.iface, dn->hwaddr); + if (!dn->arp) { + dn->arp = malloc(sizeof(struct vder_arp_entry)); + if (!dn->arp) + return; + memcpy(dn->arp->macaddr, dn->hwaddr, HLEN_ETHER); + dn->arp->ipaddr = Settings.pool_next; + Settings.pool_next = htonl(ntohl(Settings.pool_next) + 1); + vder_add_arp_entry(Settings.iface, dn->arp); + } + } + + if (!ip_inrange(dn->arp->ipaddr)) + return; + + + opt_type = dhcp_get_next_option(dhdr->options, opt_data, &opt_len, &nextopt); + while (opt_type != DHCPOPT_END) { + /* parse interesting options here */ + if (opt_type == DHCPOPT_MSGTYPE) { + + /* server simple state machine */ + uint8_t msg_type = opt_data[0]; + if (msg_type == DHCP_MSG_DISCOVER) { + dhcpd_make_offer(dn); + dn->state = DHCPSTATE_OFFER; + return; + } else if (msg_type == DHCP_MSG_REQUEST) { + dhcpd_make_ack(dn); + return; + } + } + opt_len = 20; + opt_type = dhcp_get_next_option(NULL, opt_data, &opt_len, &nextopt); + } +} + + +void *dhcp_server_loop(void *ptr_settings) +{ + uint32_t from_ip; + uint16_t from_port; + + unsigned char buffer[2000]; + int len; + + memcpy(&Settings, ptr_settings, sizeof(struct vder_dhcpd_settings)); + Settings.pool_next = Settings.pool_start; + free(ptr_settings); + + + if(!Settings.iface) + return NULL; + if (!udpsock) + udpsock = vder_udpsocket_open(DHCPD_PORT); + if (!udpsock) + return NULL; + + + while(1) { + len = vder_udpsocket_recvfrom(udpsock, buffer, 2000, &from_ip, &from_port, -1); + if (len < 0) { + perror("udp recv"); + return NULL; + } + if ((from_ip == 0) && (from_port == DHCP_CLIENT_PORT)) { + dhcp_recv(buffer, len); + } + } +} + + + +struct dhcp_client_cookie +{ + uint32_t xid; + uint32_t address; + uint32_t netmask; + uint32_t gateway; + uint32_t server_id; + uint32_t lease_time; + struct vder_udp_socket *socket; + struct vder_iface *iface; + struct timeval start_time; + int attempt; + enum dhcp_negotiation_state state; +}; + +static int dhclient_recv_offer(struct dhcp_client_cookie *cli, uint8_t *data, int len) +{ + struct dhcphdr *dhdr = (struct dhcphdr *) data; + uint8_t *nextopt, opt_data[20], opt_type; + int opt_len = 20; + uint8_t msg_type = 0xFF; + + + if (dhdr->xid != cli->xid) { + printf("bad xid\n"); + return 0; + } + + if (!is_options_valid(dhdr->options, len - sizeof(struct dhcphdr))) { + printf("bad options\n"); + return 0; + } + + cli->address = dhdr->yiaddr; + + opt_type = dhcp_get_next_option(dhdr->options, opt_data, &opt_len, &nextopt); + while (opt_type != DHCPOPT_END) { + if (opt_type == DHCPOPT_MSGTYPE) + msg_type = opt_data[0]; + if ((opt_type == DHCPOPT_LEASETIME) && (opt_len == 4)) + memcpy(&cli->lease_time, opt_data, 4); + if ((opt_type == DHCPOPT_ROUTER) && (opt_len == 4)) + memcpy(&cli->gateway, opt_data, 4); + if ((opt_type == DHCPOPT_NETMASK) && (opt_len == 4)) + memcpy(&cli->netmask, opt_data, 4); + if ((opt_type == DHCPOPT_SERVERID) && (opt_len == 4)) + memcpy(&cli->server_id, opt_data, 4); + + opt_len = 20; + opt_type = dhcp_get_next_option(NULL, opt_data, &opt_len, &nextopt); + } + if ((msg_type != DHCP_MSG_OFFER) || !cli->lease_time || !cli->netmask || !cli->server_id ) + return 0; + return 1; +} + +static int dhclient_recv_ack(struct dhcp_client_cookie *cli, uint8_t *data, int len) +{ + struct dhcphdr *dhdr = (struct dhcphdr *) data; + uint8_t *nextopt, opt_data[20], opt_type; + int opt_len = 20; + uint8_t msg_type = 0xFF; + + if (dhdr->xid != cli->xid) + return 0; + + if (!is_options_valid(dhdr->options, len - sizeof(struct dhcphdr))) + return 0; + + + opt_type = dhcp_get_next_option(dhdr->options, opt_data, &opt_len, &nextopt); + while (opt_type != DHCPOPT_END) { + if (opt_type == DHCPOPT_MSGTYPE) + msg_type = opt_data[0]; + + opt_len = 20; + opt_type = dhcp_get_next_option(NULL, opt_data, &opt_len, &nextopt); + } + if (msg_type != DHCP_MSG_ACK) + return 0; + return 1; +} + + +static void dhclient_send(struct dhcp_client_cookie *cli, uint8_t msg_type) +{ + + uint8_t buf_out[DHCP_DATAGRAM_SIZE] = {0}; + struct dhcphdr *dh_out = (struct dhcphdr *) buf_out; + int sent = 0; + struct timeval now; + int i = 0; + gettimeofday(&now, NULL); + + memcpy(dh_out->hwaddr, cli->iface->macaddr, HLEN_ETHER); + dh_out->op = DHCP_OP_REQUEST; + dh_out->htype = HTYPE_ETHER; + dh_out->hlen = HLEN_ETHER; + dh_out->xid = cli->xid; + dh_out->secs = (msg_type == DHCP_MSG_REQUEST)?0:htons(now.tv_sec - cli->start_time.tv_sec); + dh_out->dhcp_magic = DHCPD_MAGIC_COOKIE; + + + /* Option: msg type, len 1 */ + dh_out->options[i++] = DHCPOPT_MSGTYPE; + dh_out->options[i++] = 1; + dh_out->options[i++] = msg_type; + + if (msg_type == DHCP_MSG_REQUEST) { + dh_out->options[i++] = DHCPOPT_REQIP; + dh_out->options[i++] = 4; + dh_out->options[i++] = (ntohl(cli->address) & 0xFF000000) >> 24; + dh_out->options[i++] = (ntohl(cli->address) & 0xFF0000) >> 16; + dh_out->options[i++] = (ntohl(cli->address) & 0xFF00) >> 8; + dh_out->options[i++] = (ntohl(cli->address) & 0xFF); + dh_out->options[i++] = DHCPOPT_SERVERID; + dh_out->options[i++] = 4; + dh_out->options[i++] = (ntohl(cli->server_id) & 0xFF000000) >> 24; + dh_out->options[i++] = (ntohl(cli->server_id) & 0xFF0000) >> 16; + dh_out->options[i++] = (ntohl(cli->server_id) & 0xFF00) >> 8; + dh_out->options[i++] = (ntohl(cli->server_id) & 0xFF); + } + + /* Option: req list, len 4 */ + dh_out->options[i++] = DHCPOPT_PARMLIST; + dh_out->options[i++] = 5; + dh_out->options[i++] = DHCPOPT_NETMASK; + dh_out->options[i++] = DHCPOPT_BCAST; + dh_out->options[i++] = DHCPOPT_TIME; + dh_out->options[i++] = DHCPOPT_ROUTER; + dh_out->options[i++] = DHCPOPT_HOSTNAME; + + dh_out->options[i] = DHCPOPT_END; + + sent = vder_udpsocket_sendto_broadcast(cli->socket, buf_out, DHCP_DATAGRAM_SIZE, cli->iface, (uint32_t)(-1), DHCPD_PORT); + if (sent < 0) { + perror("udp sendto"); + } +} + +void dhcp_retry(struct dhcp_client_cookie *client) +{ + const int MAX_RETRY = 5; + if (++client->attempt > MAX_RETRY) { + gettimeofday(&client->start_time, NULL); + client->attempt = 0; + client->xid ^= client->start_time.tv_usec ^ client->start_time.tv_sec; + } +} + +void *dhcp_client_loop(void *iface) +{ + unsigned char buffer[2000]; + int len; + struct dhcp_client_cookie client; + uint16_t from_port; + uint32_t from_ip; + + memset(&client, 0, sizeof(client)); + + client.iface = (struct vder_iface *) iface; + client.state = DHCPSTATE_DISCOVER; + client.socket = vder_udpsocket_open(DHCP_CLIENT_PORT); + if (!client.socket) { + perror("dhcp client socket"); + return NULL; + } + + gettimeofday(&client.start_time, NULL); + client.attempt = 0; + client.xid = client.start_time.tv_usec ^ client.start_time.tv_sec; + + + if (!client.socket) { + return NULL; + } + + while(1) { + switch (client.state) { + case DHCPSTATE_DISCOVER: + dhcp_retry(&client); + dhclient_send(&client, DHCP_MSG_DISCOVER); + len = vder_udpsocket_recvfrom(client.socket, buffer, 2000, &from_ip, &from_port, 5000); + if (len < 0) { + perror("udp recv"); + return NULL; + } + if (len > 0) { + if (dhclient_recv_offer(&client, buffer, len)) { + client.state = DHCPSTATE_REQUEST; + } + } + break; + case DHCPSTATE_REQUEST: + dhclient_send(&client, DHCP_MSG_REQUEST); + len = vder_udpsocket_recvfrom(client.socket, buffer, 2000, &from_ip, &from_port, 10000); + if (len < 0) { + perror("udp recv"); + return NULL; + } + if (len == 0) + break; + if (dhclient_recv_ack(&client, buffer, len)) + client.state = DHCPSTATE_ACK; + else { + if (client.address) + vder_iface_address_del(client.iface, client.address); + client.state = DHCPSTATE_DISCOVER; + client.address = 0; + client.netmask = 0; + client.gateway = 0; + } + break; + case DHCPSTATE_ACK: + vder_iface_address_del(client.iface, (uint32_t)-1); + vder_iface_address_add(client.iface, client.address, client.netmask); + if ((client.gateway != 0) && ((client.gateway & client.netmask) == (client.address & client.netmask))) + vder_route_add(0, 0, client.gateway, 1, client.iface); + sleep(client.lease_time); + client.state = DHCPSTATE_REQUEST; + break; + default: + client.address = 0; + client.netmask = 0; + client.gateway = 0; + client.state = DHCPSTATE_DISCOVER; + } + } +} Copied: branches/vde-router/vde-2/src/vde_router/vder_dhcp.h (from rev 527, branches/vde-router/vde-2/src/vde_router/vder_dhcpd.h) =================================================================== --- branches/vde-router/vde-2/src/vde_router/vder_dhcp.h (rev 0) +++ branches/vde-router/vde-2/src/vde_router/vder_dhcp.h 2011-12-30 10:33:54 UTC (rev 528) @@ -0,0 +1,110 @@ +#ifndef __VDER_DHCPD +#define __VDER_DHCPD + +#include "vder_arp.h" + +#define DHCPD_PORT (htons(67)) +#define DHCP_CLIENT_PORT (htons(68)) + + +#define DHCP_GATEWAY 0x01 +#define DHCP_DNS 0x02 + +struct vder_dhcpd_settings +{ + struct vder_iface *iface; + uint32_t my_ip; + uint32_t netmask; + uint32_t pool_start; + uint32_t pool_next; + uint32_t pool_end; + unsigned long lease_time; + uint8_t flags; +}; + +#define DHCP_OP_REQUEST 1 +#define DHCP_OP_REPLY 2 + +#define HTYPE_ETHER 1 +#define HLEN_ETHER 6 + +#define FLAG_BROADCAST (htons(0xF000)) + +#define DHCPD_MAGIC_COOKIE (htonl(0x63825363)) + +/* DHCP OPTIONS, RFC2132 */ +#define DHCPOPT_PAD 0x00 +#define DHCPOPT_NETMASK 0x01 +#define DHCPOPT_TIME 0x02 +#define DHCPOPT_ROUTER 0x03 +#define DHCPOPT_DNS 0x06 +#define DHCPOPT_HOSTNAME 0x0c +#define DHCPOPT_DOMAINNAME 0x0f +#define DHCPOPT_MTU 0x1a +#define DHCPOPT_BCAST 0x1c +#define DHCPOPT_NETBIOSNS 0x2c +#define DHCPOPT_NETBIOSSCOPE 0x2f + +#define DHCPOPT_REQIP 0x32 +#define DHCPOPT_LEASETIME 0x33 +#define DHCPOPT_MSGTYPE 0x35 +#define DHCPOPT_SERVERID 0x36 +#define DHCPOPT_PARMLIST 0x37 +#define DHCPOPT_RENEWALTIME 0x3a +#define DHCPOPT_REBINDINGTIME 0x3b +#define DHCPOPT_DOMAINSEARCH 0x77 +#define DHCPOPT_STATICROUTE 0x79 +#define DHCPOPT_END 0xFF + +/* DHCP MESSAGE TYPE */ +#define DHCP_MSG_DISCOVER 1 +#define DHCP_MSG_OFFER 2 +#define DHCP_MSG_REQUEST 3 +#define DHCP_MSG_DECLINE 4 +#define DHCP_MSG_ACK 5 +#define DHCP_MSG_NAK 6 +#define DHCP_MSG_RELEASE 7 +#define DHCP_MSG_INFORM 8 + + +struct __attribute__((packed)) dhcphdr +{ + uint8_t op; + uint8_t htype; + uint8_t hlen; + uint8_t hops; //zero + uint32_t xid; //store this in the request + uint16_t secs; // ignore + uint16_t flags; + uint32_t ciaddr; // client address - if asking for renewal + uint32_t yiaddr; // your address (client) + uint32_t siaddr; // dhcp offered address + uint32_t giaddr; // relay agent, bootp. + uint8_t hwaddr[6]; + uint8_t hwaddr_padding[10]; + char hostname[64]; + char bootp_filename[128]; + uint32_t dhcp_magic; + uint8_t options[0]; +}; + +enum dhcp_negotiation_state { + DHCPSTATE_DISCOVER = 0, + DHCPSTATE_OFFER, + DHCPSTATE_REQUEST, + DHCPSTATE_ACK +}; + +struct vder_dhcp_negotiation { + struct vder_dhcp_negotiation *next; + uint32_t xid; + uint8_t hwaddr[6]; + uint32_t assigned_address; + enum dhcp_negotiation_state state; + struct vder_arp_entry *arp; +}; + +void *dhcp_server_loop(void *ptr_iface); +void *dhcp_client_loop(void *ptr_iface); + +#endif Deleted: branches/vde-router/vde-2/src/vde_router/vder_dhcpd.c =================================================================== --- branches/vde-router/vde-2/src/vde_router/vder_dhcpd.c 2011-12-27 14:45:35 UTC (rev 527) +++ branches/vde-router/vde-2/src/vde_router/vder_dhcpd.c 2011-12-30 10:33:54 UTC (rev 528) @@ -1,232 +0,0 @@ -#include "vder_udp.h" -#include "vder_arp.h" -#include "vder_dhcpd.h" -#include <stdio.h> - -static struct vder_dhcp_negotiation *Negotiation_list; -static struct vder_udp_socket *udpsock; -static struct vder_dhcpd_settings Settings; - -static struct vder_dhcp_negotiation * -get_negotiation_by_xid(uint32_t xid) -{ - struct vder_dhcp_negotiation *cur = Negotiation_list; - while (cur) { - if (cur->xid == xid) - return cur; - cur = cur->next; - } - return NULL; -} - -static uint8_t dhcp_get_next_option(uint8_t *begin, uint8_t *data, int *len, uint8_t **nextopt) -{ - uint8_t *p; - uint8_t type; - uint8_t opt_len; - - if (!begin) - p = *nextopt; - else - p = begin; - - type = *p; - *nextopt = ++p; - if ((type == DHCPOPT_END) || (type == DHCPOPT_PAD)) { - memset(data, 0, *len); - len = 0; - return type; - } - opt_len = *p; - p++; - if (*len > opt_len) - *len = opt_len; - memcpy(data, p, *len); - *nextopt = p + opt_len; - return type; -} - -static int is_options_valid(uint8_t *opt_buffer, int len) -{ - uint8_t *p = opt_buffer; - while (len > 0) { - if (*p == DHCPOPT_END) - return 1; - else if (*p == DHCPOPT_PAD) { - p++; - len--; - } else { - uint8_t opt_len; - p++; - len--; - opt_len = *p; - p += opt_len + 1; - len -= opt_len; - } - } - return 0; -} - -#define DHCP_OFFER_SIZE 308 -#define OPENDNS (htonl(0xd043dede)) - -static void dhcpd_make_reply(struct vder_dhcp_negotiation *dn, uint8_t reply_type) -{ - - uint8_t buf_out[DHCP_OFFER_SIZE] = {0}; - struct dhcphdr *dh_out = (struct dhcphdr *) buf_out; - uint32_t server_address = vder_get_right_localip(Settings.iface, Settings.pool_next); - uint32_t netmask = vder_get_netmask(Settings.iface, server_address); - uint32_t bcast = vder_get_broadcast(server_address, netmask); - uint32_t dns_server = OPENDNS; - - int sent = 0; - - - memcpy(dh_out->hwaddr, dn->hwaddr, HLEN_ETHER); - dh_out->op = DHCP_OP_REPLY; - dh_out->htype = HTYPE_ETHER; - dh_out->hlen = HLEN_ETHER; - dh_out->xid = dn->xid; - dh_out->yiaddr = dn->arp->ipaddr; - dh_out->siaddr = server_address; - dh_out->dhcp_magic = DHCPD_MAGIC_COOKIE; - - /* Option: msg type, len 1 */ - dh_out->options[0] = DHCPOPT_MSGTYPE; - dh_out->options[1] = 1; - dh_out->options[2] = reply_type; - - /* Option: server id, len 4 */ - dh_out->options[3] = DHCPOPT_SERVERID; - dh_out->options[4] = 4; - memcpy(dh_out->options + 5, &server_address, 4); - - /* Option: Lease time, len 4 */ - dh_out->options[9] = DHCPOPT_LEASETIME; - dh_out->options[10] = 4; - memcpy(dh_out->options + 11, &Settings.lease_time, 4); - - /* Option: Netmask, len 4 */ - dh_out->options[15] = DHCPOPT_NETMASK; - dh_out->options[16] = 4; - memcpy(dh_out->options + 17, &netmask, 4); - - /* Option: Router, len 4 */ - dh_out->options[21] = DHCPOPT_ROUTER; - dh_out->options[22] = 4; - memcpy(dh_out->options + 23, &server_address, 4); - - /* Option: Broadcast, len 4 */ - dh_out->options[27] = DHCPOPT_BCAST; - dh_out->options[28] = 4; - memcpy(dh_out->options + 29, &bcast, 4); - - /* Option: DNS, len 4 */ - dh_out->options[33] = DHCPOPT_DNS; - dh_out->options[34] = 4; - memcpy(dh_out->options + 35, &dns_server, 4); - - dh_out->options[40] = DHCPOPT_END; - - sent = vder_udpsocket_sendto(udpsock, buf_out, DHCP_OFFER_SIZE, dh_out->yiaddr, DHCP_CLIENT_PORT); - if (sent < 0) { - perror("udp sendto"); - } -} - -#define dhcpd_make_offer(x) dhcpd_make_reply(x, DHCP_MSG_OFFER) -#define dhcpd_make_ack(x) dhcpd_make_reply(x, DHCP_MSG_ACK) - -#define ip_inrange(x) ((ntohl(x) >= ntohl(Settings.pool_start)) && (ntohl(x) <= ntohl(Settings.pool_end))) - -static void dhcp_recv(uint8_t *buffer, int len) -{ - struct dhcphdr *dhdr = (struct dhcphdr *) buffer; - struct vder_dhcp_negotiation *dn = get_negotiation_by_xid(dhdr->xid); - uint8_t *nextopt, opt_data[20], opt_type; - int opt_len = 20; - - - if (!is_options_valid(dhdr->options, len - sizeof(struct dhcphdr))) - return; - - - - if (!dn) { - dn = malloc(sizeof(struct vder_dhcp_negotiation)); - memset(dn, 0, sizeof(struct vder_dhcp_negotiation)); - dn->xid = dhdr->xid; - dn->state = DHCPSTATE_DISCOVER; - memcpy(dn->hwaddr, dhdr->hwaddr, HLEN_ETHER); - dn->next = Negotiation_list; - Negotiation_list = dn; - dn->arp = vder_arp_get_record_by_macaddr(Settings.iface, dn->hwaddr); - if (!dn->arp) { - dn->arp = malloc(sizeof(struct vder_arp_entry)); - if (!dn->arp) - return; - memcpy(dn->arp->macaddr, dn->hwaddr, HLEN_ETHER); - dn->arp->ipaddr = Settings.pool_next; - Settings.pool_next = htonl(ntohl(Settings.pool_next) + 1); - vder_add_arp_entry(Settings.iface, dn->arp); - } - } - - if (!ip_inrange(dn->arp->ipaddr)) - return; - - - opt_type = dhcp_get_next_option(dhdr->options, opt_data, &opt_len, &nextopt); - while (opt_type != DHCPOPT_END) { - /* parse interesting options here */ - if (opt_type == DHCPOPT_MSGTYPE) { - - /* server simple state machine */ - uint8_t msg_type = opt_data[0]; - if (msg_type == DHCP_MSG_DISCOVER) { - dhcpd_make_offer(dn); - dn->state = DHCPSTATE_OFFER; - return; - } else if (msg_type == DHCP_MSG_REQUEST) { - dhcpd_make_ack(dn); - return; - } - } - opt_len = 20; - opt_type = dhcp_get_next_option(NULL, opt_data, &opt_len, &nextopt); - } -} - - -void *dhcp_server_loop(void *ptr_settings) -{ - uint32_t from_ip; - uint16_t from_port; - - unsigned char buffer[2000]; - int len; - - memcpy(&Settings, ptr_settings, sizeof(struct vder_dhcpd_settings)); - Settings.pool_next = Settings.pool_start; - free(ptr_settings); - - - if(!Settings.iface) - return NULL; - udpsock = vder_udpsocket_open(DHCPD_PORT); - if (!udpsock) { - return NULL; - } - - while(1) { - len = vder_udpsocket_recvfrom(udpsock, buffer, 2000, &from_ip, &from_port); - if (len < 0) { - perror("udp recv"); - return NULL; - } - if ((from_ip == 0) && (from_port == DHCP_CLIENT_PORT)) { - dhcp_recv(buffer, len); - } - } -} Deleted: branches/vde-router/vde-2/src/vde_router/vder_dhcpd.h =================================================================== --- branches/vde-router/vde-2/src/vde_router/vder_dhcpd.h 2011-12-27 14:45:35 UTC (rev 527) +++ branches/vde-router/vde-2/src/vde_router/vder_dhcpd.h 2011-12-30 10:33:54 UTC (rev 528) @@ -1,100 +0,0 @@ -#ifndef __VDER_DHCPD -#define __VDER_DHCPD - -#include "vder_arp.h" - -#define DHCPD_PORT (htons(67)) -#define DHCP_CLIENT_PORT (htons(68)) - - -#define DHCP_GATEWAY 0x01 -#define DHCP_DNS 0x02 - -struct vder_dhcpd_settings -{ - struct vder_iface *iface; - uint32_t my_ip; - uint32_t netmask; - uint32_t pool_start; - uint32_t pool_next; - uint32_t pool_end; - unsigned long lease_time; - uint8_t flags; -}; - -#define DHCP_OP_REQUEST 1 -#define DHCP_OP_REPLY 2 - -#define HTYPE_ETHER 1 -#define HLEN_ETHER 6 - -#define FLAG_BROADCAST (htons(0xF000)) - -#define DHCPD_MAGIC_COOKIE (htonl(0x63825363)) - -/* DHCP OPTIONS, RFC2132 */ -#define DHCPOPT_PAD 0x00 -#define DHCPOPT_NETMASK 0x01 -#define DHCPOPT_ROUTER 0x03 -#define DHCPOPT_DNS 0x06 -#define DHCPOPT_BCAST 0x1c -#define DHCPOPT_REQIP 0x32 -#define DHCPOPT_LEASETIME 0x33 -#define DHCPOPT_MSGTYPE 0x35 -#define DHCPOPT_SERVERID 0x36 -#define DHCPOPT_PARMLIST 0x37 -#define DHCPOPT_RENEWALTIME 0x3a -#define DHCPOPT_REBINDINGTIME 0x3b -#define DHCPOPT_END 0xFF - -/* DHCP MESSAGE TYPE */ -#define DHCP_MSG_DISCOVER 1 -#define DHCP_MSG_OFFER 2 -#define DHCP_MSG_REQUEST 3 -#define DHCP_MSG_DECLINE 4 -#define DHCP_MSG_ACK 5 -#define DHCP_MSG_NAK 6 -#define DHCP_MSG_RELEASE 7 -#define DHCP_MSG_INFORM 8 - - -struct __attribute__((packed)) dhcphdr -{ - uint8_t op; - uint8_t htype; - uint8_t hlen; - uint8_t hops; //zero - uint32_t xid; //store this in the request - uint16_t secs; // ignore - uint16_t flags; - uint32_t ciaddr; // client address - if asking for renewal - uint32_t yiaddr; // your address (client) - uint32_t siaddr; // dhcp offered address - uint32_t giaddr; // relay agent, bootp. - uint8_t hwaddr[6]; - uint8_t hwaddr_padding[10]; - char hostname[64]; - char bootp_filename[128]; - uint32_t dhcp_magic; - uint8_t options[0]; -}; - -enum dhcp_negotiation_state { - DHCPSTATE_DISCOVER = 0, - DHCPSTATE_OFFER, - DHCPSTATE_REQUEST, - DHCPSTATE_ACK -}; - -struct vder_dhcp_negotiation { - struct vder_dhcp_negotiation *next; - uint32_t xid; - uint8_t hwaddr[6]; - uint32_t assigned_address; - enum dhcp_negotiation_state state; - struct vder_arp_entry *arp; -}; - -void *dhcp_server_loop(void *ptr_iface); - -#endif Modified: branches/vde-router/vde-2/src/vde_router/vder_packet.c =================================================================== --- branches/vde-router/vde-2/src/vde_router/vder_packet.c 2011-12-27 14:45:35 UTC (rev 527) +++ branches/vde-router/vde-2/src/vde_router/vder_packet.c 2011-12-30 10:33:54 UTC (rev 528) @@ -77,9 +77,9 @@ int recvd = 0; int is_broadcast = vder_ipaddress_is_broadcast(iph->daddr); - if (!vder_ipaddress_is_local(iph->daddr) && !is_broadcast) return 0; + switch(iph->protocol) { case PROTO_ICMP: vder_icmp_recv(vb); @@ -125,7 +125,28 @@ return vder_sendto(ro->iface, vdb, ae->macaddr); } +int vder_packet_broadcast(struct vde_buff *vdb, struct vder_iface *iface, uint32_t dst_ip, uint8_t protocol) +{ + struct iphdr *iph=iphead(vdb); + struct vde_ethernet_header *eth = ethhead(vdb); + uint8_t bcast_macaddr[6] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}; + eth->buftype = htons(PTYPE_IP); + + memset(iph,0x45,1); + iph->tos = 0; + iph->frag_off=htons(0x4000); // Don't fragment. + iph->tot_len = htons(vdb->len - sizeof(struct vde_ethernet_header)); + iph->id = 0; + iph->protocol = protocol; + iph->ttl = DEFAULT_TTL; + iph->daddr = dst_ip; + //iph->saddr = vder_get_right_localip(iface, iph->daddr); + iph->saddr = 0; + iph->check = htons(vder_ip_checksum(iph)); + return vder_sendto(iface, vdb, bcast_macaddr); +} + void vder_packet_recv(struct vder_iface *vif, int timeout) { struct pollfd pfd; Modified: branches/vde-router/vde-2/src/vde_router/vder_packet.h =================================================================== --- branches/vde-router/vde-2/src/vde_router/vder_packet.h 2011-12-27 14:45:35 UTC (rev 527) +++ branches/vde-router/vde-2/src/vde_router/vder_packet.h 2011-12-30 10:33:54 UTC (rev 528) @@ -11,6 +11,7 @@ void vder_packet_recv(struct vder_iface *vif, int timeout); uint16_t net_checksum(void *inbuf, int len); int vder_packet_send(struct vde_buff *vdb, uint32_t dst_ip, uint8_t protocol); +int vder_packet_broadcast(struct vde_buff *vdb, struct vder_iface *iface, uint32_t dst_ip, uint8_t protocol); char *vder_ntoa(uint32_t addr); #endif Modified: branches/vde-router/vde-2/src/vde_router/vder_queue.c =================================================================== --- branches/vde-router/vde-2/src/vde_router/vder_queue.c 2011-12-27 14:45:35 UTC (rev 527) +++ branches/vde-router/vde-2/src/vde_router/vder_queue.c 2011-12-30 10:33:54 UTC (rev 528) @@ -156,8 +156,6 @@ } - - void qred_setup(struct vder_queue *q, uint32_t min, uint32_t max, double P, uint32_t limit) { pthread_mutex_lock(&q->lock); Modified: branches/vde-router/vde-2/src/vde_router/vder_udp.c =================================================================== --- branches/vde-router/vde-2/src/vde_router/vder_udp.c 2011-12-27 14:45:35 UTC (rev 527) +++ branches/vde-router/vde-2/src/vde_router/vder_udp.c 2011-12-30 10:33:54 UTC (rev 528) @@ -1,5 +1,6 @@ #include "vder_udp.h" #include <stdio.h> +#include <unistd.h> /* UDP header, rfc 768 */ @@ -17,6 +18,7 @@ int found = 0; struct vde_buff *copy = NULL; uint16_t port = transport_dport(buf); + while(cur) { if (cur->port == port) { if (!found) { @@ -88,6 +90,7 @@ errno = EINVAL; return -1; } + ro = vder_get_route(dst); if (!ro) { errno = EHOSTUNREACH; @@ -113,8 +116,39 @@ return len; } +int vder_udpsocket_sendto_broadcast(struct vder_udp_socket *sock, void *data, size_t len, + struct vder_iface *iface, uint32_t dst, uint16_t dstport) +{ + struct vde_buff *b; + struct udphdr *uh; + uint8_t *datagram; + int bufsize; + if (len <= 0) { + errno = EINVAL; + return -1; + } -int vder_udpsocket_recvfrom(struct vder_udp_socket *sock, void *data, size_t len, uint32_t *from, uint16_t *fromport) + bufsize = sizeof(struct vde_buff) + sizeof(struct vde_ethernet_header) + sizeof(struct iphdr) + sizeof(struct udphdr) + len; + b = malloc(bufsize); + if (!b) + return -1; + b->len = bufsize - sizeof(struct vde_buff); + b->src = NULL; + b->priority = PRIO_BESTEFFORT; + uh = (struct udphdr *) payload(b); + datagram = (uint8_t *)((payload(b) + sizeof(struct udphdr))); + memcpy(datagram, data, len); + + uh->sport = sock->port; + uh->dport = dstport; + uh->len = htons(len); + uh->crc = 0; + vder_packet_broadcast(b, iface, dst, PROTO_UDP); + return len; +} + + +int vder_udpsocket_recvfrom(struct vder_udp_socket *sock, void *data, size_t len, uint32_t *from, uint16_t *fromport, int timeout) { struct vde_buff *b; struct udphdr *uh; @@ -125,6 +159,17 @@ return -1; } + while ((timeout > 0) && (sock->inq.n == 0)) { + usleep(10000); + timeout -= 10; + if (timeout < 0) + timeout = 0; + } + + if ((timeout == 0) && (sock->inq.n == 0)) { + return 0; + } + do { b = dequeue(&sock->inq); } while(!b); Modified: branches/vde-router/vde-2/src/vde_router/vder_udp.h =================================================================== --- branches/vde-router/vde-2/src/vde_router/vder_udp.h 2011-12-27 14:45:35 UTC (rev 527) +++ branches/vde-router/vde-2/src/vde_router/vder_udp.h 2011-12-30 10:33:54 UTC (rev 528) @@ -27,6 +27,8 @@ struct vder_udp_socket *vder_udpsocket_open(uint16_t port); void vder_udp_close(struct vder_udp_socket *sock); int vder_udpsocket_sendto(struct vder_udp_socket *sock, void *data, size_t len, uint32_t dst, uint16_t dstport); -int vder_udpsocket_recvfrom(struct vder_udp_socket *sock, void *data, size_t len, uint32_t *from, uint16_t *fromport); +int vder_udpsocket_sendto_broadcast(struct vder_udp_socket *sock, void *data, size_t len, + struct vder_iface *iface, uint32_t dst, uint16_t dstport); +int vder_udpsocket_recvfrom(struct vder_udp_socket *sock, void *data, size_t len, uint32_t *from, uint16_t *fromport, int timeout); #endif This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. ------------------------------------------------------------------------------ Ridiculously easy VDI. With Citrix VDI-in-a-Box, you don't need a complex infrastructure or vast IT resources to deliver seamless, secure access to virtual desktops. With this all-in-one solution, easily deploy virtual desktops for less than the cost of PCs and save 60% on VDI infrastructure costs. Try it free! http://p.sf.net/sfu/Citrix-VDIinabox _______________________________________________ vde-users mailing list vde-users@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/vde-users