diff --git a/extras/dhcpserver/dhcpserver.c b/extras/dhcpserver/dhcpserver.c index d87daab..12b5982 100644 --- a/extras/dhcpserver/dhcpserver.c +++ b/extras/dhcpserver/dhcpserver.c @@ -14,17 +14,20 @@ * BSD Licensed as described in the file LICENSE */ #include +#include #include #include #include #include +#include "esplibs/libmain.h" /* Grow the size of the lwip dhcp_msg struct's options field, as LWIP defaults to a 68 octet options field for its DHCP client, and most full-sized clients send us more than this. */ #define DHCP_OPTIONS_LEN 312 +#include #include _Static_assert(sizeof(struct dhcp_msg) == offsetof(struct dhcp_msg, options) + 312, "dhcp_msg_t should have extended options size"); @@ -35,6 +38,7 @@ _Static_assert(sizeof(struct dhcp_msg) == offsetof(struct dhcp_msg, options) + 3 typedef struct { uint8_t hwaddr[NETIF_MAX_HWADDR_LEN]; + uint8_t active; uint32_t expires; } dhcp_lease_t; @@ -44,6 +48,10 @@ typedef struct { ip_addr_t first_client_addr; struct netif *server_if; dhcp_lease_t *leases; /* length max_leases */ + /* Optional router */ + ip_addr_t router; + /* Optional DNS server */ + ip_addr_t dns; } server_state_t; /* Only one DHCP server task can run at once, so we have global state @@ -70,7 +78,7 @@ static dhcp_lease_t *find_lease_slot(uint8_t *hwaddr); /* Copy IP address as dotted decimal to 'dest', must be at least 16 bytes long */ inline static void sprintf_ipaddr(const ip_addr_t *addr, char *dest) { - if(addr == NULL) + if (addr == NULL) sprintf(dest, "NULL"); else sprintf(dest, "%d.%d.%d.%d", ip4_addr1(addr), @@ -80,34 +88,49 @@ inline static void sprintf_ipaddr(const ip_addr_t *addr, char *dest) void dhcpserver_start(const ip_addr_t *first_client_addr, uint8_t max_leases) { /* Stop any existing running dhcpserver */ - if(dhcpserver_task_handle) + if (dhcpserver_task_handle) dhcpserver_stop(); state = malloc(sizeof(server_state_t)); state->max_leases = max_leases; state->leases = calloc(max_leases, sizeof(dhcp_lease_t)); + bzero(state->leases, max_leases * sizeof(dhcp_lease_t)); // state->server_if is assigned once the task is running - see comment in dhcpserver_task() ip_addr_copy(state->first_client_addr, *first_client_addr); - xTaskCreate(dhcpserver_task, "DHCPServer", 768, NULL, 8, &dhcpserver_task_handle); + /* Clear options */ + ip_addr_set_zero(&state->router); + ip_addr_set_zero(&state->dns); + + xTaskCreate(dhcpserver_task, "DHCP Server", 448, NULL, 2, &dhcpserver_task_handle); } void dhcpserver_stop(void) { - if(dhcpserver_task_handle) { + if (dhcpserver_task_handle) { vTaskDelete(dhcpserver_task_handle); free(state); dhcpserver_task_handle = NULL; } } +void dhcpserver_set_router(const ip_addr_t *router) +{ + ip_addr_copy(state->router, *router); +} + +void dhcpserver_set_dns(const ip_addr_t *dns) +{ + ip_addr_copy(state->dns, *dns); +} + static void dhcpserver_task(void *pxParameter) { /* netif_list isn't assigned until after user_init completes, which is why we do it inside the task */ state->server_if = netif_list; /* TODO: Make this configurable */ state->nc = netconn_new (NETCONN_UDP); - if(!state->nc) { + if (!state->nc) { printf("DHCP Server Error: Failed to allocate socket.\r\n"); return; } @@ -121,29 +144,32 @@ static void dhcpserver_task(void *pxParameter) /* Receive a DHCP packet */ err_t err = netconn_recv(state->nc, &netbuf); - if(err != ERR_OK) { + if (err != ERR_OK) { printf("DHCP Server Error: Failed to receive DHCP packet. err=%d\r\n", err); continue; } /* expire any leases that have passed */ uint32_t now = xTaskGetTickCount(); - for(int i = 0; i < state->max_leases; i++) { - uint32_t expires = state->leases[i].expires; - if(expires && expires < now) - state->leases[i].expires = 0; + for (int i = 0; i < state->max_leases; i++) { + if (state->leases[i].active) { + uint32_t expires = state->leases[i].expires - now; + if (expires >= 0x80000000) { + state->leases[i].active = 0; + } + } } ip_addr_t received_ip; u16_t port; netconn_addr(state->nc, &received_ip, &port); - if(netbuf_len(netbuf) < offsetof(struct dhcp_msg, options)) { + if (netbuf_len(netbuf) < offsetof(struct dhcp_msg, options)) { /* too short to be a valid DHCP client message */ netbuf_delete(netbuf); continue; } - if(netbuf_len(netbuf) >= sizeof(struct dhcp_msg)) { + if (netbuf_len(netbuf) >= sizeof(struct dhcp_msg)) { printf("DHCP Server Warning: Client sent more options than we know how to parse. len=%d\r\n", netbuf_len(netbuf)); } @@ -152,18 +178,18 @@ static void dhcpserver_task(void *pxParameter) uint8_t *message_type = find_dhcp_option(&received, DHCP_OPTION_MESSAGE_TYPE, DHCP_OPTION_MESSAGE_TYPE_LEN, NULL); - if(!message_type) { + if (!message_type) { printf("DHCP Server Error: No message type field found"); continue; } - printf("State dump. Message type %d\n", *message_type); - for(int i = 0; i < state->max_leases; i++) { + for (int i = 0; i < state->max_leases; i++) { dhcp_lease_t *lease = &state->leases[i]; - printf("lease slot %d expiry %d hwaddr %02x:%02x:%02x:%02x:%02x:%02x\r\n", i, lease->expires, lease->hwaddr[0], - lease->hwaddr[1], lease->hwaddr[2], lease->hwaddr[3], lease->hwaddr[4], - lease->hwaddr[5]); + printf("lease slot %d active %d expiry %d hwaddr %02x:%02x:%02x:%02x:%02x:%02x\r\n", i, + lease->active, lease->expires - now, + lease->hwaddr[0], lease->hwaddr[1], lease->hwaddr[2], + lease->hwaddr[3], lease->hwaddr[4], lease->hwaddr[5]); } switch(*message_type) { @@ -184,13 +210,13 @@ static void dhcpserver_task(void *pxParameter) static void handle_dhcp_discover(struct dhcp_msg *dhcpmsg) { - if(dhcpmsg->htype != DHCP_HTYPE_ETH) + if (dhcpmsg->htype != DHCP_HTYPE_ETH) return; - if(dhcpmsg->hlen > NETIF_MAX_HWADDR_LEN) + if (dhcpmsg->hlen > NETIF_MAX_HWADDR_LEN) return; dhcp_lease_t *freelease = find_lease_slot(dhcpmsg->chaddr); - if(!freelease) { + if (!freelease) { printf("DHCP Server: All leases taken.\r\n"); return; /* Nothing available, so do nothing */ } @@ -199,13 +225,19 @@ static void handle_dhcp_discover(struct dhcp_msg *dhcpmsg) dhcpmsg->op = DHCP_BOOTREPLY; bzero(dhcpmsg->options, DHCP_OPTIONS_LEN); - ip_addr_copy(dhcpmsg->yiaddr, state->first_client_addr); - ip4_addr4(&(dhcpmsg->yiaddr)) += (freelease - state->leases); + dhcpmsg->yiaddr.addr = lwip_htonl(lwip_ntohl(state->first_client_addr.addr) + (freelease - state->leases)); uint8_t *opt = (uint8_t *)&dhcpmsg->options; opt = add_dhcp_option_byte(opt, DHCP_OPTION_MESSAGE_TYPE, DHCP_OFFER); opt = add_dhcp_option_bytes(opt, DHCP_OPTION_SERVER_ID, &state->server_if->ip_addr, 4); opt = add_dhcp_option_bytes(opt, DHCP_OPTION_SUBNET_MASK, &state->server_if->netmask, 4); + if (!ip_addr_isany(&state->router)) { + opt = add_dhcp_option_bytes(opt, DHCP_OPTION_ROUTER, &state->router, 4); + } + if (!ip_addr_isany(&state->dns)) { + opt = add_dhcp_option_bytes(opt, DHCP_OPTION_DNS_SERVER, &state->dns, 4); + } + opt = add_dhcp_option_bytes(opt, DHCP_OPTION_END, NULL, 0); struct netbuf *netbuf = netbuf_new(); @@ -218,16 +250,16 @@ static void handle_dhcp_discover(struct dhcp_msg *dhcpmsg) static void handle_dhcp_request(struct dhcp_msg *dhcpmsg) { static char ipbuf[16]; - if(dhcpmsg->htype != DHCP_HTYPE_ETH) + if (dhcpmsg->htype != DHCP_HTYPE_ETH) return; - if(dhcpmsg->hlen > NETIF_MAX_HWADDR_LEN) + if (dhcpmsg->hlen > NETIF_MAX_HWADDR_LEN) return; ip_addr_t requested_ip; uint8_t *requested_ip_opt = find_dhcp_option(dhcpmsg, DHCP_OPTION_REQUESTED_IP, 4, NULL); - if(requested_ip_opt) { - memcpy(&requested_ip.addr, requested_ip_opt, 4); - } else if(ip_addr_cmp(&requested_ip, IP_ADDR_ANY)) { + if (requested_ip_opt) { + memcpy(&requested_ip.addr, requested_ip_opt, 4); + } else if (ip_addr_cmp(&requested_ip, IP_ADDR_ANY)) { ip_addr_copy(requested_ip, dhcpmsg->ciaddr); } else { printf("DHCP Server Error: No requested IP\r\n"); @@ -236,7 +268,7 @@ static void handle_dhcp_request(struct dhcp_msg *dhcpmsg) } /* Test the first 4 octets match */ - if(ip4_addr1(&requested_ip) != ip4_addr1(&state->first_client_addr) + if (ip4_addr1(&requested_ip) != ip4_addr1(&state->first_client_addr) || ip4_addr2(&requested_ip) != ip4_addr2(&state->first_client_addr) || ip4_addr3(&requested_ip) != ip4_addr3(&state->first_client_addr)) { sprintf_ipaddr(&requested_ip, ipbuf); @@ -246,14 +278,14 @@ static void handle_dhcp_request(struct dhcp_msg *dhcpmsg) } /* Test the last octet is in the MAXCLIENTS range */ int16_t octet_offs = ip4_addr4(&requested_ip) - ip4_addr4(&state->first_client_addr); - if(octet_offs < 0 || octet_offs >= state->max_leases) { + if (octet_offs < 0 || octet_offs >= state->max_leases) { printf("DHCP Server Error: Address out of range\r\n"); send_dhcp_nak(dhcpmsg); return; } dhcp_lease_t *requested_lease = state->leases + octet_offs; - if(requested_lease->expires != 0 && memcmp(requested_lease->hwaddr, dhcpmsg->chaddr,dhcpmsg->hlen)) + if (requested_lease->active && memcmp(requested_lease->hwaddr, dhcpmsg->chaddr,dhcpmsg->hlen)) { printf("DHCP Server Error: Lease for address already taken\r\n"); send_dhcp_nak(dhcpmsg); @@ -265,7 +297,9 @@ static void handle_dhcp_request(struct dhcp_msg *dhcpmsg) printf("DHCP lease addr %s assigned to MAC %02x:%02x:%02x:%02x:%02x:%02x\r\n", ipbuf, requested_lease->hwaddr[0], requested_lease->hwaddr[1], requested_lease->hwaddr[2], requested_lease->hwaddr[3], requested_lease->hwaddr[4], requested_lease->hwaddr[5]); - requested_lease->expires = DHCPSERVER_LEASE_TIME * configTICK_RATE_HZ; + uint32_t now = xTaskGetTickCount(); + requested_lease->expires = now + DHCPSERVER_LEASE_TIME * configTICK_RATE_HZ; + requested_lease->active = 1; /* Reuse the REQUEST message as the ACK message */ dhcpmsg->op = DHCP_BOOTREPLY; @@ -279,6 +313,13 @@ static void handle_dhcp_request(struct dhcp_msg *dhcpmsg) opt = add_dhcp_option_bytes(opt, DHCP_OPTION_LEASE_TIME, &expiry, 4); opt = add_dhcp_option_bytes(opt, DHCP_OPTION_SERVER_ID, &state->server_if->ip_addr, 4); opt = add_dhcp_option_bytes(opt, DHCP_OPTION_SUBNET_MASK, &state->server_if->netmask, 4); + if (!ip_addr_isany(&state->router)) { + opt = add_dhcp_option_bytes(opt, DHCP_OPTION_ROUTER, &state->router, 4); + } + if (!ip_addr_isany(&state->dns)) { + opt = add_dhcp_option_bytes(opt, DHCP_OPTION_DNS_SERVER, &state->dns, 4); + } + opt = add_dhcp_option_bytes(opt, DHCP_OPTION_END, NULL, 0); struct netbuf *netbuf = netbuf_new(); @@ -291,7 +332,8 @@ static void handle_dhcp_request(struct dhcp_msg *dhcpmsg) static void handle_dhcp_release(struct dhcp_msg *dhcpmsg) { dhcp_lease_t *lease = find_lease_slot(dhcpmsg->chaddr); - if(lease) { + if (lease) { + lease->active = 0; lease->expires = 0; } } @@ -319,17 +361,17 @@ static uint8_t *find_dhcp_option(struct dhcp_msg *msg, uint8_t option_num, uint8 uint8_t *start = (uint8_t *)&msg->options; uint8_t *msg_end = (uint8_t *)msg + sizeof(struct dhcp_msg); - for(uint8_t *p = start; p < msg_end-2;) { + for (uint8_t *p = start; p < msg_end-2;) { uint8_t type = *p++; uint8_t len = *p++; - if(type == DHCP_OPTION_END) + if (type == DHCP_OPTION_END) return NULL; - if(p+len >= msg_end) + if (p+len >= msg_end) break; /* We've overrun our valid DHCP message size, or this isn't a valid option */ - if(type == option_num) { - if(len < min_length) + if (type == option_num) { + if (len < min_length) break; - if(length) + if (length) *length = len; return p; /* start of actual option data */ } @@ -349,7 +391,7 @@ static uint8_t *add_dhcp_option_byte(uint8_t *opt, uint8_t type, uint8_t value) static uint8_t *add_dhcp_option_bytes(uint8_t *opt, uint8_t type, void *value, uint8_t len) { *opt++ = type; - if(len) { + if (len) { *opt++ = len; memcpy(opt, value, len); } @@ -360,8 +402,8 @@ static uint8_t *add_dhcp_option_bytes(uint8_t *opt, uint8_t type, void *value, u static dhcp_lease_t *find_lease_slot(uint8_t *hwaddr) { dhcp_lease_t *empty_lease = NULL; - for(int i = 0; i < state->max_leases; i++) { - if(state->leases[i].expires == 0 && !empty_lease) + for (int i = 0; i < state->max_leases; i++) { + if (!state->leases[i].active && !empty_lease) empty_lease = &state->leases[i]; else if (memcmp(hwaddr, state->leases[i].hwaddr, 6) == 0) return &state->leases[i]; diff --git a/extras/dhcpserver/include/dhcpserver.h b/extras/dhcpserver/include/dhcpserver.h index 95a59b8..f5c7f06 100644 --- a/extras/dhcpserver/include/dhcpserver.h +++ b/extras/dhcpserver/include/dhcpserver.h @@ -34,6 +34,12 @@ void dhcpserver_get_lease(const ip_addr_t *first_client_addr, uint8_t max_leases */ void dhcpserver_stop(void); +/* Set a router address to send as an option. */ +void dhcpserver_set_router(const ip_addr_t *router); + +/* Set a DNS address to send as an option. */ +void dhcpserver_set_dns(const ip_addr_t *dns); + #ifdef __cplusplus } #endif