Goto sanos source index
//
// dhcp.c
//
// Dynamic Host Configuration Protocol (DHCP)
//
// Copyright (C) 2002 Michael Ringgaard. All rights reserved.
// Portions Copyright (C) 2001, 2002 Leon Woestenberg <leon.woestenberg@axon.nl>
// Portions Copyright (C) 2001, 2002 Axon Digital Design B.V., The Netherlands.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// 3. Neither the name of the project nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
#include <net/net.h>
static struct dhcp_state *dhcp_client_list = NULL;
int dhcp_arp_check = 0;
//
// DHCP client state machine functions
//
static void dhcp_handle_ack(struct dhcp_state *state);
static void dhcp_handle_nak(struct dhcp_state *state);
static void dhcp_handle_offer(struct dhcp_state *state);
static err_t dhcp_discover(struct dhcp_state *state);
static err_t dhcp_select(struct dhcp_state *state);
static void dhcp_check(struct dhcp_state *state);
static err_t dhcp_decline(struct dhcp_state *state);
static void dhcp_bind(struct dhcp_state *state);
static err_t dhcp_rebind(struct dhcp_state *state);
static err_t dhcp_release(struct dhcp_state *state);
static void dhcp_set_state(struct dhcp_state *state, unsigned char new_state);
//
// Receive, unfold, process and free incoming messages
//
static err_t dhcp_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, struct ip_addr *addr, unsigned short port);
static err_t dhcp_unfold_reply(struct dhcp_state *state);
static unsigned char *dhcp_get_option_ptr(struct dhcp_state *state, unsigned char option_type);
static unsigned char dhcp_get_option_byte(unsigned char *ptr);
static unsigned short dhcp_get_option_short(unsigned char *ptr);
static unsigned long dhcp_get_option_long(unsigned char *ptr);
static void dhcp_free_reply(struct dhcp_state *state);
static void dhcp_timeout_handler(struct dhcp_state *state);
static void dhcp_t1_timeout(struct dhcp_state *state);
static void dhcp_t2_timeout(struct dhcp_state *state);
void dhcp_arp_reply(struct ip_addr *addr);
//
// Build outgoing messages
//
static err_t dhcp_create_request(struct dhcp_state *state);
static void dhcp_delete_request(struct dhcp_state *state);
static void dhcp_option(struct dhcp_state *state, unsigned char option_type, unsigned char option_len);
static void dhcp_option_byte(struct dhcp_state *state, unsigned char value);
static void dhcp_option_short(struct dhcp_state *state, unsigned short value);
static void dhcp_option_long(struct dhcp_state *state, unsigned long value);
static void dhcp_option_trailer(struct dhcp_state *state);
struct dhcp_state *dhcp_find_client(struct netif *netif);
static void dhcp_dump_options(struct dhcp_state *state);
//
// dhcpstat_proc
//
static int dhcpstat_proc(struct proc_file *pf, void *arg) {
struct dhcp_state *state;
static char *statename[] = {
"<none>", "REQUESTING", "INIT", "REBOOTING", "REBINDING",
"RENEWING", "SELECTING", "INFORMING", "CHECKING", "PERMANENT",
"BOUND", "BACKING OFF", "OFF"
};
state = dhcp_client_list;
while (state) {
time_t lease_age = time(NULL) - state->bind_time;
pprintf(pf, "DHCP configuration for %s:\n", state->netif->name);
pprintf(pf, " State .................. : %s\n", statename[state->state]);
pprintf(pf, " DHCP Server ............ : %a\n", &state->server_ip_addr);
pprintf(pf, " IP Adress .............. : %a\n", &state->offered_ip_addr);
pprintf(pf, " Subnet Mask ............ : %a\n", &state->offered_sn_mask);
pprintf(pf, " Gateway Adress ......... : %a\n", &state->offered_gw_addr);
pprintf(pf, " Broadcast Address ...... : %a\n", &state->offered_bc_addr);
pprintf(pf, " Primary DNS Server ..... : %a\n", &state->offered_dns1_addr);
pprintf(pf, " Secondary DNS Server ... : %a\n", &state->offered_dns2_addr);
pprintf(pf, " Primary NTP Server ..... : %a\n", &state->offered_ntpserv1_addr);
pprintf(pf, " Secondary NTP Server ... : %a\n", &state->offered_ntpserv2_addr);
pprintf(pf, " Domain Name ............ : %s\n", state->offered_domain_name);
pprintf(pf, " Lease Period ........... : %d seconds (%d left)\n", state->offered_t0_lease, state->offered_t0_lease - lease_age);
pprintf(pf, " Renew Period ........... : %d seconds (%d left)\n", state->offered_t1_renew, state->offered_t1_renew - lease_age);
pprintf(pf, " Rebind Period .......... : %d seconds (%d left)\n", state->offered_t2_rebind, state->offered_t2_rebind - lease_age);
pprintf(pf, " Lease Age .............. : %d seconds\n", lease_age);
if (state->next) pprintf(pf, "\n");
state = state->next;
}
return 0;
}
//
// dhcp_handle_nak
//
static void dhcp_handle_nak(struct dhcp_state *state) {
int msecs = 10 * 1000;
mod_timer(&state->request_timeout_timer, ticks + msecs / MSECS_PER_TICK);
kprintf(KERN_WARNING "dhcp_handle_nak: request timeout %u msecs\n", msecs);
dhcp_set_state(state, DHCP_BACKING_OFF);
}
//
// dhcp_check
//
// Checks if the offered address is already in use.
// It does so by sending an ARP query.
//
static void dhcp_check(struct dhcp_state *state) {
struct pbuf *p;
err_t result;
int msecs;
p = arp_query(state->netif, (struct eth_addr *) &state->netif->hwaddr, &state->offered_ip_addr);
if (p != NULL) {
kprintf("dhcp_check: sending ARP request len %u\n", p->tot_len);
result = dev_transmit((dev_t) state->netif->state, p);
pbuf_free(p);
//return result;
}
state->tries++;
msecs = state->tries * 1000;
mod_timer(&state->request_timeout_timer, ticks + msecs / MSECS_PER_TICK);
dhcp_set_state(state, DHCP_CHECKING);
}
//
// dhcp_handle_offer
//
static void dhcp_handle_offer(struct dhcp_state *state)
{
unsigned char *option_ptr = dhcp_get_option_ptr(state, DHCP_OPTION_DHCP_SERVER_IDENTIFIER);
if (option_ptr != NULL) {
state->server_ip_addr.addr = htonl(dhcp_get_option_long(&option_ptr[2]));
//kprintf("dhcp_handle_offer: server 0x%08lx\n", state->server_ip_addr.addr);
ip_addr_set(&state->offered_ip_addr, (struct ip_addr *) &state->msg_in->yiaddr);
//kprintf("dhcp_handle_offer: offer for 0x%08lx\n", state->offered_ip_addr.addr);
dhcp_select(state);
}
}
//
// dhcp_select
//
// Select a DHCP server offer out of all offers.
// We simply select the first offer received.
//
static err_t dhcp_select(struct dhcp_state *state) {
err_t result;
int msecs;
// Create and initialize the DHCP message header
result = dhcp_create_request(state);
if (result == 0) {
dhcp_option(state, DHCP_OPTION_DHCP_MESSAGE_TYPE, 1);
dhcp_option_byte(state, DHCP_REQUEST);
dhcp_option(state, DHCP_OPTION_DHCP_MAX_MESSAGE_SIZE, 2);
dhcp_option_short(state, 576);
dhcp_option(state, DHCP_OPTION_DHCP_REQUESTED_ADDRESS, 4);
dhcp_option_long(state, ntohl(state->offered_ip_addr.addr));
dhcp_option(state, DHCP_OPTION_DHCP_SERVER_IDENTIFIER, 4);
dhcp_option_long(state, ntohl(state->server_ip_addr.addr));
dhcp_option_trailer(state);
pbuf_realloc(state->p_out, sizeof(struct dhcp_msg) + state->options_out_len);
udp_bind(state->pcb, IP_ADDR_ANY, DHCP_CLIENT_PORT);
udp_connect(state->pcb, IP_ADDR_BROADCAST, DHCP_SERVER_PORT);
if (udp_send(state->pcb, state->p_out, NULL, 0, state->netif) >= 0) state->p_out = NULL;
// reconnect to any (or to server here?!)
udp_connect(state->pcb, IP_ADDR_ANY, DHCP_SERVER_PORT);
dhcp_delete_request(state);
}
state->tries++;
msecs = state->tries < 4 ? state->tries * 1000 : 4 * 1000;
mod_timer(&state->request_timeout_timer, ticks + msecs / MSECS_PER_TICK);
dhcp_set_state(state, DHCP_REQUESTING);
return result;
}
//
// dhcp_timeout_handler
//
// Something has timed out, handle this
//
static void dhcp_timeout_handler(struct dhcp_state *state) {
if (state->state == DHCP_BACKING_OFF || state->state == DHCP_SELECTING) {
kprintf(KERN_WARNING "dhcp_timeout: restarting discovery\n");
dhcp_discover(state);
} else if (state->state == DHCP_REQUESTING) {
kprintf(KERN_WARNING "dhcp_timeout: REQUESTING, DHCP request timed out\n");
if (state->tries <= 5) {
dhcp_select(state);
} else {
struct netif *netif = state->netif;
kprintf(KERN_WARNING "dhcp_timeout: REQUESTING, releasing, restarting\n");
dhcp_release(state);
dhcp_discover(state);
}
} else if (state->state == DHCP_CHECKING) {
kprintf(KERN_WARNING "dhcp_timeout: CHECKING, ARP request timed out\n");
if (state->tries <= 1) {
dhcp_check(state);
} else {
// No ARP replies on the offered address, looks like the IP address is indeed free
dhcp_bind(state);
}
} else if (state->state == DHCP_RENEWING) {
kprintf(KERN_WARNING "dhcp_timeout: RENEWING, DHCP request timed out\n");
dhcp_renew(state);
} else if (state->state == DHCP_REBINDING) {
kprintf(KERN_WARNING "dhcp_timeout: REBINDING, DHCP request timed out\n");
if (state->tries <= 8) {
dhcp_rebind(state);
} else {
struct netif *netif = state->netif;
kprintf(KERN_WARNING "dhcp_timeout: REBINDING, release, restart\n");
dhcp_release(state);
dhcp_discover(state);
}
}
}
//
// dhcp_timeout
//
static void dhcp_timeout(void *arg) {
struct dhcp_state *state = arg;
queue_task(&sys_task_queue, &state->request_timeout_task, (taskproc_t) dhcp_timeout_handler, state);
}
//
// dhcp_t1_timeout
//
static void dhcp_t1_timeout(struct dhcp_state *state) {
if (state->state == DHCP_REQUESTING || state->state == DHCP_BOUND || state->state == DHCP_RENEWING) {
kprintf(KERN_WARNING "dhcp_t1_timeout: must renew\n");
queue_task(&sys_task_queue, &state->t1_timeout_task, (taskproc_t) dhcp_renew, state);
}
}
//
// dhcp_t2_timeout
//
static void dhcp_t2_timeout(struct dhcp_state *state) {
if (state->state == DHCP_REQUESTING || state->state == DHCP_BOUND || state->state == DHCP_RENEWING) {
kprintf(KERN_WARNING "dhcp_t2_timeout: must rebind\n");
queue_task(&sys_task_queue, &state->t2_timeout_task, (taskproc_t) dhcp_rebind, state);
}
}
//
// dhcp_handle_ack
//
static void dhcp_handle_ack(struct dhcp_state *state) {
unsigned char *option_ptr;
state->offered_sn_mask.addr = 0;
state->offered_gw_addr.addr = 0;
state->offered_bc_addr.addr = 0;
state->offered_dns1_addr.addr = 0;
state->offered_dns2_addr.addr = 0;
state->bind_time = time(NULL);
option_ptr = dhcp_get_option_ptr(state, DHCP_OPTION_DHCP_LEASE_TIME);
if (option_ptr != NULL) {
state->offered_t0_lease = dhcp_get_option_long(option_ptr + 2);
state->offered_t1_renew = state->offered_t0_lease / 2;
state->offered_t2_rebind = state->offered_t0_lease;
}
option_ptr = dhcp_get_option_ptr(state, DHCP_OPTION_DHCP_RENEWAL_TIME);
if (option_ptr != NULL) {
state->offered_t1_renew = dhcp_get_option_long(option_ptr + 2);
}
option_ptr = dhcp_get_option_ptr(state, DHCP_OPTION_DHCP_REBINDING_TIME);
if (option_ptr != NULL) {
state->offered_t2_rebind = dhcp_get_option_long(option_ptr + 2);
}
ip_addr_set(&state->offered_ip_addr, &state->msg_in->yiaddr);
option_ptr = dhcp_get_option_ptr(state, DHCP_OPTION_SUBNET_MASK);
if (option_ptr != NULL) {
state->offered_sn_mask.addr = htonl(dhcp_get_option_long(option_ptr + 2));
}
option_ptr = dhcp_get_option_ptr(state, DHCP_OPTION_ROUTERS);
if (option_ptr != NULL) {
state->offered_gw_addr.addr = htonl(dhcp_get_option_long(option_ptr + 2));
}
option_ptr = dhcp_get_option_ptr(state, DHCP_OPTION_BROADCAST_ADDRESS);
if (option_ptr != NULL) {
state->offered_bc_addr.addr = htonl(dhcp_get_option_long(option_ptr + 2));
}
option_ptr = dhcp_get_option_ptr(state, DHCP_OPTION_DOMAIN_NAME_SERVERS);
if (option_ptr != NULL) {
if (option_ptr[1] >= 4) state->offered_dns1_addr.addr = htonl(dhcp_get_option_long(option_ptr + 2));
if (option_ptr[1] >= 8) state->offered_dns2_addr.addr = htonl(dhcp_get_option_long(option_ptr + 6));
}
option_ptr = dhcp_get_option_ptr(state, DHCP_OPTION_NTP_SERVERS);
if (option_ptr != NULL) {
if (option_ptr[1] >= 4) state->offered_ntpserv1_addr.addr = htonl(dhcp_get_option_long(option_ptr + 2));
if (option_ptr[1] >= 8) state->offered_ntpserv2_addr.addr = htonl(dhcp_get_option_long(option_ptr + 6));
}
option_ptr = dhcp_get_option_ptr(state, DHCP_OPTION_DOMAIN_NAME);
if (option_ptr != NULL) {
memcpy(state->offered_domain_name, option_ptr + 2, option_ptr[1]);
state->offered_domain_name[option_ptr[1]] = 0;
}
}
//
// dhcp_init
//
void dhcp_init() {
register_proc_inode("dhcpstat", dhcpstat_proc, NULL);
}
//
// dhcp_start
//
// Start the DHCP negotiation
//
struct dhcp_state *dhcp_start(struct netif *netif) {
struct dhcp_state *state = NULL;
struct dhcp_state *list_state;
err_t result;
state = dhcp_find_client(netif);
if (state != NULL) {
kprintf("dhcp_start: already active on interface\n");
// Just restart the DHCP negotiation
result = dhcp_discover(state);
if (result < 0) return NULL;
}
//kprintf("dhcp_start: starting new DHCP client\n");
state = kmalloc(sizeof(struct dhcp_state));
if (!state) return NULL;
memset(state, 0, sizeof(struct dhcp_state));
state->pcb = udp_new();
if (!state->pcb) {
kfree(state);
return NULL;
}
state->pcb->flags |= UDP_FLAGS_BROADCAST;
state->netif = netif;
state->next = NULL;
init_event(&state->binding_complete, 0, 0);
init_task(&state->request_timeout_task);
init_task(&state->t1_timeout_task);
init_task(&state->t2_timeout_task);
init_timer(&state->request_timeout_timer, (timerproc_t) dhcp_timeout, state);
init_timer(&state->t1_timeout_timer, (timerproc_t) dhcp_t1_timeout, state);
init_timer(&state->t2_timeout_timer, (timerproc_t) dhcp_t2_timeout, state);
if (!dhcp_client_list) {
dhcp_client_list = state;
} else {
list_state = dhcp_client_list;
while (list_state->next != NULL) list_state = list_state->next;
list_state->next = state;
}
dhcp_discover(state);
return state;
}
//
// dhcp_inform
//
err_t dhcp_inform(struct netif *netif) {
struct dhcp_state *state;
err_t result;
state = kmalloc(sizeof(struct dhcp_state));
if (!state) return -ENOMEM;
memset(state, 0, sizeof(struct dhcp_state));
state->pcb = udp_new();
if (!state->pcb) {
kfree(state);
return -ENOMEM;
}
state->pcb->flags |= UDP_FLAGS_BROADCAST;
state->netif = netif;
state->next = NULL;
// Create and initialize the DHCP message header
result = dhcp_create_request(state);
if (!result) return -ENOMEM;
dhcp_option(state, DHCP_OPTION_DHCP_MESSAGE_TYPE, 1);
dhcp_option_byte(state, DHCP_INFORM);
dhcp_option(state, DHCP_OPTION_DHCP_MAX_MESSAGE_SIZE, 2);
dhcp_option_short(state, 576);
dhcp_option_trailer(state);
pbuf_realloc(state->p_out, sizeof(struct dhcp_msg) + state->options_out_len);
udp_bind(state->pcb, IP_ADDR_ANY, DHCP_CLIENT_PORT);
udp_connect(state->pcb, IP_ADDR_BROADCAST, DHCP_SERVER_PORT);
if (udp_send(state->pcb, state->p_out, NULL, 0, state->netif) >= 0) state->p_out = NULL;
udp_connect(state->pcb, IP_ADDR_ANY, DHCP_SERVER_PORT);
dhcp_delete_request(state);
udp_remove(state->pcb);
kfree(state);
return 0;
}
//
// dhcp_arp_reply
//
void dhcp_arp_reply(struct ip_addr *addr) {
struct dhcp_state *state = dhcp_client_list;
while (state != NULL) {
// Is this DHCP client doing an ARP check?
if (state->state == DHCP_CHECKING) {
kprintf("dhcp_arp_reply: CHECKING, arp reply for 0x%08lx\n", addr->addr);
// Does a host respond with the address we were offered by the DHCP server?
if (ip_addr_cmp(addr, &state->offered_ip_addr)) {
// We will not accept the offered address
kprintf("dhcp_arp_reply: arp reply matched with offered address, declining\n");
dhcp_decline(state);
}
}
state = state->next;
}
}
//
// dhcp_decline
//
static err_t dhcp_decline(struct dhcp_state *state) {
err_t result;
int msecs;
// Create and initialize the DHCP message header
result = dhcp_create_request(state);
if (result == 0) {
dhcp_option(state, DHCP_OPTION_DHCP_MESSAGE_TYPE, 1);
dhcp_option_byte(state, DHCP_DECLINE);
dhcp_option(state, DHCP_OPTION_DHCP_MAX_MESSAGE_SIZE, 2);
dhcp_option_short(state, 576);
dhcp_option_trailer(state);
pbuf_realloc(state->p_out, sizeof(struct dhcp_msg) + state->options_out_len);
udp_bind(state->pcb, IP_ADDR_ANY, DHCP_CLIENT_PORT);
udp_connect(state->pcb, &state->server_ip_addr, DHCP_SERVER_PORT);
if (udp_send(state->pcb, state->p_out, NULL, 0, state->netif) >= 0) state->p_out = NULL;
dhcp_delete_request(state);
}
state->tries++;
msecs = 10 * 1000;
mod_timer(&state->request_timeout_timer, ticks + msecs / MSECS_PER_TICK);
dhcp_set_state(state, DHCP_BACKING_OFF);
return result;
}
//
// dhcp_discover
//
// Start the DHCP process, discover a server
//
static err_t dhcp_discover(struct dhcp_state *state) {
err_t result;
int msecs;
ip_addr_set(&state->offered_ip_addr, IP_ADDR_ANY);
// Create and initialize the DHCP message header
result = dhcp_create_request(state);
if (result == 0) {
dhcp_option(state, DHCP_OPTION_DHCP_MESSAGE_TYPE, 1);
dhcp_option_byte(state, DHCP_DISCOVER);
dhcp_option(state, DHCP_OPTION_DHCP_MAX_MESSAGE_SIZE, 2);
dhcp_option_short(state, 576);
#if 0
dhcp_option(state, DHCP_OPTION_DHCP_PARAMETER_REQUEST_LIST, 6);
dhcp_option_byte(state, DHCP_OPTION_SUBNET_MASK);
dhcp_option_byte(state, DHCP_OPTION_ROUTERS);
dhcp_option_byte(state, DHCP_OPTION_BROADCAST_ADDRESS);
dhcp_option_byte(state, DHCP_OPTION_DOMAIN_NAME_SERVERS);
dhcp_option_byte(state, DHCP_OPTION_DOMAIN_NAME);
dhcp_option_byte(state, DHCP_OPTION_NTP_SERVERS);
#endif
dhcp_option_trailer(state);
pbuf_realloc(state->p_out, sizeof(struct dhcp_msg) + state->options_out_len);
udp_recv(state->pcb, dhcp_recv, state);
udp_bind(state->pcb, IP_ADDR_ANY, DHCP_CLIENT_PORT);
udp_connect(state->pcb, IP_ADDR_BROADCAST, DHCP_SERVER_PORT);
if (udp_send(state->pcb, state->p_out, NULL, 0, state->netif) >= 0) state->p_out = NULL;
udp_bind(state->pcb, IP_ADDR_ANY, DHCP_CLIENT_PORT);
udp_connect(state->pcb, IP_ADDR_ANY, DHCP_SERVER_PORT);
dhcp_delete_request(state);
}
state->tries++;
msecs = state->tries < 4 ? (state->tries + 1) * 1000 : 10 * 1000;
mod_timer(&state->request_timeout_timer, ticks + msecs / MSECS_PER_TICK);
dhcp_set_state(state, DHCP_SELECTING);
return result;
}
//
// dhcp_bind
//
// Bind the interface to the offered IP address
//
static void dhcp_bind(struct dhcp_state *state) {
struct ip_addr sn_mask, gw_addr;
if (state->offered_t1_renew != 0xFFFFFFFF) {
mod_timer(&state->t1_timeout_timer, ticks + state->offered_t1_renew * TICKS_PER_SEC);
}
if (state->offered_t2_rebind != 0xFFFFFFFF) {
mod_timer(&state->t2_timeout_timer, ticks + state->offered_t2_rebind * TICKS_PER_SEC);
}
ip_addr_set(&sn_mask, &state->offered_sn_mask);
if (sn_mask.addr == 0) {
// Subnet mask not given
// Choose a safe subnet mask given the network class
unsigned char first_octet = ip4_addr1(&sn_mask);
if (first_octet <= 127) {
sn_mask.addr = htonl(0xFF000000);
} else if (first_octet >= 192) {
sn_mask.addr = htonl(0xFFFFFF00);
} else {
sn_mask.addr = htonl(0xFFFF0000);
}
}
ip_addr_set(&gw_addr, &state->offered_gw_addr);
if (gw_addr.addr == 0) {
// Gateway address not given
gw_addr.addr = state->offered_ip_addr.addr;
gw_addr.addr &= sn_mask.addr;
gw_addr.addr |= 0x01000000;
}
netif_set_ipaddr(state->netif, &state->offered_ip_addr);
netif_set_netmask(state->netif, &sn_mask);
netif_set_gw(state->netif, &gw_addr);
// Determine broadcast address
state->netif->broadcast.addr = (state->netif->ipaddr.addr & state->netif->netmask.addr) | ~(state->netif->netmask.addr);
//dhcp_dump_options(state);
if (peb) {
peb->primary_dns.s_addr = state->offered_dns1_addr.addr;
peb->secondary_dns.s_addr = state->offered_dns2_addr.addr;
memcpy(peb->default_domain, state->offered_domain_name, 256);
peb->ntp_server1.s_addr = state->offered_ntpserv1_addr.addr;
peb->ntp_server2.s_addr = state->offered_ntpserv2_addr.addr;
}
set_event(&state->binding_complete);
dhcp_set_state(state, DHCP_BOUND);
}
//
// dhcp_renew
//
err_t dhcp_renew(struct dhcp_state *state) {
err_t result;
int msecs;
// Create and initialize the DHCP message header
result = dhcp_create_request(state);
if (result == 0) {
dhcp_option(state, DHCP_OPTION_DHCP_MESSAGE_TYPE, 1);
dhcp_option_byte(state, DHCP_REQUEST);
dhcp_option(state, DHCP_OPTION_DHCP_MAX_MESSAGE_SIZE, 2);
dhcp_option_short(state, 576);
dhcp_option(state, DHCP_OPTION_DHCP_REQUESTED_ADDRESS, 4);
dhcp_option_long(state, ntohl(state->offered_ip_addr.addr));
dhcp_option(state, DHCP_OPTION_DHCP_SERVER_IDENTIFIER, 4);
dhcp_option_long(state, ntohl(state->server_ip_addr.addr));
dhcp_option_trailer(state);
pbuf_realloc(state->p_out, sizeof(struct dhcp_msg) + state->options_out_len);
udp_bind(state->pcb, IP_ADDR_ANY, DHCP_CLIENT_PORT);
udp_connect(state->pcb, &state->server_ip_addr, DHCP_SERVER_PORT);
if (udp_send(state->pcb, state->p_out, NULL, 0, state->netif) >= 0) state->p_out = NULL;
dhcp_delete_request(state);
}
state->tries++;
msecs = state->tries < 10 ? state->tries * 2000 : 20 * 1000;
mod_timer(&state->request_timeout_timer, ticks + msecs / MSECS_PER_TICK);
dhcp_set_state(state, DHCP_RENEWING);
return result;
}
//
// dhcp_rebind
//
static err_t dhcp_rebind(struct dhcp_state *state) {
err_t result;
int msecs;
// create and initialize the DHCP message header
result = dhcp_create_request(state);
if (result == 0) {
dhcp_option(state, DHCP_OPTION_DHCP_MESSAGE_TYPE, 1);
dhcp_option_byte(state, DHCP_REQUEST);
dhcp_option(state, DHCP_OPTION_DHCP_MAX_MESSAGE_SIZE, 2);
dhcp_option_short(state, 576);
dhcp_option(state, DHCP_OPTION_DHCP_REQUESTED_ADDRESS, 4);
dhcp_option_long(state, ntohl(state->offered_ip_addr.addr));
dhcp_option(state, DHCP_OPTION_DHCP_SERVER_IDENTIFIER, 4);
dhcp_option_long(state, ntohl(state->server_ip_addr.addr));
dhcp_option_trailer(state);
pbuf_realloc(state->p_out, sizeof(struct dhcp_msg) - state->options_out_len);
udp_bind(state->pcb, IP_ADDR_ANY, DHCP_CLIENT_PORT);
udp_connect(state->pcb, IP_ADDR_BROADCAST, DHCP_SERVER_PORT);
if (udp_send(state->pcb, state->p_out, NULL, 0, state->netif) >= 0) state->p_out = NULL;
udp_connect(state->pcb, IP_ADDR_ANY, DHCP_SERVER_PORT);
dhcp_delete_request(state);
}
state->tries++;
msecs = state->tries < 10 ? state->tries * 1000 : 10 * 1000;
mod_timer(&state->request_timeout_timer, ticks + msecs / MSECS_PER_TICK);
dhcp_set_state(state, DHCP_REBINDING);
return result;
}
//
// dhcp_release
//
static err_t dhcp_release(struct dhcp_state *state) {
err_t result;
int msecs;
// Create and initialize the DHCP message header
result = dhcp_create_request(state);
if (result == 0) {
dhcp_option(state, DHCP_OPTION_DHCP_MESSAGE_TYPE, 1);
dhcp_option_byte(state, DHCP_RELEASE);
dhcp_option_trailer(state);
pbuf_realloc(state->p_out, sizeof(struct dhcp_msg) + state->options_out_len);
udp_bind(state->pcb, IP_ADDR_ANY, DHCP_CLIENT_PORT);
udp_connect(state->pcb, &state->server_ip_addr, DHCP_SERVER_PORT);
if (udp_send(state->pcb, state->p_out, NULL, 0, state->netif) >= 0) state->p_out = NULL;
dhcp_delete_request(state);
}
state->tries++;
msecs = state->tries < 10 ? state->tries * 1000 : 10 * 1000;
mod_timer(&state->request_timeout_timer, ticks + msecs / MSECS_PER_TICK);
// Remove IP address from interface
netif_set_ipaddr(state->netif, IP_ADDR_ANY);
netif_set_gw(state->netif, IP_ADDR_ANY);
netif_set_netmask(state->netif, IP_ADDR_ANY);
// ... and idle DHCP client
dhcp_set_state(state, DHCP_OFF);
return result;
}
//
// dhcp_stop
//
void dhcp_stop(struct netif *netif) {
struct dhcp_state *state;
struct dhcp_state *list_state;
state = dhcp_find_client(netif);
if (state) {
if (state->state == DHCP_BOUND) dhcp_release(state);
del_timer(&state->request_timeout_timer);
del_timer(&state->t1_timeout_timer);
del_timer(&state->t2_timeout_timer);
udp_remove(state->pcb);
kfree(state);
if (dhcp_client_list == state) {
dhcp_client_list = state->next;
} else {
list_state = dhcp_client_list;
while ((list_state != NULL) && (list_state->next != state)) list_state = list_state->next;
if (list_state) list_state->next = state->next;
}
}
}
//
// dhcp_set_state
//
static void dhcp_set_state(struct dhcp_state *state, unsigned char new_state) {
if (new_state != state->state) {
state->state = new_state;
state->tries = 0;
}
}
//
// dhcp_option
//
static void dhcp_option(struct dhcp_state *state, unsigned char option_type, unsigned char option_len)
{
state->msg_out->options[state->options_out_len++] = option_type;
state->msg_out->options[state->options_out_len++] = option_len;
}
//
// dhcp_option_byte
//
static void dhcp_option_byte(struct dhcp_state *state, unsigned char value)
{
state->msg_out->options[state->options_out_len++] = value;
}
//
// dhcp_option_short
//
static void dhcp_option_short(struct dhcp_state *state, unsigned short value)
{
state->msg_out->options[state->options_out_len++] = (value & 0xFF00) >> 8;
state->msg_out->options[state->options_out_len++] = value & 0x00FF;
}
//
// dhcp_option_long
//
static void dhcp_option_long(struct dhcp_state *state, unsigned long value)
{
state->msg_out->options[state->options_out_len++] = (unsigned char) ((value & 0xFF000000) >> 24);
state->msg_out->options[state->options_out_len++] = (unsigned char) ((value & 0x00FF0000) >> 16);
state->msg_out->options[state->options_out_len++] = (unsigned char) ((value & 0x0000FF00) >> 8);
state->msg_out->options[state->options_out_len++] = (unsigned char) (value & 0x000000FF);
}
//
// dhcp_unfold_reply
//
// Extract the dhcp_msg and options each into linear pieces of memory
//
static err_t dhcp_unfold_reply(struct dhcp_state *state) {
struct pbuf *p = state->p;
unsigned char *ptr;
int i;
int j = 0;
state->msg_in = NULL;
state->options_in = NULL;
// Options present?
if (state->p->tot_len > sizeof(struct dhcp_msg)) {
state->options_in_len = state->p->tot_len - sizeof(struct dhcp_msg);
state->options_in = kmalloc(state->options_in_len);
if (!state->options_in) return -ENOMEM;
}
state->msg_in = kmalloc(sizeof(struct dhcp_msg));
if (!state->msg_in) {
kfree(state->options_in);
state->options_in = NULL;
return -ENOMEM;
}
ptr = (unsigned char *) state->msg_in;
// Proceed through struct dhcp_msg
for (i = 0; i < sizeof(struct dhcp_msg); i++) {
*ptr++ = ((unsigned char *) p->payload)[j++];
if (j == p->len) {
p = p->next;
j = 0;
}
}
if (state->options_in) {
ptr = (unsigned char *) state->options_in;
for (i = 0; i < state->options_in_len; i++) {
*ptr++ = ((unsigned char *) p->payload)[j++];
if (j == p->len) {
p = p->next;
j = 0;
}
}
}
return 0;
}
//
// dhcp_free_reply
//
static void dhcp_free_reply(struct dhcp_state *state) {
kfree(state->msg_in);
kfree(state->options_in);
state->msg_in = NULL;
state->options_in = NULL;
state->options_in_len = 0;
}
//
// dhcp_recv
//
static err_t dhcp_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, struct ip_addr *addr, unsigned short port) {
struct dhcp_state *state = (struct dhcp_state *) arg;
struct dhcp_msg *reply_msg = (struct dhcp_msg *) p->payload;
state->p = p;
if (reply_msg->op == DHCP_BOOTREPLY) {
if (memcmp(&state->netif->hwaddr, reply_msg->chaddr, ETHER_ADDR_LEN) == 0) {
if (ntohl(reply_msg->xid) == state->xid) {
unsigned char *options_ptr;
del_timer(&state->request_timeout_timer);
if (dhcp_unfold_reply(state) == 0) {
options_ptr = dhcp_get_option_ptr(state, DHCP_OPTION_DHCP_MESSAGE_TYPE);
if (options_ptr != NULL) {
unsigned char msg_type = dhcp_get_option_byte(options_ptr + 2);
if (msg_type == DHCP_ACK) {
//kprintf("DHCP_ACK received\n");
dhcp_handle_ack(state);
if (state->state == DHCP_REQUESTING) {
if (dhcp_arp_check) {
dhcp_check(state);
} else {
dhcp_bind(state);
}
} else if (state->state == DHCP_REBOOTING || state->state == DHCP_REBINDING || state->state == DHCP_RENEWING) {
dhcp_bind(state);
}
} else if ((msg_type == DHCP_NAK) &&
(state->state == DHCP_REBOOTING || state->state == DHCP_REQUESTING ||
state->state == DHCP_REBINDING || state->state == DHCP_RENEWING)) {
// Received a DHCP_NAK in appropriate state
kprintf("DHCP_NAK received\n");
dhcp_handle_nak(state);
} else if (msg_type == DHCP_OFFER && state->state == DHCP_SELECTING) {
// Received a DHCP_OFFER in DHCP_SELECTING state
//kprintf("DHCP_OFFER received in DHCP_SELECTING state\n");
dhcp_handle_offer(state);
}
}
dhcp_free_reply(state);
}
}
}
}
pbuf_free(p);
return 0;
}
//
// dhcp_create_request
//
static err_t dhcp_create_request(struct dhcp_state *state) {
int i;
state->p_out = pbuf_alloc(PBUF_TRANSPORT, sizeof(struct dhcp_msg) + DHCP_OPTIONS_LEN, PBUF_RW);
if (!state->p_out) return -ENOMEM;
state->msg_out = (struct dhcp_msg *) state->p_out->payload;
memset(state->msg_out, 0, sizeof(struct dhcp_msg) + DHCP_OPTIONS_LEN);
state->xid++;
state->msg_out->op = DHCP_BOOTREQUEST;
state->msg_out->htype = DHCP_HTYPE_ETHER;
state->msg_out->hlen = ETHER_ADDR_LEN;
state->msg_out->xid = htonl(state->xid);
state->msg_out->ciaddr.s_addr = state->netif->ipaddr.addr;
for (i = 0; i < ETHER_ADDR_LEN; i++) state->msg_out->chaddr[i] = state->netif->hwaddr.addr[i];
state->msg_out->cookie = htonl(0x63825363);
state->options_out_len = 0;
return 0;
}
//
// dhcp_delete_request
//
static void dhcp_delete_request(struct dhcp_state *state) {
pbuf_free(state->p_out);
state->p_out = NULL;
state->msg_out = NULL;
}
//
// dhcp_option_trailer
//
// Adds the END option to the DHCP message, and up to three padding bytes.
//
static void dhcp_option_trailer(struct dhcp_state *state) {
state->msg_out->options[state->options_out_len++] = DHCP_OPTION_END;
// Packet is still too small, or not 4 byte aligned?
while ((state->options_out_len < DHCP_MIN_OPTIONS_LEN) || (state->options_out_len & 3)) {
state->msg_out->options[state->options_out_len++] = 0;
}
}
//
// dhcp_get_option_ptr
//
// Return a byte offset into the udp message where the option was found, or
// NULL if the given option was not found
//
static unsigned char *dhcp_get_option_ptr(struct dhcp_state *state, unsigned char option_type) {
unsigned char overload = DHCP_OVERLOAD_NONE;
// Options available?
if (state->options_in != NULL && state->options_in_len > 0) {
// Start with options field
unsigned char *options = (unsigned char *) state->options_in;
int offset = 0;
// At least 1 byte to read and no end marker, then at least 3 bytes to read?
while ((offset < state->options_in_len) && (options[offset] != DHCP_OPTION_END)) {
// Are the sname and/or file field overloaded with options?
if (options[offset] == DHCP_OPTION_DHCP_OPTION_OVERLOAD) {
// Skip option type and length
offset += 2;
overload = options[offset++];
} else if (options[offset] == option_type) {
return &options[offset];
} else if (options[offset] == DHCP_OPTION_PAD) {
offset++;
} else {
//kprintf("opt(%d,%d)", options[offset], options[offset + 1]);
offset++;
offset += 1 + options[offset];
}
}
// Is this an overloaded message?
if (overload != DHCP_OVERLOAD_NONE) {
int field_len;
if (overload == DHCP_OVERLOAD_FILE) {
options = (unsigned char *) &state->msg_in->file;
field_len = DHCP_FILE_LEN;
} else if (overload == DHCP_OVERLOAD_SNAME) {
options = (unsigned char *) &state->msg_in->sname;
field_len = DHCP_SNAME_LEN;
} else {
options = (unsigned char *) &state->msg_in->sname;
field_len = DHCP_FILE_LEN + DHCP_SNAME_LEN;
}
offset = 0;
// At least 1 byte to read and no end marker
while ((offset < field_len) && (options[offset] != DHCP_OPTION_END)) {
if (options[offset] == option_type) {
return &options[offset];
} else if (options[offset] == DHCP_OPTION_PAD) {
offset++;
} else {
//kprintf("opt(%d,%d)", options[offset], options[offset + 1]);
offset++;
offset += 1 + options[offset];
}
}
}
}
return NULL;
}
//
// dhcp_get_option_byte
//
static unsigned char dhcp_get_option_byte(unsigned char *ptr) {
return *ptr;
}
//
// dhcp_get_option_short
//
static unsigned short dhcp_get_option_short(unsigned char *ptr) {
unsigned short value;
value = *ptr++ << 8;
value |= *ptr;
return value;
}
//
// dhcp_get_option_long
//
static unsigned long dhcp_get_option_long(unsigned char *ptr) {
unsigned long value;
value = (unsigned long) (*ptr++) << 24;
value |= (unsigned long) (*ptr++) << 16;
value |= (unsigned long) (*ptr++) << 8;
value |= (unsigned long) (*ptr++);
return value;
}
//
// dhcp_find_client
//
// Given an network interface, return the corresponding dhcp state
// or NULL if the interface was not under DHCP control.
//
struct dhcp_state *dhcp_find_client(struct netif *netif) {
struct dhcp_state *state = dhcp_client_list;
while (state) {
if (state->netif == netif) return state;
state = state->next;
}
return NULL;
}
//
// dhcp_dump_options
//
static void dhcp_dump_options(struct dhcp_state *state) {
unsigned char overload = DHCP_OVERLOAD_NONE;
kprintf("dhcp options:\n");
// Options available?
if (state->options_in != NULL && state->options_in_len > 0) {
// Start with options field
unsigned char *options = (unsigned char *) state->options_in;
int offset = 0;
// At least 1 byte to read and no end marker, then at least 3 bytes to read?
while ((offset < state->options_in_len) && (options[offset] != DHCP_OPTION_END)) {
// Are the sname and/or file field overloaded with options?
if (options[offset] == DHCP_OPTION_DHCP_OPTION_OVERLOAD) {
// Skip option type and length
offset += 2;
overload = options[offset++];
} else if (options[offset] == DHCP_OPTION_PAD) {
offset++;
} else {
kprintf(" option %d len %d\n", options[offset], options[offset + 1]);
offset++;
offset += 1 + options[offset];
}
}
// Is this an overloaded message?
if (overload != DHCP_OVERLOAD_NONE) {
int field_len;
if (overload == DHCP_OVERLOAD_FILE) {
options = (unsigned char *) &state->msg_in->file;
field_len = DHCP_FILE_LEN;
} else if (overload == DHCP_OVERLOAD_SNAME) {
options = (unsigned char *) &state->msg_in->sname;
field_len = DHCP_SNAME_LEN;
} else {
options = (unsigned char *) &state->msg_in->sname;
field_len = DHCP_FILE_LEN + DHCP_SNAME_LEN;
}
offset = 0;
// At least 1 byte to read and no end marker
while ((offset < field_len) && (options[offset] != DHCP_OPTION_END)) {
if (options[offset] == DHCP_OPTION_PAD) {
offset++;
} else {
kprintf(" option %d len %d\n", options[offset], options[offset + 1]);
offset++;
offset += 1 + options[offset];
}
}
}
}
}