#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/errno.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/sockio.h>
#include <ctype.h>
#include <net/if.h>
#include <net/ethernet.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/bootp.h>
#include <arpa/inet.h>
#include <syslog.h>
#include <TargetConditionals.h>
#include "rfc_options.h"
#include "dhcp_options.h"
#include "dhcp.h"
#include "interfaces.h"
#include "util.h"
#include <net/if_types.h>
#include "host_identifier.h"
#include "dhcplib.h"
#include "ipconfigd_threads.h"
#include "DHCPLease.h"
#include "symbol_scope.h"
#include "cfutil.h"
#include "dprintf.h"
#define SUGGESTED_LEASE_LENGTH (60 * 60 * 24 * 30 * 3)
typedef struct {
boolean_t valid;
absolute_time_t expiration;
dhcp_lease_time_t length;
absolute_time_t start;
absolute_time_t t1;
absolute_time_t t2;
boolean_t needs_write;
} lease_info_t;
typedef struct {
arp_client_t * arp;
bootp_client_t * client;
void * client_id;
int client_id_len;
struct in_addr conflicting_address;
boolean_t disable_arp_collision_detection;
boolean_t gathering;
boolean_t got_nak;
lease_info_t lease;
DHCPLeaseList lease_list;
absolute_time_t ip_assigned_time;
absolute_time_t ip_conflict_time;
int ip_conflict_count;
boolean_t must_broadcast;
struct dhcp * request;
int request_size;
absolute_time_t renew_rebind_time;
boolean_t resolve_router_timed_out;
struct saved_pkt saved;
dhcp_cstate_t state;
absolute_time_t start_secs;
timer_callout_t * timer;
int try;
uint32_t txbuf[DHCP_PACKET_MIN/sizeof(uint32_t)];
u_int32_t xid;
boolean_t user_warned;
int wait_secs;
} Service_dhcp_t;
typedef struct {
arp_client_t * arp;
bootp_client_t * client;
boolean_t gathering;
struct in_addr our_mask;
struct dhcp * request;
int request_size;
boolean_t resolve_router_timed_out;
struct saved_pkt saved;
absolute_time_t start_secs;
timer_callout_t * timer;
int try;
uint32_t txbuf[DHCP_PACKET_MIN/sizeof(uint32_t)];
u_int32_t xid;
boolean_t user_warned;
int wait_secs;
} Service_inform_t;
static void
dhcp_init(ServiceRef service_p, IFEventID_t event_id, void * event_data);
static void
dhcp_init_reboot(ServiceRef service_p, IFEventID_t event_id,
void * event_data);
static void
dhcp_arp_router(ServiceRef service_p, IFEventID_t event_id,
void * event_data);
static void
dhcp_select(ServiceRef service_p, IFEventID_t event_id, void * event_data);
static void
dhcp_bound(ServiceRef service_p, IFEventID_t event_id, void * event_data);
static void
dhcp_renew_rebind(ServiceRef service_p, IFEventID_t event_id,
void * event_data);
static void
dhcp_unbound(ServiceRef service_p, IFEventID_t event_id, void * event_data);
static void
dhcp_decline(ServiceRef service_p, IFEventID_t event_id, void * event_data);
static void
dhcp_release(ServiceRef service_p);
static void
dhcp_no_server(ServiceRef service_p, IFEventID_t event_id, void * event_data);
static boolean_t
dhcp_check_lease(ServiceRef service_p, absolute_time_t current_time);
static void
dhcp_check_router(ServiceRef service_p, IFEventID_t event_id, void * event_data);
static boolean_t
get_server_identifier(dhcpol_t * options, struct in_addr * server_ip)
{
struct in_addr * ipaddr_p;
ipaddr_p = (struct in_addr *)
dhcpol_find_with_length(options, dhcptag_server_identifier_e,
sizeof(*ipaddr_p));
if (ipaddr_p != NULL) {
*server_ip = *ipaddr_p;
}
return (ipaddr_p != NULL);
}
#define DHCP_ADDRESS_RATING_POOR 0
#define DHCP_ADDRESS_RATING_FAIR 100
#define DHCP_ADDRESS_RATING_GOOD 1000
static
int
get_rating_for_ip_address(struct in_addr * iaddr)
{
if (ip_is_private(*iaddr)) {
return (DHCP_ADDRESS_RATING_FAIR);
}
if (ip_is_linklocal(*iaddr)) {
return (DHCP_ADDRESS_RATING_POOR);
}
return (DHCP_ADDRESS_RATING_GOOD);
}
static const uint8_t dhcp_static_default_params[] = {
dhcptag_subnet_mask_e,
dhcptag_router_e,
dhcptag_domain_name_server_e,
dhcptag_domain_name_e,
dhcptag_domain_search_e,
dhcptag_proxy_auto_discovery_url_e,
#if ! TARGET_OS_EMBEDDED
dhcptag_ldap_url_e,
dhcptag_nb_over_tcpip_name_server_e,
dhcptag_nb_over_tcpip_node_type_e,
#endif
};
#define N_DHCP_STATIC_DEFAULT_PARAMS (sizeof(dhcp_static_default_params) / sizeof(dhcp_static_default_params[0]))
static uint8_t * dhcp_default_params = (uint8_t *)dhcp_static_default_params;
static int n_dhcp_default_params = N_DHCP_STATIC_DEFAULT_PARAMS;
static uint8_t * dhcp_params = (uint8_t *)dhcp_static_default_params;
static int n_dhcp_params = N_DHCP_STATIC_DEFAULT_PARAMS;
void
dhcp_set_default_parameters(uint8_t * params, int n_params)
{
if (params && n_params) {
dhcp_default_params = params;
n_dhcp_default_params = n_params;
}
else {
dhcp_default_params = (uint8_t *)dhcp_static_default_params;
n_dhcp_default_params = N_DHCP_STATIC_DEFAULT_PARAMS;
}
dhcp_params = dhcp_default_params;
n_dhcp_params = n_dhcp_default_params;
return;
}
static uint8_t *
S_merge_parameters(uint8_t * params, int n_params, int * n_ret)
{
int i;
uint8_t * ret = dhcp_default_params;
uint8_t * new = NULL;
int new_end = 0;
*n_ret = n_dhcp_default_params;
if (params == NULL || n_params == 0) {
goto done;
}
new = (uint8_t *)malloc(n_dhcp_default_params + n_params);
if (new == NULL) {
goto done;
}
bcopy(dhcp_default_params, new, n_dhcp_default_params);
for (i = 0, new_end = n_dhcp_default_params; i < n_params; i++) {
boolean_t already_there = FALSE;
int j;
for (j = 0; j < new_end; j++) {
if (new[j] == params[i]) {
already_there = TRUE;
break;
}
}
if (already_there == FALSE) {
new[new_end++] = params[i];
}
}
if (new_end > n_dhcp_default_params) {
ret = new;
*n_ret = new_end;
}
else {
free(new);
new = NULL;
}
done:
return (ret);
}
static void
S_print_char_array(CFMutableStringRef str, uint8_t * params, int n_params)
{
int i;
for (i = 0; i < n_params; i++) {
if (i == 0)
STRING_APPEND(str, "%d", params[i]);
else
STRING_APPEND(str, ", %d", params[i]);
}
return;
}
void
dhcp_set_additional_parameters(uint8_t * params, int n_params)
{
if (dhcp_params && dhcp_params != dhcp_default_params) {
free(dhcp_params);
}
dhcp_params = S_merge_parameters(params, n_params, &n_dhcp_params);
if (params) {
free(params);
}
if (G_IPConfiguration_verbose) {
CFMutableStringRef str;
str = CFStringCreateMutable(NULL, 0);
S_print_char_array(str, dhcp_params, n_dhcp_params);
my_log(LOG_DEBUG, "DHCP requested parameters = { %@ }", str);
CFRelease(str);
}
return;
}
bool
dhcp_parameter_is_ok(uint8_t param)
{
int i;
switch (param) {
case dhcptag_lease_time_e:
case dhcptag_dhcp_message_type_e:
case dhcptag_server_identifier_e:
case dhcptag_message_e:
case dhcptag_renewal_t1_time_value_e:
case dhcptag_rebinding_t2_time_value_e:
case dhcptag_vendor_class_identifier_e:
case dhcptag_client_identifier_e:
return (TRUE);
default:
break;
}
for (i = 0; i < n_dhcp_params; i++) {
if (dhcp_params[i] == param) {
return (TRUE);
}
}
return (FALSE);
}
static void
add_computer_name(dhcpoa_t * options_p)
{
char * name = computer_name();
if (name) {
if (dhcpoa_add(options_p, dhcptag_host_name_e, strlen(name), name)
!= dhcpoa_success_e) {
my_log(LOG_NOTICE, "make_dhcp_request: couldn't add host_name, %s",
dhcpoa_err(options_p));
}
}
return;
}
static struct dhcp *
make_dhcp_request(struct dhcp * request, int pkt_size,
dhcp_msgtype_t msg,
const uint8_t * hwaddr, uint8_t hwtype, uint8_t hwlen,
const void * cid, int cid_len, boolean_t must_broadcast,
dhcpoa_t * options_p)
{
char * buf = NULL;
uint8_t cid_type = 0;
if (cid == NULL || cid_len == 0) {
cid = hwaddr;
cid_len = hwlen;
cid_type = hwtype;
}
bzero(request, pkt_size);
request->dp_htype = hwtype;
request->dp_op = BOOTREQUEST;
switch (hwtype) {
default:
case ARPHRD_ETHER:
request->dp_hlen = hwlen;
bcopy(hwaddr, request->dp_chaddr, hwlen);
break;
case ARPHRD_IEEE1394:
request->dp_hlen = 0;
if (cid == hwaddr) {
cid_type = ARPHRD_IEEE1394_EUI64;
}
break;
}
if (must_broadcast || G_must_broadcast) {
request->dp_flags = htons(DHCP_FLAGS_BROADCAST);
}
bcopy(G_rfc_magic, request->dp_options, sizeof(G_rfc_magic));
dhcpoa_init(options_p, request->dp_options + sizeof(G_rfc_magic),
pkt_size - sizeof(struct dhcp) - sizeof(G_rfc_magic));
if (dhcpoa_add_dhcpmsg(options_p, msg) != dhcpoa_success_e) {
my_log(LOG_ERR,
"make_dhcp_request: couldn't add dhcp message tag %d, %s", msg,
dhcpoa_err(options_p));
goto err;
}
if (msg != dhcp_msgtype_decline_e && msg != dhcp_msgtype_release_e) {
u_int16_t max_message_size = htons(1500);
if (dhcpoa_add(options_p, dhcptag_parameter_request_list_e,
n_dhcp_params, dhcp_params)
!= dhcpoa_success_e) {
my_log(LOG_ERR, "make_dhcp_request: "
"couldn't add parameter request list, %s",
dhcpoa_err(options_p));
goto err;
}
if (dhcpoa_add(options_p, dhcptag_max_dhcp_message_size_e,
sizeof(max_message_size), &max_message_size)
!= dhcpoa_success_e) {
my_log(LOG_ERR, "make_dhcp_request: "
"couldn't add max message size, %s",
dhcpoa_err(options_p));
goto err;
}
}
buf = malloc(cid_len + 1);
if (buf == NULL) {
my_log(LOG_ERR, "make_dhcp_request: malloc failed, %s (%d)",
strerror(errno), errno);
goto err;
}
*buf = cid_type;
bcopy(cid, buf + 1, cid_len);
if (dhcpoa_add(options_p, dhcptag_client_identifier_e, cid_len + 1, buf)
!= dhcpoa_success_e) {
free(buf);
my_log(LOG_ERR, "make_dhcp_request: "
"couldn't add client identifier, %s",
dhcpoa_err(options_p));
goto err;
}
free(buf);
return (request);
err:
return (NULL);
}
static boolean_t
verify_packet(bootp_receive_data_t * pkt, u_int32_t xid, interface_t * if_p,
dhcp_msgtype_t * msgtype_p, struct in_addr * server_ip,
boolean_t * is_dhcp)
{
if (dhcp_packet_match((struct bootp *)pkt->data, xid,
(uint8_t) if_link_arptype(if_p),
if_link_address(if_p),
if_link_length(if_p))) {
if (is_dhcp_packet(&pkt->options, msgtype_p) == FALSE
|| *msgtype_p == dhcp_msgtype_discover_e
|| *msgtype_p == dhcp_msgtype_request_e
|| *msgtype_p == dhcp_msgtype_inform_e) {
if (G_dhcp_accepts_bootp == FALSE) {
return (FALSE);
}
*msgtype_p = dhcp_msgtype_none_e;
*is_dhcp = FALSE;
*server_ip = pkt->data->dp_siaddr;
return (TRUE);
}
*is_dhcp = TRUE;
server_ip->s_addr = 0;
(void)get_server_identifier(&pkt->options, server_ip);
return (TRUE);
}
return (FALSE);
}
static void
inform_set_dhcp_info(Service_inform_t * inform, dhcp_info_t * dhcp_info_p)
{
if (inform->saved.pkt_size != 0) {
dhcp_info_p->pkt = (uint8_t *)inform->saved.pkt;
dhcp_info_p->pkt_size = inform->saved.pkt_size;
dhcp_info_p->options = &inform->saved.options;
}
else {
dhcp_info_p->pkt = NULL;
dhcp_info_p->pkt_size = 0;
dhcp_info_p->options = NULL;
}
dhcp_info_p->lease_start = 0;
dhcp_info_p->lease_expiration = 0;
return;
}
static void
inform_publish_success(ServiceRef service_p)
{
dhcp_info_t dhcp_info;
Service_inform_t * inform;
inform = (Service_inform_t *)ServiceGetPrivate(service_p);
inform_set_dhcp_info(inform, &dhcp_info);
ServicePublishSuccessIPv4(service_p, &dhcp_info);
return;
}
static void
inform_cancel_pending_events(ServiceRef service_p)
{
Service_inform_t * inform;
inform = (Service_inform_t *)ServiceGetPrivate(service_p);
if (inform == NULL)
return;
if (inform->timer) {
timer_cancel(inform->timer);
}
if (inform->client) {
bootp_client_disable_receive(inform->client);
}
if (inform->arp) {
arp_client_cancel(inform->arp);
}
return;
}
static void
inform_inactive(ServiceRef service_p)
{
Service_inform_t * inform;
inform = (Service_inform_t *)ServiceGetPrivate(service_p);
inform_cancel_pending_events(service_p);
service_remove_address(service_p);
dhcpol_free(&inform->saved.options);
service_publish_failure(service_p, ipconfig_status_media_inactive_e);
return;
}
static void
inform_failed(ServiceRef service_p, ipconfig_status_t status)
{
inform_cancel_pending_events(service_p);
service_publish_failure(service_p, status);
return;
}
static void
inform_resolve_router_callback(ServiceRef service_p,
router_arp_status_t status);
static void
inform_resolve_router_retry(void * arg0, void * arg1, void * arg2)
{
Service_inform_t * inform;
ServiceRef service_p = (ServiceRef)arg0;
inform = (Service_inform_t *)ServiceGetPrivate(service_p);
service_resolve_router(service_p, inform->arp,
inform_resolve_router_callback,
inform->saved.our_ip);
return;
}
static void
inform_resolve_router_callback(ServiceRef service_p,
router_arp_status_t status)
{
Service_inform_t * inform;
struct timeval tv;
inform = (Service_inform_t *)ServiceGetPrivate(service_p);
switch (status) {
case router_arp_status_no_response_e:
tv.tv_sec = 60;
tv.tv_usec = 0;
timer_set_relative(inform->timer, tv,
(timer_func_t *)inform_resolve_router_retry,
service_p, NULL, NULL);
if (inform->resolve_router_timed_out) {
break;
}
inform->resolve_router_timed_out = TRUE;
inform_publish_success(service_p);
break;
case router_arp_status_success_e:
inform->resolve_router_timed_out = FALSE;
inform_publish_success(service_p);
break;
default:
case router_arp_status_failed_e:
break;
}
}
static void
inform_success(ServiceRef service_p)
{
Service_inform_t * inform;
void * option;
inform = (Service_inform_t *)ServiceGetPrivate(service_p);
option = dhcpol_find_with_length(&inform->saved.options,
dhcptag_subnet_mask_e,
sizeof(struct in_addr));
if (service_requested_ip_mask(service_p).s_addr == 0
&& option != NULL) {
inform->our_mask = *((struct in_addr *)option);
(void)service_set_address(service_p,
service_requested_ip_addr(service_p),
inform->our_mask,
G_ip_zeroes);
}
inform_cancel_pending_events(service_p);
inform->resolve_router_timed_out = FALSE;
if (service_update_router_address(service_p, &inform->saved.options,
inform->saved.our_ip)
&& service_resolve_router(service_p, inform->arp,
inform_resolve_router_callback,
service_requested_ip_addr(service_p))) {
}
else {
inform_publish_success(service_p);
}
return;
}
static void
inform_request(ServiceRef service_p, IFEventID_t event_id, void * event_data)
{
absolute_time_t current_time = timer_current_secs();
interface_t * if_p = service_interface(service_p);
Service_inform_t * inform;
ipconfig_status_t status = ipconfig_status_success_e;
inform = (Service_inform_t *)ServiceGetPrivate(service_p);
switch (event_id) {
case IFEventID_start_e: {
dhcpoa_t options;
inform->start_secs = current_time;
inform_cancel_pending_events(service_p);
inform->request = make_dhcp_request((struct dhcp *)(void *)inform->txbuf,
sizeof(inform->txbuf),
dhcp_msgtype_inform_e,
if_link_address(if_p),
if_link_arptype(if_p),
if_link_length(if_p),
NULL, 0,
FALSE,
&options);
if (inform->request == NULL) {
my_log(LOG_ERR, "INFORM %s: make_dhcp_request failed",
if_name(if_p));
status = ipconfig_status_allocation_failed_e;
goto error;
}
inform->request->dp_ciaddr = service_requested_ip_addr(service_p);
add_computer_name(&options);
if (dhcpoa_add(&options, dhcptag_end_e, 0, 0)
!= dhcpoa_success_e) {
my_log(LOG_ERR, "INFORM %s: failed to terminate options",
if_name(if_p));
status = ipconfig_status_allocation_failed_e;
goto error;
}
inform->request_size = sizeof(*inform->request) + sizeof(G_rfc_magic)
+ dhcpoa_used(&options);
if (inform->request_size < sizeof(struct bootp)) {
inform->request_size = sizeof(struct bootp);
}
inform->try = 0;
inform->gathering = FALSE;
inform->wait_secs = G_initial_wait_secs;
bootp_client_enable_receive(inform->client,
(bootp_receive_func_t *)inform_request,
service_p, (void *)IFEventID_data_e);
inform->saved.rating = 0;
inform->xid++;
}
case IFEventID_timeout_e: {
struct timeval tv;
if (inform->gathering == TRUE) {
inform_success(service_p);
return;
}
inform->try++;
if (inform->try > 1) {
link_status_t link_status = service_link_status(service_p);
if (link_status.valid
&& link_status.active == FALSE) {
inform_inactive(service_p);
break;
}
}
if (inform->try > (G_max_retries + 1)) {
inform_success(service_p);
break;
}
inform->request->dp_xid = htonl(inform->xid);
inform->request->dp_secs
= htons((uint16_t)(current_time - inform->start_secs));
if (bootp_client_transmit(inform->client,
G_ip_broadcast, G_ip_zeroes,
G_server_port, G_client_port,
inform->request,
inform->request_size) < 0) {
my_log(LOG_ERR,
"INFORM %s: transmit failed", if_name(if_p));
}
tv.tv_sec = inform->wait_secs;
tv.tv_usec = random_range(0, USECS_PER_SEC - 1);
my_log(LOG_DEBUG, "INFORM %s: waiting at %d for %d.%06d",
if_name(if_p),
current_time - inform->start_secs,
tv.tv_sec, tv.tv_usec);
timer_set_relative(inform->timer, tv,
(timer_func_t *)inform_request,
service_p, (void *)IFEventID_timeout_e, NULL);
inform->wait_secs *= 2;
if (inform->wait_secs > G_max_wait_secs) {
inform->wait_secs = G_max_wait_secs;
}
break;
}
case IFEventID_data_e: {
boolean_t is_dhcp = TRUE;
bootp_receive_data_t *pkt = (bootp_receive_data_t *)event_data;
dhcp_msgtype_t reply_msgtype = dhcp_msgtype_none_e;
struct in_addr server_ip;
if (verify_packet(pkt, inform->xid, if_p, &reply_msgtype,
&server_ip, &is_dhcp) == FALSE) {
break;
}
if (is_dhcp == FALSE
|| (reply_msgtype == dhcp_msgtype_ack_e)) {
int rating = 0;
rating = dhcpol_count_params(&pkt->options,
dhcp_params, n_dhcp_params);
if (inform->saved.pkt_size == 0
|| (is_dhcp == TRUE && (inform->saved.is_dhcp == FALSE
|| rating > inform->saved.rating))
|| (is_dhcp == FALSE && inform->saved.is_dhcp == FALSE
&& rating > inform->saved.rating)) {
dhcpol_free(&inform->saved.options);
bcopy(pkt->data, inform->saved.pkt, pkt->size);
inform->saved.pkt_size = pkt->size;
inform->saved.rating = rating;
(void)dhcpol_parse_packet(&inform->saved.options,
(void *)inform->saved.pkt,
inform->saved.pkt_size, NULL);
inform->saved.server_ip = server_ip;
inform->saved.is_dhcp = is_dhcp;
if (is_dhcp && rating == n_dhcp_params) {
inform_success(service_p);
return;
}
if (inform->gathering == FALSE) {
struct timeval t = {0,0};
t.tv_sec = G_gather_secs;
my_log(LOG_DEBUG, "INFORM %s: gathering began at %d",
if_name(if_p),
current_time - inform->start_secs);
inform->gathering = TRUE;
timer_set_relative(inform->timer, t,
(timer_func_t *)inform_request,
service_p, (void *)IFEventID_timeout_e,
NULL);
}
}
}
break;
}
default:
break;
}
return;
error:
inform_failed(service_p, status);
return;
}
static void
inform_start(ServiceRef service_p, IFEventID_t event_id, void * event_data)
{
interface_t * if_p = service_interface(service_p);
Service_inform_t * inform;
inform = (Service_inform_t *)ServiceGetPrivate(service_p);
switch (event_id) {
case IFEventID_start_e: {
inform_cancel_pending_events(service_p);
arp_client_probe(inform->arp,
(arp_result_func_t *)inform_start, service_p,
(void *)IFEventID_arp_e, G_ip_zeroes,
service_requested_ip_addr(service_p));
break;
}
case IFEventID_arp_e: {
link_status_t link_status;
arp_result_t * result = (arp_result_t *)event_data;
if (result->error) {
my_log(LOG_DEBUG, "INFORM %s: arp probe failed, %s",
if_name(if_p),
arp_client_errmsg(inform->arp));
inform_failed(service_p, ipconfig_status_internal_error_e);
break;
}
else {
if (result->in_use) {
struct in_addr requested_ip;
char msg[128];
struct timeval tv;
requested_ip = service_requested_ip_addr(service_p);
snprintf(msg, sizeof(msg),
IP_FORMAT " in use by " EA_FORMAT,
IP_LIST(&requested_ip),
EA_LIST(result->addr.target_hardware));
if (inform->user_warned == FALSE) {
inform->user_warned = TRUE;
ServiceReportIPv4AddressConflict(service_p,
requested_ip);
}
my_log(LOG_ERR, "INFORM %s: %s", if_name(if_p),
msg);
(void)service_remove_address(service_p);
inform_failed(service_p, ipconfig_status_address_in_use_e);
if (G_manual_conflict_retry_interval_secs > 0) {
tv.tv_sec = G_manual_conflict_retry_interval_secs;
tv.tv_usec = 0;
timer_set_relative(inform->timer, tv,
(timer_func_t *)inform_start,
service_p, IFEventID_start_e, NULL);
}
break;
}
}
link_status = service_link_status(service_p);
if (link_status.valid == TRUE
&& link_status.active == FALSE) {
inform_inactive(service_p);
break;
}
(void)service_set_address(service_p,
service_requested_ip_addr(service_p),
inform->our_mask,
G_ip_zeroes);
ServiceRemoveAddressConflict(service_p);
inform_request(service_p, IFEventID_start_e, 0);
break;
}
default: {
break;
}
}
return;
}
PRIVATE_EXTERN ipconfig_status_t
inform_thread(ServiceRef service_p, IFEventID_t event_id, void * event_data)
{
interface_t * if_p = service_interface(service_p);
Service_inform_t * inform;
ipconfig_status_t status = ipconfig_status_success_e;
inform = (Service_inform_t *)ServiceGetPrivate(service_p);
switch (event_id) {
case IFEventID_start_e: {
ipconfig_method_data_t * method_data;
method_data = (ipconfig_method_data_t *)event_data;
if (if_flags(if_p) & IFF_LOOPBACK) {
status = ipconfig_status_invalid_operation_e;
break;
}
if (inform != NULL) {
my_log(LOG_ERR, "INFORM %s: re-entering start state",
if_name(if_p));
status = ipconfig_status_internal_error_e;
break;
}
inform = malloc(sizeof(*inform));
if (inform == NULL) {
my_log(LOG_ERR, "INFORM %s: malloc failed",
if_name(if_p));
status = ipconfig_status_allocation_failed_e;
break;
}
ServiceSetPrivate(service_p, inform);
bzero(inform, sizeof(*inform));
dhcpol_init(&inform->saved.options);
service_set_requested_ip_addr(service_p, method_data->manual.addr);
service_set_requested_ip_mask(service_p, method_data->manual.mask);
inform->our_mask = service_requested_ip_mask(service_p);
inform->timer = timer_callout_init();
if (inform->timer == NULL) {
my_log(LOG_ERR, "INFORM %s: timer_callout_init failed",
if_name(if_p));
status = ipconfig_status_allocation_failed_e;
goto stop;
}
inform->client = bootp_client_init(G_bootp_session, if_p);
if (inform->client == NULL) {
my_log(LOG_ERR, "INFORM %s: bootp_client_init failed",
if_name(if_p));
status = ipconfig_status_allocation_failed_e;
goto stop;
}
inform->arp = arp_client_init(G_arp_session, if_p);
if (inform->arp == NULL) {
my_log(LOG_ERR, "INFORM %s: arp_client_init failed",
if_name(if_p));
status = ipconfig_status_allocation_failed_e;
goto stop;
}
my_log(LOG_DEBUG, "INFORM %s: start", if_name(if_p));
inform->xid = arc4random();
inform_start(service_p, IFEventID_start_e, NULL);
break;
}
case IFEventID_stop_e: {
stop:
my_log(LOG_DEBUG, "INFORM %s: stop", if_name(if_p));
if (inform == NULL) {
break;
}
service_remove_address(service_p);
if (inform->timer) {
timer_callout_free(&inform->timer);
}
if (inform->client) {
bootp_client_free(&inform->client);
}
if (inform->arp) {
arp_client_free(&inform->arp);
}
dhcpol_free(&inform->saved.options);
if (inform)
free(inform);
ServiceSetPrivate(service_p, NULL);
break;
}
case IFEventID_change_e: {
change_event_data_t * change_event;
ipconfig_method_data_t * method_data;
if (inform == NULL) {
my_log(LOG_DEBUG, "INFORM %s: private data is NULL",
if_name(if_p));
return (ipconfig_status_internal_error_e);
}
change_event = ((change_event_data_t *)event_data);
method_data = change_event->method_data;
change_event->needs_stop = FALSE;
if (method_data->manual.addr.s_addr
!= service_requested_ip_addr(service_p).s_addr) {
change_event->needs_stop = TRUE;
}
else if (method_data->manual.mask.s_addr != 0
&& (method_data->manual.mask.s_addr
!= service_requested_ip_mask(service_p).s_addr)) {
service_set_requested_ip_mask(service_p,
method_data->manual.mask);
inform->our_mask = method_data->manual.mask;
(void)service_set_address(service_p,
method_data->manual.addr,
inform->our_mask, G_ip_zeroes);
}
return (ipconfig_status_success_e);
}
case IFEventID_arp_collision_e: {
arp_collision_data_t * arpc;
char msg[128];
arpc = (arp_collision_data_t *)event_data;
if (inform == NULL) {
return (ipconfig_status_internal_error_e);
}
if (arpc->ip_addr.s_addr
!= service_requested_ip_addr(service_p).s_addr) {
break;
}
snprintf(msg, sizeof(msg),
IP_FORMAT " in use by " EA_FORMAT,
IP_LIST(&arpc->ip_addr),
EA_LIST(arpc->hwaddr));
if (inform->user_warned == FALSE) {
inform->user_warned = TRUE;
ServiceReportIPv4AddressConflict(service_p,
arpc->ip_addr);
}
my_log(LOG_ERR, "INFORM %s: %s", if_name(if_p),
msg);
break;
}
case IFEventID_renew_e:
case IFEventID_link_status_changed_e: {
link_status_t link_status;
if (inform == NULL)
return (ipconfig_status_internal_error_e);
inform->user_warned = FALSE;
link_status = service_link_status(service_p);
if (link_status.valid == TRUE) {
if (link_status.active == TRUE) {
inform_start(service_p, IFEventID_start_e, 0);
}
else if (event_id == IFEventID_link_status_changed_e) {
inform_cancel_pending_events(service_p);
}
}
break;
}
case IFEventID_link_timer_expired_e:
inform_inactive(service_p);
break;
case IFEventID_get_dhcp_info_e: {
if (ServiceGetActiveIPAddress(service_p).s_addr == 0
|| inform->saved.pkt_size == 0) {
break;
}
inform_set_dhcp_info(inform, (dhcp_info_t *)event_data);
break;
}
default:
break;
}
return (status);
}
static void
dhcp_set_dhcp_info(Service_dhcp_t * dhcp, dhcp_info_t * dhcp_info_p)
{
dhcp_info_p->pkt = (uint8_t *)dhcp->saved.pkt;
dhcp_info_p->pkt_size = dhcp->saved.pkt_size;
dhcp_info_p->options = &dhcp->saved.options;
dhcp_info_p->lease_start = dhcp->lease.start;
dhcp_info_p->lease_expiration = dhcp->lease.expiration;
return;
}
static void
dhcp_publish_success(ServiceRef service_p)
{
Service_dhcp_t * dhcp = (Service_dhcp_t *)ServiceGetPrivate(service_p);
dhcp_info_t dhcp_info;
dhcp_set_dhcp_info(dhcp, &dhcp_info);
ServicePublishSuccessIPv4(service_p, &dhcp_info);
return;
}
static void
dhcp_cancel_pending_events(ServiceRef service_p)
{
Service_dhcp_t * dhcp = (Service_dhcp_t *)ServiceGetPrivate(service_p);
if (dhcp == NULL)
return;
timer_cancel(dhcp->timer);
bootp_client_disable_receive(dhcp->client);
arp_client_cancel(dhcp->arp);
return;
}
static void
dhcp_failed(ServiceRef service_p, ipconfig_status_t status)
{
Service_dhcp_t * dhcp = (Service_dhcp_t *)ServiceGetPrivate(service_p);
dhcp_cancel_pending_events(service_p);
service_disable_autoaddr(service_p);
dhcpol_free(&dhcp->saved.options);
service_remove_address(service_p);
service_publish_failure(service_p, status);
dhcp->state = dhcp_cstate_none_e;
return;
}
static void
dhcp_inactive(ServiceRef service_p)
{
Service_dhcp_t * dhcp = (Service_dhcp_t *)ServiceGetPrivate(service_p);
dhcp_cancel_pending_events(service_p);
ServiceSetStatus(service_p, ipconfig_status_media_inactive_e);
service_remove_address(service_p);
service_disable_autoaddr(service_p);
service_publish_failure(service_p, ipconfig_status_media_inactive_e);
dhcp->state = dhcp_cstate_none_e;
return;
}
static void
dhcp_set_lease_params(ServiceRef service_p, char * descr, boolean_t is_dhcp,
absolute_time_t lease_start,
dhcp_lease_time_t lease, dhcp_lease_time_t t1,
dhcp_lease_time_t t2)
{
Service_dhcp_t * dhcp = (Service_dhcp_t *)ServiceGetPrivate(service_p);
interface_t * if_p = service_interface(service_p);
dhcp->lease.start = lease_start;
if (is_dhcp == FALSE) {
dhcp->lease.length = DHCP_INFINITE_LEASE;
}
else {
dhcp->lease.length = lease;
}
if (dhcp->lease.length == DHCP_INFINITE_LEASE) {
dhcp->lease.t1 = dhcp->lease.t2 = dhcp->lease.expiration = 0;
}
else {
dhcp->lease.expiration = dhcp->lease.start + dhcp->lease.length;
dhcp->lease.t1 = dhcp->lease.start + t1;
dhcp->lease.t2 = dhcp->lease.start + t2;
}
my_log(LOG_DEBUG,
"DHCP %s: %s lease"
" start = 0x%x, t1 = 0x%x, t2 = 0x%x, expiration 0x%x",
if_name(if_p), descr, dhcp->lease.start,
dhcp->lease.t1, dhcp->lease.t2, dhcp->lease.expiration);
return;
}
static void
dhcp_adjust_lease_info(ServiceRef service_p, absolute_time_t delta)
{
Service_dhcp_t * dhcp = (Service_dhcp_t *)ServiceGetPrivate(service_p);
interface_t * if_p = service_interface(service_p);
lease_info_t * info_p = &dhcp->lease;
info_p->expiration += delta;
info_p->start += delta;
info_p->t1 += delta;
info_p->t2 += delta;
my_log(LOG_DEBUG,
"DHCP %s: adjusted lease by %d seconds", if_name(if_p), delta);
return;
}
static void
get_client_id(Service_dhcp_t * dhcp, interface_t * if_p, const void * * cid_p,
uint8_t * cid_type_p, int * cid_length_p)
{
if (dhcp->client_id != NULL) {
*cid_type_p = 0;
*cid_p = dhcp->client_id;
*cid_length_p = dhcp->client_id_len;
}
else {
*cid_type_p = if_link_arptype(if_p);
*cid_p = if_link_address(if_p);
*cid_length_p = if_link_length(if_p);
}
return;
}
static void
_dhcp_lease_save(ServiceRef service_p, absolute_time_t lease_start,
const uint8_t * pkt, int pkt_size, boolean_t write_lease)
{
Service_dhcp_t * dhcp = (Service_dhcp_t *)ServiceGetPrivate(service_p);
interface_t * if_p = service_interface(service_p);
const void * cid;
uint8_t cid_type;
int cid_length;
struct in_addr router_ip = { 0 };
const uint8_t * router_hwaddr = NULL;
int router_hwaddr_length = 0;
if (service_router_is_iaddr_valid(service_p)) {
router_ip = service_router_iaddr(service_p);
if (service_router_is_hwaddr_valid(service_p)) {
router_hwaddr = service_router_hwaddr(service_p);
router_hwaddr_length = if_link_length(if_p);
}
}
get_client_id(dhcp, if_p, &cid, &cid_type, &cid_length);
DHCPLeaseListUpdateLease(&dhcp->lease_list, dhcp->saved.our_ip,
router_ip, router_hwaddr, router_hwaddr_length,
dhcp->lease.start, dhcp->lease.length,
pkt, pkt_size, ServiceGetSSID(service_p));
if (write_lease) {
DHCPLeaseListWrite(&dhcp->lease_list,
if_name(if_p), cid_type, cid, cid_length);
}
return;
}
static void
_dhcp_lease_clear(ServiceRef service_p, bool nak)
{
Service_dhcp_t * dhcp = (Service_dhcp_t *)ServiceGetPrivate(service_p);
interface_t * if_p = service_interface(service_p);
struct in_addr router_ip = { 0 };
const uint8_t * router_hwaddr = NULL;
int router_hwaddr_length = 0;
if (service_router_is_iaddr_valid(service_p)) {
router_ip = service_router_iaddr(service_p);
if (service_router_is_hwaddr_valid(service_p)) {
router_hwaddr = service_router_hwaddr(service_p);
router_hwaddr_length = if_link_length(if_p);
}
}
if (nak == FALSE
|| router_ip.s_addr == 0
|| router_hwaddr_length == 0) {
const void * cid;
uint8_t cid_type;
int cid_length;
get_client_id(dhcp, if_p, &cid, &cid_type, &cid_length);
DHCPLeaseListRemoveLease(&dhcp->lease_list,
dhcp->saved.our_ip, router_ip,
router_hwaddr, router_hwaddr_length);
DHCPLeaseListWrite(&dhcp->lease_list,
if_name(if_p), cid_type, cid, cid_length);
}
else {
DHCPLeaseRef lease_p;
int where;
where = DHCPLeaseListFindLease(&dhcp->lease_list,
dhcp->saved.our_ip,
router_ip,
router_hwaddr,
router_hwaddr_length);
if (where != -1) {
lease_p = DHCPLeaseListElement(&dhcp->lease_list, where);
DHCPLeaseSetNAK(lease_p, TRUE);
}
}
return;
}
static boolean_t
switch_to_lease(ServiceRef service_p, DHCPLeaseRef lease_p)
{
Service_dhcp_t * dhcp = (Service_dhcp_t *)ServiceGetPrivate(service_p);
interface_t * if_p = service_interface(service_p);
dhcp_lease_time_t lease_time;
dhcp_lease_time_t t1_time;
dhcp_lease_time_t t2_time;
if (lease_p->our_ip.s_addr == dhcp->saved.our_ip.s_addr) {
if (ip_is_private(lease_p->our_ip) == FALSE) {
if (G_IPConfiguration_verbose) {
my_log(LOG_DEBUG, "DHCP %s: switch_to_lease returns FALSE, "
"public IP is the same", if_name(if_p));
}
return (FALSE);
}
if (service_router_is_iaddr_valid(service_p)
&& service_router_is_hwaddr_valid(service_p)
&& (lease_p->router_ip.s_addr
== service_router_iaddr(service_p).s_addr)
&& (bcmp(lease_p->router_hwaddr,
service_router_hwaddr(service_p),
if_link_length(if_p)) == 0)) {
if (G_IPConfiguration_verbose) {
my_log(LOG_DEBUG, "DHCP %s: switch_to_lease returns FALSE, "
"private IP has same router", if_name(if_p));
}
return (FALSE);
}
}
(void)service_remove_address(service_p);
if (lease_p->pkt_length > sizeof(dhcp->saved.pkt)) {
dhcp->saved.pkt_size = sizeof(dhcp->saved.pkt);
}
else {
dhcp->saved.pkt_size = lease_p->pkt_length;
}
bcopy(lease_p->pkt, dhcp->saved.pkt, dhcp->saved.pkt_size);
dhcpol_free(&dhcp->saved.options);
(void)dhcpol_parse_packet(&dhcp->saved.options,
(void *)dhcp->saved.pkt,
dhcp->saved.pkt_size, NULL);
dhcp_get_lease_from_options(&dhcp->saved.options, &lease_time,
&t1_time, &t2_time);
dhcp_set_lease_params(service_p, "RECOVERED",
TRUE, lease_p->lease_start, lease_time,
t1_time, t2_time);
dhcp->lease.valid = TRUE;
dhcp->saved.rating = 0;
dhcp->saved.our_ip = lease_p->our_ip;
get_server_identifier(&dhcp->saved.options, &dhcp->saved.server_ip);
dhcp->saved.is_dhcp = TRUE;
service_router_clear(service_p);
if (lease_p->router_ip.s_addr != 0) {
service_router_set_iaddr(service_p, lease_p->router_ip);
service_router_set_iaddr_valid(service_p);
if (lease_p->router_hwaddr_length > 0) {
int len;
len = lease_p->router_hwaddr_length;
if (len > service_router_hwaddr_size(service_p)) {
len = service_router_hwaddr_size(service_p);
}
bcopy(lease_p->router_hwaddr,
service_router_hwaddr(service_p), len);
service_router_set_hwaddr_valid(service_p);
}
}
if (G_IPConfiguration_verbose) {
my_log(LOG_DEBUG, "DHCP %s: switched to lease for IP " IP_FORMAT,
if_name(if_p), IP_LIST(&lease_p->our_ip));
}
return (TRUE);
}
static boolean_t
recover_lease(ServiceRef service_p, struct in_addr * our_ip)
{
const void * cid;
uint8_t cid_type;
int cid_length;
int count;
Service_dhcp_t * dhcp = (Service_dhcp_t *)ServiceGetPrivate(service_p);
interface_t * if_p = service_interface(service_p);
DHCPLeaseRef lease_p;
get_client_id(dhcp, if_p, &cid, &cid_type, &cid_length);
DHCPLeaseListRead(&dhcp->lease_list, if_name(if_p),
if_is_wireless(if_p),
cid_type, cid, cid_length);
count = DHCPLeaseListCount(&dhcp->lease_list);
if (count == 0) {
goto failed;
}
lease_p = DHCPLeaseListElement(&dhcp->lease_list, count - 1);
(void)switch_to_lease(service_p, lease_p);
*our_ip = lease_p->our_ip;
if (G_IPConfiguration_verbose) {
my_log(LOG_DEBUG, "DHCP %s: recovered lease for IP " IP_FORMAT,
if_name(if_p), IP_LIST(our_ip));
}
return (TRUE);
failed:
return (FALSE);
}
static boolean_t
dhcp_check_lease(ServiceRef service_p, absolute_time_t current_time)
{
Service_dhcp_t * dhcp = (Service_dhcp_t *)ServiceGetPrivate(service_p);
if (dhcp->lease.valid) {
if (dhcp->lease.length != DHCP_INFINITE_LEASE
&& (current_time >= dhcp->lease.expiration)) {
dhcp->lease.valid = FALSE;
service_router_clear(service_p);
(void)service_remove_address(service_p);
service_publish_failure(service_p,
ipconfig_status_media_inactive_e);
}
}
return (dhcp->lease.valid);
}
static void
dhcp_check_link(ServiceRef service_p, IFEventID_t event_id)
{
absolute_time_t current_time = timer_current_secs();
Service_dhcp_t * dhcp = (Service_dhcp_t *)ServiceGetPrivate(service_p);
link_status_t link_status = service_link_status(service_p);
if (link_status.valid == FALSE
|| link_status.active == TRUE) {
dhcp->conflicting_address.s_addr = 0;
dhcp->user_warned = FALSE;
if (dhcp_check_lease(service_p, current_time)) {
if (event_id == IFEventID_renew_e
|| dhcp->state != dhcp_cstate_init_reboot_e
|| dhcp->try != 1) {
struct in_addr our_ip;
our_ip = dhcp->saved.our_ip;
dhcp_init_reboot(service_p, IFEventID_start_e,
&our_ip);
}
return;
}
if (event_id == IFEventID_renew_e
|| dhcp->state != dhcp_cstate_init_e
|| dhcp->try != 1) {
dhcp_init(service_p, IFEventID_start_e, NULL);
return;
}
}
else {
dhcp->try = 0;
dhcp->state = dhcp_cstate_none_e;
dhcp_cancel_pending_events(service_p);
}
return;
}
INLINE boolean_t
S_dhcp_cstate_is_bound_renew_or_rebind(dhcp_cstate_t state)
{
boolean_t ret;
switch (state) {
case dhcp_cstate_bound_e:
case dhcp_cstate_renew_e:
case dhcp_cstate_rebind_e:
ret = TRUE;
break;
default:
ret = FALSE;
break;
}
return (ret);
}
PRIVATE_EXTERN ipconfig_status_t
dhcp_thread(ServiceRef service_p, IFEventID_t event_id, void * event_data)
{
Service_dhcp_t * dhcp = (Service_dhcp_t *)ServiceGetPrivate(service_p);
interface_t * if_p = service_interface(service_p);
ipconfig_status_t status = ipconfig_status_success_e;
switch (event_id) {
case IFEventID_start_e: {
ipconfig_method_data_t * method_data;
struct in_addr our_ip = { 0 };
if (if_flags(if_p) & IFF_LOOPBACK) {
status = ipconfig_status_invalid_operation_e;
break;
}
if (dhcp != NULL) {
my_log(LOG_ERR, "DHCP %s: re-entering start state",
if_name(if_p));
status = ipconfig_status_internal_error_e;
break;
}
dhcp = malloc(sizeof(*dhcp));
if (dhcp == NULL) {
my_log(LOG_ERR, "DHCP %s: malloc failed",
if_name(if_p));
status = ipconfig_status_allocation_failed_e;
break;
}
bzero(dhcp, sizeof(*dhcp));
dhcp->must_broadcast = (if_link_arptype(if_p) == ARPHRD_IEEE1394);
dhcpol_init(&dhcp->saved.options);
ServiceSetPrivate(service_p, dhcp);
dhcp->lease.valid = FALSE;
service_router_clear(service_p);
dhcp->state = dhcp_cstate_none_e;
dhcp->timer = timer_callout_init();
if (dhcp->timer == NULL) {
my_log(LOG_ERR, "DHCP %s: timer_callout_init failed",
if_name(if_p));
status = ipconfig_status_allocation_failed_e;
goto stop;
}
(void)service_enable_autoaddr(service_p);
dhcp->client = bootp_client_init(G_bootp_session, if_p);
if (dhcp->client == NULL) {
my_log(LOG_ERR, "DHCP %s: bootp_client_init failed",
if_name(if_p));
status = ipconfig_status_allocation_failed_e;
goto stop;
}
dhcp->arp = arp_client_init(G_arp_session, if_p);
if (dhcp->arp == NULL) {
my_log(LOG_ERR, "DHCP %s: arp_client_init failed",
if_name(if_p));
status = ipconfig_status_allocation_failed_e;
goto stop;
}
method_data = (ipconfig_method_data_t *)event_data;
if (method_data != NULL && method_data->dhcp.client_id_len > 0) {
dhcp->client_id_len = method_data->dhcp.client_id_len;
dhcp->client_id = malloc(dhcp->client_id_len);
if (dhcp->client_id == NULL) {
my_log(LOG_ERR, "DHCP %s: malloc client ID failed",
if_name(if_p));
status = ipconfig_status_allocation_failed_e;
goto stop;
}
bcopy(method_data->dhcp.client_id,
dhcp->client_id, dhcp->client_id_len);
}
my_log(LOG_DEBUG, "DHCP %s: start", if_name(if_p));
dhcp->xid = arc4random();
DHCPLeaseListInit(&dhcp->lease_list);
if (ServiceIsNetBoot(service_p)
|| recover_lease(service_p, &our_ip)) {
dhcp_init_reboot(service_p, IFEventID_start_e, &our_ip);
}
else {
dhcp_init(service_p, IFEventID_start_e, NULL);
}
break;
}
case IFEventID_stop_e: {
stop:
my_log(LOG_DEBUG, "DHCP %s: stop", if_name(if_p));
if (dhcp == NULL) {
my_log(LOG_DEBUG, "DHCP %s: already stopped",
if_name(if_p));
status = ipconfig_status_internal_error_e;
break;
}
if (event_id == IFEventID_stop_e) {
(void)dhcp_release(service_p);
}
service_remove_address(service_p);
service_disable_autoaddr(service_p);
if (dhcp->timer) {
timer_callout_free(&dhcp->timer);
}
if (dhcp->client) {
bootp_client_free(&dhcp->client);
}
if (dhcp->arp) {
arp_client_free(&dhcp->arp);
}
if (dhcp->client_id) {
free(dhcp->client_id);
dhcp->client_id = NULL;
}
DHCPLeaseListFree(&dhcp->lease_list);
dhcpol_free(&dhcp->saved.options);
if (dhcp)
free(dhcp);
ServiceSetPrivate(service_p, NULL);
break;
}
case IFEventID_change_e: {
change_event_data_t * change_event;
ipconfig_method_data_t * method_data;
if (dhcp == NULL) {
my_log(LOG_DEBUG, "DHCP %s: private data is NULL",
if_name(if_p));
return (ipconfig_status_internal_error_e);
}
change_event = (change_event_data_t *)event_data;
method_data = change_event->method_data;
change_event->needs_stop = FALSE;
if (method_data != NULL && method_data->dhcp.client_id_len > 0) {
if (dhcp->client_id == NULL
|| dhcp->client_id_len != method_data->dhcp.client_id_len
|| bcmp(dhcp->client_id, method_data->dhcp.client_id,
dhcp->client_id_len)) {
change_event->needs_stop = TRUE;
}
}
else {
if (dhcp->client_id != NULL) {
change_event->needs_stop = TRUE;
}
}
return (ipconfig_status_success_e);
}
case IFEventID_arp_collision_e: {
arp_collision_data_t * arpc;
absolute_time_t current_time;
struct timeval tv;
arpc = (arp_collision_data_t *)event_data;
if (dhcp == NULL) {
return (ipconfig_status_internal_error_e);
}
if (dhcp->disable_arp_collision_detection
|| (ServiceGetActiveIPAddress(service_p).s_addr
!= dhcp->saved.our_ip.s_addr)
|| arpc->ip_addr.s_addr != dhcp->saved.our_ip.s_addr) {
break;
}
current_time = timer_current_secs();
if ((current_time - dhcp->ip_assigned_time) >
G_dhcp_defend_ip_address_interval_secs) {
if (dhcp->ip_conflict_count > 0
&& ((current_time - dhcp->ip_conflict_time)
> G_dhcp_defend_ip_address_interval_secs)) {
dhcp->ip_conflict_count = 0;
}
dhcp->ip_conflict_time = current_time;
dhcp->ip_conflict_count++;
if (dhcp->ip_conflict_count <= G_dhcp_defend_ip_address_count) {
arp_client_defend(dhcp->arp, dhcp->saved.our_ip);
my_log(LOG_ERR, "DHCP %s: defending IP " IP_FORMAT
" against " EA_FORMAT " %d (of %d)",
if_name(if_p), IP_LIST(&dhcp->saved.our_ip),
EA_LIST(arpc->hwaddr),
dhcp->ip_conflict_count,
G_dhcp_defend_ip_address_count);
break;
}
}
my_log(LOG_ERR, "DHCP %s: "
IP_FORMAT " in use by " EA_FORMAT ", DHCP Server "
IP_FORMAT,
if_name(if_p),
IP_LIST(&dhcp->saved.our_ip),
EA_LIST(arpc->hwaddr),
IP_LIST(&dhcp->saved.server_ip));
ServiceReportIPv4AddressConflict(service_p,
dhcp->saved.our_ip);
_dhcp_lease_clear(service_p, FALSE);
service_publish_failure(service_p,
ipconfig_status_address_in_use_e);
if (dhcp->saved.is_dhcp) {
dhcp_decline(service_p, IFEventID_start_e, NULL);
break;
}
dhcp_cancel_pending_events(service_p);
(void)service_disable_autoaddr(service_p);
dhcp->saved.our_ip.s_addr = 0;
dhcp->lease.valid = FALSE;
service_router_clear(service_p);
tv.tv_sec = 10;
tv.tv_usec = 0;
timer_set_relative(dhcp->timer, tv,
(timer_func_t *)dhcp_init,
service_p, (void *)IFEventID_start_e, NULL);
break;
}
case IFEventID_renew_e:
case IFEventID_link_status_changed_e: {
void * network_changed = event_data;
if (dhcp == NULL) {
return (ipconfig_status_internal_error_e);
}
if (network_changed != NULL) {
(void)service_remove_address(service_p);
service_publish_failure(service_p,
ipconfig_status_network_changed_e);
linklocal_service_change(service_p, LINKLOCAL_NO_ALLOCATE);
}
dhcp_check_link(service_p, event_id);
break;
}
case IFEventID_link_timer_expired_e:
dhcp_inactive(service_p);
break;
case IFEventID_power_off_e:
case IFEventID_sleep_e: {
if (dhcp == NULL) {
return (ipconfig_status_internal_error_e);
}
if (dhcp->lease.valid && dhcp->saved.is_dhcp) {
_dhcp_lease_save(service_p, dhcp->lease.start,
(uint8_t *)dhcp->saved.pkt, dhcp->saved.pkt_size,
TRUE);
}
break;
}
case IFEventID_wake_e: {
link_status_t link_status;
wake_data_t * wake_data = (wake_data_t *)event_data;
if (ServiceIsNetBoot(service_p)) {
break;
}
link_status = service_link_status(service_p);
if ((link_status.valid && link_status.active == FALSE)
|| (wake_data->flags & kWakeFlagsFromHibernation) != 0
|| (if_is_wireless(if_p)
&& (wake_data->flags & kWakeFlagsSSIDChanged) != 0)
|| (if_is_wireless(if_p) == FALSE
&& link_status.wake_on_same_network == FALSE)) {
service_remove_address(service_p);
service_publish_failure(service_p,
ipconfig_status_media_inactive_e);
if ((wake_data->flags & kWakeFlagsFromHibernation) != 0) {
my_log(LOG_DEBUG, "DHCP %s: wake from hibernation",
if_name(if_p));
DHCPLeaseListRemoveAllButLastLease(&dhcp->lease_list);
}
dhcp_check_link(service_p, event_id);
}
else {
absolute_time_t current_time;
current_time = timer_current_secs();
if (dhcp_check_lease(service_p, current_time) == FALSE) {
dhcp_init(service_p, IFEventID_start_e, NULL);
break;
}
if (S_dhcp_cstate_is_bound_renew_or_rebind(dhcp->state)
== FALSE) {
struct in_addr our_ip;
our_ip = dhcp->saved.our_ip;
dhcp_init_reboot(service_p, IFEventID_start_e, &our_ip);
break;
}
if ((wake_data->flags & kWakeFlagsBSSIDChanged) != 0) {
arp_address_info_t info;
if (service_populate_router_arpinfo(service_p,
&info)) {
dhcp_check_router(service_p, IFEventID_start_e,
(void *)&info);
break;
}
}
if (dhcp->lease.length == DHCP_INFINITE_LEASE) {
break;
}
else if (current_time < dhcp->renew_rebind_time) {
struct timeval tv;
tv.tv_sec = dhcp->renew_rebind_time - current_time;
tv.tv_usec = 0;
timer_set_relative(dhcp->timer, tv,
(timer_func_t *)dhcp_renew_rebind,
service_p, (void *)IFEventID_start_e,
NULL);
}
else {
dhcp_renew_rebind(service_p, IFEventID_start_e, NULL);
}
}
break;
}
case IFEventID_get_dhcp_info_e: {
dhcp_info_t * dhcp_info_p = ((dhcp_info_t *)event_data);
if (ServiceGetActiveIPAddress(service_p).s_addr == 0
|| dhcp->saved.pkt_size == 0) {
break;
}
dhcp_set_dhcp_info(dhcp, dhcp_info_p);
break;
}
case IFEventID_bssid_changed_e: {
arp_address_info_t info;
if (S_dhcp_cstate_is_bound_renew_or_rebind(dhcp->state)
== FALSE) {
break;
}
if (service_populate_router_arpinfo(service_p,
&info)) {
dhcp_check_router(service_p, IFEventID_start_e,
(void *)&info);
}
break;
}
default:
break;
}
return (status);
}
static void
dhcp_init(ServiceRef service_p, IFEventID_t event_id, void * event_data)
{
absolute_time_t current_time = timer_current_secs();
Service_dhcp_t * dhcp = (Service_dhcp_t *)ServiceGetPrivate(service_p);
interface_t * if_p = service_interface(service_p);
ipconfig_status_t status = ipconfig_status_success_e;
struct timeval tv;
switch (event_id) {
case IFEventID_start_e: {
dhcp_lease_time_t lease_option = htonl(SUGGESTED_LEASE_LENGTH);
dhcpoa_t options;
dhcp_cstate_t prev_state = dhcp->state;
my_log(LOG_DEBUG, "DHCP %s: INIT", if_name(if_p));
dhcp->state = dhcp_cstate_init_e;
dhcp_cancel_pending_events(service_p);
dhcp->request = make_dhcp_request((struct dhcp *)(void *)dhcp->txbuf,
sizeof(dhcp->txbuf),
dhcp_msgtype_discover_e,
if_link_address(if_p),
if_link_arptype(if_p),
if_link_length(if_p),
dhcp->client_id,
dhcp->client_id_len,
dhcp->must_broadcast,
&options);
if (dhcp->request == NULL) {
my_log(LOG_ERR, "DHCP %s: INIT make_dhcp_request failed",
if_name(if_p));
status = ipconfig_status_allocation_failed_e;
goto error;
}
if (dhcpoa_add(&options, dhcptag_lease_time_e,
sizeof(lease_option), &lease_option)
!= dhcpoa_success_e) {
my_log(LOG_ERR, "DHCP %s: INIT dhcpoa_add lease time failed, %s",
if_name(if_p), dhcpoa_err(&options));
status = ipconfig_status_allocation_failed_e;
goto error;
}
add_computer_name(&options);
if (dhcpoa_add(&options, dhcptag_end_e, 0, 0)
!= dhcpoa_success_e) {
my_log(LOG_ERR, "DHCP %s: INIT failed to terminate options",
if_name(if_p));
status = ipconfig_status_allocation_failed_e;
goto error;
}
dhcp->request_size = sizeof(*dhcp->request) + sizeof(G_rfc_magic)
+ dhcpoa_used(&options);
if (dhcp->request_size < sizeof(struct bootp)) {
dhcp->request_size = sizeof(struct bootp);
}
if (prev_state != dhcp_cstate_init_reboot_e) {
dhcp->start_secs = current_time;
}
dhcp->wait_secs = G_initial_wait_secs;
dhcp->try = 0;
dhcp->xid++;
dhcp->gathering = FALSE;
dhcp->saved.rating = 0;
dhcp->got_nak = FALSE;
(void)service_enable_autoaddr(service_p);
bootp_client_enable_receive(dhcp->client,
(bootp_receive_func_t *)dhcp_init,
service_p, (void *)IFEventID_data_e);
}
case IFEventID_timeout_e: {
if (dhcp->gathering == TRUE) {
if (dhcp->saved.is_dhcp) {
dhcp_select(service_p, IFEventID_start_e, NULL);
break;
}
dhcp_bound(service_p, IFEventID_start_e, NULL);
break;
}
dhcp->try++;
if (dhcp->try > 1) {
link_status_t link_status = service_link_status(service_p);
if (link_status.valid
&& link_status.active == FALSE) {
dhcp_inactive(service_p);
break;
}
}
if (dhcp->try >= (G_dhcp_router_arp_at_retry_count + 1)) {
dhcp_arp_router(service_p, IFEventID_start_e, ¤t_time);
}
if (service_router_all_valid(service_p) == FALSE
&& dhcp->try >= (G_dhcp_allocate_linklocal_at_retry_count + 1)) {
if (G_dhcp_failure_configures_linklocal) {
ServiceSetStatus(service_p,
ipconfig_status_no_server_e);
linklocal_service_change(service_p, LINKLOCAL_ALLOCATE);
}
}
if (dhcp->try > (G_max_retries + 1)) {
if (service_router_all_valid(service_p)) {
dhcp_bound(service_p, IFEventID_start_e, NULL);
break;
}
dhcp_no_server(service_p, IFEventID_start_e, NULL);
break;
}
dhcp->request->dp_xid = htonl(dhcp->xid);
dhcp->request->dp_secs
= htons((uint16_t)(current_time - dhcp->start_secs));
if (bootp_client_transmit(dhcp->client,
G_ip_broadcast, G_ip_zeroes,
G_server_port, G_client_port,
dhcp->request, dhcp->request_size) < 0) {
my_log(LOG_ERR,
"DHCP %s: INIT transmit failed", if_name(if_p));
}
tv.tv_sec = dhcp->wait_secs;
tv.tv_usec = random_range(0, USECS_PER_SEC - 1);
my_log(LOG_DEBUG, "DHCP %s: INIT waiting at %d for %d.%06d",
if_name(if_p),
current_time - dhcp->start_secs,
tv.tv_sec, tv.tv_usec);
timer_set_relative(dhcp->timer, tv,
(timer_func_t *)dhcp_init,
service_p, (void *)IFEventID_timeout_e, NULL);
dhcp->wait_secs *= 2;
if (dhcp->wait_secs > G_max_wait_secs)
dhcp->wait_secs = G_max_wait_secs;
break;
}
case IFEventID_data_e: {
boolean_t is_dhcp = TRUE;
dhcp_lease_time_t lease;
bootp_receive_data_t *pkt = (bootp_receive_data_t *)event_data;
dhcp_msgtype_t reply_msgtype = dhcp_msgtype_none_e;
struct in_addr server_ip;
dhcp_lease_time_t t1;
dhcp_lease_time_t t2;
if (verify_packet(pkt, dhcp->xid, if_p, &reply_msgtype,
&server_ip, &is_dhcp) == FALSE
|| server_ip.s_addr == 0
|| ip_valid(pkt->data->dp_yiaddr) == FALSE) {
break;
}
if (is_dhcp == FALSE
|| reply_msgtype == dhcp_msgtype_offer_e) {
int rating = 0;
int dhcp_packet_ideal_rating = n_dhcp_params
+ DHCP_ADDRESS_RATING_GOOD;
dhcp_get_lease_from_options(&pkt->options, &lease, &t1, &t2);
rating = dhcpol_count_params(&pkt->options,
dhcp_params, n_dhcp_params);
rating +=
get_rating_for_ip_address(&pkt->data->dp_yiaddr);
if (dhcp->saved.pkt_size == 0
|| (is_dhcp == TRUE && (dhcp->saved.is_dhcp == FALSE
|| rating > dhcp->saved.rating))
|| (is_dhcp == FALSE && dhcp->saved.is_dhcp == FALSE
&& rating > dhcp->saved.rating)) {
service_router_clear(service_p);
dhcpol_free(&dhcp->saved.options);
bcopy(pkt->data, dhcp->saved.pkt, pkt->size);
dhcp->saved.pkt_size = pkt->size;
dhcp->saved.rating = rating;
(void)dhcpol_parse_packet(&dhcp->saved.options,
(void *)dhcp->saved.pkt,
dhcp->saved.pkt_size, NULL);
dhcp->saved.our_ip = pkt->data->dp_yiaddr;
dhcp->saved.server_ip = server_ip;
dhcp->saved.is_dhcp = is_dhcp;
dhcp_set_lease_params(service_p, "INIT", is_dhcp,
current_time, lease, t1, t2);
if (is_dhcp && rating == dhcp_packet_ideal_rating) {
dhcp_select(service_p, IFEventID_start_e, NULL);
break;
}
if (dhcp->gathering == FALSE) {
struct timeval t = {0,0};
t.tv_sec = G_gather_secs;
my_log(LOG_DEBUG, "DHCP %s: INIT gathering began at %d",
if_name(if_p),
current_time - dhcp->start_secs);
dhcp->gathering = TRUE;
timer_set_relative(dhcp->timer, t,
(timer_func_t *)dhcp_init,
service_p, (void *)IFEventID_timeout_e,
NULL);
}
}
}
break;
}
default:
break;
}
return;
error:
dhcp_failed(service_p, status);
return;
}
static void
dhcp_init_reboot(ServiceRef service_p, IFEventID_t evid, void * event_data)
{
absolute_time_t current_time = timer_current_secs();
Service_dhcp_t * dhcp = (Service_dhcp_t *)ServiceGetPrivate(service_p);
interface_t * if_p = service_interface(service_p);
struct in_addr source_ip = G_ip_zeroes;
ipconfig_status_t status = ipconfig_status_success_e;
struct timeval tv;
struct in_addr server_ip = G_ip_broadcast;
if (ServiceIsNetBoot(service_p)) {
netboot_addresses(&source_ip, NULL);
}
switch (evid) {
case IFEventID_start_e: {
struct in_addr our_ip;
dhcp_lease_time_t lease_option = htonl(SUGGESTED_LEASE_LENGTH);
dhcpoa_t options;
dhcp->start_secs = current_time;
dhcp->try = 0;
dhcp->wait_secs = G_initial_wait_secs;
dhcp->state = dhcp_cstate_init_reboot_e;
if (source_ip.s_addr == 0) {
our_ip = *((struct in_addr *)event_data);
}
else {
our_ip = source_ip;
}
if (G_IPConfiguration_verbose) {
my_log(LOG_DEBUG, "DHCP %s: INIT-REBOOT (" IP_FORMAT ")",
if_name(if_p), IP_LIST(&our_ip));
}
dhcp_cancel_pending_events(service_p);
dhcp->request = make_dhcp_request((struct dhcp *)(void *)dhcp->txbuf,
sizeof(dhcp->txbuf),
dhcp_msgtype_request_e,
if_link_address(if_p),
if_link_arptype(if_p),
if_link_length(if_p),
dhcp->client_id,
dhcp->client_id_len,
dhcp->must_broadcast,
&options);
if (dhcp->request == NULL) {
status = ipconfig_status_allocation_failed_e;
goto error;
}
if (dhcpoa_add(&options, dhcptag_requested_ip_address_e,
sizeof(our_ip), &our_ip)
!= dhcpoa_success_e) {
my_log(LOG_ERR, "DHCP %s: INIT-REBOOT add request ip failed, %s",
if_name(if_p), dhcpoa_err(&options));
status = ipconfig_status_allocation_failed_e;
goto error;
}
if (dhcpoa_add(&options, dhcptag_lease_time_e,
sizeof(lease_option), &lease_option)
!= dhcpoa_success_e) {
my_log(LOG_ERR, "DHCP %s: INIT-REBOOT add lease time failed, %s",
if_name(if_p), dhcpoa_err(&options));
status = ipconfig_status_allocation_failed_e;
goto error;
}
add_computer_name(&options);
if (dhcpoa_add(&options, dhcptag_end_e, 0, 0)
!= dhcpoa_success_e) {
my_log(LOG_ERR, "DHCP %s: INIT-REBOOT failed to terminate options",
if_name(if_p));
status = ipconfig_status_allocation_failed_e;
goto error;
}
dhcp->request_size = sizeof(*dhcp->request) + sizeof(G_rfc_magic)
+ dhcpoa_used(&options);
if (dhcp->request_size < sizeof(struct bootp)) {
dhcp->request_size = sizeof(struct bootp);
}
dhcp->got_nak = FALSE;
dhcp->gathering = FALSE;
dhcp->xid++;
dhcp->saved.our_ip = our_ip;
dhcp->saved.rating = 0;
(void)service_enable_autoaddr(service_p);
bootp_client_enable_receive(dhcp->client,
(bootp_receive_func_t *)dhcp_init_reboot,
service_p, (void *)IFEventID_data_e);
}
case IFEventID_timeout_e: {
if (dhcp->gathering == TRUE) {
dhcp_bound(service_p, IFEventID_start_e, NULL);
break;
}
dhcp->try++;
if (dhcp->try > 1) {
link_status_t link_status = service_link_status(service_p);
if (link_status.valid
&& link_status.active == FALSE) {
dhcp_inactive(service_p);
break;
}
}
dhcp_arp_router(service_p, IFEventID_start_e, ¤t_time);
if (service_router_all_valid(service_p) == FALSE
&& dhcp->try >= (G_dhcp_allocate_linklocal_at_retry_count + 1)) {
if (G_dhcp_failure_configures_linklocal) {
ServiceSetStatus(service_p, ipconfig_status_no_server_e);
linklocal_service_change(service_p, LINKLOCAL_ALLOCATE);
}
}
if (dhcp->try > (G_dhcp_init_reboot_retry_count + 1)) {
if (G_IPConfiguration_verbose) {
my_log(LOG_DEBUG, "DHCP %s: INIT-REBOOT (" IP_FORMAT
") timed out", if_name(if_p),
IP_LIST(&dhcp->saved.our_ip));
}
(void)service_remove_address(service_p);
service_publish_failure_sync(service_p,
ipconfig_status_server_not_responding_e,
FALSE);
dhcp->try--;
dhcp_init(service_p, IFEventID_start_e, NULL);
break;
}
dhcp->request->dp_xid = htonl(dhcp->xid);
dhcp->request->dp_secs
= htons((uint16_t)(current_time - dhcp->start_secs));
if (bootp_client_transmit(dhcp->client,
server_ip, source_ip,
G_server_port, G_client_port,
dhcp->request, dhcp->request_size) < 0) {
my_log(LOG_ERR,
"DHCP %s: INIT-REBOOT transmit failed",
if_name(if_p));
}
tv.tv_sec = dhcp->wait_secs;
tv.tv_usec = random_range(0, USECS_PER_SEC - 1);
if (G_IPConfiguration_verbose) {
my_log(LOG_DEBUG,
"DHCP %s: INIT-REBOOT (" IP_FORMAT
") waiting at %d for %d.%06d",
if_name(if_p),
IP_LIST(&dhcp->saved.our_ip),
current_time - dhcp->start_secs,
tv.tv_sec, tv.tv_usec);
}
timer_set_relative(dhcp->timer, tv,
(timer_func_t *)dhcp_init_reboot,
service_p, (void *)IFEventID_timeout_e, NULL);
dhcp->wait_secs *= 2;
if (dhcp->wait_secs > G_max_wait_secs)
dhcp->wait_secs = G_max_wait_secs;
break;
}
case IFEventID_data_e: {
boolean_t is_dhcp = TRUE;
dhcp_lease_time_t lease;
bootp_receive_data_t *pkt = (bootp_receive_data_t *)event_data;
dhcp_msgtype_t reply_msgtype = dhcp_msgtype_none_e;
struct in_addr server_ip;
dhcp_lease_time_t t1;
dhcp_lease_time_t t2;
if (verify_packet(pkt, dhcp->xid, if_p, &reply_msgtype,
&server_ip, &is_dhcp) == FALSE) {
break;
}
if (ServiceIsNetBoot(service_p) == FALSE
&& reply_msgtype == dhcp_msgtype_nak_e) {
if (G_IPConfiguration_verbose) {
my_log(LOG_DEBUG, "DHCP %s: got DHCP NAK",
if_name(if_p));
}
if (arp_client_is_active(dhcp->arp)) {
dhcp->got_nak = TRUE;
}
else {
service_publish_failure(service_p,
ipconfig_status_lease_terminated_e);
dhcp_unbound(service_p, IFEventID_start_e, (void *)TRUE);
break;
}
}
if (server_ip.s_addr == 0
|| pkt->data->dp_yiaddr.s_addr != dhcp->saved.our_ip.s_addr) {
break;
}
if (is_dhcp == FALSE
|| reply_msgtype == dhcp_msgtype_ack_e) {
int rating = 0;
dhcp_get_lease_from_options(&pkt->options, &lease, &t1, &t2);
rating = dhcpol_count_params(&pkt->options,
dhcp_params, n_dhcp_params);
if (dhcp->saved.pkt_size == 0
|| (is_dhcp == TRUE && (dhcp->saved.is_dhcp == FALSE
|| rating > dhcp->saved.rating))
|| (is_dhcp == FALSE && dhcp->saved.is_dhcp == FALSE
&& rating > dhcp->saved.rating)) {
service_router_clear(service_p);
dhcpol_free(&dhcp->saved.options);
bcopy(pkt->data, dhcp->saved.pkt, pkt->size);
dhcp->saved.pkt_size = pkt->size;
dhcp->saved.rating = rating;
(void)dhcpol_parse_packet(&dhcp->saved.options,
(void *)dhcp->saved.pkt,
dhcp->saved.pkt_size, NULL);
dhcp->saved.our_ip = pkt->data->dp_yiaddr;
dhcp->saved.server_ip = server_ip;
dhcp->saved.is_dhcp = is_dhcp;
dhcp_set_lease_params(service_p, "INIT-REBOOT", is_dhcp,
current_time, lease, t1, t2);
if (is_dhcp && rating == n_dhcp_params) {
dhcp_bound(service_p, IFEventID_start_e, NULL);
break;
}
if (dhcp->gathering == FALSE) {
struct timeval t = {0,0};
t.tv_sec = G_gather_secs;
if (G_IPConfiguration_verbose) {
my_log(LOG_DEBUG,
"DHCP %s: INIT-REBOOT ("
IP_FORMAT ") gathering began at %d",
if_name(if_p),
IP_LIST(&dhcp->saved.our_ip),
current_time - dhcp->start_secs);
}
arp_client_cancel(dhcp->arp);
dhcp->gathering = TRUE;
timer_set_relative(dhcp->timer, t,
(timer_func_t *)dhcp_init_reboot,
service_p,
(void *)IFEventID_timeout_e,
NULL);
}
}
}
break;
}
default:
break;
}
return;
error:
dhcp_failed(service_p, status);
return;
}
static void
dhcp_select(ServiceRef service_p, IFEventID_t evid, void * event_data)
{
absolute_time_t current_time = timer_current_secs();
Service_dhcp_t * dhcp = (Service_dhcp_t *)ServiceGetPrivate(service_p);
interface_t * if_p = service_interface(service_p);
ipconfig_status_t status = ipconfig_status_success_e;
struct timeval tv;
switch (evid) {
case IFEventID_start_e: {
dhcpoa_t options;
my_log(LOG_DEBUG, "DHCP %s: SELECT", if_name(if_p));
dhcp_cancel_pending_events(service_p);
dhcp->state = dhcp_cstate_select_e;
dhcp->request = make_dhcp_request((struct dhcp *)(void *)dhcp->txbuf,
sizeof(dhcp->txbuf),
dhcp_msgtype_request_e,
if_link_address(if_p),
if_link_arptype(if_p),
if_link_length(if_p),
dhcp->client_id,
dhcp->client_id_len,
dhcp->must_broadcast,
&options);
if (dhcp->request == NULL) {
my_log(LOG_ERR, "DHCP %s: SELECT make_dhcp_request failed",
if_name(if_p));
status = ipconfig_status_allocation_failed_e;
goto error;
}
if (dhcpoa_add(&options, dhcptag_requested_ip_address_e,
sizeof(dhcp->saved.our_ip), &dhcp->saved.our_ip)
!= dhcpoa_success_e) {
my_log(LOG_ERR, "DHCP %s: SELECT add requested ip failed, %s",
if_name(if_p), dhcpoa_err(&options));
status = ipconfig_status_allocation_failed_e;
goto error;
}
if (dhcpoa_add(&options, dhcptag_server_identifier_e,
sizeof(dhcp->saved.server_ip), &dhcp->saved.server_ip)
!= dhcpoa_success_e) {
my_log(LOG_ERR, "DHCP %s: SELECT add server ip failed, %s",
if_name(if_p), dhcpoa_err(&options));
status = ipconfig_status_allocation_failed_e;
goto error;
}
add_computer_name(&options);
if (dhcpoa_add(&options, dhcptag_end_e, 0, 0)
!= dhcpoa_success_e) {
my_log(LOG_ERR, "DHCP %s: SELECT failed to terminate options",
if_name(if_p));
status = ipconfig_status_allocation_failed_e;
goto error;
}
dhcp->request_size = sizeof(*dhcp->request) + sizeof(G_rfc_magic)
+ dhcpoa_used(&options);
if (dhcp->request_size < sizeof(struct bootp)) {
dhcp->request_size = sizeof(struct bootp);
}
dhcp->try = 0;
dhcp->gathering = FALSE;
dhcp->wait_secs = G_initial_wait_secs;
bootp_client_enable_receive(dhcp->client,
(bootp_receive_func_t *)dhcp_select,
service_p, (void *)IFEventID_data_e);
}
case IFEventID_timeout_e: {
dhcp->try++;
if (dhcp->try > (G_dhcp_select_retry_count + 1)) {
my_log(LOG_DEBUG, "DHCP %s: SELECT timed out", if_name(if_p));
dhcp_init(service_p, IFEventID_start_e, NULL);
break;
}
dhcp->request->dp_xid = htonl(dhcp->xid);
dhcp->request->dp_secs
= htons((uint16_t)(current_time - dhcp->start_secs));
if (bootp_client_transmit(dhcp->client,
G_ip_broadcast, G_ip_zeroes,
G_server_port, G_client_port,
dhcp->request, dhcp->request_size) < 0) {
my_log(LOG_ERR,
"DHCP %s: SELECT transmit failed",
if_name(if_p));
}
tv.tv_sec = dhcp->wait_secs;
tv.tv_usec = 0;
my_log(LOG_DEBUG, "DHCP %s: SELECT waiting at %d for %d.%06d",
if_name(if_p),
current_time - dhcp->start_secs,
tv.tv_sec, tv.tv_usec);
timer_set_relative(dhcp->timer, tv,
(timer_func_t *)dhcp_select,
service_p, (void *)IFEventID_timeout_e, NULL);
dhcp->wait_secs *= 2;
if (dhcp->wait_secs > G_max_wait_secs)
dhcp->wait_secs = G_max_wait_secs;
break;
}
case IFEventID_data_e: {
boolean_t is_dhcp = TRUE;
dhcp_lease_time_t lease = SUGGESTED_LEASE_LENGTH;
bootp_receive_data_t *pkt = (bootp_receive_data_t *)event_data;
dhcp_msgtype_t reply_msgtype = dhcp_msgtype_none_e;
struct in_addr server_ip = { 0 };
dhcp_lease_time_t t1;
dhcp_lease_time_t t2;
if (verify_packet(pkt, dhcp->xid, if_p, &reply_msgtype,
&server_ip, &is_dhcp) == FALSE
|| is_dhcp == FALSE) {
break;
}
if (reply_msgtype == dhcp_msgtype_nak_e) {
if (server_ip.s_addr == 0
|| server_ip.s_addr != dhcp->saved.server_ip.s_addr) {
break;
}
dhcp_cancel_pending_events(service_p);
tv.tv_sec = 10;
tv.tv_usec = 0;
timer_set_relative(dhcp->timer, tv,
(timer_func_t *)dhcp_init,
service_p, (void *)IFEventID_start_e, NULL);
break;
}
if (reply_msgtype != dhcp_msgtype_ack_e
|| ip_valid(pkt->data->dp_yiaddr) == FALSE) {
break;
}
dhcp_get_lease_from_options(&pkt->options, &lease, &t1, &t2);
dhcp_set_lease_params(service_p, "SELECT", is_dhcp,
current_time, lease, t1, t2);
dhcpol_free(&dhcp->saved.options);
bcopy(pkt->data, dhcp->saved.pkt, pkt->size);
dhcp->saved.pkt_size = pkt->size;
dhcp->saved.rating = 0;
(void)dhcpol_parse_packet(&dhcp->saved.options,
(void *)dhcp->saved.pkt,
dhcp->saved.pkt_size, NULL);
dhcp->saved.our_ip = pkt->data->dp_yiaddr;
if (server_ip.s_addr != 0) {
dhcp->saved.server_ip = server_ip;
}
dhcp->saved.is_dhcp = TRUE;
dhcp_bound(service_p, IFEventID_start_e, NULL);
break;
}
default:
break;
}
return;
error:
dhcp_failed(service_p, status);
return;
}
static void
dhcp_check_router(ServiceRef service_p,
IFEventID_t event_id, void * event_data)
{
Service_dhcp_t * dhcp = (Service_dhcp_t *)ServiceGetPrivate(service_p);
interface_t * if_p = service_interface(service_p);
switch (event_id) {
case IFEventID_start_e: {
arp_address_info_t* info_p = (arp_address_info_t*) event_data;
info_p->sender_ip = dhcp->saved.our_ip;
arp_client_detect(dhcp->arp,
(arp_result_func_t *)
dhcp_check_router,
service_p,
(void *)IFEventID_arp_e,
info_p, 1, TRUE);
break;
}
case IFEventID_arp_e: {
arp_result_t * result = (arp_result_t *)event_data;
if (result->error || result->in_use == FALSE) {
if (result->error) {
my_log(LOG_ERR, "DHCP %s: ARP default gateway failed, %s",
if_name(if_p),
arp_client_errmsg(dhcp->arp));
}
else {
my_log(LOG_DEBUG,
"DHCP %s: ARP detect default gateway got "
"no response", if_name(if_p));
}
dhcp_check_link(service_p, event_id);
}
else {
absolute_time_t current_time = timer_current_secs();
my_log(LOG_DEBUG, "DHCP %s: ARP detect default gateway got "
"a response", if_name(if_p));
if (dhcp->lease.length == DHCP_INFINITE_LEASE) {
break;
}
else if (current_time < dhcp->renew_rebind_time) {
struct timeval tv;
tv.tv_sec = dhcp->renew_rebind_time - current_time;
tv.tv_usec = 0;
timer_set_relative(dhcp->timer, tv,
(timer_func_t *)dhcp_renew_rebind,
service_p, (void *)IFEventID_start_e,
NULL);
}
else {
dhcp_renew_rebind(service_p, IFEventID_start_e, NULL);
}
}
break;
}
default:
break;
}
}
static void
dhcp_arp_router(ServiceRef service_p, IFEventID_t event_id, void * event_data)
{
Service_dhcp_t * dhcp = (Service_dhcp_t *)ServiceGetPrivate(service_p);
interface_t * if_p = service_interface(service_p);
switch (event_id) {
case IFEventID_start_e: {
int info_count;
arp_address_info_t * info_p;
CFStringRef ssid;
absolute_time_t start_time_threshold;
absolute_time_t * start_time_threshold_p;
bool tentative_ok;
if (G_router_arp == FALSE) {
break;
}
if (service_router_is_arp_verified(service_p)) {
break;
}
tentative_ok = (dhcp->state == dhcp_cstate_init_e);
if (if_is_wireless(if_p)) {
absolute_time_t * current_time_p;
ssid = ServiceGetSSID(service_p);
if (ssid == NULL) {
my_log(LOG_NOTICE, "dhcp_arp_router: %s SSID unavailable",
if_name(if_p));
break;
}
current_time_p = (absolute_time_t *)event_data;
start_time_threshold = *current_time_p
- G_router_arp_wifi_lease_start_threshold_secs;
start_time_threshold_p = &start_time_threshold;
}
else {
start_time_threshold_p = NULL;
ssid = NULL;
}
info_p = DHCPLeaseListCopyARPAddressInfo(&dhcp->lease_list,
ssid,
start_time_threshold_p,
tentative_ok,
&info_count);
if (info_p == NULL) {
my_log(LOG_DEBUG,
"DHCP %s: ARP router: No leases to query for",
if_name(if_p));
break;
}
if (G_IPConfiguration_verbose) {
int i;
my_log(LOG_DEBUG, "DHCP %s: ARP detect router starting",
if_name(if_p));
for (i = 0; i < info_count; i++) {
my_log(LOG_DEBUG, "%d. sender " IP_FORMAT
" target " IP_FORMAT "", i + 1,
IP_LIST(&info_p[i].sender_ip),
IP_LIST(&info_p[i].target_ip));
}
}
arp_client_detect(dhcp->arp,
(arp_result_func_t *)
dhcp_arp_router,
service_p,
(void *)IFEventID_arp_e,
info_p, info_count, FALSE);
free(info_p);
break;
}
case IFEventID_arp_e: {
arp_address_info_t * info_p;
DHCPLeaseRef lease_p;
void * option;
struct in_addr mask = {0};
arp_result_t * result = (arp_result_t *)event_data;
int where;
if (result->error || result->in_use == FALSE) {
if (result->error) {
my_log(LOG_ERR, "DHCP %s: ARP detect ROUTER failed, %s",
if_name(if_p),
arp_client_errmsg(dhcp->arp));
}
else {
my_log(LOG_DEBUG,
"DHCP %s: ARP detect router got no response",
if_name(if_p));
}
if (dhcp->got_nak) {
service_publish_failure(service_p,
ipconfig_status_lease_terminated_e);
dhcp_unbound(service_p, IFEventID_start_e, (void *)TRUE);
break;
}
break;
}
info_p = &result->addr;
if (G_IPConfiguration_verbose) {
my_log(LOG_DEBUG, "DHCP %s: got response for sender "
IP_FORMAT " target " IP_FORMAT,
if_name(if_p), IP_LIST(&info_p->sender_ip),
IP_LIST(&info_p->target_ip));
}
where = DHCPLeaseListFindLease(&dhcp->lease_list,
info_p->sender_ip,
info_p->target_ip,
info_p->target_hardware,
if_link_length(if_p));
if (where == -1) {
if (G_IPConfiguration_verbose) {
my_log(LOG_DEBUG, "DHCP %s: lease for "
IP_FORMAT " is no longer available",
if_name(if_p), IP_LIST(&info_p->sender_ip));
}
if (dhcp->got_nak) {
service_publish_failure(service_p,
ipconfig_status_lease_terminated_e);
dhcp_unbound(service_p, IFEventID_start_e, NULL);
}
break;
}
lease_p = DHCPLeaseListElement(&dhcp->lease_list, where);
if (G_IPConfiguration_verbose) {
my_log(LOG_DEBUG, "DHCP %s: identified lease for " IP_FORMAT,
if_name(if_p), IP_LIST(&lease_p->our_ip));
}
if (dhcp->state == dhcp_cstate_init_e) {
if (dhcp->gathering == TRUE) {
break;
}
switch_to_lease(service_p, lease_p);
service_router_set_all_valid(service_p);
break;
}
if (dhcp->state != dhcp_cstate_init_reboot_e) {
break;
}
if (dhcp->gathering == TRUE) {
struct in_addr * router_p;
if (lease_p->our_ip.s_addr != dhcp->saved.our_ip.s_addr) {
break;
}
router_p = dhcp_get_router_from_options(&dhcp->saved.options,
dhcp->saved.our_ip);
if (router_p == NULL
|| router_p->s_addr != lease_p->router_ip.s_addr) {
break;
}
}
else if (switch_to_lease(service_p, lease_p)) {
dhcpol_t options;
struct in_addr * req_ip_p;
dhcp->request->dp_xid = htonl(++dhcp->xid);
dhcpol_init(&options);
(void)dhcpol_parse_packet(&options,
(struct dhcp *)(void *)dhcp->txbuf,
sizeof(dhcp->txbuf), NULL);
req_ip_p
= dhcpol_find_with_length(&options,
dhcptag_requested_ip_address_e,
sizeof(*req_ip_p));
if (req_ip_p != NULL) {
*req_ip_p = lease_p->our_ip;
dhcp->try = 0;
}
dhcpol_free(&options);
}
else if (dhcp->got_nak) {
service_publish_failure(service_p,
ipconfig_status_lease_terminated_e);
dhcp_unbound(service_p, IFEventID_start_e, NULL);
break;
}
service_router_set_all_valid(service_p);
option = dhcpol_find_with_length(&dhcp->saved.options,
dhcptag_subnet_mask_e,
sizeof(mask));
if (option != NULL) {
mask = *((struct in_addr *)option);
}
dhcp->conflicting_address.s_addr = 0;
dhcp->user_warned = FALSE;
(void)service_set_address(service_p, dhcp->saved.our_ip,
mask, G_ip_zeroes);
dhcp_publish_success(service_p);
dhcp->ip_assigned_time = timer_current_secs();
dhcp->ip_conflict_count = 0;
if (G_dhcp_success_deconfigures_linklocal) {
linklocal_service_change(service_p, LINKLOCAL_NO_ALLOCATE);
}
break;
}
default:
break;
}
return;
}
static void
dhcp_resolve_router_callback(ServiceRef service_p, router_arp_status_t status);
static void
dhcp_resolve_router_retry(void * arg0, void * arg1, void * arg2)
{
Service_dhcp_t * dhcp;
ServiceRef service_p = (ServiceRef)arg0;
dhcp = (Service_dhcp_t *)ServiceGetPrivate(service_p);
service_resolve_router(service_p, dhcp->arp,
dhcp_resolve_router_callback,
dhcp->saved.our_ip);
return;
}
static void
dhcp_resolve_router_callback(ServiceRef service_p, router_arp_status_t status)
{
Service_dhcp_t * dhcp = (Service_dhcp_t *)ServiceGetPrivate(service_p);
struct timeval tv;
switch (status) {
case router_arp_status_no_response_e:
tv.tv_sec = 60;
tv.tv_usec = 0;
timer_set_relative(dhcp->timer, tv,
(timer_func_t *)dhcp_resolve_router_retry,
service_p, NULL, NULL);
if (dhcp->resolve_router_timed_out) {
break;
}
dhcp->resolve_router_timed_out = TRUE;
dhcp_publish_success(service_p);
if (dhcp->saved.is_dhcp) {
_dhcp_lease_save(service_p, dhcp->lease.start,
(uint8_t *)dhcp->saved.pkt,
dhcp->saved.pkt_size,
TRUE);
}
break;
case router_arp_status_success_e:
if (dhcp->saved.is_dhcp) {
_dhcp_lease_save(service_p, dhcp->lease.start,
(uint8_t *)dhcp->saved.pkt,
dhcp->saved.pkt_size,
dhcp->lease.needs_write);
dhcp->lease.needs_write = FALSE;
}
dhcp_publish_success(service_p);
break;
default:
case router_arp_status_failed_e:
break;
}
}
static boolean_t
should_write_lease(Service_dhcp_t * dhcp, absolute_time_t current_time)
{
if (dhcp->lease.t1 < current_time) {
return (FALSE);
}
if ((dhcp->lease.t1 - current_time)
> G_dhcp_lease_write_t1_threshold_secs) {
return (TRUE);
}
return (FALSE);
}
static void
dhcp_bound(ServiceRef service_p, IFEventID_t event_id, void * event_data)
{
absolute_time_t current_time = timer_current_secs();
Service_dhcp_t * dhcp = (Service_dhcp_t *)ServiceGetPrivate(service_p);
boolean_t do_arp = TRUE;
interface_t * if_p = service_interface(service_p);
struct in_addr mask = {0};
void * option;
struct timeval tv = {0, 0};
switch (event_id) {
case IFEventID_start_e: {
dhcp_cstate_t prev_state = dhcp->state;
my_log(LOG_DEBUG, "DHCP %s: BOUND", if_name(if_p));
dhcp->state = dhcp_cstate_bound_e;
dhcp->lease.needs_write = TRUE;
dhcp->disable_arp_collision_detection = TRUE;
dhcp_cancel_pending_events(service_p);
switch (prev_state) {
case dhcp_cstate_renew_e:
case dhcp_cstate_rebind_e:
dhcp->lease.needs_write
= should_write_lease(dhcp, current_time);
break;
default:
service_disable_autoaddr(service_p);
break;
}
if (prev_state == dhcp_cstate_rebind_e ||
prev_state == dhcp_cstate_renew_e
|| ServiceIsNetBoot(service_p)) {
break;
}
if (prev_state == dhcp_cstate_select_e) {
arp_client_probe(dhcp->arp,
(arp_result_func_t *)dhcp_bound, service_p,
(void *)IFEventID_arp_e, G_ip_zeroes,
dhcp->saved.our_ip);
return;
}
if (ServiceGetActiveIPAddress(service_p).s_addr
== dhcp->saved.our_ip.s_addr) {
if (prev_state == dhcp_cstate_init_reboot_e ||
prev_state == dhcp_cstate_init_e) {
arp_client_announce(dhcp->arp,
(arp_result_func_t *)dhcp_bound, service_p,
(void *)IFEventID_arp_e, G_ip_zeroes,
dhcp->saved.our_ip, TRUE);
return;
}
}
break;
}
case IFEventID_arp_e: {
arp_result_t * result = (arp_result_t *)event_data;
if (result->error) {
my_log(LOG_ERR, "DHCP %s: ARP probe failed, %s",
if_name(if_p),
arp_client_errmsg(dhcp->arp));
dhcp_failed(service_p, ipconfig_status_internal_error_e);
return;
}
if (result->in_use) {
char msg[128];
struct timeval tv;
snprintf(msg, sizeof(msg),
IP_FORMAT " in use by " EA_FORMAT
", DHCP Server "
IP_FORMAT, IP_LIST(&dhcp->saved.our_ip),
EA_LIST(result->addr.target_hardware),
IP_LIST(&dhcp->saved.server_ip));
if (dhcp->conflicting_address.s_addr
!= dhcp->saved.our_ip.s_addr) {
dhcp->user_warned = FALSE;
}
else if (dhcp->user_warned == FALSE) {
ServiceReportIPv4AddressConflict(service_p,
dhcp->saved.our_ip);
dhcp->user_warned = TRUE;
}
dhcp->conflicting_address = dhcp->saved.our_ip;
my_log(LOG_ERR, "DHCP %s: %s", if_name(if_p), msg);
_dhcp_lease_clear(service_p, FALSE);
dhcp->lease.valid = FALSE;
service_router_clear(service_p);
service_publish_failure(service_p,
ipconfig_status_address_in_use_e);
if (dhcp->saved.is_dhcp) {
dhcp_decline(service_p, IFEventID_start_e, NULL);
return;
}
dhcp_cancel_pending_events(service_p);
(void)service_disable_autoaddr(service_p);
dhcp->saved.our_ip.s_addr = 0;
dhcp->lease.valid = FALSE;
service_router_clear(service_p);
tv.tv_sec = 10;
tv.tv_usec = 0;
timer_set_relative(dhcp->timer, tv,
(timer_func_t *)dhcp_init,
service_p, (void *)IFEventID_start_e, NULL);
return;
}
dhcp_cancel_pending_events(service_p);
break;
}
default:
return;
}
dhcp->lease.valid = TRUE;
dhcp->conflicting_address.s_addr = 0;
dhcp->user_warned = FALSE;
option = dhcpol_find_with_length(&dhcp->saved.options,
dhcptag_subnet_mask_e, sizeof(mask));
if (option != NULL) {
mask = *((struct in_addr *)option);
}
if ((dhcp->saved.our_ip.s_addr
!= ServiceGetActiveIPAddress(service_p).s_addr)
|| (mask.s_addr
!= ServiceGetActiveSubnetMask(service_p).s_addr)) {
(void)service_set_address(service_p, dhcp->saved.our_ip,
mask, G_ip_zeroes);
dhcp->ip_assigned_time = timer_current_secs();
dhcp->ip_conflict_count = 0;
}
if (G_dhcp_success_deconfigures_linklocal) {
linklocal_service_change(service_p, LINKLOCAL_NO_ALLOCATE);
}
dhcp->disable_arp_collision_detection = FALSE;
if (dhcp->lease.length == DHCP_INFINITE_LEASE) {
my_log(LOG_DEBUG, "DHCP %s: infinite lease",
if_name(if_p));
}
else {
if (dhcp->lease.t1 < current_time) {
tv.tv_sec = 0;
tv.tv_usec = 1;
do_arp = FALSE;
}
else {
tv.tv_sec = dhcp->lease.t1 - current_time;
tv.tv_usec = 0;
}
dhcp->renew_rebind_time = dhcp->lease.t1;
timer_set_relative(dhcp->timer, tv,
(timer_func_t *)dhcp_renew_rebind,
service_p, (void *)IFEventID_start_e, NULL);
}
dhcp->resolve_router_timed_out = FALSE;
if (do_arp
&& service_update_router_address(service_p, &dhcp->saved.options,
dhcp->saved.our_ip)
&& service_resolve_router(service_p, dhcp->arp,
dhcp_resolve_router_callback,
dhcp->saved.our_ip)) {
}
else {
if (dhcp->saved.is_dhcp) {
_dhcp_lease_save(service_p, dhcp->lease.start,
(uint8_t *)dhcp->saved.pkt, dhcp->saved.pkt_size,
dhcp->lease.needs_write);
dhcp->lease.needs_write = FALSE;
}
dhcp_publish_success(service_p);
}
return;
}
static void
dhcp_no_server(ServiceRef service_p, IFEventID_t event_id, void * event_data)
{
Service_dhcp_t * dhcp = (Service_dhcp_t *)ServiceGetPrivate(service_p);
struct timeval tv;
switch (event_id) {
case IFEventID_start_e: {
if (G_dhcp_failure_configures_linklocal) {
linklocal_service_change(service_p, LINKLOCAL_ALLOCATE);
}
dhcp_cancel_pending_events(service_p);
service_publish_failure(service_p, ipconfig_status_no_server_e);
#define INIT_RETRY_INTERVAL_SECS (1 * 60)
tv.tv_sec = INIT_RETRY_INTERVAL_SECS;
tv.tv_usec = 0;
timer_set_relative(dhcp->timer, tv,
(timer_func_t *)dhcp_init,
service_p, (void *)IFEventID_start_e, NULL);
break;
}
default: {
break;
}
}
return;
}
static void
dhcp_decline(ServiceRef service_p, IFEventID_t event_id, void * event_data)
{
Service_dhcp_t * dhcp = (Service_dhcp_t *)ServiceGetPrivate(service_p);
interface_t * if_p = service_interface(service_p);
ipconfig_status_t status = ipconfig_status_success_e;
struct timeval tv;
switch (event_id) {
case IFEventID_start_e: {
dhcpoa_t options;
my_log(LOG_DEBUG, "DHCP %s: DECLINE", if_name(if_p));
dhcp_cancel_pending_events(service_p);
dhcp->state = dhcp_cstate_decline_e;
dhcp->request = make_dhcp_request((struct dhcp *)(void *)dhcp->txbuf,
sizeof(dhcp->txbuf),
dhcp_msgtype_decline_e,
if_link_address(if_p),
if_link_arptype(if_p),
if_link_length(if_p),
dhcp->client_id,
dhcp->client_id_len,
FALSE,
&options);
if (dhcp->request == NULL) {
status = ipconfig_status_allocation_failed_e;
goto error;
}
if (dhcpoa_add(&options, dhcptag_requested_ip_address_e,
sizeof(dhcp->saved.our_ip), &dhcp->saved.our_ip)
!= dhcpoa_success_e) {
my_log(LOG_ERR, "DHCP %s: DECLINE couldn't add our ip, %s",
if_name(if_p), dhcpoa_err(&options));
status = ipconfig_status_allocation_failed_e;
goto error;
}
if (dhcpoa_add(&options, dhcptag_server_identifier_e,
sizeof(dhcp->saved.server_ip), &dhcp->saved.server_ip)
!= dhcpoa_success_e) {
my_log(LOG_ERR, "DHCP %s: DECLINE couldn't add server ip, %s",
if_name(if_p), dhcpoa_err(&options));
status = ipconfig_status_allocation_failed_e;
goto error;
}
if (dhcpoa_add(&options, dhcptag_end_e, 0, 0)
!= dhcpoa_success_e) {
my_log(LOG_ERR, "DHCP %s: DECLINE failed to terminate options",
if_name(if_p));
status = ipconfig_status_allocation_failed_e;
goto error;
}
if (bootp_client_transmit(dhcp->client,
G_ip_broadcast, G_ip_zeroes,
G_server_port, G_client_port,
dhcp->request, dhcp->request_size) < 0) {
my_log(LOG_ERR,
"DHCP %s: DECLINE transmit failed",
if_name(if_p));
}
(void)service_remove_address(service_p);
dhcp->saved.our_ip.s_addr = 0;
dhcp->lease.valid = FALSE;
service_router_clear(service_p);
service_disable_autoaddr(service_p);
tv.tv_sec = 10;
tv.tv_usec = 0;
timer_set_relative(dhcp->timer, tv,
(timer_func_t *)dhcp_init,
service_p, (void *)IFEventID_start_e, NULL);
break;
}
default:
break;
}
return;
error:
dhcp_failed(service_p, status);
return;
}
static void
dhcp_unbound(ServiceRef service_p, IFEventID_t event_id, void * event_data)
{
Service_dhcp_t * dhcp = (Service_dhcp_t *)ServiceGetPrivate(service_p);
interface_t * if_p = service_interface(service_p);
struct timeval tv = {0,0};
switch (event_id) {
case IFEventID_start_e: {
int nak = (int)(intptr_t)event_data;
my_log(LOG_DEBUG, "DHCP %s: UNBOUND%s", if_name(if_p),
nak ? " (NAK)" : "");
dhcp_cancel_pending_events(service_p);
dhcp->state = dhcp_cstate_unbound_e;
_dhcp_lease_clear(service_p, nak);
(void)service_remove_address(service_p);
dhcp->saved.our_ip.s_addr = 0;
dhcp->lease.valid = FALSE;
dhcp->got_nak = FALSE;
service_router_clear(service_p);
tv.tv_sec = 0;
tv.tv_usec = 1000;
timer_set_relative(dhcp->timer, tv,
(timer_func_t *)dhcp_init,
service_p, (void *)IFEventID_start_e, NULL);
break;
}
default:
break;
}
return;
}
static void
dhcp_renew_rebind(ServiceRef service_p, IFEventID_t event_id, void * event_data)
{
absolute_time_t current_time = timer_current_secs();
Service_dhcp_t * dhcp = (Service_dhcp_t *)ServiceGetPrivate(service_p);
interface_t * if_p = service_interface(service_p);
ipconfig_status_t status = ipconfig_status_success_e;
struct timeval tv;
switch (event_id) {
case IFEventID_start_e: {
dhcp_lease_time_t lease_option;
dhcpoa_t options;
dhcp_cancel_pending_events(service_p);
dhcp->start_secs = current_time;
dhcp->state = dhcp_cstate_renew_e;
my_log(LOG_DEBUG, "DHCP %s: RENEW/REBIND", if_name(if_p));
dhcp->request = make_dhcp_request((struct dhcp *)(void *)dhcp->txbuf,
sizeof(dhcp->txbuf),
dhcp_msgtype_request_e,
if_link_address(if_p),
if_link_arptype(if_p),
if_link_length(if_p),
dhcp->client_id,
dhcp->client_id_len,
FALSE,
&options);
if (dhcp->request == NULL) {
status = ipconfig_status_allocation_failed_e;
goto error;
}
dhcp->try = 0;
dhcp->request->dp_ciaddr = dhcp->saved.our_ip;
lease_option = htonl(SUGGESTED_LEASE_LENGTH);
if (dhcpoa_add(&options, dhcptag_lease_time_e, sizeof(lease_option),
&lease_option) != dhcpoa_success_e) {
my_log(LOG_ERR, "DHCP %s: RENEW/REBIND couldn't add"
" lease time: %s", if_name(if_p),
dhcpoa_err(&options));
status = ipconfig_status_allocation_failed_e;
goto error;
}
add_computer_name(&options);
if (dhcpoa_add(&options, dhcptag_end_e, 0, 0)
!= dhcpoa_success_e) {
my_log(LOG_ERR, "DHCP %s: RENEW/REBIND failed to terminate options",
if_name(if_p));
status = ipconfig_status_allocation_failed_e;
goto error;
}
bootp_client_enable_receive(dhcp->client,
(bootp_receive_func_t *)dhcp_renew_rebind,
service_p, (void *)IFEventID_data_e);
}
case IFEventID_timeout_e: {
struct in_addr dest_ip = {0};
absolute_time_t wakeup_time;
if (timer_still_pending(dhcp->timer) == FALSE
&& timer_time_changed(dhcp->timer)) {
dhcp_adjust_lease_info(service_p,
(current_time - dhcp->renew_rebind_time));
}
if (current_time >= dhcp->lease.expiration) {
service_publish_failure(service_p,
ipconfig_status_server_not_responding_e);
dhcp_unbound(service_p, IFEventID_start_e, NULL);
return;
}
if (current_time < dhcp->lease.t2) {
dhcp->state = dhcp_cstate_renew_e;
wakeup_time = current_time + (dhcp->lease.t2 - current_time) / 2;
dest_ip = dhcp->saved.server_ip;
}
else {
dhcp->state = dhcp_cstate_rebind_e;
wakeup_time = current_time
+ (dhcp->lease.expiration - current_time) / 2;
dest_ip = G_ip_broadcast;
}
dhcp->request->dp_xid = htonl(++dhcp->xid);
dhcp->request->dp_secs
= htons((uint16_t)(current_time - dhcp->start_secs));
if (bootp_client_transmit(dhcp->client,
dest_ip, dhcp->saved.our_ip,
G_server_port, G_client_port,
dhcp->request, dhcp->request_size) < 0) {
my_log(LOG_ERR,
"DHCP %s: RENEW/REBIND transmit failed",
if_name(if_p));
}
#define RENEW_REBIND_MIN_WAIT_SECS 60
if ((wakeup_time - current_time) < RENEW_REBIND_MIN_WAIT_SECS) {
tv.tv_sec = RENEW_REBIND_MIN_WAIT_SECS;
dhcp->renew_rebind_time = current_time + tv.tv_sec;
}
else {
tv.tv_sec = wakeup_time - current_time;
dhcp->renew_rebind_time = wakeup_time;
}
tv.tv_usec = 0;
my_log(LOG_DEBUG, "DHCP %s: RENEW/REBIND waiting at %d for %d.%06d",
if_name(if_p),
current_time - dhcp->start_secs,
tv.tv_sec, tv.tv_usec);
timer_set_relative(dhcp->timer, tv,
(timer_func_t *)dhcp_renew_rebind,
service_p, (void *)IFEventID_timeout_e, NULL);
break;
}
case IFEventID_data_e: {
boolean_t is_dhcp = TRUE;
dhcp_lease_time_t lease = SUGGESTED_LEASE_LENGTH;
bootp_receive_data_t *pkt = (bootp_receive_data_t *)event_data;
dhcp_msgtype_t reply_msgtype = dhcp_msgtype_none_e;
struct in_addr server_ip;
dhcp_lease_time_t t1;
dhcp_lease_time_t t2;
if (verify_packet(pkt, dhcp->xid, if_p, &reply_msgtype,
&server_ip, &is_dhcp) == FALSE
|| is_dhcp == FALSE) {
return;
}
if (reply_msgtype == dhcp_msgtype_nak_e) {
service_publish_failure(service_p,
ipconfig_status_lease_terminated_e);
dhcp_unbound(service_p, IFEventID_start_e, NULL);
return;
}
if (reply_msgtype != dhcp_msgtype_ack_e
|| server_ip.s_addr == 0
|| ip_valid(pkt->data->dp_yiaddr) == FALSE) {
return;
}
dhcp_get_lease_from_options(&pkt->options, &lease, &t1, &t2);
if (pkt->data->dp_yiaddr.s_addr != dhcp->saved.our_ip.s_addr) {
service_publish_failure(service_p,
ipconfig_status_server_error_e);
dhcp_unbound(service_p, IFEventID_start_e, NULL);
return;
}
dhcp_set_lease_params(service_p, "RENEW/REBIND", is_dhcp,
current_time, lease, t1, t2);
dhcpol_free(&dhcp->saved.options);
bcopy(pkt->data, dhcp->saved.pkt, pkt->size);
dhcp->saved.pkt_size = pkt->size;
dhcp->saved.rating = 0;
(void)dhcpol_parse_packet(&dhcp->saved.options,
(void *)dhcp->saved.pkt,
dhcp->saved.pkt_size, NULL);
dhcp->saved.server_ip = server_ip;
dhcp->saved.is_dhcp = TRUE;
dhcp_bound(service_p, IFEventID_start_e, NULL);
break;
}
default:
return;
}
return;
error:
dhcp_failed(service_p, status);
return;
}
#include "arp.h"
static void
dhcp_wait_until_arp_completes(ServiceRef service_p)
{
struct in_addr addr_wait;
Service_dhcp_t * dhcp;
int i;
int if_index;
interface_t * if_p = service_interface(service_p);
route_msg msg;
struct in_addr our_mask;
boolean_t resolved = FALSE;
int s;
struct sockaddr_dl * sdl;
struct sockaddr_inarp * sin;
struct in_addr subnet;
dhcp = (Service_dhcp_t *)ServiceGetPrivate(service_p);
our_mask = ServiceGetActiveSubnetMask(service_p);
subnet.s_addr = dhcp->saved.our_ip.s_addr & our_mask.s_addr;
if (in_subnet(subnet, our_mask, dhcp->saved.server_ip)) {
addr_wait = dhcp->saved.server_ip;
}
else {
struct in_addr * router_p;
router_p = dhcpol_find_with_length(&dhcp->saved.options,
dhcptag_router_e,
sizeof(*router_p));
if (router_p == NULL
|| router_p->s_addr == dhcp->saved.our_ip.s_addr) {
addr_wait = dhcp->saved.server_ip;
}
else {
addr_wait = *router_p;
}
}
s = arp_open_routing_socket();
if (s == -1) {
my_log(LOG_ERR, "DHCP %s: arp_open_routing_socket() failed, %s",
if_name(if_p),
strerror(errno));
return;
}
sin = (struct sockaddr_inarp *)(void *)msg.m_space;
if_index = if_link_index(if_p);
#define N_ARP_GET_TRIES 5
i = 1;
while (TRUE) {
if (arp_get(s, &msg, addr_wait, if_index) != ARP_RETURN_SUCCESS) {
goto failed;
}
sdl = (struct sockaddr_dl *)(void *)(sin->sin_len + (char *)sin);
if (sdl->sdl_family == AF_LINK && sdl->sdl_alen != 0) {
resolved = TRUE;
break;
}
if (i == N_ARP_GET_TRIES) {
break;
}
i++;
usleep(1000);
}
if (G_IPConfiguration_verbose) {
if (resolved == FALSE) {
my_log(LOG_DEBUG, "DHCP %s:" IP_FORMAT " was NOT resolved",
if_name(if_p), IP_LIST(&addr_wait));
}
else {
my_log(LOG_DEBUG,
"DHCP %s: " IP_FORMAT
" is resolved, %s after trying %d time(s)",
if_name(if_p), IP_LIST(&addr_wait),
link_ntoa(sdl), i);
}
}
failed:
close(s);
return;
}
static void
dhcp_release(ServiceRef service_p)
{
interface_t * if_p = service_interface(service_p);
Service_dhcp_t * dhcp = (Service_dhcp_t *)ServiceGetPrivate(service_p);
link_status_t link_status;
dhcpoa_t options;
if (dhcp->saved.is_dhcp == FALSE || dhcp->lease.valid == FALSE) {
return;
}
my_log(LOG_DEBUG, "DHCP %s: RELEASE", if_name(if_p));
_dhcp_lease_clear(service_p, FALSE);
dhcp->lease.valid = FALSE;
service_router_clear(service_p);
dhcp_cancel_pending_events(service_p);
link_status = service_link_status(service_p);
if (link_status.valid
&& link_status.active == FALSE) {
return;
}
dhcp->request = make_dhcp_request((struct dhcp *)(void *)dhcp->txbuf,
sizeof(dhcp->txbuf),
dhcp_msgtype_release_e,
if_link_address(if_p),
if_link_arptype(if_p),
if_link_length(if_p),
dhcp->client_id, dhcp->client_id_len,
FALSE,
&options);
if (dhcp->request == NULL) {
return;
}
dhcp->request->dp_xid = htonl(++dhcp->xid);
dhcp->request->dp_ciaddr = dhcp->saved.our_ip;
if (dhcpoa_add(&options, dhcptag_server_identifier_e,
sizeof(dhcp->saved.server_ip), &dhcp->saved.server_ip)
!= dhcpoa_success_e) {
my_log(LOG_ERR, "DHCP %s: RELEASE couldn't add server ip, %s",
if_name(if_p), dhcpoa_err(&options));
return;
}
if (dhcpoa_add(&options, dhcptag_end_e, 0, 0)
!= dhcpoa_success_e) {
my_log(LOG_ERR, "DHCP %s: RELEASE failed to terminate options",
if_name(if_p));
return;
}
if (bootp_client_transmit(dhcp->client,
dhcp->saved.server_ip, dhcp->saved.our_ip,
G_server_port, G_client_port,
dhcp->request, dhcp->request_size) < 0) {
my_log(LOG_ERR,
"DHCP %s: RELEASE transmit failed",
if_name(if_p));
return;
}
dhcp_wait_until_arp_completes(service_p);
dhcp->saved.our_ip.s_addr = 0;
return;
}
#define DEFAULT_LEASE_LENGTH (60 * 60)
#define MIN_LEASE_LENGTH (3)
#define MIN_T1_VAL (2)
#define MIN_T2_VAL (2)
void
dhcp_get_lease_from_options(dhcpol_t * options, dhcp_lease_time_t * lease,
dhcp_lease_time_t * t1, dhcp_lease_time_t * t2)
{
dhcp_lease_time_t * lease_opt;
dhcp_lease_time_t * t1_opt;
dhcp_lease_time_t * t2_opt;
lease_opt = (dhcp_lease_time_t *)
dhcpol_find_with_length(options, dhcptag_lease_time_e,
sizeof(dhcp_lease_time_t));
t1_opt = (dhcp_lease_time_t *)
dhcpol_find_with_length(options, dhcptag_renewal_t1_time_value_e,
sizeof(dhcp_lease_time_t));
t2_opt = (dhcp_lease_time_t *)
dhcpol_find_with_length(options, dhcptag_rebinding_t2_time_value_e,
sizeof(dhcp_lease_time_t));
if (lease_opt != NULL) {
*lease = ntohl(*lease_opt);
if (*lease < MIN_LEASE_LENGTH) {
*lease = MIN_LEASE_LENGTH;
}
}
if (t1_opt != NULL) {
*t1 = ntohl(*t1_opt);
if (*t1 < MIN_T1_VAL) {
*t1 = MIN_T1_VAL;
}
}
if (t2_opt != NULL) {
*t2 = ntohl(*t2_opt);
if (*t2 < MIN_T2_VAL) {
*t2 = MIN_T2_VAL;
}
}
if (lease_opt == NULL) {
if (t1_opt != NULL) {
*lease = *t1;
}
else if (t2_opt != NULL) {
*lease = *t2;
}
else {
*lease = DEFAULT_LEASE_LENGTH;
}
}
if (*lease == DHCP_INFINITE_LEASE) {
*t1 = *t2 = 0;
}
else if (t1_opt == NULL || *t1 >= *lease
|| t2_opt == NULL || *t2 >= *lease
|| *t2 < *t1) {
*t1 = (*lease) / 2;
*t2 = (dhcp_lease_time_t) ((double)(*lease) * 0.875);
}
return;
}
struct in_addr *
dhcp_get_router_from_options(dhcpol_t * options_p, struct in_addr our_ip)
{
struct in_addr * router_p;
router_p = dhcpol_find_with_length(options_p, dhcptag_router_e,
sizeof(*router_p));
if (router_p == NULL) {
goto failed;
}
if (router_p->s_addr == our_ip.s_addr) {
router_p = dhcpol_find_with_length(options_p,
dhcptag_domain_name_server_e,
sizeof(*router_p));
if (router_p == NULL) {
goto failed;
}
}
if (router_p->s_addr == 0 || router_p->s_addr == 0xffffffff) {
goto failed;
}
return (router_p);
failed:
return (NULL);
}