/* libdhcp/dhcp_client.c - A DHCP client * * Copyright (C) 2024 Luke T. Shumaker * 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 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 /* for strlen(), memcpy(), memset() */ #include #include #include #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 #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("<> state : STATE_DHCP_DISCOVER");*/ dhcp_send_DISCOVER(sock); break; case STATE_DHCP_REQUEST : /*debugf("<> state : STATE_DHCP_REQUEST");*/ dhcp_send_REQUEST(sock); break; case STATE_DHCP_REREQUEST : /*debugf("<> 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; }