/** ****************************************************************************** * @file dhcpd_eloop.c * @author HUGE-IC Application Team * @version V1.0.0 * @date 2021-07-16 * @brief a simple dhcp server ****************************************************************************** * @attention * *

© COPYRIGHT 2021 HUGE-IC

* * ****************************************************************************** */ #include "sys_config.h" #include "typesdef.h" #include "list.h" #include "osal/task.h" #include "osal/string.h" #include "osal/sleep.h" #include "lib/common/sysevt.h" #include "lib/net/utils.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" #include "lwip/prot/dhcp.h" #include "dhcpd_eloop.h" #include "event.h" #define dhcpd_dbg(fmt, ...) os_printf(fmt, ##__VA_ARGS__) #define dhcpd_err(fmt, ...) os_printf(fmt, ##__VA_ARGS__) #define DHCPD_RECVBUF_SIZE (1024) struct dhcpd_ipaddr { struct list_head list; uint32 ip; uint64 lifetime; uint8 mac[6]; }; struct dhcpd_optinfo { uint32 request_ip; uint8 msg_type; }; struct dhcpd_mgr { uint8 stop: 1; uint8 recvbuf[DHCPD_RECVBUF_SIZE]; int32 sock; struct dhcpd_param param; struct dhcpd_optinfo info; struct list_head addr_list; uint32 nextip; // LITTLE_ENDIAN struct netif *netif; void *dhcp_event; void *dhcp_timer_event; }; static struct dhcpd_mgr *dhcpd; #if 0 static int32 dhcpd_recv(uint32 tmo) { uint32 ret = 0; fd_set rset; socklen_t len = 0; struct timeval tv; struct sockaddr_in addr; FD_ZERO(&rset); FD_SET(dhcpd->sock, &rset); tv.tv_sec = tmo / 1000; tv.tv_usec = (tmo % 1000) * 1000; ret = select(dhcpd->sock + 1, &rset, NULL, NULL, &tv); if (ret > 0) { ret = recvfrom(dhcpd->sock, dhcpd->recvbuf, DHCPD_RECVBUF_SIZE, 0, (struct sockaddr *)&addr, &len); } return ret; } #endif static void dhcpd_send(uint8 *dest, uint32 dstip, uint8 *data, uint32 len) { struct udpdata_info udp; uint8 bcast_addr[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; struct pbuf *buf = pbuf_alloc(PBUF_RAW, len + 14 + 20 + 12, PBUF_POOL); if (buf) { udp.dest = dest ? dest : bcast_addr; udp.src = dhcpd->netif->hwaddr; udp.dest_ip = ntohl(dstip); udp.src_ip = ntohl(dhcpd->netif->ip_addr.addr); udp.dest_port = 68; udp.src_port = 67; udp.data = data; udp.len = len; net_setup_udpdata(&udp, buf->payload); dhcpd->netif->linkoutput(dhcpd->netif, buf); pbuf_free(buf); } } static void dhcpd_check_expired(void *ei, void *d) { uint64 jiff = os_jiffies(); struct dhcpd_ipaddr *entry = NULL; struct dhcpd_ipaddr *nxt = NULL; struct dhcpd_mgr *dhcpd = (struct dhcpd_mgr *)d; struct list_head *head = &dhcpd->addr_list; list_for_each_entry_safe(entry, nxt, head, list) { if (TIME_AFTER(jiff, entry->lifetime + os_msecs_to_jiffies(dhcpd->param.lease_time * 1000))) { list_del(&entry->list); dhcpd_dbg("ip (%s-"MACSTR") expired, del it\r\n", inet_ntoa(entry->ip), MAC2STR(entry->mac)); os_free(entry); } } } void dhcpd_dump_ippool(void) { struct dhcpd_ipaddr *entry = NULL; struct list_head *head = &dhcpd->addr_list; dhcpd_dbg("IP Pool:\r\n"); list_for_each_entry(entry, head, list) { dhcpd_dbg(" ip:%s - "MACSTR"\r\n", inet_ntoa(entry->ip), MAC2STR(entry->mac)); } } bool dhcpd_get_mac_by_ip(uint32 ip,uint8 *mac){ struct dhcpd_ipaddr *entry = NULL; struct list_head *head = &dhcpd->addr_list; list_for_each_entry(entry, head, list) { if(ip == entry->ip){ memcpy(mac,entry->mac,6); return RET_OK; } } return RET_ERR; } static uint32 dhcpd_next_ip(uint32 ip, uint32 mask) { uint32 subip = (ip&~mask)+1; uint32 newip = (ip&mask)+(subip&~mask); if(newip < ntohl(dhcpd->param.start_ip) || newip > ntohl(dhcpd->param.end_ip)){ newip = ntohl(dhcpd->param.start_ip); } dhcpd_dbg("Next IP: "IPSTR"\r\n", IP2STR_H(newip)); return newip; } static struct dhcpd_ipaddr *dhcpd_find_ip(uint32 ip, uint8 *mac) { struct dhcpd_ipaddr *entry = NULL; struct list_head *head = &dhcpd->addr_list; if (ip || mac) { list_for_each_entry(entry, head, list) { if ((ip && htonl(ip) == entry->ip) || (mac && os_memcmp(mac, entry->mac, 6) == 0)) { return entry; } } } return NULL; } static uint32 dhcpd_check_ip(uint32 ip, uint8 *mac) { struct dhcpd_ipaddr *entry = dhcpd_find_ip(ip, 0); return (entry ? (os_memcmp(entry->mac, mac, 6) == 0) : 1); } static uint32 dhcpd_netcmp(uint32 ip) { /* !0 if the network identifiers of both address match */ return ((ip & ntohl(dhcpd->param.netmask)) == (ntohl(dhcpd->param.start_ip) & ntohl(dhcpd->param.netmask))); } static uint32 dhcpd_free_ipaddr() { uint32 ip = dhcpd->nextip; while(dhcpd_find_ip(dhcpd->nextip, 0)){ dhcpd->nextip = dhcpd_next_ip(dhcpd->nextip, ntohl(dhcpd->param.netmask)); if(dhcpd->nextip == ip){ return 0; } } return dhcpd->nextip; } static uint32 dhcpd_get_ip(uint32 ip, uint8 *mac) { struct dhcpd_ipaddr *entry = dhcpd_find_ip(0, mac); if (entry == NULL) { if(ip == 0){ if(!dhcpd_free_ipaddr()){ SYSEVT_NEW_NETWORK_EVT(SYSEVT_DHCPD_IPPOOL_FULL, 0); return 0; } ip = dhcpd->nextip; } entry = os_zalloc(sizeof(struct dhcpd_ipaddr)); if (entry) { list_add(&entry->list, &dhcpd->addr_list); entry->ip = htonl(ip); os_memcpy(entry->mac, mac, 6); entry->lifetime = os_jiffies(); dhcpd->nextip = dhcpd_next_ip(dhcpd->nextip, ntohl(dhcpd->param.netmask)); } }else{ if(ip) entry->ip = htonl(ip); entry->lifetime = os_jiffies(); } return (entry ? entry->ip: 0); } static void dhcpd_send_nak(struct dhcp_msg *msg) { uint16 flags = get_unaligned_be16(&msg->flags); uint8 *opt = (uint8 *)msg + DHCP_OPTIONS_OFS; msg->op = 2; *opt++ = DHCP_OPTION_MESSAGE_TYPE; *opt++ = DHCP_OPTION_MESSAGE_TYPE_LEN; *opt++ = DHCP_NAK; *opt++ = DHCP_OPTION_END; dhcpd_dbg("send DHCP_NAK to "MACSTR"\r\n", MAC2STR(msg->chaddr)); dhcpd_send((flags&0x8000) ? NULL : msg->chaddr, 0xffffffff, (uint8 *)msg, (uint32)opt - (uint32)msg); } static void dhcpd_reply(struct dhcp_msg *msg, uint8 reply) { uint8 *opt = (uint8 *)msg + DHCP_OPTIONS_OFS; uint32 ip = dhcpd_get_ip(dhcpd->info.request_ip, msg->chaddr); uint16 flags = get_unaligned_be16(&msg->flags); /* dhcp server don't need setup client ip addr */ put_unaligned_le32(0, &msg->ciaddr); if (ip == 0) { dhcpd_send_nak(msg); dhcpd_err("no free ip address\r\n"); return; } // little order IP use IP2STR_H, and can not use inet_ntoa() at same statement dhcpd_err("Assign IP "IPSTR" for "MACSTR", flags=%x (next:"IPSTR")\r\n", IP2STR_N(ip), MAC2STR(msg->chaddr), flags, IP2STR_H(dhcpd->nextip)); msg->op = 2; put_unaligned_le32(ip, &msg->yiaddr); *opt++ = DHCP_OPTION_MESSAGE_TYPE; *opt++ = 1; *opt++ = reply; *opt++ = DHCP_OPTION_SERVER_ID; *opt++ = 4; put_unaligned_le32(dhcpd->netif->ip_addr.addr, opt); opt += 4; *opt++ = DHCP_OPTION_LEASE_TIME; *opt++ = 4; put_unaligned_be32(dhcpd->param.lease_time, opt); opt += 4; *opt++ = DHCP_OPTION_SUBNET_MASK; *opt++ = 4; put_unaligned_le32(dhcpd->param.netmask, opt); opt += 4; if (dhcpd->param.router) { *opt++ = DHCP_OPTION_ROUTER; *opt++ = 4; put_unaligned_le32(dhcpd->param.router, opt); opt += 4; } if (dhcpd->param.dns1) { *opt++ = DHCP_OPTION_DNS_SERVER; *opt++ = 8; put_unaligned_le32(dhcpd->param.dns1, opt); opt += 4; put_unaligned_le32(dhcpd->param.dns2, opt); opt += 4; } *opt++ = DHCP_OPTION_END; dhcpd_send((flags&0x8000) ? NULL : msg->chaddr, (flags&0x8000) ? 0xffffffff : ip, (uint8 *)msg, (uint32)opt - (uint32)msg); } static void dhcpd_send_ack(struct dhcp_msg *msg) { dhcpd_dbg("send DHCP_ACK ...\r\n"); return dhcpd_reply(msg, DHCP_ACK); } static void dhcpd_send_offer(struct dhcp_msg *msg) { dhcpd_dbg("send DHCP_OFFER ...\r\n"); return dhcpd_reply(msg, DHCP_OFFER); } static void dhcpd_parse_opt(uint8 *dhcp_opt, int32 len, struct dhcpd_optinfo *info) { uint8 *opts = dhcp_opt; uint8 option; uint8 opt_len; info->request_ip = 0; info->msg_type = 0; while (opts < dhcp_opt + len) { option = *opts++; opt_len = *opts++; switch (option) { case DHCP_OPTION_REQUESTED_IP: info->request_ip = get_unaligned_be32(opts); break; case DHCP_OPTION_MESSAGE_TYPE: info->msg_type = *opts; break; case DHCP_OPTION_END: return; default: break; } opts += opt_len; } } static void dhcpd_task_eloop(void *ei, void *d) { int32 ret = 0; uint8 *dhcp_opt; socklen_t len = 0; struct sockaddr_in addr; struct dhcp_msg *msg; struct dhcpd_mgr *dhcpd = (struct dhcpd_mgr *)d; ret = recvfrom(dhcpd->sock, dhcpd->recvbuf, DHCPD_RECVBUF_SIZE, 0, (struct sockaddr *)&addr, &len); if (ret >= (int32)DHCP_OPTIONS_OFS && !dhcpd->stop) { msg = (struct dhcp_msg *)dhcpd->recvbuf; if (msg->op == DHCP_BOOTREQUEST && msg->cookie == PP_HTONL(DHCP_MAGIC_COOKIE)) { os_memset(&dhcpd->info, 0, sizeof(dhcpd->info)); dhcp_opt = dhcpd->recvbuf + DHCP_OPTIONS_OFS; dhcpd_parse_opt(dhcp_opt, ret - DHCP_OPTIONS_OFS, &dhcpd->info); if (dhcpd->info.msg_type == DHCP_DISCOVER) { if (!dhcpd_netcmp(dhcpd->info.request_ip) || !dhcpd_check_ip(dhcpd->info.request_ip, msg->chaddr)) { dhcpd->info.request_ip = 0; } dhcpd_send_offer(msg); } else if (dhcpd->info.msg_type == DHCP_REQUEST) { /* request_ip is empty then check client ip addr */ if (dhcpd->info.request_ip == 0) { dhcpd->info.request_ip = ntohl(msg->ciaddr.addr); } if(dhcpd->info.request_ip && dhcpd_netcmp(dhcpd->info.request_ip)){ if(!dhcpd_check_ip(dhcpd->info.request_ip, msg->chaddr)){ dhcpd_send_nak(msg); } else{ dhcpd_send_ack(msg); SYSEVT_NEW_NETWORK_EVT(SYSEVT_DHCPD_NEW_IP, dhcpd->info.request_ip); dhcpd_dump_ippool(); } } } else { dhcpd_dbg("unsupport msg:%d\r\n", dhcpd->info.msg_type); } } } //dhcpd_check_expired(); } __init static int32 _dhcpd_start(char *ifname, struct dhcpd_param *param) { int32 ret = -1; int32 sock = -1; int32 optval = 1; struct sockaddr_in local_addr; struct ifreq nif; dhcpd = os_zalloc(sizeof(struct dhcpd_mgr)); if(dhcpd == NULL) return -ENOMEM; INIT_LIST_HEAD(&dhcpd->addr_list); dhcpd->netif = netif_find(ifname); if (dhcpd->netif == NULL) { dhcpd_err("netif:%s is not exist\r\n", ifname); return RET_ERR; } sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock == -1) { dhcpd_err("create socket failed\r\n"); return RET_ERR; } fcntl(sock, F_SETFL, O_NONBLOCK); setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &optval, sizeof(optval)); os_strncpy(nif.ifr_name, dhcpd->netif->name, 2); setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, &nif, sizeof(nif)); os_memset(&local_addr, 0, sizeof(local_addr)); local_addr.sin_family = AF_INET; local_addr.sin_port = htons(67); local_addr.sin_addr.s_addr = dhcpd->netif->ip_addr.addr; ret = bind(sock, (struct sockaddr *)&local_addr, sizeof(struct sockaddr)); if (ret == -1) { dhcpd_err("bind fail\r\n"); closesocket(sock); return RET_ERR; } dhcpd->sock = sock; os_memcpy(&dhcpd->param, param, sizeof(struct dhcpd_param)); dhcpd->nextip = ntohl(dhcpd->param.start_ip); //OS_TASK_INIT("dhcpd", &dhcpd->task, dhcpd_task, NULL, OS_TASK_PRIORITY_NORMAL, 1024); dhcpd->dhcp_event = (void*) eloop_add_fd( dhcpd->sock, EVENT_READ, EVENT_F_ENABLED, dhcpd_task_eloop, dhcpd ); dhcpd->dhcp_timer_event = (void*) eloop_add_timer(500, EVENT_F_ENABLED, dhcpd_check_expired, dhcpd); return RET_OK; } int32 dhcpd_start_eloop(char *ifname, struct dhcpd_param *param) { int32 ret = 0; if (ifname == NULL || param == NULL) { dhcpd_err("invalid param!\r\n"); return RET_ERR; } if(dhcpd){ dhcpd->stop = 1; if(dhcpd->dhcp_event) { eloop_remove_event(dhcpd->dhcp_event); } if(dhcpd->dhcp_timer_event) { eloop_remove_event(dhcpd->dhcp_timer_event); } if(dhcpd->sock) { closesocket(dhcpd->sock); } dhcpd_err("dhcpd already running ...,first stop last dhcp\r\n"); //return RET_OK; os_free(dhcpd); dhcpd = NULL; } ret = _dhcpd_start(ifname, param); ASSERT(!ret); return ret; } void dhcpd_stop_eloop(char *ifname) { if(dhcpd){ dhcpd->stop = 1; if(dhcpd->dhcp_event) { eloop_remove_event(dhcpd->dhcp_event); } if(dhcpd->dhcp_timer_event) { eloop_remove_event(dhcpd->dhcp_timer_event); } if(dhcpd->sock) { closesocket(dhcpd->sock); } dhcpd_err("stop last dhcp\r\n"); //return RET_OK; os_free(dhcpd); dhcpd = NULL; } }