esp32-s2_dfu/lib/networking/dhserver.c

349 lines
8.7 KiB
C

/*
* The MIT License (MIT)
*
* Copyright (c) 2015 by Sergey Fetisov <fsenok@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "dhserver.h"
/* DHCP message type */
#define DHCP_DISCOVER 1
#define DHCP_OFFER 2
#define DHCP_REQUEST 3
#define DHCP_DECLINE 4
#define DHCP_ACK 5
#define DHCP_NAK 6
#define DHCP_RELEASE 7
#define DHCP_INFORM 8
/* DHCP options */
enum DHCP_OPTIONS
{
DHCP_PAD = 0,
DHCP_SUBNETMASK = 1,
DHCP_ROUTER = 3,
DHCP_DNSSERVER = 6,
DHCP_HOSTNAME = 12,
DHCP_DNSDOMAIN = 15,
DHCP_MTU = 26,
DHCP_BROADCAST = 28,
DHCP_PERFORMROUTERDISC = 31,
DHCP_STATICROUTE = 33,
DHCP_NISDOMAIN = 40,
DHCP_NISSERVER = 41,
DHCP_NTPSERVER = 42,
DHCP_VENDOR = 43,
DHCP_IPADDRESS = 50,
DHCP_LEASETIME = 51,
DHCP_OPTIONSOVERLOADED = 52,
DHCP_MESSAGETYPE = 53,
DHCP_SERVERID = 54,
DHCP_PARAMETERREQUESTLIST = 55,
DHCP_MESSAGE = 56,
DHCP_MAXMESSAGESIZE = 57,
DHCP_RENEWALTIME = 58,
DHCP_REBINDTIME = 59,
DHCP_CLASSID = 60,
DHCP_CLIENTID = 61,
DHCP_USERCLASS = 77, /* RFC 3004 */
DHCP_FQDN = 81,
DHCP_DNSSEARCH = 119, /* RFC 3397 */
DHCP_CSR = 121, /* RFC 3442 */
DHCP_MSCSR = 249, /* MS code for RFC 3442 */
DHCP_END = 255
};
typedef struct
{
uint8_t dp_op; /* packet opcode type */
uint8_t dp_htype; /* hardware addr type */
uint8_t dp_hlen; /* hardware addr length */
uint8_t dp_hops; /* gateway hops */
uint32_t dp_xid; /* transaction ID */
uint16_t dp_secs; /* seconds since boot began */
uint16_t dp_flags;
uint8_t dp_ciaddr[4]; /* client IP address */
uint8_t dp_yiaddr[4]; /* 'your' IP address */
uint8_t dp_siaddr[4]; /* server IP address */
uint8_t dp_giaddr[4]; /* gateway IP address */
uint8_t dp_chaddr[16]; /* client hardware address */
uint8_t dp_legacy[192];
uint8_t dp_magic[4];
uint8_t dp_options[275]; /* options area */
} DHCP_TYPE;
DHCP_TYPE dhcp_data;
static struct udp_pcb *pcb = NULL;
static const dhcp_config_t *config = NULL;
char magic_cookie[] = {0x63,0x82,0x53,0x63};
static ip4_addr_t get_ip(const uint8_t *pnt)
{
ip4_addr_t result;
memcpy(&result, pnt, sizeof(result));
return result;
}
static void set_ip(uint8_t *pnt, ip4_addr_t value)
{
memcpy(pnt, &value.addr, sizeof(value.addr));
}
static dhcp_entry_t *entry_by_ip(ip4_addr_t ip)
{
int i;
for (i = 0; i < config->num_entry; i++)
if (config->entries[i].addr.addr == ip.addr)
return &config->entries[i];
return NULL;
}
static dhcp_entry_t *entry_by_mac(uint8_t *mac)
{
int i;
for (i = 0; i < config->num_entry; i++)
if (memcmp(config->entries[i].mac, mac, 6) == 0)
return &config->entries[i];
return NULL;
}
static __inline bool is_vacant(dhcp_entry_t *entry)
{
return memcmp("\0\0\0\0\0", entry->mac, 6) == 0;
}
static dhcp_entry_t *vacant_address(void)
{
int i;
for (i = 0; i < config->num_entry; i++)
if (is_vacant(config->entries + i))
return config->entries + i;
return NULL;
}
static __inline void free_entry(dhcp_entry_t *entry)
{
memset(entry->mac, 0, 6);
}
uint8_t *find_dhcp_option(uint8_t *attrs, int size, uint8_t attr)
{
int i = 0;
while ((i + 1) < size)
{
int next = i + attrs[i + 1] + 2;
if (next > size) return NULL;
if (attrs[i] == attr)
return attrs + i;
i = next;
}
return NULL;
}
int fill_options(void *dest,
uint8_t msg_type,
const char *domain,
ip4_addr_t dns,
int lease_time,
ip4_addr_t serverid,
ip4_addr_t router,
ip4_addr_t subnet)
{
uint8_t *ptr = (uint8_t *)dest;
/* ACK message type */
*ptr++ = 53;
*ptr++ = 1;
*ptr++ = msg_type;
/* dhcp server identifier */
*ptr++ = DHCP_SERVERID;
*ptr++ = 4;
set_ip(ptr, serverid);
ptr += 4;
/* lease time */
*ptr++ = DHCP_LEASETIME;
*ptr++ = 4;
*ptr++ = (lease_time >> 24) & 0xFF;
*ptr++ = (lease_time >> 16) & 0xFF;
*ptr++ = (lease_time >> 8) & 0xFF;
*ptr++ = (lease_time >> 0) & 0xFF;
/* subnet mask */
*ptr++ = DHCP_SUBNETMASK;
*ptr++ = 4;
set_ip(ptr, subnet);
ptr += 4;
/* router */
if (router.addr != 0)
{
*ptr++ = DHCP_ROUTER;
*ptr++ = 4;
set_ip(ptr, router);
ptr += 4;
}
/* domain name */
if (domain != NULL)
{
int len = strlen(domain);
*ptr++ = DHCP_DNSDOMAIN;
*ptr++ = len;
memcpy(ptr, domain, len);
ptr += len;
}
/* domain name server (DNS) */
if (dns.addr != 0)
{
*ptr++ = DHCP_DNSSERVER;
*ptr++ = 4;
set_ip(ptr, dns);
ptr += 4;
}
/* end */
*ptr++ = DHCP_END;
return ptr - (uint8_t *)dest;
}
static void udp_recv_proc(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
{
uint8_t *ptr;
dhcp_entry_t *entry;
struct pbuf *pp;
struct netif *netif = netif_get_by_index(p->if_idx);
(void)arg;
(void)addr;
unsigned n = p->len;
if (n > sizeof(dhcp_data)) n = sizeof(dhcp_data);
memcpy(&dhcp_data, p->payload, n);
switch (dhcp_data.dp_options[2])
{
case DHCP_DISCOVER:
entry = entry_by_mac(dhcp_data.dp_chaddr);
if (entry == NULL) entry = vacant_address();
if (entry == NULL) break;
dhcp_data.dp_op = 2; /* reply */
dhcp_data.dp_secs = 0;
dhcp_data.dp_flags = 0;
set_ip(dhcp_data.dp_yiaddr, entry->addr);
memcpy(dhcp_data.dp_magic, magic_cookie, 4);
memset(dhcp_data.dp_options, 0, sizeof(dhcp_data.dp_options));
fill_options(dhcp_data.dp_options,
DHCP_OFFER,
config->domain,
config->dns,
entry->lease,
*netif_ip4_addr(netif),
config->router,
*netif_ip4_netmask(netif));
pp = pbuf_alloc(PBUF_TRANSPORT, sizeof(dhcp_data), PBUF_POOL);
if (pp == NULL) break;
memcpy(pp->payload, &dhcp_data, sizeof(dhcp_data));
udp_sendto(upcb, pp, IP_ADDR_BROADCAST, port);
pbuf_free(pp);
break;
case DHCP_REQUEST:
/* 1. find requested ipaddr in option list */
ptr = find_dhcp_option(dhcp_data.dp_options, sizeof(dhcp_data.dp_options), DHCP_IPADDRESS);
if (ptr == NULL) break;
if (ptr[1] != 4) break;
ptr += 2;
/* 2. does hw-address registered? */
entry = entry_by_mac(dhcp_data.dp_chaddr);
if (entry != NULL) free_entry(entry);
/* 3. find requested ipaddr */
entry = entry_by_ip(get_ip(ptr));
if (entry == NULL) break;
if (!is_vacant(entry)) break;
/* 4. fill struct fields */
memcpy(dhcp_data.dp_yiaddr, ptr, 4);
dhcp_data.dp_op = 2; /* reply */
dhcp_data.dp_secs = 0;
dhcp_data.dp_flags = 0;
memcpy(dhcp_data.dp_magic, magic_cookie, 4);
/* 5. fill options */
memset(dhcp_data.dp_options, 0, sizeof(dhcp_data.dp_options));
fill_options(dhcp_data.dp_options,
DHCP_ACK,
config->domain,
config->dns,
entry->lease,
*netif_ip4_addr(netif),
config->router,
*netif_ip4_netmask(netif));
/* 6. send ACK */
pp = pbuf_alloc(PBUF_TRANSPORT, sizeof(dhcp_data), PBUF_POOL);
if (pp == NULL) break;
memcpy(entry->mac, dhcp_data.dp_chaddr, 6);
memcpy(pp->payload, &dhcp_data, sizeof(dhcp_data));
udp_sendto(upcb, pp, IP_ADDR_BROADCAST, port);
pbuf_free(pp);
break;
default:
break;
}
pbuf_free(p);
}
err_t dhserv_init(const dhcp_config_t *c)
{
err_t err;
udp_init();
dhserv_free();
pcb = udp_new();
if (pcb == NULL)
return ERR_MEM;
err = udp_bind(pcb, IP_ADDR_ANY, c->port);
if (err != ERR_OK)
{
dhserv_free();
return err;
}
udp_recv(pcb, udp_recv_proc, NULL);
config = c;
return ERR_OK;
}
void dhserv_free(void)
{
if (pcb == NULL) return;
udp_remove(pcb);
pcb = NULL;
}