/* * DhcpClient.cpp * * Created on: Mar 25, 2023 * Author: ZKSWE Develop Team */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "DhcpClient.h" #include "utils/Log.h" namespace net { #define PORT_BOOTP_SERVER 67 #define PORT_BOOTP_CLIENT 68 #define OP_BOOTREQUEST 1 #define OP_BOOTREPLY 2 #define FLAGS_BROADCAST 0x8000 #define HTYPE_ETHER 1 #define DHCP_MSG_FIXED_SIZE 236 /* first four bytes of options are a cookie to indicate that ** the payload are DHCP options as opposed to some other BOOTP ** extension. */ #define OPT_COOKIE1 0x63 #define OPT_COOKIE2 0x82 #define OPT_COOKIE3 0x53 #define OPT_COOKIE4 0x63 /* BOOTP/DHCP options - see RFC 2132 */ #define OPT_PAD 0 #define OPT_SUBNET_MASK 1 /* 4 */ #define OPT_TIME_OFFSET 2 /* 4 */ #define OPT_GATEWAY 3 /* 4*n * n */ #define OPT_DNS 6 /* 4*n * n */ #define OPT_HOST_NAME 12 #define OPT_DOMAIN_NAME 15 /* n */ #define OPT_BROADCAST_ADDR 28 /* 4 */ #define OPT_REQUESTED_IP 50 /* 4 */ #define OPT_LEASE_TIME 51 /* 4 */ #define OPT_MESSAGE_TYPE 53 /* 1 */ #define OPT_SERVER_ID 54 /* 4 */ #define OPT_PARAMETER_LIST 55 /* n * n */ #define OPT_MESSAGE 56 /* n */ #define OPT_CLASS_ID 60 /* n */ #define OPT_CLIENT_ID 61 /* n */ #define OPT_END 255 /* DHCP message types */ #define DHCPDISCOVER 1 #define DHCPOFFER 2 #define DHCPREQUEST 3 #define DHCPDECLINE 4 #define DHCPACK 5 #define DHCPNAK 6 #define DHCPRELEASE 7 #define DHCPINFORM 8 #ifndef TEMP_FAILURE_RETRY #define TEMP_FAILURE_RETRY(exp) ({ \ long int _rc; \ do { \ _rc = (exp); \ } while (_rc == -1 && errno == EINTR); \ _rc; }) #endif typedef struct { uint8_t op; /* BOOTREQUEST / BOOTREPLY */ uint8_t htype; /* hw addr type */ uint8_t hlen; /* hw addr len */ uint8_t hops; /* client set to 0 */ uint32_t xid; /* transaction id */ uint16_t secs; /* seconds since start of acq */ uint16_t flags; uint32_t ciaddr; /* client IP addr */ uint32_t yiaddr; /* your (client) IP addr */ uint32_t siaddr; /* ip addr of next server */ /* (DHCPOFFER and DHCPACK) */ uint32_t giaddr; /* relay agent IP addr */ uint8_t chaddr[16]; /* client hw addr */ char sname[64]; /* asciiz server hostname */ char file[128]; /* asciiz boot file name */ uint8_t options[312]; /* optional parameters */ } dhcp_msg; typedef struct { uint32_t type; uint32_t ipaddr; uint32_t gateway; uint32_t prefixLength; uint32_t dns1; uint32_t dns2; uint32_t saddr; uint32_t lease; } dhcp_info; static bool _s_verbose = true; static uint32_t get_sec() { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return ts.tv_sec; } static int mask_to_prelen(in_addr_t mask) { int len = 0; uint32_t m = (uint32_t) ntohl(mask); while (m & 0x80000000) { len++; m = m << 1; } return len; } static void macaddr_to_hwaddr(const char *macaddr, uint8_t hwaddr[6]) { sscanf(macaddr, "%02x:%02x:%02x:%02x:%02x:%02x", hwaddr, hwaddr + 1, hwaddr + 2, hwaddr + 3, hwaddr + 4, hwaddr + 5); } static uint8_t *init_dhcp_msg(dhcp_msg *msg, int type, uint8_t *hwaddr, uint32_t xid) { uint8_t *x; memset(msg, 0, sizeof(dhcp_msg)); msg->op = OP_BOOTREQUEST; msg->htype = HTYPE_ETHER; msg->hlen = 6; msg->hops = 0; msg->xid = xid; memcpy(msg->chaddr, hwaddr, 6); x = msg->options; *x++ = OPT_COOKIE1; *x++ = OPT_COOKIE2; *x++ = OPT_COOKIE3; *x++ = OPT_COOKIE4; *x++ = OPT_MESSAGE_TYPE; *x++ = 1; *x++ = type; return x; } static int init_dhcp_renew_msg(dhcp_msg *msg, uint8_t *hwaddr, uint32_t xid, uint32_t ipaddr, uint32_t saddr) { uint8_t *x; x = init_dhcp_msg(msg, DHCPREQUEST, hwaddr, xid); msg->ciaddr = ipaddr; *x++ = OPT_CLIENT_ID; *x++ = 7; *x++ = HTYPE_ETHER; memcpy(x, hwaddr, 6); x += 6; *x++ = OPT_PARAMETER_LIST; *x++ = 4; *x++ = OPT_SUBNET_MASK; *x++ = OPT_GATEWAY; *x++ = OPT_DNS; *x++ = OPT_BROADCAST_ADDR; *x++ = OPT_REQUESTED_IP; *x++ = 4; memcpy(x, &ipaddr, 4); x += 4; *x++ = OPT_SERVER_ID; *x++ = 4; memcpy(x, &saddr, 4); x += 4; *x++ = OPT_END; return DHCP_MSG_FIXED_SIZE + (x - msg->options); } static const char *dhcp_type_to_name(uint32_t type) { switch (type) { case DHCPDISCOVER: return "discover"; case DHCPOFFER: return "offer"; case DHCPREQUEST: return "request"; case DHCPDECLINE: return "decline"; case DHCPACK: return "ack"; case DHCPNAK: return "nak"; case DHCPRELEASE: return "release"; case DHCPINFORM: return "inform"; default: return "???"; } } static void dump_dhcp_info(dhcp_info *info) { char addr[20], gway[20], dns[20]; LOGD("[dhcp] %s (%d) ---\n", dhcp_type_to_name(info->type), info->type); inet_ntop(AF_INET, &info->ipaddr, addr, sizeof(addr)); inet_ntop(AF_INET, &info->gateway, gway, sizeof(gway)); LOGD("[dhcp] ip %s gw %s prefixLength %d\n", addr, gway, info->prefixLength); if (info->dns1) LOGD("[dhcp] dns1: %s\n", inet_ntop(AF_INET, &info->dns1, dns, sizeof(dns))); if (info->dns2) LOGD("[dhcp] dns2: %s\n", inet_ntop(AF_INET, &info->dns2, dns, sizeof(dns))); LOGD("[dhcp] server %s, lease %d seconds\n", inet_ntop(AF_INET, &info->saddr, addr, sizeof(addr)), info->lease); } static int decode_dhcp_msg(dhcp_msg *msg, int len, dhcp_info *info) { uint8_t *x; uint32_t opt; int optlen; memset(info, 0, sizeof(dhcp_info)); if (len < (DHCP_MSG_FIXED_SIZE + 4)) return -1; len -= (DHCP_MSG_FIXED_SIZE + 4); if (msg->options[0] != OPT_COOKIE1) return -1; if (msg->options[1] != OPT_COOKIE2) return -1; if (msg->options[2] != OPT_COOKIE3) return -1; if (msg->options[3] != OPT_COOKIE4) return -1; x = msg->options + 4; while (len > 2) { opt = *x++; if (opt == OPT_PAD) { len--; continue; } if (opt == OPT_END) { break; } optlen = *x++; len -= 2; if (optlen > len) { break; } switch(opt) { case OPT_SUBNET_MASK: if (optlen >= 4) { in_addr_t mask; memcpy(&mask, x, 4); info->prefixLength = mask_to_prelen(mask); } break; case OPT_GATEWAY: if (optlen >= 4) memcpy(&info->gateway, x, 4); break; case OPT_DNS: if (optlen >= 4) memcpy(&info->dns1, x + 0, 4); if (optlen >= 8) memcpy(&info->dns2, x + 4, 4); break; case OPT_LEASE_TIME: if (optlen >= 4) { memcpy(&info->lease, x, 4); info->lease = ntohl(info->lease); } break; case OPT_SERVER_ID: if (optlen >= 4) memcpy(&info->saddr, x, 4); break; case OPT_MESSAGE_TYPE: info->type = *x; break; default: break; } x += optlen; len -= optlen; } info->ipaddr = msg->yiaddr; return 0; } static bool is_valid_reply(dhcp_msg *msg, dhcp_msg *reply, int sz) { if (sz < DHCP_MSG_FIXED_SIZE) { if (_s_verbose) { LOGD("netcfg: Wrong size %d != %d\n", sz, DHCP_MSG_FIXED_SIZE); } return false; } if (reply->op != OP_BOOTREPLY) { if (_s_verbose) { LOGD("netcfg: Wrong Op %d != %d\n", reply->op, OP_BOOTREPLY); } return false; } if (reply->xid != msg->xid) { if (_s_verbose) { LOGD("netcfg: Wrong Xid 0x%x != 0x%x\n", ntohl(reply->xid), ntohl(msg->xid)); } return false; } if (reply->htype != msg->htype) { if (_s_verbose) { LOGD("netcfg: Wrong Htype %d != %d\n", reply->htype, msg->htype); } return false; } if (reply->hlen != msg->hlen) { if (_s_verbose) { LOGD("netcfg: Wrong Hlen %d != %d\n", reply->hlen, msg->hlen); } return false; } if (memcmp(msg->chaddr, reply->chaddr, msg->hlen)) { if (_s_verbose) { LOGD("netcfg: Wrong chaddr %x != %x\n", *(reply->chaddr),*(msg->chaddr)); } return false; } return true; } DhcpClient::DhcpClient() : lease_cb_(NULL), record_time_(0), lease_(0), record_lease_(0) { wakeup_[0] = wakeup_[1] = -1; } DhcpClient::~DhcpClient() { stop(); } void DhcpClient::set_lease_cb(dhcp_lease_cb cb) { lease_cb_ = cb; } bool DhcpClient::start(const char *ip, const char *macaddr, const char *gateway) { if (isRunning()) { return true; } if (pipe(wakeup_) < 0) { LOGE("[dhcp] create pipe fail\n"); return false; } ip_ = ip; macaddr_ = macaddr; gateway_ = gateway; record_time_ = get_sec(); // lease_ = 60 * 60; // 1 hour lease_ = 100 * 60; // 100分钟 record_lease_ = 0; LOGD("开始请求dhcp,ip == %s", ip); return run("renew"); } void DhcpClient::stop() { LOGD("[dhcp] stop +++\n"); LOGD("正在断开dhcp,ip == %s", ip_.c_str()); if (wakeup_[1] >= 0) { TEMP_FAILURE_RETRY(write(wakeup_[1], "W", 1)); } requestExitAndWait(); if (wakeup_[0] >= 0) { close(wakeup_[0]); wakeup_[0] = -1; } if (wakeup_[1] >= 0) { close(wakeup_[1]); wakeup_[1] = -1; } LOGD("[dhcp] stop ---\n"); } bool DhcpClient::threadLoop() { uint32_t lease1_2 = lease_ / 2; // 1/2 uint32_t lease7_8 = lease_ - lease_ / 8; // 7/8 uint32_t past_time = get_sec() - record_time_; bool need_renew = false; // LOGD("lease_ %d\n", lease_); // LOGD("lease1_2 %d\n", lease1_2); // LOGD("lease7_8 %d\n", lease7_8); // LOGD("past_time %d\n", past_time); if (past_time > lease_) { LOGE("[dhcp] lease time out\n"); if (lease_cb_) { lease_cb_(E_DHCP_LEASE_TYPE_TIMEOUT, this); } return false; } else if (past_time > lease7_8) { if (record_lease_ < lease7_8) { record_lease_ = lease7_8; need_renew = true; } } else if (past_time > lease1_2) { if (record_lease_ < lease1_2) { record_lease_ = lease1_2; need_renew = true; } } if (need_renew) { bool ret = false; for (int i = 0; i < 3; ++i) { if (renew()) { ret = true; break; } sleep(100); } if (ret) { record_lease_ = 0; record_time_ = get_sec(); LOGD("[dhcp] lease ok\n"); if (lease_cb_) { lease_cb_(E_DHCP_LEASE_TYPE_SUCCESS, this); } } else { LOGE("[dhcp] lease err\n"); if (lease_cb_) { lease_cb_(E_DHCP_LEASE_TYPE_FAIL, this); } } } struct pollfd ufd = { wakeup_[0], POLLIN, 0 }; poll(&ufd, 1, 3000); return true; } bool DhcpClient::renew() { int s = -1; sockaddr_in saddr; socklen_t saddr_len; sockaddr_in caddr; socklen_t caddr_len; struct timeval tv_out; uint8_t hwaddr[6]; dhcp_msg renew_msg; dhcp_msg recv_msg; ssize_t renew_len; ssize_t recv_len; dhcp_info di; bool ret = false; s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) { LOGE("[dhcp] create socket err\n"); return false; } saddr_len = sizeof(struct sockaddr_in); memset(&saddr, 0, saddr_len); saddr.sin_family = AF_INET; saddr.sin_port = htons(PORT_BOOTP_SERVER); saddr.sin_addr.s_addr = inet_addr(gateway_.c_str()); caddr_len = sizeof(struct sockaddr_in); memset(&caddr, 0, caddr_len); caddr.sin_family = AF_INET; caddr.sin_port = htons(PORT_BOOTP_CLIENT); caddr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(s, (sockaddr *) &caddr, caddr_len) < 0) { LOGE("[dhcp] bind err\n"); goto END; } tv_out.tv_sec = 2; // 等待2秒 tv_out.tv_usec = 0; if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv_out, sizeof(tv_out)) < 0) { LOGE("[dhcp] setsockopt err\n"); goto END; } macaddr_to_hwaddr(macaddr_.c_str(), hwaddr); renew_len = init_dhcp_renew_msg(&renew_msg, hwaddr, get_sec(), inet_addr(ip_.c_str()), inet_addr(gateway_.c_str())); if (sendto(s, &renew_msg, renew_len, 0, (struct sockaddr *) &saddr, saddr_len) < 0) { LOGE("[dhcp] sendto err\n"); goto END; } recv_len = recvfrom(s, &recv_msg, sizeof(recv_msg), 0, (struct sockaddr *) &saddr, &saddr_len); if (recv_len < 0) { LOGE("[dhcp] recvfrom err\n"); goto END; } LOGD("[dhcp] recv len %d\n", recv_len); if (!is_valid_reply(&renew_msg, &recv_msg, recv_len)) { goto END; } if (decode_dhcp_msg(&recv_msg, recv_len, &di) == -1) { LOGE("[dhcp] decode msg err\n"); goto END; } dump_dhcp_info(&di); if (di.type != DHCPACK) { goto END; } lease_ = di.lease; ret = true; END: close(s); return ret; } }