/* * The MIT License (MIT) * * Copyright (c) 2015 by Sergey Fetisov * * 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; }