packet.c revision 906cb642f1d882102cc2a1ac70cfff5c5e02f2b6
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <string.h>
#include <sys/types.h>
#include <stdlib.h>
#include <dhcpmsg.h>
#include <stddef.h>
#include <assert.h>
#include <search.h>
#include <alloca.h>
#include <limits.h>
#include <stropts.h>
#include <netinet/dhcp6.h>
#include <arpa/inet.h>
#include <sys/sysmacros.h>
#include <sys/sockio.h>
#include <inet/ip6_asp.h>
#include "states.h"
#include "interface.h"
#include "agent.h"
#include "packet.h"
#include "util.h"
#include "dlpi_io.h"
int v6_sock_fd = -1;
int v4_sock_fd = -1;
const in6_addr_t ipv6_all_dhcp_relay_and_servers = {
0xff, 0x02, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x02
};
/*
* We have our own version of this constant because dhcpagent is compiled with
* -lxnet.
*/
const in6_addr_t my_in6addr_any = IN6ADDR_ANY_INIT;
static void retransmit(iu_tq_t *, void *);
static void next_retransmission(dhcp_smach_t *, boolean_t, boolean_t);
static boolean_t send_pkt_internal(dhcp_smach_t *);
/*
* pkt_send_type(): returns an integer representing the packet's type; only
* for use with outbound packets.
*
* input: dhcp_pkt_t *: the packet to examine
* output: uchar_t: the packet type (0 if unknown)
*/
static uchar_t
pkt_send_type(const dhcp_pkt_t *dpkt)
{
const uchar_t *option;
if (dpkt->pkt_isv6)
return (((const dhcpv6_message_t *)dpkt->pkt)->d6m_msg_type);
/*
* this is a little dirty but it should get the job done.
* assumes that the type is in the statically allocated part
* of the options field.
*/
option = dpkt->pkt->options;
for (;;) {
if (*option == CD_PAD) {
option++;
continue;
}
if (*option == CD_END ||
option + 2 - dpkt->pkt->options >=
sizeof (dpkt->pkt->options))
return (0);
if (*option == CD_DHCP_TYPE)
break;
option++;
option += *option + 1;
}
return (option[2]);
}
/*
* pkt_recv_type(): returns an integer representing the packet's type; only
* for use with inbound packets.
*
* input: dhcp_pkt_t *: the packet to examine
* output: uchar_t: the packet type (0 if unknown)
*/
uchar_t
pkt_recv_type(const PKT_LIST *plp)
{
if (plp->isv6)
return (((const dhcpv6_message_t *)plp->pkt)->d6m_msg_type);
else if (plp->opts[CD_DHCP_TYPE] != NULL)
return (plp->opts[CD_DHCP_TYPE]->value[0]);
else
return (0);
}
/*
* pkt_get_xid(): returns transaction ID from a DHCP packet.
*
* input: const PKT *: the packet to examine
* output: uint_t: the transaction ID (0 if unknown)
*/
uint_t
pkt_get_xid(const PKT *pkt, boolean_t isv6)
{
if (pkt == NULL)
return (0);
if (isv6)
return (DHCPV6_GET_TRANSID((const dhcpv6_message_t *)pkt));
else
return (pkt->xid);
}
/*
* init_pkt(): initializes and returns a packet of a given type
*
* input: dhcp_smach_t *: the state machine that will send the packet
* uchar_t: the packet type (DHCP message type)
* output: dhcp_pkt_t *: a pointer to the initialized packet; may be NULL
*/
dhcp_pkt_t *
init_pkt(dhcp_smach_t *dsmp, uchar_t type)
{
uint_t mtu;
dhcp_pkt_t *dpkt = &dsmp->dsm_send_pkt;
dhcp_lif_t *lif = dsmp->dsm_lif;
dhcp_pif_t *pif = lif->lif_pif;
uint32_t xid;
boolean_t isv6;
mtu = dsmp->dsm_using_dlpi ? pif->pif_max : lif->lif_max;
dpkt->pkt_isv6 = isv6 = pif->pif_isv6;
/*
* since multiple dhcp leases may be maintained over the same dlpi
* device (e.g. "hme0" and "hme0:1"), make sure the xid is unique.
*
* Note that transaction ID zero is intentionally never assigned.
* That's used to represent "no ID." Also note that transaction IDs
* are only 24 bits long in DHCPv6.
*/
do {
xid = mrand48();
if (isv6)
xid &= 0xFFFFFF;
} while (xid == 0 ||
lookup_smach_by_xid(xid, NULL, dpkt->pkt_isv6) != NULL);
if (isv6) {
dhcpv6_message_t *v6;
if (mtu != dpkt->pkt_max_len &&
(v6 = realloc(dpkt->pkt, mtu)) != NULL) {
/* LINTED: alignment known to be correct */
dpkt->pkt = (PKT *)v6;
dpkt->pkt_max_len = mtu;
}
if (sizeof (*v6) > dpkt->pkt_max_len) {
dhcpmsg(MSG_ERR, "init_pkt: cannot allocate v6 pkt: %u",
mtu);
return (NULL);
}
v6 = (dhcpv6_message_t *)dpkt->pkt;
dpkt->pkt_cur_len = sizeof (*v6);
(void) memset(v6, 0, dpkt->pkt_max_len);
v6->d6m_msg_type = type;
DHCPV6_SET_TRANSID(v6, xid);
if (dsmp->dsm_cidlen > 0 &&
add_pkt_opt(dpkt, DHCPV6_OPT_CLIENTID, dsmp->dsm_cid,
dsmp->dsm_cidlen) == NULL) {
dhcpmsg(MSG_WARNING,
"init_pkt: cannot insert client ID");
return (NULL);
}
/* For v6, time starts with the creation of a transaction */
dsmp->dsm_neg_hrtime = gethrtime();
dsmp->dsm_newstart_monosec = monosec();
} else {
static uint8_t bootmagic[] = BOOTMAGIC;
PKT *v4;
if (mtu != dpkt->pkt_max_len &&
(v4 = realloc(dpkt->pkt, mtu)) != NULL) {
dpkt->pkt = v4;
dpkt->pkt_max_len = mtu;
}
if (offsetof(PKT, options) > dpkt->pkt_max_len) {
dhcpmsg(MSG_ERR, "init_pkt: cannot allocate v4 pkt: %u",
mtu);
return (NULL);
}
v4 = dpkt->pkt;
dpkt->pkt_cur_len = offsetof(PKT, options);
(void) memset(v4, 0, dpkt->pkt_max_len);
(void) memcpy(v4->cookie, bootmagic, sizeof (bootmagic));
if (pif->pif_hwlen <= sizeof (v4->chaddr)) {
v4->hlen = pif->pif_hwlen;
(void) memcpy(v4->chaddr, pif->pif_hwaddr,
pif->pif_hwlen);
} else {
/*
* The mac address does not fit in the chaddr
* field, thus it can not be sent to the server,
* thus server can not unicast the reply. Per
* RFC 2131 4.4.1, client can set this bit in
* DISCOVER/REQUEST. If the client is already
* in BOUND/REBINDING/RENEWING state, do not set
* this bit, as it can respond to unicast responses
* from server using the 'ciaddr' address.
*/
if (type == DISCOVER ||
(type == REQUEST && dsmp->dsm_state != RENEWING &&
dsmp->dsm_state != REBINDING &&
dsmp->dsm_state != BOUND))
v4->flags = htons(BCAST_MASK);
}
v4->xid = xid;
v4->op = BOOTREQUEST;
v4->htype = pif->pif_hwtype;
if (add_pkt_opt(dpkt, CD_DHCP_TYPE, &type, 1) == NULL) {
dhcpmsg(MSG_WARNING,
"init_pkt: cannot set DHCP packet type");
return (NULL);
}
if (dsmp->dsm_cidlen > 0 &&
add_pkt_opt(dpkt, CD_CLIENT_ID, dsmp->dsm_cid,
dsmp->dsm_cidlen) == NULL) {
dhcpmsg(MSG_WARNING,
"init_pkt: cannot insert client ID");
return (NULL);
}
}
return (dpkt);
}
/*
* remove_pkt_opt(): removes the first instance of an option from a dhcp_pkt_t
*
* input: dhcp_pkt_t *: the packet to remove the option from
* uint_t: the type of option being added
* output: boolean_t: B_TRUE on success, B_FALSE on failure
* note: currently does not work with DHCPv6 suboptions, or to remove
* arbitrary option instances.
*/
boolean_t
remove_pkt_opt(dhcp_pkt_t *dpkt, uint_t opt_type)
{
uchar_t *raw_pkt, *raw_end, *next;
uint_t len;
raw_pkt = (uchar_t *)dpkt->pkt;
raw_end = raw_pkt + dpkt->pkt_cur_len;
if (dpkt->pkt_isv6) {
dhcpv6_option_t d6o;
raw_pkt += sizeof (dhcpv6_message_t);
opt_type = htons(opt_type);
while (raw_pkt + sizeof (d6o) <= raw_end) {
(void) memcpy(&d6o, raw_pkt, sizeof (d6o));
len = ntohs(d6o.d6o_len) + sizeof (d6o);
if (len > raw_end - raw_pkt)
break;
next = raw_pkt + len;
if (d6o.d6o_code == opt_type) {
if (next < raw_end) {
(void) memmove(raw_pkt, next,
raw_end - next);
}
dpkt->pkt_cur_len -= len;
return (B_TRUE);
}
raw_pkt = next;
}
} else {
uchar_t *pstart, *padrun;
raw_pkt += offsetof(PKT, options);
pstart = raw_pkt;
if (opt_type == CD_END || opt_type == CD_PAD)
return (B_FALSE);
padrun = NULL;
while (raw_pkt + 1 <= raw_end) {
if (*raw_pkt == CD_END)
break;
if (*raw_pkt == CD_PAD) {
if (padrun == NULL)
padrun = raw_pkt;
raw_pkt++;
continue;
}
if (raw_pkt + 2 > raw_end)
break;
len = raw_pkt[1];
if (len > raw_end - raw_pkt || len < 2)
break;
next = raw_pkt + len;
if (*raw_pkt == opt_type) {
if (next < raw_end) {
int toadd = (4 + ((next-pstart)&3) -
((raw_pkt-pstart)&3)) & 3;
int torem = 4 - toadd;
if (torem != 4 && padrun != NULL &&
(raw_pkt - padrun) >= torem) {
raw_pkt -= torem;
dpkt->pkt_cur_len -= torem;
} else if (toadd > 0) {
(void) memset(raw_pkt, CD_PAD,
toadd);
raw_pkt += toadd;
/* max is not an issue here */
dpkt->pkt_cur_len += toadd;
}
if (raw_pkt != next) {
(void) memmove(raw_pkt, next,
raw_end - next);
}
}
dpkt->pkt_cur_len -= len;
return (B_TRUE);
}
padrun = NULL;
raw_pkt = next;
}
}
return (B_FALSE);
}
/*
* update_v6opt_len(): updates the length field of a DHCPv6 option.
*
* input: dhcpv6_option_t *: option to be updated
* int: number of octets to add or subtract
* output: boolean_t: B_TRUE on success, B_FALSE on failure
*/
boolean_t
update_v6opt_len(dhcpv6_option_t *opt, int adjust)
{
dhcpv6_option_t optval;
(void) memcpy(&optval, opt, sizeof (optval));
adjust += ntohs(optval.d6o_len);
if (adjust < 0 || adjust > UINT16_MAX) {
return (B_FALSE);
} else {
optval.d6o_len = htons(adjust);
(void) memcpy(opt, &optval, sizeof (optval));
return (B_TRUE);
}
}
/*
* add_pkt_opt(): adds an option to a dhcp_pkt_t
*
* input: dhcp_pkt_t *: the packet to add the option to
* uint_t: the type of option being added
* const void *: the value of that option
* uint_t: the length of the value of the option
* output: void *: pointer to the option that was added, or NULL on failure.
*/
void *
add_pkt_opt(dhcp_pkt_t *dpkt, uint_t opt_type, const void *opt_val,
uint_t opt_len)
{
uchar_t *raw_pkt;
int req_len;
void *optr;
raw_pkt = (uchar_t *)dpkt->pkt;
optr = raw_pkt + dpkt->pkt_cur_len;
if (dpkt->pkt_isv6) {
dhcpv6_option_t d6o;
req_len = opt_len + sizeof (d6o);
if (dpkt->pkt_cur_len + req_len > dpkt->pkt_max_len) {
dhcpmsg(MSG_WARNING,
"add_pkt_opt: not enough room for v6 option %u in "
"packet (%u + %u > %u)", opt_type,
dpkt->pkt_cur_len, req_len, dpkt->pkt_max_len);
return (NULL);
}
d6o.d6o_code = htons(opt_type);
d6o.d6o_len = htons(opt_len);
(void) memcpy(&raw_pkt[dpkt->pkt_cur_len], &d6o, sizeof (d6o));
dpkt->pkt_cur_len += sizeof (d6o);
if (opt_len > 0) {
(void) memcpy(&raw_pkt[dpkt->pkt_cur_len], opt_val,
opt_len);
dpkt->pkt_cur_len += opt_len;
}
} else {
req_len = opt_len + 2; /* + 2 for code & length bytes */
/* CD_END and CD_PAD options don't have a length field */
if (opt_type == CD_END || opt_type == CD_PAD) {
req_len = 1;
} else if (opt_val == NULL) {
dhcpmsg(MSG_ERROR, "add_pkt_opt: option type %d is "
"missing required value", opt_type);
return (NULL);
}
if ((dpkt->pkt_cur_len + req_len) > dpkt->pkt_max_len) {
dhcpmsg(MSG_WARNING,
"add_pkt_opt: not enough room for v4 option %u in "
"packet", opt_type);
return (NULL);
}
raw_pkt[dpkt->pkt_cur_len++] = opt_type;
if (req_len > 1) {
raw_pkt[dpkt->pkt_cur_len++] = opt_len;
if (opt_len > 0) {
(void) memcpy(&raw_pkt[dpkt->pkt_cur_len],
opt_val, opt_len);
dpkt->pkt_cur_len += opt_len;
}
}
}
return (optr);
}
/*
* add_pkt_subopt(): adds an option to a dhcp_pkt_t option. DHCPv6-specific,
* but could be extended to IPv4 DHCP if necessary. Assumes
* that if the parent isn't a top-level option, the caller
* will adjust any upper-level options recursively using
* update_v6opt_len.
*
* input: dhcp_pkt_t *: the packet to add the suboption to
* dhcpv6_option_t *: the start of the option to that should contain
* it (parent)
* uint_t: the type of suboption being added
* const void *: the value of that option
* uint_t: the length of the value of the option
* output: void *: pointer to the suboption that was added, or NULL on
* failure.
*/
void *
add_pkt_subopt(dhcp_pkt_t *dpkt, dhcpv6_option_t *parentopt, uint_t opt_type,
const void *opt_val, uint_t opt_len)
{
uchar_t *raw_pkt;
int req_len;
void *optr;
dhcpv6_option_t d6o;
uchar_t *optend;
int olen;
if (!dpkt->pkt_isv6)
return (NULL);
raw_pkt = (uchar_t *)dpkt->pkt;
req_len = opt_len + sizeof (d6o);
if (dpkt->pkt_cur_len + req_len > dpkt->pkt_max_len) {
dhcpmsg(MSG_WARNING,
"add_pkt_subopt: not enough room for v6 suboption %u in "
"packet (%u + %u > %u)", opt_type,
dpkt->pkt_cur_len, req_len, dpkt->pkt_max_len);
return (NULL);
}
/*
* Update the parent option to include room for this option,
* and compute the insertion point.
*/
(void) memcpy(&d6o, parentopt, sizeof (d6o));
olen = ntohs(d6o.d6o_len);
optend = (uchar_t *)(parentopt + 1) + olen;
olen += req_len;
d6o.d6o_len = htons(olen);
(void) memcpy(parentopt, &d6o, sizeof (d6o));
/*
* If there's anything at the end to move, then move it. Also bump up
* the packet size.
*/
if (optend < raw_pkt + dpkt->pkt_cur_len) {
(void) memmove(optend + req_len, optend,
(raw_pkt + dpkt->pkt_cur_len) - optend);
}
dpkt->pkt_cur_len += req_len;
/*
* Now format the suboption and add it in.
*/
optr = optend;
d6o.d6o_code = htons(opt_type);
d6o.d6o_len = htons(opt_len);
(void) memcpy(optend, &d6o, sizeof (d6o));
if (opt_len > 0)
(void) memcpy(optend + sizeof (d6o), opt_val, opt_len);
return (optr);
}
/*
* add_pkt_opt16(): adds an option with a 16-bit value to a dhcp_pkt_t
*
* input: dhcp_pkt_t *: the packet to add the option to
* uint_t: the type of option being added
* uint16_t: the value of that option
* output: void *: pointer to the option that was added, or NULL on failure.
*/
void *
add_pkt_opt16(dhcp_pkt_t *dpkt, uint_t opt_type, uint16_t opt_value)
{
return (add_pkt_opt(dpkt, opt_type, &opt_value, 2));
}
/*
* add_pkt_opt32(): adds an option with a 32-bit value to a dhcp_pkt_t
*
* input: dhcp_pkt_t *: the packet to add the option to
* uint_t: the type of option being added
* uint32_t: the value of that option
* output: void *: pointer to the option that was added, or NULL on failure.
*/
void *
add_pkt_opt32(dhcp_pkt_t *dpkt, uint_t opt_type, uint32_t opt_value)
{
return (add_pkt_opt(dpkt, opt_type, &opt_value, 4));
}
/*
* add_pkt_prl(): adds the parameter request option to the packet
*
* input: dhcp_pkt_t *: the packet to add the option to
* dhcp_smach_t *: state machine with request option
* output: void *: pointer to the option that was added, or NULL on failure.
*/
void *
add_pkt_prl(dhcp_pkt_t *dpkt, dhcp_smach_t *dsmp)
{
uint_t len;
if (dsmp->dsm_prllen == 0)
return (0);
if (dpkt->pkt_isv6) {
uint16_t *prl;
/*
* RFC 3315 requires that we include the option, even if we
* have nothing to request.
*/
if (dsmp->dsm_prllen == 0)
prl = NULL;
else
prl = alloca(dsmp->dsm_prllen * sizeof (uint16_t));
for (len = 0; len < dsmp->dsm_prllen; len++)
prl[len] = htons(dsmp->dsm_prl[len]);
return (add_pkt_opt(dpkt, DHCPV6_OPT_ORO, prl,
len * sizeof (uint16_t)));
} else {
uint8_t *prl = alloca(dsmp->dsm_prllen);
for (len = 0; len < dsmp->dsm_prllen; len++)
prl[len] = dsmp->dsm_prl[len];
return (add_pkt_opt(dpkt, CD_REQUEST_LIST, prl, len));
}
}
/*
* add_pkt_lif(): Adds CD_REQUESTED_IP_ADDR (IPv4 DHCP) or IA_NA and IAADDR
* (DHCPv6) options to the packet to represent the given LIF.
*
* input: dhcp_pkt_t *: the packet to add the options to
* dhcp_lif_t *: the logical interface to represent
* int: status code (unused for IPv4 DHCP)
* const char *: message to include with status option, or NULL
* output: boolean_t: B_TRUE on success, B_FALSE on failure
*/
boolean_t
add_pkt_lif(dhcp_pkt_t *dpkt, dhcp_lif_t *lif, int status, const char *msg)
{
if (lif->lif_pif->pif_isv6) {
dhcp_smach_t *dsmp;
dhcpv6_message_t *d6m;
dhcpv6_ia_na_t d6in;
dhcpv6_iaaddr_t d6ia;
uint32_t iaid;
uint16_t *statusopt;
dhcpv6_option_t *d6o, *d6so;
uint_t olen;
/*
* Currently, we support just one IAID related to the primary
* LIF on the state machine.
*/
dsmp = lif->lif_lease->dl_smach;
iaid = dsmp->dsm_lif->lif_iaid;
iaid = htonl(iaid);
d6m = (dhcpv6_message_t *)dpkt->pkt;
/*
* Find or create the IA_NA needed for this LIF. If we
* supported IA_TA, we'd check the IFF_TEMPORARY bit here.
*/
d6o = NULL;
while ((d6o = dhcpv6_find_option(d6m + 1,
dpkt->pkt_cur_len - sizeof (*d6m), d6o, DHCPV6_OPT_IA_NA,
&olen)) != NULL) {
if (olen < sizeof (d6in))
continue;
(void) memcpy(&d6in, d6o, sizeof (d6in));
if (d6in.d6in_iaid == iaid)
break;
}
if (d6o == NULL) {
d6in.d6in_iaid = iaid;
d6in.d6in_t1 = 0;
d6in.d6in_t2 = 0;
d6o = add_pkt_opt(dpkt, DHCPV6_OPT_IA_NA,
(dhcpv6_option_t *)&d6in + 1,
sizeof (d6in) - sizeof (*d6o));
if (d6o == NULL)
return (B_FALSE);
}
/*
* Now add the IAADDR suboption for this LIF. No need to
* search here, as we know that this is unique.
*/
d6ia.d6ia_addr = lif->lif_v6addr;
/*
* For Release and Decline, we zero out the lifetime. For
* Renew and Rebind, we report the original time as the
* preferred and valid lifetimes.
*/
if (d6m->d6m_msg_type == DHCPV6_MSG_RELEASE ||
d6m->d6m_msg_type == DHCPV6_MSG_DECLINE) {
d6ia.d6ia_preflife = 0;
d6ia.d6ia_vallife = 0;
} else {
d6ia.d6ia_preflife = htonl(lif->lif_preferred.dt_start);
d6ia.d6ia_vallife = htonl(lif->lif_expire.dt_start);
}
d6so = add_pkt_subopt(dpkt, d6o, DHCPV6_OPT_IAADDR,
(dhcpv6_option_t *)&d6ia + 1,
sizeof (d6ia) - sizeof (*d6o));
if (d6so == NULL)
return (B_FALSE);
/*
* Add a status code suboption to the IAADDR to tell the server
* why we're declining the address. Note that we must manually
* update the enclosing IA_NA, as add_pkt_subopt doesn't know
* how to do that.
*/
if (status != DHCPV6_STAT_SUCCESS || msg != NULL) {
olen = sizeof (*statusopt) +
(msg == NULL ? 0 : strlen(msg));
statusopt = alloca(olen);
*statusopt = htons(status);
if (msg != NULL) {
(void) memcpy((char *)(statusopt + 1), msg,
olen - sizeof (*statusopt));
}
d6so = add_pkt_subopt(dpkt, d6so,
DHCPV6_OPT_STATUS_CODE, statusopt, olen);
if (d6so != NULL) {
/*
* Update for length of suboption header and
* suboption contents.
*/
(void) update_v6opt_len(d6o, sizeof (*d6so) +
olen);
}
}
} else {
/*
* For DECLINE, we need to add the CD_REQUESTED_IP_ADDR option.
* In all other cases (RELEASE and REQUEST), we need to set
* ciadr.
*/
if (pkt_send_type(dpkt) == DECLINE) {
if (!add_pkt_opt32(dpkt, CD_REQUESTED_IP_ADDR,
lif->lif_addr))
return (B_FALSE);
} else {
dpkt->pkt->ciaddr.s_addr = lif->lif_addr;
}
/*
* It's not too worrisome if the message fails to fit in the
* packet. The result will still be valid.
*/
if (msg != NULL)
(void) add_pkt_opt(dpkt, CD_MESSAGE, msg,
strlen(msg) + 1);
}
return (B_TRUE);
}
/*
* free_pkt_entry(): frees a packet list list entry
*
* input: PKT_LIST *: the packet list entry to free
* output: void
*/
void
free_pkt_entry(PKT_LIST *plp)
{
if (plp != NULL) {
free(plp->pkt);
free(plp);
}
}
/*
* free_pkt_list(): frees an entire packet list
*
* input: PKT_LIST **: the packet list to free
* output: void
*/
void
free_pkt_list(PKT_LIST **head)
{
PKT_LIST *plp;
while ((plp = *head) != NULL) {
remque(plp);
free_pkt_entry(plp);
}
}
/*
* send_pkt_internal(): sends a packet out on an interface
*
* input: dhcp_smach_t *: the state machine with a packet to send
* output: boolean_t: B_TRUE if the packet is sent, B_FALSE otherwise
*/
static boolean_t
send_pkt_internal(dhcp_smach_t *dsmp)
{
ssize_t n_bytes;
dhcp_lif_t *lif = dsmp->dsm_lif;
dhcp_pif_t *pif = lif->lif_pif;
dhcp_pkt_t *dpkt = &dsmp->dsm_send_pkt;
uchar_t ptype = pkt_send_type(dpkt);
const char *pkt_name;
struct iovec iov;
struct msghdr msg;
struct cmsghdr *cmsg;
struct in6_pktinfo *ipi6;
boolean_t ismcast;
/*
* Timer should not be running at the point we go to send a packet.
*/
if (dsmp->dsm_retrans_timer != -1) {
dhcpmsg(MSG_CRIT, "send_pkt_internal: unexpected retransmit "
"timer on %s", dsmp->dsm_name);
stop_pkt_retransmission(dsmp);
}
pkt_name = pkt_type_to_string(ptype, dpkt->pkt_isv6);
/*
* if needed, schedule a retransmission timer, then attempt to
* send the packet. if we fail, then log the error. our
* return value should indicate whether or not we were
* successful in sending the request, independent of whether
* we could schedule a timer.
*/
if (dsmp->dsm_send_timeout != 0) {
if ((dsmp->dsm_retrans_timer = iu_schedule_timer_ms(tq,
dsmp->dsm_send_timeout, retransmit, dsmp)) == -1)
dhcpmsg(MSG_WARNING, "send_pkt_internal: cannot "
"schedule retransmit timer for %s packet",
pkt_name);
else
hold_smach(dsmp);
}
if (dpkt->pkt_isv6) {
hrtime_t delta;
/*
* Convert current time into centiseconds since transaction
* started. This is what DHCPv6 expects to see in the Elapsed
* Time option.
*/
delta = (gethrtime() - dsmp->dsm_neg_hrtime) /
(NANOSEC / 100);
if (delta > DHCPV6_FOREVER)
delta = DHCPV6_FOREVER;
(void) remove_pkt_opt(dpkt, DHCPV6_OPT_ELAPSED_TIME);
(void) add_pkt_opt16(dpkt, DHCPV6_OPT_ELAPSED_TIME,
htons(delta));
} else {
/*
* set the `pkt->secs' field depending on the type of packet.
* it should be zero, except in the following cases:
*
* DISCOVER: set to the number of seconds since we started
* trying to obtain a lease.
*
* INFORM: set to the number of seconds since we started
* trying to get configuration parameters.
*
* REQUEST: if in the REQUESTING state, then same value as
* DISCOVER, otherwise the number of seconds
* since we started trying to obtain a lease.
*
* we also set `dsm_newstart_monosec', to the time we sent a
* REQUEST or DISCOVER packet, so we know the lease start
* time (the DISCOVER case is for handling BOOTP servers).
*/
switch (ptype) {
case DISCOVER:
dsmp->dsm_newstart_monosec = monosec();
dsmp->dsm_disc_secs = dsmp->dsm_newstart_monosec -
hrtime_to_monosec(dsmp->dsm_neg_hrtime);
dpkt->pkt->secs = htons(dsmp->dsm_disc_secs);
break;
case INFORM:
dpkt->pkt->secs = htons(monosec() -
hrtime_to_monosec(dsmp->dsm_neg_hrtime));
break;
case REQUEST:
dsmp->dsm_newstart_monosec = monosec();
if (dsmp->dsm_state == REQUESTING) {
dpkt->pkt->secs = htons(dsmp->dsm_disc_secs);
break;
}
dpkt->pkt->secs = htons(monosec() -
hrtime_to_monosec(dsmp->dsm_neg_hrtime));
break;
default:
dpkt->pkt->secs = htons(0);
break;
}
}
if (dpkt->pkt_isv6) {
struct sockaddr_in6 sin6;
(void) memset(&iov, 0, sizeof (iov));
iov.iov_base = dpkt->pkt;
iov.iov_len = dpkt->pkt_cur_len;
(void) memset(&msg, 0, sizeof (msg));
msg.msg_name = &dsmp->dsm_send_dest.v6;
msg.msg_namelen = sizeof (struct sockaddr_in6);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
/*
* If the address that's requested cannot be reached, then fall
* back to the multcast address.
*/
if (IN6_IS_ADDR_MULTICAST(&dsmp->dsm_send_dest.v6.sin6_addr)) {
ismcast = B_TRUE;
} else {
struct dstinforeq dinfo;
struct strioctl str;
ismcast = B_FALSE;
(void) memset(&dinfo, 0, sizeof (dinfo));
dinfo.dir_daddr = dsmp->dsm_send_dest.v6.sin6_addr;
str.ic_cmd = SIOCGDSTINFO;
str.ic_timout = 0;
str.ic_len = sizeof (dinfo);
str.ic_dp = (char *)&dinfo;
if (ioctl(v6_sock_fd, I_STR, &str) == -1) {
dhcpmsg(MSG_ERR,
"send_pkt_internal: ioctl SIOCGDSTINFO");
} else if (!dinfo.dir_dreachable) {
char abuf[INET6_ADDRSTRLEN];
dhcpmsg(MSG_DEBUG, "send_pkt_internal: %s is "
"not reachable; using multicast instead",
inet_ntop(AF_INET6, &dinfo.dir_daddr, abuf,
sizeof (abuf)));
sin6 = dsmp->dsm_send_dest.v6;
sin6.sin6_addr =
ipv6_all_dhcp_relay_and_servers;
msg.msg_name = &sin6;
ismcast = B_TRUE;
}
}
/*
* Make room for our ancillary data option as well as a dummy
* option used by CMSG_NXTHDR.
*/
msg.msg_controllen = sizeof (*cmsg) + _MAX_ALIGNMENT +
sizeof (*ipi6) + _MAX_ALIGNMENT + sizeof (*cmsg);
msg.msg_control = alloca(msg.msg_controllen);
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = IPPROTO_IPV6;
cmsg->cmsg_type = IPV6_PKTINFO;
/* LINTED: alignment */
ipi6 = (struct in6_pktinfo *)CMSG_DATA(cmsg);
if (ismcast)
ipi6->ipi6_addr = lif->lif_v6addr;
else
ipi6->ipi6_addr = my_in6addr_any;
ipi6->ipi6_ifindex = lif->lif_pif->pif_index;
cmsg->cmsg_len = (char *)(ipi6 + 1) - (char *)cmsg;
/*
* Now correct the control message length.
*/
cmsg = CMSG_NXTHDR(&msg, cmsg);
msg.msg_controllen = (char *)cmsg - (char *)msg.msg_control;
n_bytes = sendmsg(v6_sock_fd, &msg, 0);
} else {
if (dsmp->dsm_using_dlpi) {
n_bytes = dlpi_sendto(pif->pif_dlpi_hd, dpkt->pkt,
dpkt->pkt_cur_len, &dsmp->dsm_send_dest.v4,
pif->pif_daddr, pif->pif_dlen);
/* dlpi_sendto calls putmsg */
if (n_bytes == 0)
n_bytes = dpkt->pkt_cur_len;
} else {
n_bytes = sendto(lif->lif_sock_ip_fd, dpkt->pkt,
dpkt->pkt_cur_len, 0,
(struct sockaddr *)&dsmp->dsm_send_dest.v4,
sizeof (struct sockaddr_in));
}
}
if (n_bytes != dpkt->pkt_cur_len) {
if (dsmp->dsm_retrans_timer == -1)
dhcpmsg(MSG_WARNING, "send_pkt_internal: cannot send "
"%s packet to server", pkt_name);
else
dhcpmsg(MSG_WARNING, "send_pkt_internal: cannot send "
"%s packet to server (will retry in %u seconds)",
pkt_name, dsmp->dsm_send_timeout / MILLISEC);
return (B_FALSE);
}
dhcpmsg(MSG_VERBOSE, "sent %s xid %x packet out %s", pkt_name,
pkt_get_xid(dpkt->pkt, dpkt->pkt_isv6), dsmp->dsm_name);
dsmp->dsm_packet_sent++;
dsmp->dsm_sent++;
return (B_TRUE);
}
/*
* send_pkt(): sends a packet out
*
* input: dhcp_smach_t *: the state machine sending the packet
* dhcp_pkt_t *: the packet to send out
* in_addr_t: the destination IP address for the packet
* stop_func_t *: a pointer to function to indicate when to stop
* retransmitting the packet (if NULL, packet is
* not retransmitted)
* output: boolean_t: B_TRUE if the packet was sent, B_FALSE otherwise
*/
boolean_t
send_pkt(dhcp_smach_t *dsmp, dhcp_pkt_t *dpkt, in_addr_t dest,
stop_func_t *stop)
{
/*
* packets must be at least sizeof (PKT) or they may be dropped
* by routers. pad out the packet in this case.
*/
dpkt->pkt_cur_len = MAX(dpkt->pkt_cur_len, sizeof (PKT));
dsmp->dsm_packet_sent = 0;
(void) memset(&dsmp->dsm_send_dest.v4, 0,
sizeof (dsmp->dsm_send_dest.v4));
dsmp->dsm_send_dest.v4.sin_addr.s_addr = dest;
dsmp->dsm_send_dest.v4.sin_family = AF_INET;
dsmp->dsm_send_dest.v4.sin_port = htons(IPPORT_BOOTPS);
dsmp->dsm_send_stop_func = stop;
/*
* TODO: dispose of this gruesome assumption (there's no real
* technical gain from doing so, but it would be cleaner)
*/
assert(dpkt == &dsmp->dsm_send_pkt);
/*
* clear out any packets which had been previously received
* but not pulled off of the recv_packet queue.
*/
free_pkt_list(&dsmp->dsm_recv_pkt_list);
if (stop == NULL)
dsmp->dsm_send_timeout = 0; /* prevents retransmissions */
else
next_retransmission(dsmp, B_TRUE, B_FALSE);
return (send_pkt_internal(dsmp));
}
/*
* send_pkt_v6(): sends a DHCPv6 packet out
*
* input: dhcp_smach_t *: the state machine sending the packet
* dhcp_pkt_t *: the packet to send out
* in6_addr_t: the destination IPv6 address for the packet
* stop_func_t *: a pointer to function to indicate when to stop
* retransmitting the packet (if NULL, packet is
* not retransmitted)
* uint_t: Initial Retransmit Timer value
* uint_t: Maximum Retransmit Timer value, zero if none
* output: boolean_t: B_TRUE if the packet was sent, B_FALSE otherwise
*/
boolean_t
send_pkt_v6(dhcp_smach_t *dsmp, dhcp_pkt_t *dpkt, in6_addr_t dest,
stop_func_t *stop, uint_t irt, uint_t mrt)
{
dsmp->dsm_packet_sent = 0;
(void) memset(&dsmp->dsm_send_dest.v6, 0,
sizeof (dsmp->dsm_send_dest.v6));
dsmp->dsm_send_dest.v6.sin6_addr = dest;
dsmp->dsm_send_dest.v6.sin6_family = AF_INET6;
dsmp->dsm_send_dest.v6.sin6_port = htons(IPPORT_DHCPV6S);
dsmp->dsm_send_stop_func = stop;
/*
* TODO: dispose of this gruesome assumption (there's no real
* technical gain from doing so, but it would be cleaner)
*/
assert(dpkt == &dsmp->dsm_send_pkt);
/*
* clear out any packets which had been previously received
* but not pulled off of the recv_packet queue.
*/
free_pkt_list(&dsmp->dsm_recv_pkt_list);
if (stop == NULL) {
dsmp->dsm_send_timeout = 0; /* prevents retransmissions */
} else {
dsmp->dsm_send_timeout = irt;
dsmp->dsm_send_tcenter = mrt;
/*
* This is quite ugly, but RFC 3315 section 17.1.2 requires
* that the RAND value for the very first retransmission of a
* Solicit message is strictly greater than zero.
*/
next_retransmission(dsmp, B_TRUE,
pkt_send_type(dpkt) == DHCPV6_MSG_SOLICIT);
}
return (send_pkt_internal(dsmp));
}
/*
* retransmit(): retransmits the current packet on an interface
*
* input: iu_tq_t *: unused
* void *: the dhcp_smach_t * (state machine) sending a packet
* output: void
*/
/* ARGSUSED */
static void
retransmit(iu_tq_t *tqp, void *arg)
{
dhcp_smach_t *dsmp = arg;
dsmp->dsm_retrans_timer = -1;
if (!verify_smach(dsmp))
return;
/*
* Check the callback to see if we should keep sending retransmissions.
* Compute the next retransmission time first, so that the callback can
* cap the value if need be. (Required for DHCPv6 Confirm messages.)
*
* Hold the state machine across the callback so that the called
* function can remove the state machine from the system without
* disturbing the string used subsequently for verbose logging. The
* Release function destroys the state machine when the retry count
* expires.
*/
next_retransmission(dsmp, B_FALSE, B_FALSE);
hold_smach(dsmp);
if (dsmp->dsm_send_stop_func(dsmp, dsmp->dsm_packet_sent)) {
dhcpmsg(MSG_VERBOSE, "retransmit: time to stop on %s",
dsmp->dsm_name);
} else {
dhcpmsg(MSG_VERBOSE, "retransmit: sending another on %s",
dsmp->dsm_name);
(void) send_pkt_internal(dsmp);
}
release_smach(dsmp);
}
/*
* stop_pkt_retransmission(): stops retransmission of last sent packet
*
* input: dhcp_smach_t *: the state machine to stop retransmission on
* output: void
*/
void
stop_pkt_retransmission(dhcp_smach_t *dsmp)
{
if (dsmp->dsm_retrans_timer != -1 &&
iu_cancel_timer(tq, dsmp->dsm_retrans_timer, NULL) == 1) {
dhcpmsg(MSG_VERBOSE, "stop_pkt_retransmission: stopped on %s",
dsmp->dsm_name);
dsmp->dsm_retrans_timer = -1;
release_smach(dsmp);
}
}
/*
* retransmit_now(): force a packet retransmission right now. Used only with
* the DHCPv6 UseMulticast status code. Use with caution;
* triggered retransmissions can cause packet storms.
*
* input: dhcp_smach_t *: the state machine to force retransmission on
* output: void
*/
void
retransmit_now(dhcp_smach_t *dsmp)
{
stop_pkt_retransmission(dsmp);
(void) send_pkt_internal(dsmp);
}
/*
* alloc_pkt_entry(): Allocates a packet list entry with a given data area
* size.
*
* input: size_t: size of data area for packet
* boolean_t: B_TRUE for IPv6
* output: PKT_LIST *: allocated packet list entry
*/
PKT_LIST *
alloc_pkt_entry(size_t psize, boolean_t isv6)
{
PKT_LIST *plp;
if ((plp = calloc(1, sizeof (*plp))) == NULL ||
(plp->pkt = malloc(psize)) == NULL) {
free(plp);
plp = NULL;
} else {
plp->len = psize;
plp->isv6 = isv6;
}
return (plp);
}
/*
* sock_recvpkt(): read from the given socket into an allocated buffer and
* handles any ancillary data options.
*
* input: int: file descriptor to read
* PKT_LIST *: allocated buffer
* output: ssize_t: number of bytes read, or -1 on error
*/
static ssize_t
sock_recvpkt(int fd, PKT_LIST *plp)
{
struct iovec iov;
struct msghdr msg;
int64_t ctrl[8192 / sizeof (int64_t)];
ssize_t msglen;
(void) memset(&iov, 0, sizeof (iov));
iov.iov_base = (caddr_t)plp->pkt;
iov.iov_len = plp->len;
(void) memset(&msg, 0, sizeof (msg));
msg.msg_name = &plp->pktfrom;
msg.msg_namelen = sizeof (plp->pktfrom);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = ctrl;
msg.msg_controllen = sizeof (ctrl);
if ((msglen = recvmsg(fd, &msg, 0)) != -1) {
struct cmsghdr *cmsg;
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
cmsg = CMSG_NXTHDR(&msg, cmsg)) {
struct sockaddr_in *sinp;
struct sockaddr_in6 *sin6;
struct in6_pktinfo *ipi6;
switch (cmsg->cmsg_level) {
case IPPROTO_IP:
switch (cmsg->cmsg_type) {
case IP_RECVDSTADDR:
sinp = (struct sockaddr_in *)
&plp->pktto;
sinp->sin_family = AF_INET;
(void) memcpy(&sinp->sin_addr.s_addr,
CMSG_DATA(cmsg),
sizeof (ipaddr_t));
break;
case IP_RECVIF:
(void) memcpy(&plp->ifindex,
CMSG_DATA(cmsg), sizeof (uint_t));
break;
}
break;
case IPPROTO_IPV6:
switch (cmsg->cmsg_type) {
case IPV6_PKTINFO:
/* LINTED: alignment */
ipi6 = (struct in6_pktinfo *)
CMSG_DATA(cmsg);
sin6 = (struct sockaddr_in6 *)
&plp->pktto;
sin6->sin6_family = AF_INET6;
(void) memcpy(&sin6->sin6_addr,
&ipi6->ipi6_addr,
sizeof (ipi6->ipi6_addr));
(void) memcpy(&plp->ifindex,
&ipi6->ipi6_ifindex,
sizeof (uint_t));
break;
}
}
}
}
return (msglen);
}
/*
* recv_pkt(): receives a single DHCP packet on a given file descriptor.
*
* input: int: if not using dlpi, the file descriptor to receive the packet
* int: the maximum packet size to allow
* boolean_t: B_TRUE for IPv6
* boolean_t: B_TRUE if using DLPI
* void *: if using DLPI, structure that has DLPI handle
* output: PKT_LIST *: the received packet
*/
PKT_LIST *
recv_pkt(int fd, int mtu, boolean_t isv6, boolean_t isdlpi, dhcp_pif_t *arg)
{
PKT_LIST *plp;
ssize_t retval;
if ((plp = alloc_pkt_entry(mtu, isv6)) == NULL) {
dhcpmsg(MSG_ERROR,
"recv_pkt: allocation failure; dropped packet");
return (NULL);
}
if (isv6) {
retval = sock_recvpkt(fd, plp);
if (retval == -1) {
dhcpmsg(MSG_ERR,
"recv_pkt: recvfrom v6 failed, dropped");
goto failure;
}
plp->len = retval;
if (retval < sizeof (dhcpv6_message_t)) {
dhcpmsg(MSG_WARNING, "recv_pkt: runt message");
goto failure;
}
} else {
if (isdlpi) {
dhcp_pif_t *pif = arg;
retval = dlpi_recvfrom(pif->pif_dlpi_hd, plp->pkt, mtu,
(struct sockaddr_in *)&plp->pktfrom,
(struct sockaddr_in *)&plp->pktto);
} else {
retval = sock_recvpkt(fd, plp);
}
if (retval == -1) {
dhcpmsg(MSG_ERR,
"recv_pkt: %srecvfrom v4 failed, dropped",
isdlpi ? "dlpi_" : "");
goto failure;
}
plp->len = retval;
switch (dhcp_options_scan(plp, B_TRUE)) {
case DHCP_WRONG_MSG_TYPE:
dhcpmsg(MSG_WARNING,
"recv_pkt: unexpected DHCP message");
goto failure;
case DHCP_GARBLED_MSG_TYPE:
dhcpmsg(MSG_WARNING,
"recv_pkt: garbled DHCP message type");
goto failure;
case DHCP_BAD_OPT_OVLD:
dhcpmsg(MSG_WARNING, "recv_pkt: bad option overload");
goto failure;
case 0:
break;
default:
dhcpmsg(MSG_WARNING,
"recv_pkt: packet corrupted, dropped");
goto failure;
}
}
return (plp);
failure:
free_pkt_entry(plp);
return (NULL);
}
/*
* pkt_v4_match(): check if a given DHCPv4 message type is in a given set
*
* input: uchar_t: packet type
* dhcp_message_type_t: bit-wise OR of DHCP_P* values.
* output: boolean_t: B_TRUE if packet type is in the set
*/
boolean_t
pkt_v4_match(uchar_t type, dhcp_message_type_t match_type)
{
/*
* note: the ordering here allows direct indexing of the table
* based on the RFC2131 packet type value passed in.
*/
static dhcp_message_type_t type_map[] = {
DHCP_PUNTYPED, DHCP_PDISCOVER, DHCP_POFFER, DHCP_PREQUEST,
DHCP_PDECLINE, DHCP_PACK, DHCP_PNAK, DHCP_PRELEASE,
DHCP_PINFORM
};
if (type < (sizeof (type_map) / sizeof (*type_map)))
return ((type_map[type] & match_type) ? B_TRUE : B_FALSE);
else
return (B_FALSE);
}
/*
* pkt_smach_enqueue(): enqueue a packet on a given state machine
*
* input: dhcp_smach_t: state machine
* PKT_LIST *: packet to enqueue
* output: none
*/
void
pkt_smach_enqueue(dhcp_smach_t *dsmp, PKT_LIST *plp)
{
dhcpmsg(MSG_VERBOSE, "pkt_smach_enqueue: received %s %s packet on %s",
pkt_type_to_string(pkt_recv_type(plp), dsmp->dsm_isv6),
dsmp->dsm_isv6 ? "v6" : "v4", dsmp->dsm_name);
/* add to front of list */
insque(plp, &dsmp->dsm_recv_pkt_list);
}
/*
* next_retransmission(): computes the number of seconds until the next
* retransmission, based on the algorithms in RFCs 2131
* 3315.
*
* input: dhcp_smach_t *: state machine that needs a new timer
* boolean_t: B_TRUE if this is the first time sending the message
* boolean_t: B_TRUE for positive RAND values only (RFC 3315 17.1.2)
* output: none
*/
static void
next_retransmission(dhcp_smach_t *dsmp, boolean_t first_send,
boolean_t positive_only)
{
uint32_t timeout_ms;
if (dsmp->dsm_isv6) {
double randval;
/*
* The RFC specifies 0 to 10% jitter for the initial
* solicitation, and plus or minus 10% jitter for all others.
* This works out to 100 milliseconds on the shortest timer we
* use.
*/
if (positive_only)
randval = drand48() / 10.0;
else
randval = (drand48() - 0.5) / 5.0;
/* The RFC specifies doubling *after* the first transmission */
timeout_ms = dsmp->dsm_send_timeout;
if (!first_send)
timeout_ms *= 2;
timeout_ms += (int)(randval * dsmp->dsm_send_timeout);
/* This checks the MRT (maximum retransmission time) */
if (dsmp->dsm_send_tcenter != 0 &&
timeout_ms > dsmp->dsm_send_tcenter) {
timeout_ms = dsmp->dsm_send_tcenter +
(uint_t)(randval * dsmp->dsm_send_tcenter);
}
dsmp->dsm_send_timeout = timeout_ms;
} else {
if (dsmp->dsm_state == RENEWING ||
dsmp->dsm_state == REBINDING) {
monosec_t mono;
timeout_ms = dsmp->dsm_state == RENEWING ?
dsmp->dsm_leases->dl_t2.dt_start :
dsmp->dsm_leases->dl_lifs->lif_expire.dt_start;
timeout_ms += dsmp->dsm_curstart_monosec;
mono = monosec();
if (mono > timeout_ms)
timeout_ms = 0;
else
timeout_ms -= mono;
timeout_ms *= MILLISEC / 2;
} else {
/*
* Start at 4, and increase by a factor of 2 up to 64.
*/
if (first_send) {
timeout_ms = 4 * MILLISEC;
} else {
timeout_ms = MIN(dsmp->dsm_send_tcenter << 1,
64 * MILLISEC);
}
}
dsmp->dsm_send_tcenter = timeout_ms;
/*
* At each iteration, jitter the timeout by some fraction of a
* second.
*/
dsmp->dsm_send_timeout = timeout_ms +
((lrand48() % (2 * MILLISEC)) - MILLISEC);
}
}
/*
* dhcp_ip_default(): open and bind the default IP sockets used for I/O and
* interface control.
*
* input: none
* output: B_TRUE on success
*/
boolean_t
dhcp_ip_default(void)
{
int on;
if ((v4_sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
dhcpmsg(MSG_ERR,
"dhcp_ip_default: unable to create IPv4 socket");
return (B_FALSE);
}
if (setsockopt(v4_sock_fd, IPPROTO_IP, IP_RECVDSTADDR, &on,
sizeof (on)) == -1) {
dhcpmsg(MSG_ERR,
"dhcp_ip_default: unable to enable IP_RECVDSTADDR");
return (B_FALSE);
}
if (setsockopt(v4_sock_fd, IPPROTO_IP, IP_RECVIF, &on, sizeof (on)) ==
-1) {
dhcpmsg(MSG_ERR,
"dhcp_ip_default: unable to enable IP_RECVIF");
return (B_FALSE);
}
if (!bind_sock(v4_sock_fd, IPPORT_BOOTPC, INADDR_ANY)) {
dhcpmsg(MSG_ERROR,
"dhcp_ip_default: unable to bind IPv4 socket to port %d",
IPPORT_BOOTPC);
return (B_FALSE);
}
if (iu_register_event(eh, v4_sock_fd, POLLIN, dhcp_acknak_common,
NULL) == -1) {
dhcpmsg(MSG_WARNING, "dhcp_ip_default: cannot register to "
"receive IPv4 broadcasts");
return (B_FALSE);
}
if ((v6_sock_fd = socket(AF_INET6, SOCK_DGRAM, 0)) == -1) {
dhcpmsg(MSG_ERR,
"dhcp_ip_default: unable to create IPv6 socket");
return (B_FALSE);
}
if (setsockopt(v6_sock_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on,
sizeof (on)) == -1) {
dhcpmsg(MSG_ERR,
"dhcp_ip_default: unable to enable IPV6_RECVPKTINFO");
return (B_FALSE);
}
if (!bind_sock_v6(v6_sock_fd, IPPORT_DHCPV6C, NULL)) {
dhcpmsg(MSG_ERROR,
"dhcp_ip_default: unable to bind IPv6 socket to port %d",
IPPORT_DHCPV6C);
return (B_FALSE);
}
if (iu_register_event(eh, v6_sock_fd, POLLIN, dhcp_acknak_common,
NULL) == -1) {
dhcpmsg(MSG_WARNING, "dhcp_ip_default: cannot register to "
"receive IPv6 packets");
return (B_FALSE);
}
return (B_TRUE);
}