// decline a static err_t dhcp_decline(struct dhcp_state *state) { err_t result = ERR_OK; u16_t msecs; DEBUGF(DHCP_DEBUG, ("dhcp_decline()\n")); dhcp_set_state(state, DHCP_BACKING_OFF); // create and initialize the DHCP message header result = dhcp_create_request(state); if (result == ERR_OK) { dhcp_option(state, DHCP_OPTION_MESSAGE_TYPE, DHCP_OPTION_MESSAGE_TYPE_LEN); dhcp_option_byte(state, DHCP_DECLINE); dhcp_option(state, DHCP_OPTION_MAX_MSG_SIZE, DHCP_OPTION_MAX_MSG_SIZE_LEN); dhcp_option_short(state, 576); dhcp_option_trailer(state); // resize pbuf to reflect true size of options pbuf_realloc(state->p_out, sizeof(struct dhcp_msg) - DHCP_OPTIONS_LEN + state->options_out_len); udp_bind(state->pcb, IP_ADDR_ANY, DHCP_CLIENT_PORT); udp_connect(state->pcb, &state->server_ip_addr, DHCP_SERVER_PORT); udp_send(state->pcb, state->p_out); dhcp_delete_request(state); } state->tries++; msecs = 10*1000; state->request_timeout = (msecs + DHCP_FINE_TIMER_MSECS - 1) / DHCP_FINE_TIMER_MSECS; DEBUGF(DHCP_DEBUG, ("dhcp_decline(): request timeout %u msecs\n", msecs)); return result; }
void dhcp_inform(struct netif *netif) { struct dhcp_state *state = NULL; err_t result = ERR_OK; state = mem_malloc(sizeof(struct dhcp_state)); if (state == NULL) { DEBUGF(DHCP_DEBUG, ("dhcp_inform(): could not allocate dhcp_state\n")); return; } memset(state, 0, sizeof(struct dhcp_state)); DEBUGF(DHCP_DEBUG, ("dhcp_inform(): allocated dhcp_state\n")); state->pcb = udp_new(); if (state->pcb == NULL) { DEBUGF(DHCP_DEBUG, ("dhcp_inform(): could not obtain pcb\n")); mem_free((void *)state); return; } DEBUGF(DHCP_DEBUG, ("dhcp_inform(): created new udp pcb\n")); state->netif = netif; // we are last in list state->next = NULL; // create and initialize the DHCP message header result = dhcp_create_request(state); if (result == ERR_OK) { dhcp_option(state, DHCP_OPTION_MESSAGE_TYPE, DHCP_OPTION_MESSAGE_TYPE_LEN); dhcp_option_byte(state, DHCP_INFORM); dhcp_option(state, DHCP_OPTION_MAX_MSG_SIZE, DHCP_OPTION_MAX_MSG_SIZE_LEN); dhcp_option_short(state, 576); dhcp_option_trailer(state); pbuf_realloc(state->p_out, sizeof(struct dhcp_msg) - DHCP_OPTIONS_LEN + state->options_out_len); udp_bind(state->pcb, IP_ADDR_ANY, DHCP_CLIENT_PORT); udp_connect(state->pcb, IP_ADDR_BROADCAST, DHCP_SERVER_PORT); udp_send(state->pcb, state->p_out); udp_connect(state->pcb, IP_ADDR_ANY, DHCP_SERVER_PORT); dhcp_delete_request(state); } if (state != NULL) { if (state->pcb != NULL) udp_remove(state->pcb); state->pcb = NULL; mem_free((void *)state); } }
/** * Start the DHCP process, discover a server * */ static err_t dhcp_discover(struct dhcp_state *state) { err_t result = ERR_OK; u16_t msecs; DEBUGF(DHCP_DEBUG, ("dhcp_discover(%p)\n", state)); ip_addr_set(&state->offered_ip_addr, IP_ADDR_ANY); // create and initialize the DHCP message header DEBUGF(DHCP_DEBUG, ("set ip addr, creating request()\n")); result = dhcp_create_request(state); DEBUGF(DHCP_DEBUG, ("created request\n")); if (result == ERR_OK) { dhcp_option(state, DHCP_OPTION_MESSAGE_TYPE, DHCP_OPTION_MESSAGE_TYPE_LEN); dhcp_option_byte(state, DHCP_DISCOVER); dhcp_option(state, DHCP_OPTION_MAX_MSG_SIZE, DHCP_OPTION_MAX_MSG_SIZE_LEN); dhcp_option_short(state, 576); dhcp_option(state, DHCP_OPTION_PARAMETER_REQUEST_LIST, 3); dhcp_option_byte(state, DHCP_OPTION_SUBNET_MASK); dhcp_option_byte(state, DHCP_OPTION_ROUTER); dhcp_option_byte(state, DHCP_OPTION_BROADCAST); dhcp_option_trailer(state); pbuf_realloc(state->p_out, sizeof(struct dhcp_msg) - DHCP_OPTIONS_LEN + state->options_out_len); DEBUGF(DHCP_DEBUG, ("about to do udp recv\n")); udp_recv(state->pcb, dhcp_recv, state); DEBUGF(DHCP_DEBUG, ("about to do udp bind\n")); udp_bind(state->pcb, IP_ADDR_ANY, DHCP_CLIENT_PORT); DEBUGF(DHCP_DEBUG, ("about to do udp connect\n")); udp_connect(state->pcb, IP_ADDR_BROADCAST, DHCP_SERVER_PORT); DEBUGF(DHCP_DEBUG, ("about to do udp send\n")); udp_send(state->pcb, state->p_out); udp_bind(state->pcb, IP_ADDR_ANY, DHCP_CLIENT_PORT); udp_connect(state->pcb, IP_ADDR_ANY, DHCP_SERVER_PORT); dhcp_delete_request(state); } state->tries++; msecs = state->tries < 4 ? (state->tries + 1) * 1000 : 10 * 1000; state->request_timeout = (msecs + DHCP_FINE_TIMER_MSECS - 1) / DHCP_FINE_TIMER_MSECS; DEBUGF(DHCP_DEBUG, ("dhcp_discover(): request timeout %u msecs\n", msecs)); dhcp_set_state(state, DHCP_SELECTING); return result; }
static err_t dhcp_release(struct dhcp_state *state) { err_t result; u16_t msecs; DEBUGF(DHCP_DEBUG, ("dhcp_release()\n")); // and idle DHCP client dhcp_set_state(state, DHCP_OFF); // create and initialize the DHCP message header result = dhcp_create_request(state); if (result == ERR_OK) { dhcp_option(state, DHCP_OPTION_MESSAGE_TYPE, DHCP_OPTION_MESSAGE_TYPE_LEN); dhcp_option_byte(state, DHCP_RELEASE); dhcp_option_trailer(state); pbuf_realloc(state->p_out, sizeof(struct dhcp_msg) - DHCP_OPTIONS_LEN + state->options_out_len); udp_bind(state->pcb, IP_ADDR_ANY, DHCP_CLIENT_PORT); udp_connect(state->pcb, &state->server_ip_addr, DHCP_SERVER_PORT); udp_send(state->pcb, state->p_out); dhcp_delete_request(state); } state->tries++; msecs = state->tries < 10 ? state->tries * 1000 : 10 * 1000; state->request_timeout = (msecs + DHCP_FINE_TIMER_MSECS - 1) / DHCP_FINE_TIMER_MSECS; DEBUGF(DHCP_DEBUG, ("dhcp_release(): request timeout %u msecs\n", msecs)); // remove IP address from interface netif_set_ipaddr(state->netif, IP_ADDR_ANY); netif_set_gw(state->netif, IP_ADDR_ANY); netif_set_netmask(state->netif, IP_ADDR_ANY); return result; }
/** * Select a DHCP server offer out of all offers. * * Simply select the first offer received, ignore others */ static err_t dhcp_select(struct dhcp_state *state) { err_t result; u32_t msecs; DEBUGF(DHCP_DEBUG, ("dhcp_select()\n")); // create and initialize the DHCP message header result = dhcp_create_request(state); if (result == ERR_OK) { dhcp_option(state, DHCP_OPTION_MESSAGE_TYPE, DHCP_OPTION_MESSAGE_TYPE_LEN); dhcp_option_byte(state, DHCP_REQUEST); dhcp_option(state, DHCP_OPTION_MAX_MSG_SIZE, DHCP_OPTION_MAX_MSG_SIZE_LEN); dhcp_option_short(state, 576); /* MUST request the offered IP address */ dhcp_option(state, DHCP_OPTION_REQUESTED_IP, 4); dhcp_option_long(state, ntohl(state->offered_ip_addr.addr)); dhcp_option(state, DHCP_OPTION_SERVER_ID, 4); dhcp_option_long(state, ntohl(state->server_ip_addr.addr)); dhcp_option(state, DHCP_OPTION_PARAMETER_REQUEST_LIST, 3); dhcp_option_byte(state, DHCP_OPTION_SUBNET_MASK); dhcp_option_byte(state, DHCP_OPTION_ROUTER); dhcp_option_byte(state, DHCP_OPTION_BROADCAST); dhcp_option_trailer(state); pbuf_realloc(state->p_out, sizeof(struct dhcp_msg) - DHCP_OPTIONS_LEN + state->options_out_len); udp_bind(state->pcb, IP_ADDR_ANY, DHCP_CLIENT_PORT); udp_connect(state->pcb, IP_ADDR_BROADCAST, DHCP_SERVER_PORT); udp_send(state->pcb, state->p_out); // reconnect to any (or to server here?!) udp_connect(state->pcb, IP_ADDR_ANY, DHCP_SERVER_PORT); dhcp_delete_request(state); } state->tries++; msecs = state->tries < 4 ? state->tries * 1000 : 4 * 1000; state->request_timeout = (msecs + DHCP_FINE_TIMER_MSECS - 1) / DHCP_FINE_TIMER_MSECS; DEBUGF(DHCP_DEBUG, ("dhcp_select(): request timeout %u msecs\n", msecs)); dhcp_set_state(state, DHCP_REQUESTING); return result; }
/** * Find DHCP option within DHCP options block, and its encapsulator (if any) * * @v options DHCP options block * @v tag DHCP option tag to search for * @ret encap_offset Offset of encapsulating DHCP option * @ret offset Offset of DHCP option, or negative error * * Searches for the DHCP option matching the specified tag within the * DHCP option block. Encapsulated options may be searched for by * using DHCP_ENCAP_OPT() to construct the tag value. * * If the option is encapsulated, and @c encap_offset is non-NULL, it * will be filled in with the offset of the encapsulating option. * * This routine is designed to be paranoid. It does not assume that * the option data is well-formatted, and so must guard against flaws * such as options missing a @c DHCP_END terminator, or options whose * length would take them beyond the end of the data block. */ static int find_dhcp_option_with_encap ( struct dhcp_options *options, unsigned int tag, int *encap_offset ) { unsigned int original_tag __attribute__ (( unused )) = tag; struct dhcp_option *option; int offset = 0; ssize_t remaining = options->used_len; unsigned int option_len; /* Sanity check */ if ( tag == DHCP_PAD ) return -ENOENT; /* Search for option */ while ( remaining ) { /* Calculate length of this option. Abort processing * if the length is malformed (i.e. takes us beyond * the end of the data block). */ option = dhcp_option ( options, offset ); option_len = dhcp_option_len ( option ); remaining -= option_len; if ( remaining < 0 ) break; /* Check for explicit end marker */ if ( option->tag == DHCP_END ) { if ( tag == DHCP_END ) /* Special case where the caller is interested * in whether we have this marker or not. */ return offset; else break; } /* Check for matching tag */ if ( option->tag == tag ) { DBGC ( options, "DHCPOPT %p found %s (length %d)\n", options, dhcp_tag_name ( original_tag ), option_len ); return offset; } /* Check for start of matching encapsulation block */ if ( DHCP_IS_ENCAP_OPT ( tag ) && ( option->tag == DHCP_ENCAPSULATOR ( tag ) ) ) { if ( encap_offset ) *encap_offset = offset; /* Continue search within encapsulated option block */ tag = DHCP_ENCAPSULATED ( tag ); remaining = option_len; offset += DHCP_OPTION_HEADER_LEN; continue; } offset += option_len; } return -ENOENT; }
err_t dhcp_renew(struct dhcp_state *state) { err_t result; u16_t msecs; DEBUGF(DHCP_DEBUG, ("dhcp_renew()\n")); dhcp_set_state(state, DHCP_RENEWING); // create and initialize the DHCP message header result = dhcp_create_request(state); if (result == ERR_OK) { dhcp_option(state, DHCP_OPTION_MESSAGE_TYPE, DHCP_OPTION_MESSAGE_TYPE_LEN); dhcp_option_byte(state, DHCP_REQUEST); dhcp_option(state, DHCP_OPTION_MAX_MSG_SIZE, DHCP_OPTION_MAX_MSG_SIZE_LEN); dhcp_option_short(state, 576); #if 0 dhcp_option(state, DHCP_OPTION_REQUESTED_IP, 4); dhcp_option_long(state, ntohl(state->offered_ip_addr.addr)); #endif #if 0 dhcp_option(state, DHCP_OPTION_SERVER_ID, 4); dhcp_option_long(state, ntohl(state->server_ip_addr.addr)); #endif dhcp_option_trailer(state); pbuf_realloc(state->p_out, sizeof(struct dhcp_msg) - DHCP_OPTIONS_LEN + state->options_out_len); udp_bind(state->pcb, IP_ADDR_ANY, DHCP_CLIENT_PORT); udp_connect(state->pcb, &state->server_ip_addr, DHCP_SERVER_PORT); udp_send(state->pcb, state->p_out); dhcp_delete_request(state); } state->tries++; // back-off on retries, but to a maximum of 20 seconds msecs = state->tries < 10 ? state->tries * 2000 : 20 * 1000; state->request_timeout = (msecs + DHCP_FINE_TIMER_MSECS - 1) / DHCP_FINE_TIMER_MSECS; DEBUGF(DHCP_DEBUG, ("dhcp_renew(): request timeout %u msecs\n", msecs)); return result; }
/** * Fetch value of DHCP option setting * * @v options DHCP option block * @v tag Setting tag number * @v data Buffer to fill with setting data * @v len Length of buffer * @ret len Length of setting data, or negative error */ int dhcpopt_fetch ( struct dhcp_options *options, unsigned int tag, void *data, size_t len ) { int offset; struct dhcp_option *option; size_t option_len; offset = find_dhcp_option_with_encap ( options, tag, NULL ); if ( offset < 0 ) return offset; option = dhcp_option ( options, offset ); option_len = option->len; if ( len > option_len ) len = option_len; memcpy ( data, option->data, len ); return option_len; }
/** * Recalculate length of DHCP options block * * @v options Uninitialised DHCP option block * * The "used length" field will be updated based on scanning through * the block to find the end of the options. */ void dhcpopt_update_used_len ( struct dhcp_options *options ) { struct dhcp_option *option; int offset = 0; ssize_t remaining = options->alloc_len; unsigned int option_len; /* Find last non-pad option */ options->used_len = 0; while ( remaining ) { option = dhcp_option ( options, offset ); option_len = dhcp_option_len ( option ); remaining -= option_len; if ( remaining < 0 ) break; offset += option_len; if ( option->tag != DHCP_PAD ) options->used_len = offset; } }
/** * Set value of DHCP option * * @v options DHCP option block * @v tag DHCP option tag * @v data New value for DHCP option * @v len Length of value, in bytes * @ret offset Offset of DHCP option, or negative error * * Sets the value of a DHCP option within the options block. The * option may or may not already exist. Encapsulators will be created * (and deleted) as necessary. * * This call may fail due to insufficient space in the options block. * If it does fail, and the option existed previously, the option will * be left with its original value. */ static int set_dhcp_option ( struct dhcp_options *options, unsigned int tag, const void *data, size_t len ) { static const uint8_t empty_encap[] = { DHCP_END }; int offset; int encap_offset = -1; int creation_offset; struct dhcp_option *option; unsigned int encap_tag = DHCP_ENCAPSULATOR ( tag ); size_t old_len = 0; size_t new_len = ( len ? ( len + DHCP_OPTION_HEADER_LEN ) : 0 ); int rc; /* Sanity check */ if ( tag == DHCP_PAD ) return -ENOTTY; creation_offset = find_dhcp_option_with_encap ( options, DHCP_END, NULL ); if ( creation_offset < 0 ) creation_offset = options->used_len; /* Find old instance of this option, if any */ offset = find_dhcp_option_with_encap ( options, tag, &encap_offset ); if ( offset >= 0 ) { old_len = dhcp_option_len ( dhcp_option ( options, offset ) ); DBGC ( options, "DHCPOPT %p resizing %s from %zd to %zd\n", options, dhcp_tag_name ( tag ), old_len, new_len ); } else { DBGC ( options, "DHCPOPT %p creating %s (length %zd)\n", options, dhcp_tag_name ( tag ), new_len ); } /* Ensure that encapsulator exists, if required */ if ( encap_tag ) { if ( encap_offset < 0 ) { encap_offset = set_dhcp_option ( options, encap_tag, empty_encap, sizeof ( empty_encap ) ); } if ( encap_offset < 0 ) return encap_offset; creation_offset = ( encap_offset + DHCP_OPTION_HEADER_LEN ); } /* Create new option if necessary */ if ( offset < 0 ) offset = creation_offset; /* Resize option to fit new data */ if ( ( rc = resize_dhcp_option ( options, offset, encap_offset, old_len, new_len ) ) != 0 ) return rc; /* Copy new data into option, if applicable */ if ( len ) { option = dhcp_option ( options, offset ); option->tag = tag; option->len = len; memcpy ( &option->data, data, len ); } /* Delete encapsulator if there's nothing else left in it */ if ( encap_offset >= 0 ) { option = dhcp_option ( options, encap_offset ); if ( option->len <= 1 ) set_dhcp_option ( options, encap_tag, NULL, 0 ); } return offset; }
/** * Resize a DHCP option * * @v options DHCP option block * @v offset Offset of option to resize * @v encap_offset Offset of encapsulating offset (or -ve for none) * @v old_len Old length (including header) * @v new_len New length (including header) * @ret rc Return status code */ static int resize_dhcp_option ( struct dhcp_options *options, int offset, int encap_offset, size_t old_len, size_t new_len ) { struct dhcp_option *encapsulator; struct dhcp_option *option; ssize_t delta = ( new_len - old_len ); size_t old_alloc_len; size_t new_used_len; size_t new_encapsulator_len; void *source; void *dest; int rc; /* Check for sufficient space */ if ( new_len > DHCP_MAX_LEN ) { DBGC ( options, "DHCPOPT %p overlength option\n", options ); return -ENOSPC; } new_used_len = ( options->used_len + delta ); /* Expand options block, if necessary */ if ( new_used_len > options->alloc_len ) { /* Reallocate options block */ old_alloc_len = options->alloc_len; if ( ( rc = options->realloc ( options, new_used_len ) ) != 0 ) { DBGC ( options, "DHCPOPT %p could not reallocate to " "%zd bytes\n", options, new_used_len ); return rc; } /* Clear newly allocated space */ memset ( ( options->data + old_alloc_len ), 0, ( options->alloc_len - old_alloc_len ) ); } /* Update encapsulator, if applicable */ if ( encap_offset >= 0 ) { encapsulator = dhcp_option ( options, encap_offset ); new_encapsulator_len = ( encapsulator->len + delta ); if ( new_encapsulator_len > DHCP_MAX_LEN ) { DBGC ( options, "DHCPOPT %p overlength encapsulator\n", options ); return -ENOSPC; } encapsulator->len = new_encapsulator_len; } /* Update used length */ options->used_len = new_used_len; /* Move remainder of option data */ option = dhcp_option ( options, offset ); source = ( ( ( void * ) option ) + old_len ); dest = ( ( ( void * ) option ) + new_len ); memmove ( dest, source, ( new_used_len - offset - new_len ) ); /* Shrink options block, if applicable */ if ( new_used_len < options->alloc_len ) { if ( ( rc = options->realloc ( options, new_used_len ) ) != 0 ) { DBGC ( options, "DHCPOPT %p could not reallocate to " "%zd bytes\n", options, new_used_len ); return rc; } } return 0; }