diff options
Diffstat (limited to 'libdhcp/dhcp_client.c')
-rw-r--r-- | libdhcp/dhcp_client.c | 573 |
1 files changed, 573 insertions, 0 deletions
diff --git a/libdhcp/dhcp_client.c b/libdhcp/dhcp_client.c new file mode 100644 index 0000000..a4b015c --- /dev/null +++ b/libdhcp/dhcp_client.c @@ -0,0 +1,573 @@ +/* libdhcp/dhcp_client.c - A DHCP client + * + * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/Internet/DHCP/dhcp.c + * + * Copyright (c) 2013, WIZnet Co., LTD. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the <ORGANIZATION> nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * ----------------------------------------------------------------------------- + * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/license.txt + * + * Copyright (c) 2014 WIZnet Co.,Ltd. + * Copyright (c) WIZnet ioLibrary Project. + * All rights reserved. + * + * 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. + * + * SPDX-License-Identifier: MIT + */ + +#include <string.h> /* for strlen(), memcpy(), memset() */ + +#include <libmisc/endian.h> +#include <libmisc/vcall.h> +#include <libdhcp/dhcp.h> + +#include "dhcp_common.h" + +/* Config *********************************************************************/ + +#include "config.h" + +#ifndef CONFIG_DHCP_DEBUG + #define CONFIG_DHCP_DEBUG 1 +#endif +#ifndef CONFIG_DHCP_HOPS + #define CONFIG_DHCP_HOPS 0 +#endif +#ifndef CONFIG_DHCP_SECS + #define CONFIG_DHCP_SECS 0 +#endif + +/* Implementation *************************************************************/ + +#if CONFIG_DHCP_DEBUG + #include <stdio.h> + #define debugf(fmt, ...) printf(fmt "\n" __VA_OPT__(,) __VA_ARGS__) +#else + #define debugf(fmt, ...) ((void)0) +#endif + +/* DHCP state machine. */ +#define STATE_DHCP_INIT 0 /* Initialize */ +#define STATE_DHCP_DISCOVER 1 /* send DISCOVER and wait OFFER */ +#define STATE_DHCP_REQUEST 2 /* send REQEUST and wait ACK or NACK */ +#define STATE_DHCP_LEASED 3 /* ReceiveD ACK and IP leased */ +#define STATE_DHCP_REREQUEST 4 /* send REQUEST for maintaining leased IP */ +#define STATE_DHCP_RELEASE 5 /* No use */ +#define STATE_DHCP_STOP 6 /* Stop processing DHCP */ + +/* Global state. */ +struct net_ip4_addr DHCP_SIP; /* DHCP Server IP address */ +struct net_ip4_addr DHCP_REAL_SIP; /* For extract my DHCP server in a few DHCP server */ +struct net_ip4_addr OLD_allocated_ip = {0}; /* Previous IP address */ +struct dhcp_lease global_lease = {.lifetime = DHCP_INFINITY}; +int8_t dhcp_state = STATE_DHCP_INIT; /* DHCP state */ +int8_t dhcp_retry_count = 0; +volatile uint32_t dhcp_tick_1s = 0; /* unit 1 second */ +uint32_t dhcp_tick_next = DHCP_WAIT_TIME ; +char global_hostname[64]; +struct net_eth_addr global_eth_addr; /* DHCP Client MAC address. */ + +#define mem_encode(dst, obj) ({ \ + typeof(obj) _obj = obj; \ + memcpy(dst, &_obj, sizeof(_obj)); \ + sizeof(_obj); \ +}) + +static inline size_t str_encode(void *dst, char *str) { + size_t len = strlen(str); + memcpy(dst, str, len); + return len; +} + +/* make the common DHCP message */ +static inline void dhcp_msg_init(struct dhcp_msg *msg, size_t *optlen, + struct net_eth_addr self_eth_addr, + uint16_t flags, uint32_t xid) { + size_t tmp; + + assert(msg); + assert(optlen); + + *msg = (struct dhcp_msg){0}; + + msg->op = DHCP_OP_BOOTREQUEST; + msg->htype = DHCP_HTYPE_ETHERNET; + msg->hlen = sizeof(self_eth_addr); + msg->hops = CONFIG_DHCP_HOPS; + msg->xid = uint32be_marshal(xid); + msg->secs = uint16be_marshal(CONFIG_DHCP_SECS); + msg->flags = uint16be_marshal(flags); + msg->ciaddr = (struct net_ip4_addr){0}; + msg->yiaddr = (struct net_ip4_addr){0}; + msg->siaddr = (struct net_ip4_addr){0}; + msg->siaddr = (struct net_ip4_addr){0}; + msg->giaddr = (struct net_ip4_addr){0}; + + tmp = mem_encode(msg->chaddr, self_eth_addr); + memset(&msg->chaddr[tmp], 0, sizeof(msg->chaddr)-tmp); + + memset(msg->sname, 0, sizeof(msg->sname)); + memset(msg->file, 0, sizeof(msg->file)); + + *optlen = 0; + msg->options[*optlen++] = dhcp_magic_cookie[0]; + msg->options[*optlen++] = dhcp_magic_cookie[1]; + msg->options[*optlen++] = dhcp_magic_cookie[2]; + msg->options[*optlen++] = dhcp_magic_cookie[3]; +} + +static void dhcp_send_DISCOVER(implements_net_packet_conn *sock) { + struct dhcp_msg msg; + size_t k; + + dhcp_msg_init(&msg, &k, DHCP_FLAG_BROADCAST); + DHCP_SIP = (struct net_ip4_addr){0}; + DHCP_REAL_SIP = (struct net_ip4_addr){0}; + + /* Message type. */ + msg->options[k++] = DHCP_OPT_DHCP_MSG_TYPE; + msg->options[k++] = 1; + msg->options[k++] = DHCP_MSGTYP_DISCOVER; + + /* Our Client ID. */ + msg->options[k++] = DHCP_OPT_CLIENT_ID; + msg->options[k++] = 1+sizeof(self_eth_addr); + msg->options[k++] = DHCP_HTYPE_ETHERNET; + k += mem_encode(&msg->options[k], self_eth_addr); + + /* Our requested hostname. */ + msg->options[k++] = DHCP_OPT_HOSTNAME; + msg->options[k++] = strlen(global_hostname); + k += str_encode(&msg->options[k], global_hostname); + + /* Which parameters we want back. */ + msg->options[k++] = DHCP_OPT_PARAMETER_LIST; + msg->options[k++] = 6; + msg->options[k++] = DHCP_OPT_SUBNET_MASK; /* 1 */ + msg->options[k++] = DHCP_OPT_ROUTER; /* 2 */ + msg->options[k++] = DHCP_OPT_DOMAIN_SERVER; /* 3 */ + msg->options[k++] = DHCP_OPT_DOMAIN_NAME; /* 4 */ + msg->options[k++] = DHCP_OPT_RENEWAL_TIME; /* 5 */ + msg->options[k++] = DHCP_OPT_REBINDING_TIME; /* 6 */ + + /* End. */ + msg->options[k++] = DHCP_OPT_END; + assert(k <= CONFIG_DHCP_OPT_SIZE); + + /* Send. */ + debugf("> Send DHCP_DISCOVER"); + VCALL(sock, sendto, msg, DHCP_MSG_BASE_SIZE + k, net_ip4_addr_broadcast, DHCP_PORT_SERVER); +} + +static void dhcp_send_REQUEST(implements_net_packet_conn *sock) { + struct net_ip4_addr ip; + struct dhcp_msg msg; + size_t k; + + dhcp_msg_init(msg, &k, + (dhcp_state != STATE_DHCP_LEASED && dhcp_state != STATE_DHCP_REREQUEST) ? DHCP_FLAG_BROADCAST : 0); + + switch (dhcp_state) { + case STATE_DHCP_LEASED: + case STATE_DHCP_REREQUEST: + msg->ciaddr = global_lease.addr; + ip = DHCP_SIP; + break; + default: + ip = net_ip4_addr_broadcast; + } + + /* Message type. */ + msg->options[k++] = DHCP_OPT_DHCP_MSG_TYPE; + msg->options[k++] = 1; + msg->options[k++] = DHCP_MSGTYP_REQUEST; + + /* Our Client ID. */ + msg->options[k++] = DHCP_OPT_CLIENT_ID; + msg->options[k++] = 1+sizeof(self_eth_addr); + msg->options[k++] = DHCP_HTYPE_ETHERNET; + k += mem_encode(&msg->options[k], self_eth_addr); + + switch (dhcp_state) { + case STATE_DHCP_LEASED: + case STATE_DHCP_REREQUEST: + /* Our current IP address. */ + msg->options[k++] = DHCP_OPT_ADDRESS_REQUEST; + msg->options[k++] = sizeof(global_lease.addr); + k += mem_encode(&msg->options[k], global_lease.addr); + + /* The server we got it from. */ + msg->options[k++] = DHCP_OPT_DHCP_SERVER_ID; + msg->options[k++] = sizeof(DHCP_SIP); + k += mem_encode(&msg->options[k], DHCP_SIP); + } + + /* Our requested hostname. */ + msg->options[k++] = DHCP_OPT_HOSTNAME; + msg->options[k++] = strlen(global_hostname); + k += str_encode(&msg->options[k], global_hostname); + + /* Which parameters we want back. */ + msg->options[k++] = DHCP_OPT_PARAMETER_LIST; + msg->options[k++] = 8; + msg->options[k++] = DHCP_OPT_SUBNET_MASK; /* 1 */ + msg->options[k++] = DHCP_OPT_ROUTER; /* 2 */ + msg->options[k++] = DHCP_OPT_DOMAIN_SERVER; /* 3 */ + msg->options[k++] = DHCP_OPT_DOMAIN_NAME; /* 4 */ + msg->options[k++] = DHCP_OPT_RENEWAL_TIME; /* 5 */ + msg->options[k++] = DHCP_OPT_REBINDING_TIME; /* 6 */ + msg->options[k++] = DHCP_OPT_ROUTER_DISCOVERY; /* 7 */ + msg->options[k++] = DHCP_OPT_STATIC_ROUTE; /* 8 */ + + /* End. */ + msg->options[k++] = DHCP_OPT_END; + assert(k <= CONFIG_DHCP_OPT_SIZE); + + debugf("> Send DHCP_REQUEST"); + VCALL(sock, sendto, msg, DHCP_MSG_BASE_SIZE + k, ip, DHCP_PORT_SERVER); +} + +static void dhcp_send_DECLINE(implements_net_packet_conn *sock) { + size_t k; + + dhcp_msg_init(pDHCPMSG, &k, DHCP_FLAG_BROADCAST); + pDHCPMSG->flags = uint16be_marshal(0); + + /* Option Request Param. */ + pDHCPMSG->options[k++] = DHCP_OPT_DHCP_MSG_TYPE; + pDHCPMSG->options[k++] = 1; + pDHCPMSG->options[k++] = DHCP_MSGTYP_DECLINE; + + /* Our Client ID. */ + pDHCPMSG->options[k++] = DHCP_OPT_CLIENT_ID; + pDHCPMSG->options[k++] = 1+sizeof(self_eth_addr); + pDHCPMSG->options[k++] = DHCP_HTYPE_ETHERNET; + memcpy(&pDHCPMSG->options[k], self_eth_addr.octets, sizeof(self_eth_addr)); + k += sizeof(self_eth_addr); + + /* Our current IP address. */ + pDHCPMSG->options[k++] = DHCP_OPT_ADDRESS_REQUEST; + pDHCPMSG->options[k++] = sizeof(global_lease.addr); + k += mem_encode(&pDHCPMSG->options[k], global_lease.addr); + + /* The server we got it from. */ + pDHCPMSG->options[k++] = DHCP_OPT_DHCP_SERVER_ID; + pDHCPMSG->options[k++] = sizeof(DHCP_SIP); + k += mem_encode(&pDHCPMSG->options[k], DHCP_SIP); + + /* End. */ + pDHCPMSG->options[k++] = DHCP_OPT_END; + + debugf("> Send DHCP_DECLINE"); + assert(k <= CONFIG_DHCP_OPT_SIZE); + VCALL(sock, sendto, pDHCPMSG, DHCP_MSG_BASE_SIZE + k, net_ip4_addr_broadcast, DHCP_PORT_SERVER); +} + +static int8_t dhcp_msg_parse(implements_net_packet_conn *sock) { + struct net_ip4_addr srv_addr; + uint16_t srv_port; + size_t msg_len; + msg_len = VCALL(sock, recvfrom, pDHCPMSG, sizeof(*pDHCPMSG), &srv_addr, &srv_port); + debugf("DHCP message : %d.%d.%d.%d(%d) %d received.", + srv_addr.octets[0], srv_addr.octets[1], srv_addr.octets[2], srv_addr.octets[3], srv_port, msg_len); + /* Compare server port. */ + if (srv_port != DHCP_PORT_SERVER) { + return 0; + } + /* Compare our MAC address. */ + if (memcmp(pDHCPMSG->chaddr, self_eth_addr.octets, sizeof(self_eth_addr))) { + debugf("Not my DHCP Message. This message is ignored."); + return 0; + } + /* Compare server IP address. */ + if (memcmp(DHCP_SIP.octets, ((struct net_ip4_addr){0}).octets, sizeof(struct net_ip4_addr))) { + if ( memcmp(srv_addr.octets, DHCP_SIP.octets, sizeof(srv_addr)) && + memcmp(srv_addr.octets, DHCP_REAL_SIP.octets, sizeof(srv_addr)) ) { + debugf("Another DHCP sever send a response message. This is ignored."); + return 0; + } + } + + uint8_t msg_type = 0; + for (size_t k = 4, k_max = msg_len - DHCP_MSG_BASE_SIZE, opt_len; k < k_max; k += opt_len) { + uint8_t opt_typ; + opt_typ = pDHCPMSG->options[k++]; + switch (opt_typ) { + case DHCP_OPT_END: + opt_len = k_max - k; + break; + case DHCP_OPT_PAD: + opt_len = 0; + break; + default: + opt_len = pDHCPMSG->options[k++]; + xhandle(opt_typ, opt_len, &pDHCPMSG->options[k]); + } + switch (opt_typ) { + case DHCP_OPT_DHCP_MSG_TYPE: + if (opt_len != 1) + continue; + msg_type = pDHCPMSG->options[k]; + break; + case DHCP_OPT_ADDRESS_TIME: + if (opt_len != 4) + continue; + global_lease.lifetime = uint32be_decode(&pDHCPMSG->options[k]); +#if CONFIG_DHCP_DEBUG + global_lease.lifetime = 10; +#endif + break; + case DHCP_OPT_DHCP_SERVER_ID: + if (opt_len != 4) + continue; + DHCP_SIP.octets[0] = pDHCPMSG->options[k+0]; + DHCP_SIP.octets[1] = pDHCPMSG->options[k+1]; + DHCP_SIP.octets[2] = pDHCPMSG->options[k+2]; + DHCP_SIP.octets[3] = pDHCPMSG->options[k+3]; + DHCP_REAL_SIP = srv_addr; + break; + } + } + return msg_type; +} + +static void dhcp_reset_timeout(void) { + dhcp_tick_1s = 0; + dhcp_tick_next = DHCP_WAIT_TIME; + dhcp_retry_count = 0; +} + +static uint8_t dhcp_check_timeout(implements_net_packet_conn *sock) { + uint8_t ret = DHCP_RET_RUNNING; + + if (dhcp_retry_count < MAX_DHCP_RETRY) { + if (dhcp_tick_next < dhcp_tick_1s) { + + switch ( dhcp_state ) { + case STATE_DHCP_DISCOVER : + /*debugf("<<timeout>> state : STATE_DHCP_DISCOVER");*/ + dhcp_send_DISCOVER(sock); + break; + + case STATE_DHCP_REQUEST : + /*debugf("<<timeout>> state : STATE_DHCP_REQUEST");*/ + dhcp_send_REQUEST(sock); + break; + + case STATE_DHCP_REREQUEST : + /*debugf("<<timeout>> state : STATE_DHCP_REREQUEST");*/ + dhcp_send_REQUEST(sock); + break; + + default : + break; + } + + dhcp_tick_1s = 0; + dhcp_tick_next = dhcp_tick_1s + DHCP_WAIT_TIME; + dhcp_retry_count++; + } + } else { /* timeout occurred */ + + switch(dhcp_state) { + case STATE_DHCP_DISCOVER: + dhcp_state = STATE_DHCP_INIT; + ret = DHCP_RET_FAILED; + break; + case STATE_DHCP_REQUEST: + case STATE_DHCP_REREQUEST: + dhcp_send_DISCOVER(sock); + dhcp_state = STATE_DHCP_DISCOVER; + break; + default : + break; + } + dhcp_reset_timeout(); + } + return ret; +} + +static int8_t dhcp_check_leasedIP(implements_net_packet_conn *sock) { + int32_t ret; + + /* IP conflict detection : ARP request - ARP reply */ + /* Broadcasting ARP Request for check the IP conflict using UDP sendto() function */ + ret = VCALL(sock, sendto, "CHECK_IP_CONFLICT", 17, global_lease.addr, 5000); + + if (ret == NET_ETIMEDOUT) { + /* UDP send Timeout occurred : allocated IP address is unique, DHCP Success */ + debugf("\r\n> Check leased IP - OK"); + return 1; + } + + /* Received ARP reply or etc : IP address conflict occur, DHCP Failed */ + dhcp_send_DECLINE(sock); + + ret = dhcp_tick_1s; + while ((dhcp_tick_1s - ret) < 2) {} /* wait for 1s over; wait to complete to send DECLINE message; */ + + return 0; +} + +COROUTINE DHCP_cr(struct w5500 *chip, uint8_t socknum, + struct net_eth_addr self_eth_addr, + char *self_hostname, + dhcp_callback_t cb) { + uint32_t xid; + implements_net_packet_conn *sock; + + xid = 0x12345678; + xid += self_eth_addr.octets[3]; + xid += self_eth_addr.octets[4]; + xid += self_eth_addr.octets[5]; + xid += (self_eth_addr.octets[3] ^ + self_eth_addr.octets[4] ^ + self_eth_addr.octets[5]); + + dhcp_tick_1s = 0; + dhcp_tick_next = DHCP_WAIT_TIME; + dhcp_retry_count = 0; + dhcp_state = STATE_DHCP_INIT; + + sock = w5500_udp_conn(chip, socknum, DHCP_PORT_CLIENT); + + ret = DHCP_RET_RUNNING; + msg_type = dhcp_msg_parse(sock); + + for (;;) { + /* State transition diagram: https://datatracker.ietf.org/doc/html/rfc2131#page-35 */ + switch (state) { + case STATE_DHCP_INIT: + dhcp_send_DISCOVER(sock); + dhcp_state = STATE_DHCP_DISCOVER; + break; + case STATE_DHCP_DISCOVER : + if (msg_type == DHCP_MSGTYP_OFFER){ + debugf("> Receive DHCP_OFFER"); + global_lease.addr = pDHCPMSG->yiaddr; + + dhcp_send_REQUEST(sock); + dhcp_state = STATE_DHCP_REQUEST; + } else + ret = dhcp_check_timeout(sock); + break; + + case STATE_DHCP_REQUEST : + if (msg_type == DHCP_MSGTYP_ACK) { + debugf("> Receive DHCP_ACK"); + if (dhcp_check_leasedIP(sock)) { + /* Network info assignment from DHCP */ + cb(DHCP_ASSIGN, global_lease); + dhcp_reset_timeout(); + + dhcp_state = STATE_DHCP_LEASED; + } else { + /* IP address conflict occurred */ + dhcp_reset_timeout(); + cb(DHCP_CONFLICT, global_lease); + dhcp_state = STATE_DHCP_INIT; + } + } else if (msg_type == DHCP_MSGTYP_NAK) { + debugf("> Receive DHCP_NACK"); + + dhcp_reset_timeout(); + + dhcp_state = STATE_DHCP_DISCOVER; + } else + ret = dhcp_check_timeout(sock); + break; + + case STATE_DHCP_LEASED : + ret = DHCP_RET_IP_LEASED; + if ((global_lease.lifetime != DHCP_INFINITY) && ((global_lease.lifetime/2) < dhcp_tick_1s)) { + debugf("> Maintains the IP address"); + + msg_type = 0; + OLD_allocated_ip = global_lease.addr; + + xid++; + + dhcp_send_REQUEST(sock); + + dhcp_reset_timeout(); + + dhcp_state = STATE_DHCP_REREQUEST; + } + break; + + case STATE_DHCP_REREQUEST : + ret = DHCP_RET_IP_LEASED; + if (msg_type == DHCP_MSGTYP_ACK) { + dhcp_retry_count = 0; + if (memcmp(OLD_allocated_ip.octets, global_lease.addr.octets, sizeof(global_lease.addr))) { + ret = DHCP_RET_IP_CHANGED; + cb(DHCP_UPDATE, global_lease); + debugf(">IP changed."); + } else { + debugf(">IP is continued."); + } + dhcp_reset_timeout(); + dhcp_state = STATE_DHCP_LEASED; + } else if (msg_type == DHCP_MSGTYP_NAK) { + debugf("> Receive DHCP_NACK, Failed to maintain ip"); + dhcp_reset_timeout(); + + dhcp_state = STATE_DHCP_DISCOVER; + } else + ret = dhcp_check_timeout(sock); + break; + default : + break; + } + + return ret; +} |