/** * @file BDHCPClientCore.c * @author Ambroz Bizjak * * @section LICENSE * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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. * 3. Neither the name of the author 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 AUTHOR 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. */ #include #include #include #include #include #include #include #include #include #include #define RESET_TIMEOUT 4000 #define REQUEST_TIMEOUT 3000 #define RENEW_REQUEST_TIMEOUT 20000 #define MAX_REQUESTS 4 #define RENEW_TIMEOUT(lease) ((btime_t)500 * (lease)) #define XID_REUSE_MAX 8 #define LEASE_TIMEOUT(lease) ((btime_t)1000 * (lease) - RENEW_TIMEOUT(lease)) #define STATE_RESETTING 1 #define STATE_SENT_DISCOVER 2 #define STATE_SENT_REQUEST 3 #define STATE_FINISHED 4 #define STATE_RENEWING 5 #define IP_UDP_HEADERS_SIZE 28 static void report_up (BDHCPClientCore *o) { o->handler(o->user, BDHCPCLIENTCORE_EVENT_UP); return; } static void report_down (BDHCPClientCore *o) { o->handler(o->user, BDHCPCLIENTCORE_EVENT_DOWN); return; } static void send_message ( BDHCPClientCore *o, int type, uint32_t xid, int have_requested_ip_address, uint32_t requested_ip_address, int have_dhcp_server_identifier, uint32_t dhcp_server_identifier ) { ASSERT(type == DHCP_MESSAGE_TYPE_DISCOVER || type == DHCP_MESSAGE_TYPE_REQUEST) if (o->sending) { BLog(BLOG_ERROR, "already sending"); return; } // write header struct dhcp_header header; memset(&header, 0, sizeof(header)); header.op = hton8(DHCP_OP_BOOTREQUEST); header.htype = hton8(DHCP_HARDWARE_ADDRESS_TYPE_ETHERNET); header.hlen = hton8(6); header.xid = xid; header.secs = hton16(0); memcpy(header.chaddr, o->client_mac_addr, sizeof(o->client_mac_addr)); header.magic = hton32(DHCP_MAGIC); memcpy(o->send_buf, &header, sizeof(header)); // write options char *out = o->send_buf + sizeof(header); struct dhcp_option_header oh; // DHCP message type { oh.type = hton8(DHCP_OPTION_DHCP_MESSAGE_TYPE); oh.len = hton8(sizeof(struct dhcp_option_dhcp_message_type)); struct dhcp_option_dhcp_message_type opt; opt.type = hton8(type); memcpy(out, &oh, sizeof(oh)); memcpy(out + sizeof(oh), &opt, sizeof(opt)); out += sizeof(oh) + sizeof(opt); } if (have_requested_ip_address) { // requested IP address oh.type = hton8(DHCP_OPTION_REQUESTED_IP_ADDRESS); oh.len = hton8(sizeof(struct dhcp_option_addr)); struct dhcp_option_addr opt; opt.addr = requested_ip_address; memcpy(out, &oh, sizeof(oh)); memcpy(out + sizeof(oh), &opt, sizeof(opt)); out += sizeof(oh) + sizeof(opt); } if (have_dhcp_server_identifier) { // DHCP server identifier oh.type = hton8(DHCP_OPTION_DHCP_SERVER_IDENTIFIER); oh.len = hton8(sizeof(struct dhcp_option_dhcp_server_identifier)); struct dhcp_option_dhcp_server_identifier opt; opt.id = dhcp_server_identifier; memcpy(out, &oh, sizeof(oh)); memcpy(out + sizeof(oh), &opt, sizeof(opt)); out += sizeof(oh) + sizeof(opt); } // maximum message size { oh.type = hton8(DHCP_OPTION_MAXIMUM_MESSAGE_SIZE); oh.len = hton8(sizeof(struct dhcp_option_maximum_message_size)); struct dhcp_option_maximum_message_size opt; opt.size = hton16(IP_UDP_HEADERS_SIZE + PacketRecvInterface_GetMTU(o->recv_if)); memcpy(out, &oh, sizeof(oh)); memcpy(out + sizeof(oh), &opt, sizeof(opt)); out += sizeof(oh) + sizeof(opt); } // parameter request list { oh.type = hton8(DHCP_OPTION_PARAMETER_REQUEST_LIST); oh.len = hton8(4); uint8_t opt[4]; opt[0] = DHCP_OPTION_SUBNET_MASK; opt[1] = DHCP_OPTION_ROUTER; opt[2] = DHCP_OPTION_DOMAIN_NAME_SERVER; opt[3] = DHCP_OPTION_IP_ADDRESS_LEASE_TIME; memcpy(out, &oh, sizeof(oh)); memcpy(out + sizeof(oh), &opt, sizeof(opt)); out += sizeof(oh) + sizeof(opt); } if (o->hostname) { // host name oh.type = hton8(DHCP_OPTION_HOST_NAME); oh.len = hton8(strlen(o->hostname)); memcpy(out, &oh, sizeof(oh)); memcpy(out + sizeof(oh), o->hostname, strlen(o->hostname)); out += sizeof(oh) + strlen(o->hostname); } if (o->vendorclassid) { // vendor class identifier oh.type = hton8(DHCP_OPTION_VENDOR_CLASS_IDENTIFIER); oh.len = hton8(strlen(o->vendorclassid)); memcpy(out, &oh, sizeof(oh)); memcpy(out + sizeof(oh), o->vendorclassid, strlen(o->vendorclassid)); out += sizeof(oh) + strlen(o->vendorclassid); } if (o->clientid) { // client identifier oh.type = hton8(DHCP_OPTION_CLIENT_IDENTIFIER); oh.len = hton8(o->clientid_len); memcpy(out, &oh, sizeof(oh)); memcpy(out + sizeof(oh), o->clientid, o->clientid_len); out += sizeof(oh) + o->clientid_len; } // end option uint8_t end = 0xFF; memcpy(out, &end, sizeof(end)); out += sizeof(end); // send it PacketPassInterface_Sender_Send(o->send_if, (uint8_t *)o->send_buf, out - o->send_buf); o->sending = 1; } static void send_handler_done (BDHCPClientCore *o) { ASSERT(o->sending) DebugObject_Access(&o->d_obj); o->sending = 0; } static void recv_handler_done (BDHCPClientCore *o, int data_len) { ASSERT(data_len >= 0) DebugObject_Access(&o->d_obj); // receive more packets PacketRecvInterface_Receiver_Recv(o->recv_if, (uint8_t *)o->recv_buf); if (o->state == STATE_RESETTING) { return; } // check header if (data_len < sizeof(struct dhcp_header)) { return; } struct dhcp_header header; memcpy(&header, o->recv_buf, sizeof(header)); if (ntoh8(header.op) != DHCP_OP_BOOTREPLY) { return; } if (ntoh8(header.htype) != DHCP_HARDWARE_ADDRESS_TYPE_ETHERNET) { return; } if (ntoh8(header.hlen) != 6) { return; } if (header.xid != o->xid) { return; } if (memcmp(header.chaddr, o->client_mac_addr, sizeof(o->client_mac_addr))) { return; } if (ntoh32(header.magic) != DHCP_MAGIC) { return; } // parse and check options uint8_t *pos = (uint8_t *)o->recv_buf + sizeof(header); int len = data_len - sizeof(header); int have_end = 0; int dhcp_message_type = -1; int have_dhcp_server_identifier = 0; uint32_t dhcp_server_identifier = 0; // to remove warning int have_ip_address_lease_time = 0; uint32_t ip_address_lease_time = 0; // to remove warning int have_subnet_mask = 0; uint32_t subnet_mask = 0; // to remove warning int have_router = 0; uint32_t router = 0; // to remove warning int domain_name_servers_count = 0; uint32_t domain_name_servers[BDHCPCLIENTCORE_MAX_DOMAIN_NAME_SERVERS]; while (len > 0) { // padding option ? if (*pos == 0) { pos++; len--; continue; } if (have_end) { return; } // end option ? if (*pos == 0xff) { pos++; len--; have_end = 1; continue; } // check option header if (len < sizeof(struct dhcp_option_header)) { return; } struct dhcp_option_header opt; memcpy(&opt, pos, sizeof(opt)); pos += sizeof(opt); len -= sizeof(opt); int opt_type = ntoh8(opt.type); int opt_len = ntoh8(opt.len); // check option payload if (opt_len > len) { return; } uint8_t *optval = pos; pos += opt_len; len -= opt_len; switch (opt_type) { case DHCP_OPTION_DHCP_MESSAGE_TYPE: { if (opt_len != sizeof(struct dhcp_option_dhcp_message_type)) { return; } struct dhcp_option_dhcp_message_type val; memcpy(&val, optval, sizeof(val)); dhcp_message_type = ntoh8(val.type); } break; case DHCP_OPTION_DHCP_SERVER_IDENTIFIER: { if (opt_len != sizeof(struct dhcp_option_dhcp_server_identifier)) { return; } struct dhcp_option_dhcp_server_identifier val; memcpy(&val, optval, sizeof(val)); dhcp_server_identifier = val.id; have_dhcp_server_identifier = 1; } break; case DHCP_OPTION_IP_ADDRESS_LEASE_TIME: { if (opt_len != sizeof(struct dhcp_option_time)) { return; } struct dhcp_option_time val; memcpy(&val, optval, sizeof(val)); ip_address_lease_time = ntoh32(val.time); have_ip_address_lease_time = 1; } break; case DHCP_OPTION_SUBNET_MASK: { if (opt_len != sizeof(struct dhcp_option_addr)) { return; } struct dhcp_option_addr val; memcpy(&val, optval, sizeof(val)); subnet_mask = val.addr; have_subnet_mask = 1; } break; case DHCP_OPTION_ROUTER: { if (opt_len != sizeof(struct dhcp_option_addr)) { return; } struct dhcp_option_addr val; memcpy(&val, optval, sizeof(val)); router = val.addr; have_router = 1; } break; case DHCP_OPTION_DOMAIN_NAME_SERVER: { if (opt_len % sizeof(struct dhcp_option_addr)) { return; } int num_servers = opt_len / sizeof(struct dhcp_option_addr); int i; for (i = 0; i < num_servers && i < BDHCPCLIENTCORE_MAX_DOMAIN_NAME_SERVERS; i++) { struct dhcp_option_addr addr; memcpy(&addr, optval + i * sizeof(addr), sizeof(addr)); domain_name_servers[i] = addr.addr; } domain_name_servers_count = i; } break; } } if (!have_end) { return; } if (dhcp_message_type == -1) { return; } if (dhcp_message_type != DHCP_MESSAGE_TYPE_OFFER && dhcp_message_type != DHCP_MESSAGE_TYPE_ACK && dhcp_message_type != DHCP_MESSAGE_TYPE_NAK) { return; } if (!have_dhcp_server_identifier) { return; } if (dhcp_message_type == DHCP_MESSAGE_TYPE_NAK) { if (o->state != STATE_SENT_REQUEST && o->state != STATE_FINISHED && o->state != STATE_RENEWING) { return; } if (dhcp_server_identifier != o->offered.dhcp_server_identifier) { return; } if (o->state == STATE_SENT_REQUEST) { BLog(BLOG_INFO, "received NAK (in sent request)"); // stop request timer BReactor_RemoveTimer(o->reactor, &o->request_timer); // start reset timer BReactor_SetTimer(o->reactor, &o->reset_timer); // set state o->state = STATE_RESETTING; } else if (o->state == STATE_FINISHED) { BLog(BLOG_INFO, "received NAK (in finished)"); // stop renew timer BReactor_RemoveTimer(o->reactor, &o->renew_timer); // start reset timer BReactor_SetTimer(o->reactor, &o->reset_timer); // set state o->state = STATE_RESETTING; // report to user report_down(o); return; } else { // STATE_RENEWING BLog(BLOG_INFO, "received NAK (in renewing)"); // stop renew request timer BReactor_RemoveTimer(o->reactor, &o->renew_request_timer); // stop lease timer BReactor_RemoveTimer(o->reactor, &o->lease_timer); // start reset timer BReactor_SetTimer(o->reactor, &o->reset_timer); // set state o->state = STATE_RESETTING; // report to user report_down(o); return; } return; } if (ntoh32(header.yiaddr) == 0) { return; } if (!have_ip_address_lease_time) { return; } if (!have_subnet_mask) { return; } if (o->state == STATE_SENT_DISCOVER && dhcp_message_type == DHCP_MESSAGE_TYPE_OFFER) { BLog(BLOG_INFO, "received OFFER"); // remember offer o->offered.yiaddr = header.yiaddr; o->offered.dhcp_server_identifier = dhcp_server_identifier; // send request send_message(o, DHCP_MESSAGE_TYPE_REQUEST, o->xid, 1, o->offered.yiaddr, 1, o->offered.dhcp_server_identifier); // stop reset timer BReactor_RemoveTimer(o->reactor, &o->reset_timer); // start request timer BReactor_SetTimer(o->reactor, &o->request_timer); // set state o->state = STATE_SENT_REQUEST; // set request count o->request_count = 1; } else if (o->state == STATE_SENT_REQUEST && dhcp_message_type == DHCP_MESSAGE_TYPE_ACK) { if (header.yiaddr != o->offered.yiaddr) { return; } if (dhcp_server_identifier != o->offered.dhcp_server_identifier) { return; } BLog(BLOG_INFO, "received ACK (in sent request)"); // remember stuff o->acked.ip_address_lease_time = ip_address_lease_time; o->acked.subnet_mask = subnet_mask; o->acked.have_router = have_router; if (have_router) { o->acked.router = router; } o->acked.domain_name_servers_count = domain_name_servers_count; memcpy(o->acked.domain_name_servers, domain_name_servers, domain_name_servers_count * sizeof(uint32_t)); o->func_getsendermac(o->user, o->acked.server_mac); // stop request timer BReactor_RemoveTimer(o->reactor, &o->request_timer); // start renew timer BReactor_SetTimerAfter(o->reactor, &o->renew_timer, RENEW_TIMEOUT(o->acked.ip_address_lease_time)); // set state o->state = STATE_FINISHED; // report to user report_up(o); return; } else if (o->state == STATE_RENEWING && dhcp_message_type == DHCP_MESSAGE_TYPE_ACK) { if (header.yiaddr != o->offered.yiaddr) { return; } if (dhcp_server_identifier != o->offered.dhcp_server_identifier) { return; } // TODO: check parameters? BLog(BLOG_INFO, "received ACK (in renewing)"); // remember stuff o->acked.ip_address_lease_time = ip_address_lease_time; // stop renew request timer BReactor_RemoveTimer(o->reactor, &o->renew_request_timer); // stop lease timer BReactor_RemoveTimer(o->reactor, &o->lease_timer); // start renew timer BReactor_SetTimerAfter(o->reactor, &o->renew_timer, RENEW_TIMEOUT(o->acked.ip_address_lease_time)); // set state o->state = STATE_FINISHED; } } static void start_process (BDHCPClientCore *o, int force_new_xid) { if (force_new_xid || o->xid_reuse_counter == XID_REUSE_MAX) { // generate xid if (!BRandom2_GenBytes(o->random2, &o->xid, sizeof(o->xid))) { BLog(BLOG_ERROR, "BRandom2_GenBytes failed"); o->xid = UINT32_C(3416960072); } // reset counter o->xid_reuse_counter = 0; } // increment counter o->xid_reuse_counter++; // send discover send_message(o, DHCP_MESSAGE_TYPE_DISCOVER, o->xid, 0, 0, 0, 0); // set timer BReactor_SetTimer(o->reactor, &o->reset_timer); // set state o->state = STATE_SENT_DISCOVER; } static void reset_timer_handler (BDHCPClientCore *o) { ASSERT(o->state == STATE_RESETTING || o->state == STATE_SENT_DISCOVER) DebugObject_Access(&o->d_obj); BLog(BLOG_INFO, "reset timer"); start_process(o, (o->state == STATE_RESETTING)); } static void request_timer_handler (BDHCPClientCore *o) { ASSERT(o->state == STATE_SENT_REQUEST) ASSERT(o->request_count >= 1) ASSERT(o->request_count <= MAX_REQUESTS) DebugObject_Access(&o->d_obj); // if we have sent enough requests, start again if (o->request_count == MAX_REQUESTS) { BLog(BLOG_INFO, "request timer, aborting"); start_process(o, 0); return; } BLog(BLOG_INFO, "request timer, retrying"); // send request send_message(o, DHCP_MESSAGE_TYPE_REQUEST, o->xid, 1, o->offered.yiaddr, 1, o->offered.dhcp_server_identifier); // start request timer BReactor_SetTimer(o->reactor, &o->request_timer); // increment request count o->request_count++; } static void renew_timer_handler (BDHCPClientCore *o) { ASSERT(o->state == STATE_FINISHED) DebugObject_Access(&o->d_obj); BLog(BLOG_INFO, "renew timer"); // send request send_message(o, DHCP_MESSAGE_TYPE_REQUEST, o->xid, 1, o->offered.yiaddr, 0, 0); // start renew request timer BReactor_SetTimer(o->reactor, &o->renew_request_timer); // start lease timer BReactor_SetTimerAfter(o->reactor, &o->lease_timer, LEASE_TIMEOUT(o->acked.ip_address_lease_time)); // set state o->state = STATE_RENEWING; } static void renew_request_timer_handler (BDHCPClientCore *o) { ASSERT(o->state == STATE_RENEWING) DebugObject_Access(&o->d_obj); BLog(BLOG_INFO, "renew request timer"); // send request send_message(o, DHCP_MESSAGE_TYPE_REQUEST, o->xid, 1, o->offered.yiaddr, 0, 0); // start renew request timer BReactor_SetTimer(o->reactor, &o->renew_request_timer); } static void lease_timer_handler (BDHCPClientCore *o) { ASSERT(o->state == STATE_RENEWING) DebugObject_Access(&o->d_obj); BLog(BLOG_INFO, "lease timer"); // stop renew request timer BReactor_RemoveTimer(o->reactor, &o->renew_request_timer); // start again now start_process(o, 1); // report to user report_down(o); return; } static bsize_t maybe_len (const char *str) { return bsize_fromsize(str ? strlen(str) : 0); } int BDHCPClientCore_Init (BDHCPClientCore *o, PacketPassInterface *send_if, PacketRecvInterface *recv_if, uint8_t *client_mac_addr, struct BDHCPClientCore_opts opts, BReactor *reactor, BRandom2 *random2, void *user, BDHCPClientCore_func_getsendermac func_getsendermac, BDHCPClientCore_handler handler) { ASSERT(PacketPassInterface_GetMTU(send_if) == PacketRecvInterface_GetMTU(recv_if)) ASSERT(PacketPassInterface_GetMTU(send_if) >= 576 - IP_UDP_HEADERS_SIZE) ASSERT(func_getsendermac) ASSERT(handler) // init arguments o->send_if = send_if; o->recv_if = recv_if; memcpy(o->client_mac_addr, client_mac_addr, sizeof(o->client_mac_addr)); o->reactor = reactor; o->random2 = random2; o->user = user; o->func_getsendermac = func_getsendermac; o->handler = handler; o->hostname = NULL; o->vendorclassid = NULL; o->clientid = NULL; o->clientid_len = 0; // copy options if (opts.hostname && !(o->hostname = strdup(opts.hostname))) { BLog(BLOG_ERROR, "strdup failed"); goto fail0; } if (opts.vendorclassid && !(o->vendorclassid = strdup(opts.vendorclassid))) { BLog(BLOG_ERROR, "strdup failed"); goto fail0; } if (opts.clientid) { if (!(o->clientid = BAlloc(opts.clientid_len))) { BLog(BLOG_ERROR, "BAlloc failed"); goto fail0; } memcpy(o->clientid, opts.clientid, opts.clientid_len); o->clientid_len = opts.clientid_len; } // make sure options aren't too long bsize_t opts_size = bsize_add(maybe_len(o->hostname), bsize_add(maybe_len(o->vendorclassid), bsize_fromsize(o->clientid_len))); if (opts_size.is_overflow || opts_size.value > 100) { BLog(BLOG_ERROR, "options too long together"); goto fail0; } if (o->hostname && strlen(o->hostname) > 255) { BLog(BLOG_ERROR, "hostname too long"); goto fail0; } if (o->vendorclassid && strlen(o->vendorclassid) > 255) { BLog(BLOG_ERROR, "vendorclassid too long"); goto fail0; } if (o->clientid && o->clientid_len > 255) { BLog(BLOG_ERROR, "clientid too long"); goto fail0; } // allocate buffers if (!(o->send_buf = BAlloc(PacketPassInterface_GetMTU(send_if)))) { BLog(BLOG_ERROR, "BAlloc send buf failed"); goto fail0; } if (!(o->recv_buf = BAlloc(PacketRecvInterface_GetMTU(recv_if)))) { BLog(BLOG_ERROR, "BAlloc recv buf failed"); goto fail1; } // init send interface PacketPassInterface_Sender_Init(o->send_if, (PacketPassInterface_handler_done)send_handler_done, o); // init receive interface PacketRecvInterface_Receiver_Init(o->recv_if, (PacketRecvInterface_handler_done)recv_handler_done, o); // set not sending o->sending = 0; // init timers BTimer_Init(&o->reset_timer, RESET_TIMEOUT, (BTimer_handler)reset_timer_handler, o); BTimer_Init(&o->request_timer, REQUEST_TIMEOUT, (BTimer_handler)request_timer_handler, o); BTimer_Init(&o->renew_timer, 0, (BTimer_handler)renew_timer_handler, o); BTimer_Init(&o->renew_request_timer, RENEW_REQUEST_TIMEOUT, (BTimer_handler)renew_request_timer_handler, o); BTimer_Init(&o->lease_timer, 0, (BTimer_handler)lease_timer_handler, o); // start receving PacketRecvInterface_Receiver_Recv(o->recv_if, (uint8_t *)o->recv_buf); // start start_process(o, 1); DebugObject_Init(&o->d_obj); return 1; fail1: BFree(o->send_buf); fail0: BFree(o->clientid); free(o->vendorclassid); free(o->hostname); return 0; } void BDHCPClientCore_Free (BDHCPClientCore *o) { DebugObject_Free(&o->d_obj); // free timers BReactor_RemoveTimer(o->reactor, &o->lease_timer); BReactor_RemoveTimer(o->reactor, &o->renew_request_timer); BReactor_RemoveTimer(o->reactor, &o->renew_timer); BReactor_RemoveTimer(o->reactor, &o->request_timer); BReactor_RemoveTimer(o->reactor, &o->reset_timer); // free buffers BFree(o->recv_buf); BFree(o->send_buf); // free options BFree(o->clientid); free(o->vendorclassid); free(o->hostname); } void BDHCPClientCore_GetClientIP (BDHCPClientCore *o, uint32_t *out_ip) { ASSERT(o->state == STATE_FINISHED || o->state == STATE_RENEWING) DebugObject_Access(&o->d_obj); *out_ip = o->offered.yiaddr; } void BDHCPClientCore_GetClientMask (BDHCPClientCore *o, uint32_t *out_mask) { ASSERT(o->state == STATE_FINISHED || o->state == STATE_RENEWING) DebugObject_Access(&o->d_obj); *out_mask = o->acked.subnet_mask; } int BDHCPClientCore_GetRouter (BDHCPClientCore *o, uint32_t *out_router) { ASSERT(o->state == STATE_FINISHED || o->state == STATE_RENEWING) DebugObject_Access(&o->d_obj); if (!o->acked.have_router) { return 0; } *out_router = o->acked.router; return 1; } int BDHCPClientCore_GetDNS (BDHCPClientCore *o, uint32_t *out_dns_servers, size_t max_dns_servers) { ASSERT(o->state == STATE_FINISHED || o->state == STATE_RENEWING) DebugObject_Access(&o->d_obj); int num_return = bmin_int(o->acked.domain_name_servers_count, max_dns_servers); memcpy(out_dns_servers, o->acked.domain_name_servers, num_return * sizeof(uint32_t)); return num_return; } void BDHCPClientCore_GetServerMAC (BDHCPClientCore *o, uint8_t *out_mac) { DebugObject_Access(&o->d_obj); ASSERT(o->state == STATE_FINISHED || o->state == STATE_RENEWING) memcpy(out_mac, o->acked.server_mac, 6); }