rt-thread-official/components/net/lwip_dhcpd/dhcp_server_raw.c

743 lines
21 KiB
C
Raw Normal View History

/*
* File : dhcp_server_raw.c
* A simple DHCP server implementation
*
* This file is part of RT-Thread RTOS
* COPYRIGHT (C) 2011-2018, Shanghai Real-Thread Technology Co., Ltd
* http://www.rt-thread.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Change Logs:
* Date Author Notes
* 2014-04-01 Ren.Haibo the first version
* 2018-06-12 aozima ignore DHCP_OPTION_SERVER_ID.
*/
#include <stdio.h>
#include <stdint.h>
#include <rtthread.h>
#include <lwip/opt.h>
#include <lwip/sockets.h>
#include <lwip/inet_chksum.h>
#include <netif/etharp.h>
#include <netif/ethernetif.h>
#include <lwip/ip.h>
#include <lwip/init.h>
#if (LWIP_VERSION) < 0x02000000U
#error "not support old LWIP"
#endif
#if !LWIP_IPV4
#error "must enable IPV4"
#endif
#if (LWIP_VERSION) >= 0x02000000U
#include <lwip/prot/dhcp.h>
#endif
/* DHCP server option */
/* allocated client ip range */
#ifndef DHCPD_CLIENT_IP_MIN
#define DHCPD_CLIENT_IP_MIN 2
#endif
#ifndef DHCPD_CLIENT_IP_MAX
#define DHCPD_CLIENT_IP_MAX 254
#endif
/* the DHCP server address */
#ifndef DHCPD_SERVER_IP
#define DHCPD_SERVER_IP "192.168.169.1"
#endif
#define DHCP_DEBUG_PRINTF
#ifdef DHCP_DEBUG_PRINTF
#define DEBUG_PRINTF rt_kprintf("[DHCP] "); rt_kprintf
#else
#define DEBUG_PRINTF(...)
#endif /* DHCP_DEBUG_PRINTF */
/* we need some routines in the DHCP of lwIP */
#undef LWIP_DHCP
#define LWIP_DHCP 1
#include <lwip/dhcp.h>
/** Mac address length */
#define DHCP_MAX_HLEN 6
/** dhcp default live time */
#define DHCP_DEFAULT_LIVE_TIME 0x80510100
/** Minimum length for request before packet is parsed */
#define DHCP_MIN_REQUEST_LEN 44
#define LWIP_NETIF_LOCK(...)
#define LWIP_NETIF_UNLOCK(...)
/**
* The dhcp client node struct.
*/
struct dhcp_client_node
{
struct dhcp_client_node *next;
u8_t chaddr[DHCP_MAX_HLEN];
ip4_addr_t ipaddr;
u32_t lease_end;
};
/**
* The dhcp server struct.
*/
struct dhcp_server
{
struct dhcp_server *next;
struct netif *netif;
struct udp_pcb *pcb;
struct dhcp_client_node *node_list;
ip4_addr_t start;
ip4_addr_t end;
ip4_addr_t current;
};
static u8_t *dhcp_server_option_find(u8_t *buf, u16_t len, u8_t option);
/**
* The dhcp server struct list.
*/
static struct dhcp_server *lw_dhcp_server;
/**
* Find a dhcp client node by mac address
*
* @param dhcpserver The dhcp server
* @param chaddr Mac address
* @param hlen Mac address length
* @return dhcp client node
*/
static struct dhcp_client_node *
dhcp_client_find_by_mac(struct dhcp_server *dhcpserver, const u8_t *chaddr, u8_t hlen)
{
struct dhcp_client_node *node;
for (node = dhcpserver->node_list; node != NULL; node = node->next)
{
if (memcmp(node->chaddr, chaddr, hlen) == 0)
{
return node;
}
}
return NULL;
}
/**
* Find a dhcp client node by ip address
*
* @param dhcpserver The dhcp server
* @param chaddr Mac address
* @param hlen Mac address length
* @return dhcp client node
*/
static struct dhcp_client_node *
dhcp_client_find_by_ip(struct dhcp_server *dhcpserver, const ip4_addr_t *ip)
{
struct dhcp_client_node *node;
for (node = dhcpserver->node_list; node != NULL; node = node->next)
{
if (ip4_addr_cmp(&node->ipaddr, ip))
{
return node;
}
}
return NULL;
}
/**
* Find a dhcp client node by ip address
*
* @param dhcpserver The dhcp server
* @param chaddr Mac address
* @param hlen Mac address length
* @return dhcp client node
*/
static struct dhcp_client_node *
dhcp_client_find(struct dhcp_server *dhcpserver, struct dhcp_msg *msg,
u8_t *opt_buf, u16_t len)
{
u8_t *opt;
//u32_t ipaddr;
struct dhcp_client_node *node;
node = dhcp_client_find_by_mac(dhcpserver, msg->chaddr, msg->hlen);
if (node != NULL)
{
return node;
}
opt = dhcp_server_option_find(opt_buf, len, DHCP_OPTION_REQUESTED_IP);
if (opt != NULL)
{
node = dhcp_client_find_by_ip(dhcpserver, (ip4_addr_t *)(&opt[2]));
if (node != NULL)
{
return node;
}
}
return NULL;
}
/**
* Find a dhcp client node by ip address
*
* @param dhcpserver The dhcp server
* @param chaddr Mac address
* @param hlen Mac address length
* @return dhcp client node
*/
static struct dhcp_client_node *
dhcp_client_alloc(struct dhcp_server *dhcpserver, struct dhcp_msg *msg,
u8_t *opt_buf, u16_t len)
{
u8_t *opt;
u32_t ipaddr;
struct dhcp_client_node *node;
node = dhcp_client_find_by_mac(dhcpserver, msg->chaddr, msg->hlen);
if (node != NULL)
{
return node;
}
opt = dhcp_server_option_find(opt_buf, len, DHCP_OPTION_REQUESTED_IP);
if (opt != NULL)
{
node = dhcp_client_find_by_ip(dhcpserver, (ip4_addr_t *)(&opt[2]));
if (node != NULL)
{
return node;
}
}
dhcp_alloc_again:
node = dhcp_client_find_by_ip(dhcpserver, &dhcpserver->current);
if (node != NULL)
{
ipaddr = (ntohl(dhcpserver->current.addr) + 1);
if (ipaddr > ntohl(dhcpserver->end.addr))
{
ipaddr = ntohl(dhcpserver->start.addr);
}
dhcpserver->current.addr = htonl(ipaddr);
goto dhcp_alloc_again;
}
node = (struct dhcp_client_node *)mem_malloc(sizeof(struct dhcp_client_node));
if (node == NULL)
{
return NULL;
}
SMEMCPY(node->chaddr, msg->chaddr, msg->hlen);
node->ipaddr = dhcpserver->current;
node->next = dhcpserver->node_list;
dhcpserver->node_list = node;
return node;
}
/**
* find option from buffer.
*
* @param buf The buffer to find option
* @param len The buffer length
* @param option Which option to find
* @return dhcp option buffer
*/
static u8_t *
dhcp_server_option_find(u8_t *buf, u16_t len, u8_t option)
{
u8_t *end = buf + len;
while ((buf < end) && (*buf != DHCP_OPTION_END))
{
if (*buf == option)
{
return buf;
}
buf += (buf[1] + 2);
}
return NULL;
}
/**
* If an incoming DHCP message is in response to us, then trigger the state machine
*/
static void
dhcp_server_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *recv_addr, u16_t port)
{
struct dhcp_server *dhcp_server = (struct dhcp_server *)arg;
struct dhcp_msg *msg;
struct pbuf *q;
u8_t *opt_buf;
u8_t *opt;
struct dhcp_client_node *node;
u8_t msg_type;
u16_t length;
ip_addr_t addr = *recv_addr;
u32_t tmp;
LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("[%s:%d] %c%c recv %d\n", __FUNCTION__, __LINE__, dhcp_server->netif->name[0], dhcp_server->netif->name[1], p->tot_len));
/* prevent warnings about unused arguments */
LWIP_UNUSED_ARG(pcb);
LWIP_UNUSED_ARG(addr);
LWIP_UNUSED_ARG(port);
if (p->len < DHCP_MIN_REQUEST_LEN)
{
LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING, ("DHCP request message or pbuf too short\n"));
pbuf_free(p);
return;
}
q = pbuf_alloc(PBUF_TRANSPORT, 1500, PBUF_RAM);
if (q == NULL)
{
LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING, ("pbuf_alloc dhcp_msg failed!\n"));
pbuf_free(p);
return;
}
if (q->tot_len < p->tot_len)
{
LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING, ("pbuf_alloc dhcp_msg too small %d:%d\n", q->tot_len, p->tot_len));
pbuf_free(p);
return;
}
pbuf_copy(q, p);
pbuf_free(p);
msg = (struct dhcp_msg *)q->payload;
if (msg->op != DHCP_BOOTREQUEST)
{
LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING, ("not a DHCP request message, but type %"U16_F"\n", (u16_t)msg->op));
goto free_pbuf_and_return;
}
if (msg->cookie != PP_HTONL(DHCP_MAGIC_COOKIE))
{
LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING, ("bad DHCP_MAGIC_COOKIE!\n"));
goto free_pbuf_and_return;
}
if (msg->hlen > DHCP_MAX_HLEN)
{
goto free_pbuf_and_return;
}
opt_buf = (u8_t *)msg + DHCP_OPTIONS_OFS;
length = q->tot_len - DHCP_OPTIONS_OFS;
opt = dhcp_server_option_find(opt_buf, length, DHCP_OPTION_MESSAGE_TYPE);
if (opt)
{
msg_type = *(opt + 2);
if (msg_type == DHCP_DISCOVER)
{
node = dhcp_client_alloc(dhcp_server, msg, opt_buf, length);
if (node == NULL)
{
goto free_pbuf_and_return;
}
node->lease_end = DHCP_DEFAULT_LIVE_TIME;
/* create dhcp offer and send */
msg->op = DHCP_BOOTREPLY;
msg->hops = 0;
msg->secs = 0;
SMEMCPY(&msg->siaddr, &(dhcp_server->netif->ip_addr), 4);
msg->sname[0] = '\0';
msg->file[0] = '\0';
msg->cookie = PP_HTONL(DHCP_MAGIC_COOKIE);
SMEMCPY(&msg->yiaddr, &node->ipaddr, 4);
opt_buf = (u8_t *)msg + DHCP_OPTIONS_OFS;
/* add msg type */
*opt_buf++ = DHCP_OPTION_MESSAGE_TYPE;
*opt_buf++ = 1;
*opt_buf++ = DHCP_OFFER;
/* add server id */
*opt_buf++ = DHCP_OPTION_SERVER_ID;
*opt_buf++ = 4;
SMEMCPY(opt_buf, &(dhcp_server->netif->ip_addr), 4);
opt_buf += 4;
/* add_lease_time */
*opt_buf++ = DHCP_OPTION_LEASE_TIME;
*opt_buf++ = 4;
tmp = PP_HTONL(DHCP_DEFAULT_LIVE_TIME);
SMEMCPY(opt_buf, &tmp, 4);
opt_buf += 4;
/* add config */
*opt_buf++ = DHCP_OPTION_SUBNET_MASK;
*opt_buf++ = 4;
SMEMCPY(opt_buf, &ip_2_ip4(&dhcp_server->netif->netmask)->addr, 4);
opt_buf += 4;
*opt_buf++ = DHCP_OPTION_DNS_SERVER;
*opt_buf++ = 4;
#ifdef DHCP_DNS_SERVER_IP
{
ip_addr_t dns_addr;
ipaddr_aton(DHCP_DNS_SERVER_IP, &dns_addr);
SMEMCPY(opt_buf, &ip_2_ip4(&dns_addr)->addr, 4);
}
#else
/* default use gatewary dns server */
SMEMCPY(opt_buf, &(dhcp_server->netif->ip_addr), 4);
#endif /* DHCP_DNS_SERVER_IP */
opt_buf += 4;
*opt_buf++ = DHCP_OPTION_ROUTER;
*opt_buf++ = 4;
SMEMCPY(opt_buf, &ip_2_ip4(&dhcp_server->netif->ip_addr)->addr, 4);
opt_buf += 4;
/* add option end */
*opt_buf++ = DHCP_OPTION_END;
length = (u32_t)opt_buf - (u32_t)msg;
if (length < q->tot_len)
{
pbuf_realloc(q, length);
}
ip_2_ip4(&addr)->addr = INADDR_BROADCAST;
udp_sendto_if(pcb, q, &addr, port, dhcp_server->netif);
}
else
{
if (1)
{
if (msg_type == DHCP_REQUEST)
{
node = dhcp_client_find(dhcp_server, msg, opt_buf, length);
if (node != NULL)
{
/* Send ack */
node->lease_end = DHCP_DEFAULT_LIVE_TIME;
/* create dhcp offer and send */
msg->op = DHCP_BOOTREPLY;
msg->hops = 0;
msg->secs = 0;
SMEMCPY(&msg->siaddr, &(dhcp_server->netif->ip_addr), 4);
msg->sname[0] = '\0';
msg->file[0] = '\0';
msg->cookie = PP_HTONL(DHCP_MAGIC_COOKIE);
SMEMCPY(&msg->yiaddr, &node->ipaddr, 4);
opt_buf = (u8_t *)msg + DHCP_OPTIONS_OFS;
/* add msg type */
*opt_buf++ = DHCP_OPTION_MESSAGE_TYPE;
*opt_buf++ = 1;
*opt_buf++ = DHCP_ACK;
/* add server id */
*opt_buf++ = DHCP_OPTION_SERVER_ID;
*opt_buf++ = 4;
SMEMCPY(opt_buf, &(dhcp_server->netif->ip_addr), 4);
opt_buf += 4;
/* add_lease_time */
*opt_buf++ = DHCP_OPTION_LEASE_TIME;
*opt_buf++ = 4;
tmp = PP_HTONL(DHCP_DEFAULT_LIVE_TIME);
SMEMCPY(opt_buf, &tmp, 4);
opt_buf += 4;
/* add config */
*opt_buf++ = DHCP_OPTION_SUBNET_MASK;
*opt_buf++ = 4;
SMEMCPY(opt_buf, &ip_2_ip4(&dhcp_server->netif->netmask)->addr, 4);
opt_buf += 4;
*opt_buf++ = DHCP_OPTION_DNS_SERVER;
*opt_buf++ = 4;
#ifdef DHCP_DNS_SERVER_IP
{
ip_addr_t dns_addr;
ipaddr_aton(DHCP_DNS_SERVER_IP, &dns_addr);
SMEMCPY(opt_buf, &ip_2_ip4(&dns_addr)->addr, 4);
}
#else
/* default use gatewary dns server */
SMEMCPY(opt_buf, &(dhcp_server->netif->ip_addr), 4);
#endif /* DHCP_DNS_SERVER_IP */
opt_buf += 4;
*opt_buf++ = DHCP_OPTION_ROUTER;
*opt_buf++ = 4;
SMEMCPY(opt_buf, &ip_2_ip4(&dhcp_server->netif->ip_addr)->addr, 4);
opt_buf += 4;
/* add option end */
*opt_buf++ = DHCP_OPTION_END;
length = (u32_t)opt_buf - (u32_t)msg;
if (length < q->tot_len)
{
pbuf_realloc(q, length);
}
ip_2_ip4(&addr)->addr = INADDR_BROADCAST;
udp_sendto_if(pcb, q, &addr, port, dhcp_server->netif);
}
else
{
/* Send no ack */
/* create dhcp offer and send */
msg->op = DHCP_BOOTREPLY;
msg->hops = 0;
msg->secs = 0;
SMEMCPY(&msg->siaddr, &(dhcp_server->netif->ip_addr), 4);
msg->sname[0] = '\0';
msg->file[0] = '\0';
msg->cookie = PP_HTONL(DHCP_MAGIC_COOKIE);
memset(&msg->yiaddr, 0, 4);
opt_buf = (u8_t *)msg + DHCP_OPTIONS_OFS;
/* add msg type */
*opt_buf++ = DHCP_OPTION_MESSAGE_TYPE;
*opt_buf++ = 1;
*opt_buf++ = DHCP_NAK;
/* add server id */
*opt_buf++ = DHCP_OPTION_SERVER_ID;
*opt_buf++ = 4;
SMEMCPY(opt_buf, &(dhcp_server->netif->ip_addr), 4);
opt_buf += 4;
/* add option end */
*opt_buf++ = DHCP_OPTION_END;
length = (u32_t)opt_buf - (u32_t)msg;
if (length < q->tot_len)
{
pbuf_realloc(q, length);
}
ip_2_ip4(&addr)->addr = INADDR_BROADCAST;
udp_sendto_if(pcb, q, &addr, port, dhcp_server->netif);
}
}
else if (msg_type == DHCP_RELEASE)
{
struct dhcp_client_node *node_prev = NULL;
for (node = dhcp_server->node_list; node != NULL; node = node->next)
{
if (memcmp(node->chaddr, msg->chaddr, msg->hlen) == 0)
{
if (node == dhcp_server->node_list)
{
dhcp_server->node_list = node->next;
}
else
{
node_prev->next = node->next;
}
break;
}
node_prev = node;
node = node->next;
}
if (node != NULL)
{
mem_free(node);
}
}
else if (msg_type == DHCP_DECLINE)
{
;
}
else if (msg_type == DHCP_INFORM)
{
;
}
}
}
}
free_pbuf_and_return:
pbuf_free(q);
}
/**
* start dhcp server for a netif
*
* @param netif The netif which use dhcp server
* @param start The Start IP address
* @param end The netif which use dhcp server
* @return lwIP error code
* - ERR_OK - No error
* - ERR_MEM - Out of memory
*/
err_t
dhcp_server_start(struct netif *netif, ip4_addr_t *start, ip4_addr_t *end)
{
struct dhcp_server *dhcp_server;
/* If this netif alreday use the dhcp server. */
for (dhcp_server = lw_dhcp_server; dhcp_server != NULL; dhcp_server = dhcp_server->next)
{
if (dhcp_server->netif == netif)
{
dhcp_server->start = *start;
dhcp_server->end = *end;
dhcp_server->current = *start;
return ERR_OK;
}
}
dhcp_server = NULL;
LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_server_start(): starting new DHCP server\n"));
dhcp_server = (struct dhcp_server *)mem_malloc(sizeof(struct dhcp_server));
if (dhcp_server == NULL)
{
LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_server_start(): could not allocate dhcp\n"));
return ERR_MEM;
}
/* clear data structure */
memset(dhcp_server, 0, sizeof(struct dhcp_server));
/* store this dhcp server to list */
dhcp_server->next = lw_dhcp_server;
lw_dhcp_server = dhcp_server;
dhcp_server->netif = netif;
dhcp_server->node_list = NULL;
dhcp_server->start = *start;
dhcp_server->end = *end;
dhcp_server->current = *start;
/* allocate UDP PCB */
dhcp_server->pcb = udp_new();
if (dhcp_server->pcb == NULL)
{
LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_server_start(): could not obtain pcb\n"));
return ERR_MEM;
}
ip_set_option(dhcp_server->pcb, SOF_BROADCAST);
/* set up local and remote port for the pcb */
udp_bind(dhcp_server->pcb, IP_ADDR_ANY, DHCP_SERVER_PORT);
//udp_connect(dhcp_server->pcb, IP_ADDR_ANY, DHCP_CLIENT_PORT);
/* set up the recv callback and argument */
udp_recv(dhcp_server->pcb, dhcp_server_recv, dhcp_server);
LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_server_start(): starting DHCP server\n"));
return ERR_OK;
}
void dhcpd_start(const char *netif_name)
{
struct netif *netif = netif_list;
err_t res;
DEBUG_PRINTF("%s: %s\r\n", __FUNCTION__, netif_name);
LWIP_NETIF_LOCK();
if (strlen(netif_name) > sizeof(netif->name))
{
DEBUG_PRINTF("network interface name too long!\r\n");
goto _exit;
}
while (netif != RT_NULL)
{
if (strncmp(netif_name, netif->name, sizeof(netif->name)) == 0)
break;
netif = netif->next;
if (netif == RT_NULL)
{
DEBUG_PRINTF("network interface: %s not found!\r\n", netif_name);
break;
}
}
if (netif == RT_NULL)
{
goto _exit;
}
if (1)
{
extern void set_if(const char *netif_name, const char *ip_addr, const char *gw_addr, const char *nm_addr);
dhcp_stop(netif);
set_if(netif_name, DHCPD_SERVER_IP, "0.0.0.0", "255.255.255.0");
netif_set_up(netif);
}
{
char str_tmp[4 * 4 + 4] = DHCPD_SERVER_IP;
char *p = str_tmp;
ip4_addr_t ip_start, ip_end;
p = strchr(str_tmp, '.');
if (p)
{
p = strchr(p + 1, '.');
if (p)
{
p = strchr(p + 1, '.');
}
}
if (!p)
{
DEBUG_PRINTF("DHCPD_SERVER_IP: %s error!\r\n", str_tmp);
goto _exit;
}
p = p + 1; /* move to xxx.xxx.xxx.^ */
sprintf(p, "%d", DHCPD_CLIENT_IP_MIN);
ip4addr_aton(str_tmp, &ip_start);
DEBUG_PRINTF("ip_start: [%s]\r\n", str_tmp);
sprintf(p, "%d", DHCPD_CLIENT_IP_MAX);
ip4addr_aton(str_tmp, &ip_end);
DEBUG_PRINTF("ip_start: [%s]\r\n", str_tmp);
res = dhcp_server_start(netif, &ip_start, &ip_end);
if (res != 0)
{
DEBUG_PRINTF("dhcp_server_start res: %s.\r\n", res);
}
}
_exit:
LWIP_NETIF_UNLOCK();
return;
}