10Sstevel@tonic-gate /*
20Sstevel@tonic-gate * CDDL HEADER START
30Sstevel@tonic-gate *
40Sstevel@tonic-gate * The contents of this file are subject to the terms of the
53431Scarlsonj * Common Development and Distribution License (the "License").
63431Scarlsonj * You may not use this file except in compliance with the License.
70Sstevel@tonic-gate *
80Sstevel@tonic-gate * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
90Sstevel@tonic-gate * or http://www.opensolaris.org/os/licensing.
100Sstevel@tonic-gate * See the License for the specific language governing permissions
110Sstevel@tonic-gate * and limitations under the License.
120Sstevel@tonic-gate *
130Sstevel@tonic-gate * When distributing Covered Code, include this CDDL HEADER in each
140Sstevel@tonic-gate * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
150Sstevel@tonic-gate * If applicable, add the following below this CDDL HEADER, with the
160Sstevel@tonic-gate * fields enclosed by brackets "[]" replaced with your own identifying
170Sstevel@tonic-gate * information: Portions Copyright [yyyy] [name of copyright owner]
180Sstevel@tonic-gate *
190Sstevel@tonic-gate * CDDL HEADER END
200Sstevel@tonic-gate */
210Sstevel@tonic-gate /*
22*8485SPeter.Memishian@Sun.COM * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
230Sstevel@tonic-gate * Use is subject to license terms.
240Sstevel@tonic-gate */
250Sstevel@tonic-gate
260Sstevel@tonic-gate #include <string.h>
270Sstevel@tonic-gate #include <sys/types.h>
280Sstevel@tonic-gate #include <stdlib.h>
290Sstevel@tonic-gate #include <dhcpmsg.h>
300Sstevel@tonic-gate #include <stddef.h>
310Sstevel@tonic-gate #include <assert.h>
323431Scarlsonj #include <search.h>
333431Scarlsonj #include <alloca.h>
343431Scarlsonj #include <limits.h>
353431Scarlsonj #include <stropts.h>
363431Scarlsonj #include <netinet/dhcp6.h>
373431Scarlsonj #include <arpa/inet.h>
383431Scarlsonj #include <sys/sysmacros.h>
393431Scarlsonj #include <sys/sockio.h>
403431Scarlsonj #include <inet/ip6_asp.h>
410Sstevel@tonic-gate
420Sstevel@tonic-gate #include "states.h"
430Sstevel@tonic-gate #include "interface.h"
440Sstevel@tonic-gate #include "agent.h"
450Sstevel@tonic-gate #include "packet.h"
460Sstevel@tonic-gate #include "util.h"
470Sstevel@tonic-gate
483431Scarlsonj int v6_sock_fd = -1;
493431Scarlsonj int v4_sock_fd = -1;
503431Scarlsonj
513431Scarlsonj const in6_addr_t ipv6_all_dhcp_relay_and_servers = {
523431Scarlsonj 0xff, 0x02, 0x00, 0x00,
533431Scarlsonj 0x00, 0x00, 0x00, 0x00,
543431Scarlsonj 0x00, 0x00, 0x00, 0x00,
553431Scarlsonj 0x00, 0x01, 0x00, 0x02
563431Scarlsonj };
570Sstevel@tonic-gate
580Sstevel@tonic-gate /*
593431Scarlsonj * We have our own version of this constant because dhcpagent is compiled with
603431Scarlsonj * -lxnet.
610Sstevel@tonic-gate */
623431Scarlsonj const in6_addr_t my_in6addr_any = IN6ADDR_ANY_INIT;
630Sstevel@tonic-gate
643431Scarlsonj static void retransmit(iu_tq_t *, void *);
653431Scarlsonj static void next_retransmission(dhcp_smach_t *, boolean_t, boolean_t);
663431Scarlsonj static boolean_t send_pkt_internal(dhcp_smach_t *);
670Sstevel@tonic-gate
680Sstevel@tonic-gate /*
693431Scarlsonj * pkt_send_type(): returns an integer representing the packet's type; only
703431Scarlsonj * for use with outbound packets.
710Sstevel@tonic-gate *
723431Scarlsonj * input: dhcp_pkt_t *: the packet to examine
730Sstevel@tonic-gate * output: uchar_t: the packet type (0 if unknown)
740Sstevel@tonic-gate */
750Sstevel@tonic-gate
760Sstevel@tonic-gate static uchar_t
pkt_send_type(const dhcp_pkt_t * dpkt)773431Scarlsonj pkt_send_type(const dhcp_pkt_t *dpkt)
780Sstevel@tonic-gate {
793431Scarlsonj const uchar_t *option;
803431Scarlsonj
813431Scarlsonj if (dpkt->pkt_isv6)
823431Scarlsonj return (((const dhcpv6_message_t *)dpkt->pkt)->d6m_msg_type);
830Sstevel@tonic-gate
840Sstevel@tonic-gate /*
850Sstevel@tonic-gate * this is a little dirty but it should get the job done.
860Sstevel@tonic-gate * assumes that the type is in the statically allocated part
870Sstevel@tonic-gate * of the options field.
880Sstevel@tonic-gate */
890Sstevel@tonic-gate
903431Scarlsonj option = dpkt->pkt->options;
913431Scarlsonj for (;;) {
923431Scarlsonj if (*option == CD_PAD) {
933431Scarlsonj option++;
943431Scarlsonj continue;
953431Scarlsonj }
963431Scarlsonj if (*option == CD_END ||
973431Scarlsonj option + 2 - dpkt->pkt->options >=
983431Scarlsonj sizeof (dpkt->pkt->options))
990Sstevel@tonic-gate return (0);
1003431Scarlsonj if (*option == CD_DHCP_TYPE)
1013431Scarlsonj break;
1020Sstevel@tonic-gate option++;
1033431Scarlsonj option += *option + 1;
1040Sstevel@tonic-gate }
1050Sstevel@tonic-gate
1060Sstevel@tonic-gate return (option[2]);
1070Sstevel@tonic-gate }
1080Sstevel@tonic-gate
1090Sstevel@tonic-gate /*
1103431Scarlsonj * pkt_recv_type(): returns an integer representing the packet's type; only
1113431Scarlsonj * for use with inbound packets.
1123431Scarlsonj *
1133431Scarlsonj * input: dhcp_pkt_t *: the packet to examine
1143431Scarlsonj * output: uchar_t: the packet type (0 if unknown)
1153431Scarlsonj */
1163431Scarlsonj
1173431Scarlsonj uchar_t
pkt_recv_type(const PKT_LIST * plp)1183431Scarlsonj pkt_recv_type(const PKT_LIST *plp)
1193431Scarlsonj {
1203431Scarlsonj if (plp->isv6)
1213431Scarlsonj return (((const dhcpv6_message_t *)plp->pkt)->d6m_msg_type);
1223431Scarlsonj else if (plp->opts[CD_DHCP_TYPE] != NULL)
1233431Scarlsonj return (plp->opts[CD_DHCP_TYPE]->value[0]);
1243431Scarlsonj else
1253431Scarlsonj return (0);
1263431Scarlsonj }
1273431Scarlsonj
1283431Scarlsonj /*
1293431Scarlsonj * pkt_get_xid(): returns transaction ID from a DHCP packet.
1303431Scarlsonj *
1313431Scarlsonj * input: const PKT *: the packet to examine
1323431Scarlsonj * output: uint_t: the transaction ID (0 if unknown)
1333431Scarlsonj */
1343431Scarlsonj
1353431Scarlsonj uint_t
pkt_get_xid(const PKT * pkt,boolean_t isv6)1363431Scarlsonj pkt_get_xid(const PKT *pkt, boolean_t isv6)
1373431Scarlsonj {
1383431Scarlsonj if (pkt == NULL)
1393431Scarlsonj return (0);
1403431Scarlsonj if (isv6)
1413431Scarlsonj return (DHCPV6_GET_TRANSID((const dhcpv6_message_t *)pkt));
1423431Scarlsonj else
1433431Scarlsonj return (pkt->xid);
1443431Scarlsonj }
1453431Scarlsonj
1463431Scarlsonj /*
1470Sstevel@tonic-gate * init_pkt(): initializes and returns a packet of a given type
1480Sstevel@tonic-gate *
1493431Scarlsonj * input: dhcp_smach_t *: the state machine that will send the packet
1500Sstevel@tonic-gate * uchar_t: the packet type (DHCP message type)
1513431Scarlsonj * output: dhcp_pkt_t *: a pointer to the initialized packet; may be NULL
1520Sstevel@tonic-gate */
1530Sstevel@tonic-gate
1540Sstevel@tonic-gate dhcp_pkt_t *
init_pkt(dhcp_smach_t * dsmp,uchar_t type)1553431Scarlsonj init_pkt(dhcp_smach_t *dsmp, uchar_t type)
1560Sstevel@tonic-gate {
1573431Scarlsonj dhcp_pkt_t *dpkt = &dsmp->dsm_send_pkt;
1583431Scarlsonj dhcp_lif_t *lif = dsmp->dsm_lif;
1593431Scarlsonj dhcp_pif_t *pif = lif->lif_pif;
1605381Smeem uint_t mtu = lif->lif_max;
1610Sstevel@tonic-gate uint32_t xid;
1623431Scarlsonj boolean_t isv6;
1630Sstevel@tonic-gate
1643431Scarlsonj dpkt->pkt_isv6 = isv6 = pif->pif_isv6;
1650Sstevel@tonic-gate
1660Sstevel@tonic-gate /*
1675381Smeem * Since multiple dhcp leases may be maintained over the same pif
1685381Smeem * (e.g. "hme0" and "hme0:1"), make sure the xid is unique.
1693431Scarlsonj *
1703431Scarlsonj * Note that transaction ID zero is intentionally never assigned.
1713431Scarlsonj * That's used to represent "no ID." Also note that transaction IDs
1723431Scarlsonj * are only 24 bits long in DHCPv6.
1730Sstevel@tonic-gate */
1740Sstevel@tonic-gate
1750Sstevel@tonic-gate do {
1760Sstevel@tonic-gate xid = mrand48();
1773431Scarlsonj if (isv6)
1783431Scarlsonj xid &= 0xFFFFFF;
1793431Scarlsonj } while (xid == 0 ||
1803431Scarlsonj lookup_smach_by_xid(xid, NULL, dpkt->pkt_isv6) != NULL);
1813431Scarlsonj
1823431Scarlsonj if (isv6) {
1833431Scarlsonj dhcpv6_message_t *v6;
1843431Scarlsonj
1853431Scarlsonj if (mtu != dpkt->pkt_max_len &&
1863431Scarlsonj (v6 = realloc(dpkt->pkt, mtu)) != NULL) {
1873431Scarlsonj /* LINTED: alignment known to be correct */
1883431Scarlsonj dpkt->pkt = (PKT *)v6;
1893431Scarlsonj dpkt->pkt_max_len = mtu;
1903431Scarlsonj }
1913431Scarlsonj
1923431Scarlsonj if (sizeof (*v6) > dpkt->pkt_max_len) {
1933431Scarlsonj dhcpmsg(MSG_ERR, "init_pkt: cannot allocate v6 pkt: %u",
1943431Scarlsonj mtu);
1953431Scarlsonj return (NULL);
1963431Scarlsonj }
1973431Scarlsonj
1983431Scarlsonj v6 = (dhcpv6_message_t *)dpkt->pkt;
1993431Scarlsonj dpkt->pkt_cur_len = sizeof (*v6);
2003431Scarlsonj
2013431Scarlsonj (void) memset(v6, 0, dpkt->pkt_max_len);
2023431Scarlsonj
2033431Scarlsonj v6->d6m_msg_type = type;
2043431Scarlsonj DHCPV6_SET_TRANSID(v6, xid);
2053431Scarlsonj
2063431Scarlsonj if (dsmp->dsm_cidlen > 0 &&
2073431Scarlsonj add_pkt_opt(dpkt, DHCPV6_OPT_CLIENTID, dsmp->dsm_cid,
2083431Scarlsonj dsmp->dsm_cidlen) == NULL) {
2093431Scarlsonj dhcpmsg(MSG_WARNING,
2103431Scarlsonj "init_pkt: cannot insert client ID");
2113431Scarlsonj return (NULL);
2123431Scarlsonj }
2133431Scarlsonj
2143431Scarlsonj /* For v6, time starts with the creation of a transaction */
2153431Scarlsonj dsmp->dsm_neg_hrtime = gethrtime();
2163431Scarlsonj dsmp->dsm_newstart_monosec = monosec();
2173431Scarlsonj } else {
2183431Scarlsonj static uint8_t bootmagic[] = BOOTMAGIC;
2193431Scarlsonj PKT *v4;
2203431Scarlsonj
2213431Scarlsonj if (mtu != dpkt->pkt_max_len &&
2223431Scarlsonj (v4 = realloc(dpkt->pkt, mtu)) != NULL) {
2233431Scarlsonj dpkt->pkt = v4;
2243431Scarlsonj dpkt->pkt_max_len = mtu;
2253431Scarlsonj }
2260Sstevel@tonic-gate
2273431Scarlsonj if (offsetof(PKT, options) > dpkt->pkt_max_len) {
2283431Scarlsonj dhcpmsg(MSG_ERR, "init_pkt: cannot allocate v4 pkt: %u",
2293431Scarlsonj mtu);
2303431Scarlsonj return (NULL);
2313431Scarlsonj }
2323431Scarlsonj
2333431Scarlsonj v4 = dpkt->pkt;
2343431Scarlsonj dpkt->pkt_cur_len = offsetof(PKT, options);
2350Sstevel@tonic-gate
2363431Scarlsonj (void) memset(v4, 0, dpkt->pkt_max_len);
2373431Scarlsonj (void) memcpy(v4->cookie, bootmagic, sizeof (bootmagic));
2383431Scarlsonj if (pif->pif_hwlen <= sizeof (v4->chaddr)) {
2393431Scarlsonj v4->hlen = pif->pif_hwlen;
2403431Scarlsonj (void) memcpy(v4->chaddr, pif->pif_hwaddr,
2413431Scarlsonj pif->pif_hwlen);
2423431Scarlsonj } else {
2433431Scarlsonj /*
2443431Scarlsonj * The mac address does not fit in the chaddr
2453431Scarlsonj * field, thus it can not be sent to the server,
2463431Scarlsonj * thus server can not unicast the reply. Per
2473431Scarlsonj * RFC 2131 4.4.1, client can set this bit in
2483431Scarlsonj * DISCOVER/REQUEST. If the client is already
2495381Smeem * in a bound state, do not set this bit, as it
2505381Smeem * can respond to unicast responses from server
2515381Smeem * using the 'ciaddr' address.
2523431Scarlsonj */
2535381Smeem if (type == DISCOVER || (type == REQUEST &&
2545381Smeem !is_bound_state(dsmp->dsm_state)))
2553431Scarlsonj v4->flags = htons(BCAST_MASK);
2563431Scarlsonj }
2573431Scarlsonj
2583431Scarlsonj v4->xid = xid;
2593431Scarlsonj v4->op = BOOTREQUEST;
2603431Scarlsonj v4->htype = pif->pif_hwtype;
2613431Scarlsonj
2623431Scarlsonj if (add_pkt_opt(dpkt, CD_DHCP_TYPE, &type, 1) == NULL) {
2633431Scarlsonj dhcpmsg(MSG_WARNING,
2643431Scarlsonj "init_pkt: cannot set DHCP packet type");
2653431Scarlsonj return (NULL);
2663431Scarlsonj }
2673431Scarlsonj
2683431Scarlsonj if (dsmp->dsm_cidlen > 0 &&
2693431Scarlsonj add_pkt_opt(dpkt, CD_CLIENT_ID, dsmp->dsm_cid,
2703431Scarlsonj dsmp->dsm_cidlen) == NULL) {
2713431Scarlsonj dhcpmsg(MSG_WARNING,
2723431Scarlsonj "init_pkt: cannot insert client ID");
2733431Scarlsonj return (NULL);
2743431Scarlsonj }
2753431Scarlsonj }
2760Sstevel@tonic-gate
2770Sstevel@tonic-gate return (dpkt);
2780Sstevel@tonic-gate }
2790Sstevel@tonic-gate
2800Sstevel@tonic-gate /*
2813431Scarlsonj * remove_pkt_opt(): removes the first instance of an option from a dhcp_pkt_t
2823431Scarlsonj *
2833431Scarlsonj * input: dhcp_pkt_t *: the packet to remove the option from
2843431Scarlsonj * uint_t: the type of option being added
2853431Scarlsonj * output: boolean_t: B_TRUE on success, B_FALSE on failure
2863431Scarlsonj * note: currently does not work with DHCPv6 suboptions, or to remove
2873431Scarlsonj * arbitrary option instances.
2883431Scarlsonj */
2893431Scarlsonj
2903431Scarlsonj boolean_t
remove_pkt_opt(dhcp_pkt_t * dpkt,uint_t opt_type)2913431Scarlsonj remove_pkt_opt(dhcp_pkt_t *dpkt, uint_t opt_type)
2923431Scarlsonj {
2933431Scarlsonj uchar_t *raw_pkt, *raw_end, *next;
2943431Scarlsonj uint_t len;
2953431Scarlsonj
2963431Scarlsonj raw_pkt = (uchar_t *)dpkt->pkt;
2973431Scarlsonj raw_end = raw_pkt + dpkt->pkt_cur_len;
2983431Scarlsonj if (dpkt->pkt_isv6) {
2993431Scarlsonj dhcpv6_option_t d6o;
3003431Scarlsonj
3013431Scarlsonj raw_pkt += sizeof (dhcpv6_message_t);
3023431Scarlsonj
3033431Scarlsonj opt_type = htons(opt_type);
3043431Scarlsonj while (raw_pkt + sizeof (d6o) <= raw_end) {
3053431Scarlsonj (void) memcpy(&d6o, raw_pkt, sizeof (d6o));
3063431Scarlsonj len = ntohs(d6o.d6o_len) + sizeof (d6o);
3073431Scarlsonj if (len > raw_end - raw_pkt)
3083431Scarlsonj break;
3093431Scarlsonj next = raw_pkt + len;
3103431Scarlsonj if (d6o.d6o_code == opt_type) {
3113431Scarlsonj if (next < raw_end) {
3123431Scarlsonj (void) memmove(raw_pkt, next,
3133431Scarlsonj raw_end - next);
3143431Scarlsonj }
3153431Scarlsonj dpkt->pkt_cur_len -= len;
3163431Scarlsonj return (B_TRUE);
3173431Scarlsonj }
3183431Scarlsonj raw_pkt = next;
3193431Scarlsonj }
3203431Scarlsonj } else {
3213431Scarlsonj uchar_t *pstart, *padrun;
3223431Scarlsonj
3233431Scarlsonj raw_pkt += offsetof(PKT, options);
3243431Scarlsonj pstart = raw_pkt;
3253431Scarlsonj
3263431Scarlsonj if (opt_type == CD_END || opt_type == CD_PAD)
3273431Scarlsonj return (B_FALSE);
3283431Scarlsonj
3293431Scarlsonj padrun = NULL;
3303431Scarlsonj while (raw_pkt + 1 <= raw_end) {
3313431Scarlsonj if (*raw_pkt == CD_END)
3323431Scarlsonj break;
3333431Scarlsonj if (*raw_pkt == CD_PAD) {
3343431Scarlsonj if (padrun == NULL)
3353431Scarlsonj padrun = raw_pkt;
3363431Scarlsonj raw_pkt++;
3373431Scarlsonj continue;
3383431Scarlsonj }
3393431Scarlsonj if (raw_pkt + 2 > raw_end)
3403431Scarlsonj break;
3413431Scarlsonj len = raw_pkt[1];
3423431Scarlsonj if (len > raw_end - raw_pkt || len < 2)
3433431Scarlsonj break;
3443431Scarlsonj next = raw_pkt + len;
3453431Scarlsonj if (*raw_pkt == opt_type) {
3463431Scarlsonj if (next < raw_end) {
3473431Scarlsonj int toadd = (4 + ((next-pstart)&3) -
3483431Scarlsonj ((raw_pkt-pstart)&3)) & 3;
3493431Scarlsonj int torem = 4 - toadd;
3503431Scarlsonj
3513431Scarlsonj if (torem != 4 && padrun != NULL &&
3523431Scarlsonj (raw_pkt - padrun) >= torem) {
3533431Scarlsonj raw_pkt -= torem;
3543431Scarlsonj dpkt->pkt_cur_len -= torem;
3553431Scarlsonj } else if (toadd > 0) {
3563431Scarlsonj (void) memset(raw_pkt, CD_PAD,
3573431Scarlsonj toadd);
3583431Scarlsonj raw_pkt += toadd;
3593431Scarlsonj /* max is not an issue here */
3603431Scarlsonj dpkt->pkt_cur_len += toadd;
3613431Scarlsonj }
3623431Scarlsonj if (raw_pkt != next) {
3633431Scarlsonj (void) memmove(raw_pkt, next,
3643431Scarlsonj raw_end - next);
3653431Scarlsonj }
3663431Scarlsonj }
3673431Scarlsonj dpkt->pkt_cur_len -= len;
3683431Scarlsonj return (B_TRUE);
3693431Scarlsonj }
3703431Scarlsonj padrun = NULL;
3713431Scarlsonj raw_pkt = next;
3723431Scarlsonj }
3733431Scarlsonj }
3743431Scarlsonj return (B_FALSE);
3753431Scarlsonj }
3763431Scarlsonj
3773431Scarlsonj /*
3783431Scarlsonj * update_v6opt_len(): updates the length field of a DHCPv6 option.
3793431Scarlsonj *
3803431Scarlsonj * input: dhcpv6_option_t *: option to be updated
3813431Scarlsonj * int: number of octets to add or subtract
3823431Scarlsonj * output: boolean_t: B_TRUE on success, B_FALSE on failure
3833431Scarlsonj */
3843431Scarlsonj
3853431Scarlsonj boolean_t
update_v6opt_len(dhcpv6_option_t * opt,int adjust)3863431Scarlsonj update_v6opt_len(dhcpv6_option_t *opt, int adjust)
3873431Scarlsonj {
3883431Scarlsonj dhcpv6_option_t optval;
3893431Scarlsonj
3903431Scarlsonj (void) memcpy(&optval, opt, sizeof (optval));
3913431Scarlsonj adjust += ntohs(optval.d6o_len);
3923431Scarlsonj if (adjust < 0 || adjust > UINT16_MAX) {
3933431Scarlsonj return (B_FALSE);
3943431Scarlsonj } else {
3953431Scarlsonj optval.d6o_len = htons(adjust);
3963431Scarlsonj (void) memcpy(opt, &optval, sizeof (optval));
3973431Scarlsonj return (B_TRUE);
3983431Scarlsonj }
3993431Scarlsonj }
4003431Scarlsonj
4013431Scarlsonj /*
4020Sstevel@tonic-gate * add_pkt_opt(): adds an option to a dhcp_pkt_t
4030Sstevel@tonic-gate *
4040Sstevel@tonic-gate * input: dhcp_pkt_t *: the packet to add the option to
4053431Scarlsonj * uint_t: the type of option being added
4060Sstevel@tonic-gate * const void *: the value of that option
4073431Scarlsonj * uint_t: the length of the value of the option
4083431Scarlsonj * output: void *: pointer to the option that was added, or NULL on failure.
4090Sstevel@tonic-gate */
4100Sstevel@tonic-gate
4113431Scarlsonj void *
add_pkt_opt(dhcp_pkt_t * dpkt,uint_t opt_type,const void * opt_val,uint_t opt_len)4123431Scarlsonj add_pkt_opt(dhcp_pkt_t *dpkt, uint_t opt_type, const void *opt_val,
4133431Scarlsonj uint_t opt_len)
4140Sstevel@tonic-gate {
4153431Scarlsonj uchar_t *raw_pkt;
4163431Scarlsonj int req_len;
4173431Scarlsonj void *optr;
4183431Scarlsonj
4193431Scarlsonj raw_pkt = (uchar_t *)dpkt->pkt;
4203431Scarlsonj optr = raw_pkt + dpkt->pkt_cur_len;
4213431Scarlsonj if (dpkt->pkt_isv6) {
4223431Scarlsonj dhcpv6_option_t d6o;
4233431Scarlsonj
4243431Scarlsonj req_len = opt_len + sizeof (d6o);
4253431Scarlsonj
4263431Scarlsonj if (dpkt->pkt_cur_len + req_len > dpkt->pkt_max_len) {
4273431Scarlsonj dhcpmsg(MSG_WARNING,
4283431Scarlsonj "add_pkt_opt: not enough room for v6 option %u in "
4293431Scarlsonj "packet (%u + %u > %u)", opt_type,
4303431Scarlsonj dpkt->pkt_cur_len, req_len, dpkt->pkt_max_len);
4313431Scarlsonj return (NULL);
4323431Scarlsonj }
4333431Scarlsonj d6o.d6o_code = htons(opt_type);
4343431Scarlsonj d6o.d6o_len = htons(opt_len);
4353431Scarlsonj (void) memcpy(&raw_pkt[dpkt->pkt_cur_len], &d6o, sizeof (d6o));
4363431Scarlsonj dpkt->pkt_cur_len += sizeof (d6o);
4373431Scarlsonj if (opt_len > 0) {
4383431Scarlsonj (void) memcpy(&raw_pkt[dpkt->pkt_cur_len], opt_val,
4393431Scarlsonj opt_len);
4403431Scarlsonj dpkt->pkt_cur_len += opt_len;
4413431Scarlsonj }
4423431Scarlsonj } else {
4433431Scarlsonj req_len = opt_len + 2; /* + 2 for code & length bytes */
4443431Scarlsonj
4453431Scarlsonj /* CD_END and CD_PAD options don't have a length field */
4463431Scarlsonj if (opt_type == CD_END || opt_type == CD_PAD) {
4473431Scarlsonj req_len = 1;
4483431Scarlsonj } else if (opt_val == NULL) {
4493431Scarlsonj dhcpmsg(MSG_ERROR, "add_pkt_opt: option type %d is "
4503431Scarlsonj "missing required value", opt_type);
4513431Scarlsonj return (NULL);
4523431Scarlsonj }
4533431Scarlsonj
4543431Scarlsonj if ((dpkt->pkt_cur_len + req_len) > dpkt->pkt_max_len) {
4553431Scarlsonj dhcpmsg(MSG_WARNING,
4563431Scarlsonj "add_pkt_opt: not enough room for v4 option %u in "
4573431Scarlsonj "packet", opt_type);
4583431Scarlsonj return (NULL);
4593431Scarlsonj }
4603431Scarlsonj
4613431Scarlsonj raw_pkt[dpkt->pkt_cur_len++] = opt_type;
4620Sstevel@tonic-gate
4633431Scarlsonj if (req_len > 1) {
4643431Scarlsonj raw_pkt[dpkt->pkt_cur_len++] = opt_len;
4653431Scarlsonj if (opt_len > 0) {
4663431Scarlsonj (void) memcpy(&raw_pkt[dpkt->pkt_cur_len],
4673431Scarlsonj opt_val, opt_len);
4683431Scarlsonj dpkt->pkt_cur_len += opt_len;
4693431Scarlsonj }
4703431Scarlsonj }
4713431Scarlsonj }
4723431Scarlsonj return (optr);
4733431Scarlsonj }
4740Sstevel@tonic-gate
4753431Scarlsonj /*
4763431Scarlsonj * add_pkt_subopt(): adds an option to a dhcp_pkt_t option. DHCPv6-specific,
4773431Scarlsonj * but could be extended to IPv4 DHCP if necessary. Assumes
4783431Scarlsonj * that if the parent isn't a top-level option, the caller
4793431Scarlsonj * will adjust any upper-level options recursively using
4803431Scarlsonj * update_v6opt_len.
4813431Scarlsonj *
4823431Scarlsonj * input: dhcp_pkt_t *: the packet to add the suboption to
4833431Scarlsonj * dhcpv6_option_t *: the start of the option to that should contain
4843431Scarlsonj * it (parent)
4853431Scarlsonj * uint_t: the type of suboption being added
4863431Scarlsonj * const void *: the value of that option
4873431Scarlsonj * uint_t: the length of the value of the option
4883431Scarlsonj * output: void *: pointer to the suboption that was added, or NULL on
4893431Scarlsonj * failure.
4903431Scarlsonj */
4913431Scarlsonj
4923431Scarlsonj void *
add_pkt_subopt(dhcp_pkt_t * dpkt,dhcpv6_option_t * parentopt,uint_t opt_type,const void * opt_val,uint_t opt_len)4933431Scarlsonj add_pkt_subopt(dhcp_pkt_t *dpkt, dhcpv6_option_t *parentopt, uint_t opt_type,
4943431Scarlsonj const void *opt_val, uint_t opt_len)
4953431Scarlsonj {
4963431Scarlsonj uchar_t *raw_pkt;
4973431Scarlsonj int req_len;
4983431Scarlsonj void *optr;
4993431Scarlsonj dhcpv6_option_t d6o;
5003431Scarlsonj uchar_t *optend;
5013431Scarlsonj int olen;
5023431Scarlsonj
5033431Scarlsonj if (!dpkt->pkt_isv6)
5043431Scarlsonj return (NULL);
5053431Scarlsonj
5063431Scarlsonj raw_pkt = (uchar_t *)dpkt->pkt;
5073431Scarlsonj req_len = opt_len + sizeof (d6o);
5083431Scarlsonj
5093431Scarlsonj if (dpkt->pkt_cur_len + req_len > dpkt->pkt_max_len) {
5103431Scarlsonj dhcpmsg(MSG_WARNING,
5113431Scarlsonj "add_pkt_subopt: not enough room for v6 suboption %u in "
5123431Scarlsonj "packet (%u + %u > %u)", opt_type,
5133431Scarlsonj dpkt->pkt_cur_len, req_len, dpkt->pkt_max_len);
5143431Scarlsonj return (NULL);
5150Sstevel@tonic-gate }
5160Sstevel@tonic-gate
5173431Scarlsonj /*
5183431Scarlsonj * Update the parent option to include room for this option,
5193431Scarlsonj * and compute the insertion point.
5203431Scarlsonj */
5213431Scarlsonj (void) memcpy(&d6o, parentopt, sizeof (d6o));
5223431Scarlsonj olen = ntohs(d6o.d6o_len);
5233431Scarlsonj optend = (uchar_t *)(parentopt + 1) + olen;
5243431Scarlsonj olen += req_len;
5253431Scarlsonj d6o.d6o_len = htons(olen);
5263431Scarlsonj (void) memcpy(parentopt, &d6o, sizeof (d6o));
5270Sstevel@tonic-gate
5283431Scarlsonj /*
5293431Scarlsonj * If there's anything at the end to move, then move it. Also bump up
5303431Scarlsonj * the packet size.
5313431Scarlsonj */
5323431Scarlsonj if (optend < raw_pkt + dpkt->pkt_cur_len) {
5333431Scarlsonj (void) memmove(optend + req_len, optend,
5343431Scarlsonj (raw_pkt + dpkt->pkt_cur_len) - optend);
5350Sstevel@tonic-gate }
5363431Scarlsonj dpkt->pkt_cur_len += req_len;
5373431Scarlsonj
5383431Scarlsonj /*
5393431Scarlsonj * Now format the suboption and add it in.
5403431Scarlsonj */
5413431Scarlsonj optr = optend;
5423431Scarlsonj d6o.d6o_code = htons(opt_type);
5433431Scarlsonj d6o.d6o_len = htons(opt_len);
5443431Scarlsonj (void) memcpy(optend, &d6o, sizeof (d6o));
5453431Scarlsonj if (opt_len > 0)
5463431Scarlsonj (void) memcpy(optend + sizeof (d6o), opt_val, opt_len);
5473431Scarlsonj return (optr);
5480Sstevel@tonic-gate }
5490Sstevel@tonic-gate
5500Sstevel@tonic-gate /*
5510Sstevel@tonic-gate * add_pkt_opt16(): adds an option with a 16-bit value to a dhcp_pkt_t
5520Sstevel@tonic-gate *
5530Sstevel@tonic-gate * input: dhcp_pkt_t *: the packet to add the option to
5543431Scarlsonj * uint_t: the type of option being added
5550Sstevel@tonic-gate * uint16_t: the value of that option
5563431Scarlsonj * output: void *: pointer to the option that was added, or NULL on failure.
5570Sstevel@tonic-gate */
5580Sstevel@tonic-gate
5593431Scarlsonj void *
add_pkt_opt16(dhcp_pkt_t * dpkt,uint_t opt_type,uint16_t opt_value)5603431Scarlsonj add_pkt_opt16(dhcp_pkt_t *dpkt, uint_t opt_type, uint16_t opt_value)
5610Sstevel@tonic-gate {
5623431Scarlsonj return (add_pkt_opt(dpkt, opt_type, &opt_value, 2));
5630Sstevel@tonic-gate }
5640Sstevel@tonic-gate
5650Sstevel@tonic-gate /*
5660Sstevel@tonic-gate * add_pkt_opt32(): adds an option with a 32-bit value to a dhcp_pkt_t
5670Sstevel@tonic-gate *
5680Sstevel@tonic-gate * input: dhcp_pkt_t *: the packet to add the option to
5693431Scarlsonj * uint_t: the type of option being added
5700Sstevel@tonic-gate * uint32_t: the value of that option
5713431Scarlsonj * output: void *: pointer to the option that was added, or NULL on failure.
5723431Scarlsonj */
5733431Scarlsonj
5743431Scarlsonj void *
add_pkt_opt32(dhcp_pkt_t * dpkt,uint_t opt_type,uint32_t opt_value)5753431Scarlsonj add_pkt_opt32(dhcp_pkt_t *dpkt, uint_t opt_type, uint32_t opt_value)
5763431Scarlsonj {
5773431Scarlsonj return (add_pkt_opt(dpkt, opt_type, &opt_value, 4));
5783431Scarlsonj }
5793431Scarlsonj
5803431Scarlsonj /*
5813431Scarlsonj * add_pkt_prl(): adds the parameter request option to the packet
5823431Scarlsonj *
5833431Scarlsonj * input: dhcp_pkt_t *: the packet to add the option to
5843431Scarlsonj * dhcp_smach_t *: state machine with request option
5853431Scarlsonj * output: void *: pointer to the option that was added, or NULL on failure.
5860Sstevel@tonic-gate */
5870Sstevel@tonic-gate
5883431Scarlsonj void *
add_pkt_prl(dhcp_pkt_t * dpkt,dhcp_smach_t * dsmp)5893431Scarlsonj add_pkt_prl(dhcp_pkt_t *dpkt, dhcp_smach_t *dsmp)
5900Sstevel@tonic-gate {
5913431Scarlsonj uint_t len;
5923431Scarlsonj
5933431Scarlsonj if (dsmp->dsm_prllen == 0)
5943431Scarlsonj return (0);
5953431Scarlsonj
5963431Scarlsonj if (dpkt->pkt_isv6) {
5973431Scarlsonj uint16_t *prl;
5983431Scarlsonj
5993431Scarlsonj /*
6003431Scarlsonj * RFC 3315 requires that we include the option, even if we
6013431Scarlsonj * have nothing to request.
6023431Scarlsonj */
6033431Scarlsonj if (dsmp->dsm_prllen == 0)
6043431Scarlsonj prl = NULL;
6053431Scarlsonj else
6063431Scarlsonj prl = alloca(dsmp->dsm_prllen * sizeof (uint16_t));
6073431Scarlsonj
6083431Scarlsonj for (len = 0; len < dsmp->dsm_prllen; len++)
6093431Scarlsonj prl[len] = htons(dsmp->dsm_prl[len]);
6103431Scarlsonj return (add_pkt_opt(dpkt, DHCPV6_OPT_ORO, prl,
6113431Scarlsonj len * sizeof (uint16_t)));
6123431Scarlsonj } else {
6133431Scarlsonj uint8_t *prl = alloca(dsmp->dsm_prllen);
6143431Scarlsonj
6153431Scarlsonj for (len = 0; len < dsmp->dsm_prllen; len++)
6163431Scarlsonj prl[len] = dsmp->dsm_prl[len];
6173431Scarlsonj return (add_pkt_opt(dpkt, CD_REQUEST_LIST, prl, len));
6183431Scarlsonj }
6190Sstevel@tonic-gate }
6200Sstevel@tonic-gate
6210Sstevel@tonic-gate /*
6223431Scarlsonj * add_pkt_lif(): Adds CD_REQUESTED_IP_ADDR (IPv4 DHCP) or IA_NA and IAADDR
6233431Scarlsonj * (DHCPv6) options to the packet to represent the given LIF.
6240Sstevel@tonic-gate *
6253431Scarlsonj * input: dhcp_pkt_t *: the packet to add the options to
6263431Scarlsonj * dhcp_lif_t *: the logical interface to represent
6273431Scarlsonj * int: status code (unused for IPv4 DHCP)
6283431Scarlsonj * const char *: message to include with status option, or NULL
6293431Scarlsonj * output: boolean_t: B_TRUE on success, B_FALSE on failure
6300Sstevel@tonic-gate */
6310Sstevel@tonic-gate
6323431Scarlsonj boolean_t
add_pkt_lif(dhcp_pkt_t * dpkt,dhcp_lif_t * lif,int status,const char * msg)6333431Scarlsonj add_pkt_lif(dhcp_pkt_t *dpkt, dhcp_lif_t *lif, int status, const char *msg)
6340Sstevel@tonic-gate {
6353431Scarlsonj if (lif->lif_pif->pif_isv6) {
6363431Scarlsonj dhcp_smach_t *dsmp;
6373431Scarlsonj dhcpv6_message_t *d6m;
6383431Scarlsonj dhcpv6_ia_na_t d6in;
6393431Scarlsonj dhcpv6_iaaddr_t d6ia;
6403431Scarlsonj uint32_t iaid;
6413431Scarlsonj uint16_t *statusopt;
6423431Scarlsonj dhcpv6_option_t *d6o, *d6so;
6433431Scarlsonj uint_t olen;
6443431Scarlsonj
6453431Scarlsonj /*
6463431Scarlsonj * Currently, we support just one IAID related to the primary
6473431Scarlsonj * LIF on the state machine.
6483431Scarlsonj */
6493431Scarlsonj dsmp = lif->lif_lease->dl_smach;
6503431Scarlsonj iaid = dsmp->dsm_lif->lif_iaid;
6513431Scarlsonj iaid = htonl(iaid);
6523431Scarlsonj
6533431Scarlsonj d6m = (dhcpv6_message_t *)dpkt->pkt;
6540Sstevel@tonic-gate
6553431Scarlsonj /*
6563431Scarlsonj * Find or create the IA_NA needed for this LIF. If we
6573431Scarlsonj * supported IA_TA, we'd check the IFF_TEMPORARY bit here.
6583431Scarlsonj */
6593431Scarlsonj d6o = NULL;
6603431Scarlsonj while ((d6o = dhcpv6_find_option(d6m + 1,
6613431Scarlsonj dpkt->pkt_cur_len - sizeof (*d6m), d6o, DHCPV6_OPT_IA_NA,
6623431Scarlsonj &olen)) != NULL) {
6633431Scarlsonj if (olen < sizeof (d6in))
6643431Scarlsonj continue;
6653431Scarlsonj (void) memcpy(&d6in, d6o, sizeof (d6in));
6663431Scarlsonj if (d6in.d6in_iaid == iaid)
6673431Scarlsonj break;
6683431Scarlsonj }
6693431Scarlsonj if (d6o == NULL) {
6703431Scarlsonj d6in.d6in_iaid = iaid;
6713431Scarlsonj d6in.d6in_t1 = 0;
6723431Scarlsonj d6in.d6in_t2 = 0;
6733431Scarlsonj d6o = add_pkt_opt(dpkt, DHCPV6_OPT_IA_NA,
6743431Scarlsonj (dhcpv6_option_t *)&d6in + 1,
6753431Scarlsonj sizeof (d6in) - sizeof (*d6o));
6763431Scarlsonj if (d6o == NULL)
6773431Scarlsonj return (B_FALSE);
6783431Scarlsonj }
6790Sstevel@tonic-gate
6803431Scarlsonj /*
6813431Scarlsonj * Now add the IAADDR suboption for this LIF. No need to
6823431Scarlsonj * search here, as we know that this is unique.
6833431Scarlsonj */
6843431Scarlsonj d6ia.d6ia_addr = lif->lif_v6addr;
6850Sstevel@tonic-gate
6863431Scarlsonj /*
6873431Scarlsonj * For Release and Decline, we zero out the lifetime. For
6883431Scarlsonj * Renew and Rebind, we report the original time as the
6893431Scarlsonj * preferred and valid lifetimes.
6903431Scarlsonj */
6913431Scarlsonj if (d6m->d6m_msg_type == DHCPV6_MSG_RELEASE ||
6923431Scarlsonj d6m->d6m_msg_type == DHCPV6_MSG_DECLINE) {
6933431Scarlsonj d6ia.d6ia_preflife = 0;
6943431Scarlsonj d6ia.d6ia_vallife = 0;
6953431Scarlsonj } else {
6963431Scarlsonj d6ia.d6ia_preflife = htonl(lif->lif_preferred.dt_start);
6973431Scarlsonj d6ia.d6ia_vallife = htonl(lif->lif_expire.dt_start);
6983431Scarlsonj }
6993431Scarlsonj d6so = add_pkt_subopt(dpkt, d6o, DHCPV6_OPT_IAADDR,
7003431Scarlsonj (dhcpv6_option_t *)&d6ia + 1,
7013431Scarlsonj sizeof (d6ia) - sizeof (*d6o));
7023431Scarlsonj if (d6so == NULL)
7033431Scarlsonj return (B_FALSE);
7040Sstevel@tonic-gate
7053431Scarlsonj /*
7063431Scarlsonj * Add a status code suboption to the IAADDR to tell the server
7073431Scarlsonj * why we're declining the address. Note that we must manually
7083431Scarlsonj * update the enclosing IA_NA, as add_pkt_subopt doesn't know
7093431Scarlsonj * how to do that.
7103431Scarlsonj */
7113431Scarlsonj if (status != DHCPV6_STAT_SUCCESS || msg != NULL) {
7123431Scarlsonj olen = sizeof (*statusopt) +
7133431Scarlsonj (msg == NULL ? 0 : strlen(msg));
7143431Scarlsonj statusopt = alloca(olen);
7153431Scarlsonj *statusopt = htons(status);
7163431Scarlsonj if (msg != NULL) {
7173431Scarlsonj (void) memcpy((char *)(statusopt + 1), msg,
7183431Scarlsonj olen - sizeof (*statusopt));
7193431Scarlsonj }
7203431Scarlsonj d6so = add_pkt_subopt(dpkt, d6so,
7213431Scarlsonj DHCPV6_OPT_STATUS_CODE, statusopt, olen);
7223431Scarlsonj if (d6so != NULL) {
7233431Scarlsonj /*
7243431Scarlsonj * Update for length of suboption header and
7253431Scarlsonj * suboption contents.
7263431Scarlsonj */
7273431Scarlsonj (void) update_v6opt_len(d6o, sizeof (*d6so) +
7283431Scarlsonj olen);
7293431Scarlsonj }
7303431Scarlsonj }
7313431Scarlsonj } else {
7323431Scarlsonj /*
7333431Scarlsonj * For DECLINE, we need to add the CD_REQUESTED_IP_ADDR option.
7343431Scarlsonj * In all other cases (RELEASE and REQUEST), we need to set
7353431Scarlsonj * ciadr.
7363431Scarlsonj */
7373431Scarlsonj if (pkt_send_type(dpkt) == DECLINE) {
7383431Scarlsonj if (!add_pkt_opt32(dpkt, CD_REQUESTED_IP_ADDR,
7393431Scarlsonj lif->lif_addr))
7403431Scarlsonj return (B_FALSE);
7413431Scarlsonj } else {
7423431Scarlsonj dpkt->pkt->ciaddr.s_addr = lif->lif_addr;
7433431Scarlsonj }
7443431Scarlsonj
7453431Scarlsonj /*
7463431Scarlsonj * It's not too worrisome if the message fails to fit in the
7473431Scarlsonj * packet. The result will still be valid.
7483431Scarlsonj */
7493431Scarlsonj if (msg != NULL)
7503431Scarlsonj (void) add_pkt_opt(dpkt, CD_MESSAGE, msg,
7513431Scarlsonj strlen(msg) + 1);
7520Sstevel@tonic-gate }
7533431Scarlsonj return (B_TRUE);
7540Sstevel@tonic-gate }
7550Sstevel@tonic-gate
7560Sstevel@tonic-gate /*
7573431Scarlsonj * free_pkt_entry(): frees a packet list list entry
7580Sstevel@tonic-gate *
7593431Scarlsonj * input: PKT_LIST *: the packet list entry to free
7603431Scarlsonj * output: void
7610Sstevel@tonic-gate */
7623431Scarlsonj void
free_pkt_entry(PKT_LIST * plp)7633431Scarlsonj free_pkt_entry(PKT_LIST *plp)
7640Sstevel@tonic-gate {
7653431Scarlsonj if (plp != NULL) {
7663431Scarlsonj free(plp->pkt);
7673431Scarlsonj free(plp);
7683431Scarlsonj }
7690Sstevel@tonic-gate }
7700Sstevel@tonic-gate
7710Sstevel@tonic-gate /*
7723431Scarlsonj * free_pkt_list(): frees an entire packet list
7730Sstevel@tonic-gate *
7740Sstevel@tonic-gate * input: PKT_LIST **: the packet list to free
7750Sstevel@tonic-gate * output: void
7760Sstevel@tonic-gate */
7770Sstevel@tonic-gate
7780Sstevel@tonic-gate void
free_pkt_list(PKT_LIST ** head)7793431Scarlsonj free_pkt_list(PKT_LIST **head)
7800Sstevel@tonic-gate {
7813431Scarlsonj PKT_LIST *plp;
7820Sstevel@tonic-gate
7833431Scarlsonj while ((plp = *head) != NULL) {
7843431Scarlsonj remque(plp);
7853431Scarlsonj free_pkt_entry(plp);
7860Sstevel@tonic-gate }
7870Sstevel@tonic-gate }
7880Sstevel@tonic-gate
7890Sstevel@tonic-gate /*
7900Sstevel@tonic-gate * send_pkt_internal(): sends a packet out on an interface
7910Sstevel@tonic-gate *
7923431Scarlsonj * input: dhcp_smach_t *: the state machine with a packet to send
7933431Scarlsonj * output: boolean_t: B_TRUE if the packet is sent, B_FALSE otherwise
7940Sstevel@tonic-gate */
7950Sstevel@tonic-gate
7963431Scarlsonj static boolean_t
send_pkt_internal(dhcp_smach_t * dsmp)7973431Scarlsonj send_pkt_internal(dhcp_smach_t *dsmp)
7980Sstevel@tonic-gate {
7990Sstevel@tonic-gate ssize_t n_bytes;
8003431Scarlsonj dhcp_lif_t *lif = dsmp->dsm_lif;
8013431Scarlsonj dhcp_pkt_t *dpkt = &dsmp->dsm_send_pkt;
8023431Scarlsonj uchar_t ptype = pkt_send_type(dpkt);
8033431Scarlsonj const char *pkt_name;
8043431Scarlsonj struct iovec iov;
8053431Scarlsonj struct msghdr msg;
8063431Scarlsonj struct cmsghdr *cmsg;
8073431Scarlsonj struct in6_pktinfo *ipi6;
8083431Scarlsonj boolean_t ismcast;
8095381Smeem int msgtype;
8103431Scarlsonj
8113431Scarlsonj /*
8123431Scarlsonj * Timer should not be running at the point we go to send a packet.
8133431Scarlsonj */
8143431Scarlsonj if (dsmp->dsm_retrans_timer != -1) {
8153431Scarlsonj dhcpmsg(MSG_CRIT, "send_pkt_internal: unexpected retransmit "
8163431Scarlsonj "timer on %s", dsmp->dsm_name);
8173431Scarlsonj stop_pkt_retransmission(dsmp);
8183431Scarlsonj }
8193431Scarlsonj
8203431Scarlsonj pkt_name = pkt_type_to_string(ptype, dpkt->pkt_isv6);
8210Sstevel@tonic-gate
8220Sstevel@tonic-gate /*
8230Sstevel@tonic-gate * if needed, schedule a retransmission timer, then attempt to
8240Sstevel@tonic-gate * send the packet. if we fail, then log the error. our
8250Sstevel@tonic-gate * return value should indicate whether or not we were
8260Sstevel@tonic-gate * successful in sending the request, independent of whether
8270Sstevel@tonic-gate * we could schedule a timer.
8280Sstevel@tonic-gate */
8290Sstevel@tonic-gate
8303431Scarlsonj if (dsmp->dsm_send_timeout != 0) {
8313431Scarlsonj if ((dsmp->dsm_retrans_timer = iu_schedule_timer_ms(tq,
8323431Scarlsonj dsmp->dsm_send_timeout, retransmit, dsmp)) == -1)
8330Sstevel@tonic-gate dhcpmsg(MSG_WARNING, "send_pkt_internal: cannot "
8340Sstevel@tonic-gate "schedule retransmit timer for %s packet",
8350Sstevel@tonic-gate pkt_name);
8360Sstevel@tonic-gate else
8373431Scarlsonj hold_smach(dsmp);
8383431Scarlsonj }
8393431Scarlsonj
8403431Scarlsonj if (dpkt->pkt_isv6) {
8413431Scarlsonj hrtime_t delta;
8423431Scarlsonj
8433431Scarlsonj /*
8443431Scarlsonj * Convert current time into centiseconds since transaction
8453431Scarlsonj * started. This is what DHCPv6 expects to see in the Elapsed
8463431Scarlsonj * Time option.
8473431Scarlsonj */
8483431Scarlsonj delta = (gethrtime() - dsmp->dsm_neg_hrtime) /
8493431Scarlsonj (NANOSEC / 100);
8503431Scarlsonj if (delta > DHCPV6_FOREVER)
8513431Scarlsonj delta = DHCPV6_FOREVER;
8523431Scarlsonj (void) remove_pkt_opt(dpkt, DHCPV6_OPT_ELAPSED_TIME);
8533431Scarlsonj (void) add_pkt_opt16(dpkt, DHCPV6_OPT_ELAPSED_TIME,
8543431Scarlsonj htons(delta));
8553431Scarlsonj } else {
8563431Scarlsonj /*
8573431Scarlsonj * set the `pkt->secs' field depending on the type of packet.
8583431Scarlsonj * it should be zero, except in the following cases:
8593431Scarlsonj *
8603431Scarlsonj * DISCOVER: set to the number of seconds since we started
8613431Scarlsonj * trying to obtain a lease.
8623431Scarlsonj *
8633431Scarlsonj * INFORM: set to the number of seconds since we started
8643431Scarlsonj * trying to get configuration parameters.
8653431Scarlsonj *
8663431Scarlsonj * REQUEST: if in the REQUESTING state, then same value as
8673431Scarlsonj * DISCOVER, otherwise the number of seconds
8683431Scarlsonj * since we started trying to obtain a lease.
8693431Scarlsonj *
8703431Scarlsonj * we also set `dsm_newstart_monosec', to the time we sent a
8713431Scarlsonj * REQUEST or DISCOVER packet, so we know the lease start
8723431Scarlsonj * time (the DISCOVER case is for handling BOOTP servers).
8733431Scarlsonj */
8743431Scarlsonj
8753431Scarlsonj switch (ptype) {
8763431Scarlsonj
8773431Scarlsonj case DISCOVER:
8783431Scarlsonj dsmp->dsm_newstart_monosec = monosec();
8793431Scarlsonj dsmp->dsm_disc_secs = dsmp->dsm_newstart_monosec -
8803431Scarlsonj hrtime_to_monosec(dsmp->dsm_neg_hrtime);
8813431Scarlsonj dpkt->pkt->secs = htons(dsmp->dsm_disc_secs);
8823431Scarlsonj break;
8833431Scarlsonj
8843431Scarlsonj case INFORM:
8853431Scarlsonj dpkt->pkt->secs = htons(monosec() -
8863431Scarlsonj hrtime_to_monosec(dsmp->dsm_neg_hrtime));
8873431Scarlsonj break;
8883431Scarlsonj
8893431Scarlsonj case REQUEST:
8903431Scarlsonj dsmp->dsm_newstart_monosec = monosec();
8913431Scarlsonj
8923431Scarlsonj if (dsmp->dsm_state == REQUESTING) {
8933431Scarlsonj dpkt->pkt->secs = htons(dsmp->dsm_disc_secs);
8943431Scarlsonj break;
8953431Scarlsonj }
8963431Scarlsonj
8973431Scarlsonj dpkt->pkt->secs = htons(monosec() -
8983431Scarlsonj hrtime_to_monosec(dsmp->dsm_neg_hrtime));
8993431Scarlsonj break;
9003431Scarlsonj
9013431Scarlsonj default:
9023431Scarlsonj dpkt->pkt->secs = htons(0);
9033431Scarlsonj break;
9043431Scarlsonj }
9050Sstevel@tonic-gate }
9060Sstevel@tonic-gate
9073431Scarlsonj if (dpkt->pkt_isv6) {
9083431Scarlsonj struct sockaddr_in6 sin6;
9093431Scarlsonj
9103431Scarlsonj (void) memset(&iov, 0, sizeof (iov));
9113431Scarlsonj iov.iov_base = dpkt->pkt;
9123431Scarlsonj iov.iov_len = dpkt->pkt_cur_len;
9133431Scarlsonj
9143431Scarlsonj (void) memset(&msg, 0, sizeof (msg));
9153431Scarlsonj msg.msg_name = &dsmp->dsm_send_dest.v6;
9163431Scarlsonj msg.msg_namelen = sizeof (struct sockaddr_in6);
9173431Scarlsonj msg.msg_iov = &iov;
9183431Scarlsonj msg.msg_iovlen = 1;
9193431Scarlsonj
9203431Scarlsonj /*
9213431Scarlsonj * If the address that's requested cannot be reached, then fall
9223431Scarlsonj * back to the multcast address.
9233431Scarlsonj */
9243431Scarlsonj if (IN6_IS_ADDR_MULTICAST(&dsmp->dsm_send_dest.v6.sin6_addr)) {
9253431Scarlsonj ismcast = B_TRUE;
9263431Scarlsonj } else {
9273431Scarlsonj struct dstinforeq dinfo;
9283431Scarlsonj struct strioctl str;
9290Sstevel@tonic-gate
9303431Scarlsonj ismcast = B_FALSE;
9313431Scarlsonj (void) memset(&dinfo, 0, sizeof (dinfo));
9323431Scarlsonj dinfo.dir_daddr = dsmp->dsm_send_dest.v6.sin6_addr;
9333431Scarlsonj str.ic_cmd = SIOCGDSTINFO;
9343431Scarlsonj str.ic_timout = 0;
9353431Scarlsonj str.ic_len = sizeof (dinfo);
9363431Scarlsonj str.ic_dp = (char *)&dinfo;
9373431Scarlsonj if (ioctl(v6_sock_fd, I_STR, &str) == -1) {
9383431Scarlsonj dhcpmsg(MSG_ERR,
9393431Scarlsonj "send_pkt_internal: ioctl SIOCGDSTINFO");
9403431Scarlsonj } else if (!dinfo.dir_dreachable) {
9413431Scarlsonj char abuf[INET6_ADDRSTRLEN];
9420Sstevel@tonic-gate
9433431Scarlsonj dhcpmsg(MSG_DEBUG, "send_pkt_internal: %s is "
9443431Scarlsonj "not reachable; using multicast instead",
9453431Scarlsonj inet_ntop(AF_INET6, &dinfo.dir_daddr, abuf,
9463431Scarlsonj sizeof (abuf)));
9473431Scarlsonj sin6 = dsmp->dsm_send_dest.v6;
9483431Scarlsonj sin6.sin6_addr =
9493431Scarlsonj ipv6_all_dhcp_relay_and_servers;
9503431Scarlsonj msg.msg_name = &sin6;
9513431Scarlsonj ismcast = B_TRUE;
9523431Scarlsonj }
9530Sstevel@tonic-gate }
9540Sstevel@tonic-gate
9553431Scarlsonj /*
9563431Scarlsonj * Make room for our ancillary data option as well as a dummy
9573431Scarlsonj * option used by CMSG_NXTHDR.
9583431Scarlsonj */
9593431Scarlsonj msg.msg_controllen = sizeof (*cmsg) + _MAX_ALIGNMENT +
9603431Scarlsonj sizeof (*ipi6) + _MAX_ALIGNMENT + sizeof (*cmsg);
9613431Scarlsonj msg.msg_control = alloca(msg.msg_controllen);
9623431Scarlsonj cmsg = CMSG_FIRSTHDR(&msg);
9633431Scarlsonj cmsg->cmsg_level = IPPROTO_IPV6;
9643431Scarlsonj cmsg->cmsg_type = IPV6_PKTINFO;
9653431Scarlsonj /* LINTED: alignment */
9663431Scarlsonj ipi6 = (struct in6_pktinfo *)CMSG_DATA(cmsg);
9673431Scarlsonj if (ismcast)
9683431Scarlsonj ipi6->ipi6_addr = lif->lif_v6addr;
9693431Scarlsonj else
9703431Scarlsonj ipi6->ipi6_addr = my_in6addr_any;
971*8485SPeter.Memishian@Sun.COM if (lif->lif_pif->pif_under_ipmp)
972*8485SPeter.Memishian@Sun.COM ipi6->ipi6_ifindex = lif->lif_pif->pif_grindex;
973*8485SPeter.Memishian@Sun.COM else
974*8485SPeter.Memishian@Sun.COM ipi6->ipi6_ifindex = lif->lif_pif->pif_index;
9753431Scarlsonj cmsg->cmsg_len = (char *)(ipi6 + 1) - (char *)cmsg;
9760Sstevel@tonic-gate
9773431Scarlsonj /*
9783431Scarlsonj * Now correct the control message length.
9793431Scarlsonj */
9803431Scarlsonj cmsg = CMSG_NXTHDR(&msg, cmsg);
9813431Scarlsonj msg.msg_controllen = (char *)cmsg - (char *)msg.msg_control;
9820Sstevel@tonic-gate
9833431Scarlsonj n_bytes = sendmsg(v6_sock_fd, &msg, 0);
9843431Scarlsonj } else {
9855381Smeem n_bytes = sendto(lif->lif_sock_ip_fd, dpkt->pkt,
9865381Smeem dpkt->pkt_cur_len, 0,
9875381Smeem (struct sockaddr *)&dsmp->dsm_send_dest.v4,
9885381Smeem sizeof (struct sockaddr_in));
9890Sstevel@tonic-gate }
9900Sstevel@tonic-gate
9910Sstevel@tonic-gate if (n_bytes != dpkt->pkt_cur_len) {
9925381Smeem msgtype = (n_bytes == -1) ? MSG_ERR : MSG_WARNING;
9933431Scarlsonj if (dsmp->dsm_retrans_timer == -1)
9945381Smeem dhcpmsg(msgtype, "send_pkt_internal: cannot send "
9950Sstevel@tonic-gate "%s packet to server", pkt_name);
9960Sstevel@tonic-gate else
9975381Smeem dhcpmsg(msgtype, "send_pkt_internal: cannot send "
9980Sstevel@tonic-gate "%s packet to server (will retry in %u seconds)",
9993431Scarlsonj pkt_name, dsmp->dsm_send_timeout / MILLISEC);
10003431Scarlsonj return (B_FALSE);
10010Sstevel@tonic-gate }
10020Sstevel@tonic-gate
10033431Scarlsonj dhcpmsg(MSG_VERBOSE, "sent %s xid %x packet out %s", pkt_name,
10043431Scarlsonj pkt_get_xid(dpkt->pkt, dpkt->pkt_isv6), dsmp->dsm_name);
10050Sstevel@tonic-gate
10063431Scarlsonj dsmp->dsm_packet_sent++;
10073431Scarlsonj dsmp->dsm_sent++;
10083431Scarlsonj return (B_TRUE);
10090Sstevel@tonic-gate }
10100Sstevel@tonic-gate
10110Sstevel@tonic-gate /*
10123431Scarlsonj * send_pkt(): sends a packet out
10130Sstevel@tonic-gate *
10143431Scarlsonj * input: dhcp_smach_t *: the state machine sending the packet
10150Sstevel@tonic-gate * dhcp_pkt_t *: the packet to send out
10160Sstevel@tonic-gate * in_addr_t: the destination IP address for the packet
10170Sstevel@tonic-gate * stop_func_t *: a pointer to function to indicate when to stop
10180Sstevel@tonic-gate * retransmitting the packet (if NULL, packet is
10190Sstevel@tonic-gate * not retransmitted)
10203431Scarlsonj * output: boolean_t: B_TRUE if the packet was sent, B_FALSE otherwise
10210Sstevel@tonic-gate */
10220Sstevel@tonic-gate
10233431Scarlsonj boolean_t
send_pkt(dhcp_smach_t * dsmp,dhcp_pkt_t * dpkt,in_addr_t dest,stop_func_t * stop)10243431Scarlsonj send_pkt(dhcp_smach_t *dsmp, dhcp_pkt_t *dpkt, in_addr_t dest,
10250Sstevel@tonic-gate stop_func_t *stop)
10260Sstevel@tonic-gate {
10270Sstevel@tonic-gate /*
10280Sstevel@tonic-gate * packets must be at least sizeof (PKT) or they may be dropped
10290Sstevel@tonic-gate * by routers. pad out the packet in this case.
10300Sstevel@tonic-gate */
10310Sstevel@tonic-gate
10320Sstevel@tonic-gate dpkt->pkt_cur_len = MAX(dpkt->pkt_cur_len, sizeof (PKT));
10330Sstevel@tonic-gate
10343431Scarlsonj dsmp->dsm_packet_sent = 0;
10350Sstevel@tonic-gate
10363431Scarlsonj (void) memset(&dsmp->dsm_send_dest.v4, 0,
10373431Scarlsonj sizeof (dsmp->dsm_send_dest.v4));
10383431Scarlsonj dsmp->dsm_send_dest.v4.sin_addr.s_addr = dest;
10393431Scarlsonj dsmp->dsm_send_dest.v4.sin_family = AF_INET;
10403431Scarlsonj dsmp->dsm_send_dest.v4.sin_port = htons(IPPORT_BOOTPS);
10413431Scarlsonj dsmp->dsm_send_stop_func = stop;
10420Sstevel@tonic-gate
10430Sstevel@tonic-gate /*
10440Sstevel@tonic-gate * TODO: dispose of this gruesome assumption (there's no real
10450Sstevel@tonic-gate * technical gain from doing so, but it would be cleaner)
10460Sstevel@tonic-gate */
10470Sstevel@tonic-gate
10483431Scarlsonj assert(dpkt == &dsmp->dsm_send_pkt);
10490Sstevel@tonic-gate
10500Sstevel@tonic-gate /*
10510Sstevel@tonic-gate * clear out any packets which had been previously received
10520Sstevel@tonic-gate * but not pulled off of the recv_packet queue.
10530Sstevel@tonic-gate */
10540Sstevel@tonic-gate
10553431Scarlsonj free_pkt_list(&dsmp->dsm_recv_pkt_list);
10563431Scarlsonj
10573431Scarlsonj if (stop == NULL)
10583431Scarlsonj dsmp->dsm_send_timeout = 0; /* prevents retransmissions */
10593431Scarlsonj else
10603431Scarlsonj next_retransmission(dsmp, B_TRUE, B_FALSE);
10613431Scarlsonj
10623431Scarlsonj return (send_pkt_internal(dsmp));
10633431Scarlsonj }
10643431Scarlsonj
10653431Scarlsonj /*
10663431Scarlsonj * send_pkt_v6(): sends a DHCPv6 packet out
10673431Scarlsonj *
10683431Scarlsonj * input: dhcp_smach_t *: the state machine sending the packet
10693431Scarlsonj * dhcp_pkt_t *: the packet to send out
10703431Scarlsonj * in6_addr_t: the destination IPv6 address for the packet
10713431Scarlsonj * stop_func_t *: a pointer to function to indicate when to stop
10723431Scarlsonj * retransmitting the packet (if NULL, packet is
10733431Scarlsonj * not retransmitted)
10743431Scarlsonj * uint_t: Initial Retransmit Timer value
10753431Scarlsonj * uint_t: Maximum Retransmit Timer value, zero if none
10763431Scarlsonj * output: boolean_t: B_TRUE if the packet was sent, B_FALSE otherwise
10773431Scarlsonj */
10783431Scarlsonj
10793431Scarlsonj 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)10803431Scarlsonj send_pkt_v6(dhcp_smach_t *dsmp, dhcp_pkt_t *dpkt, in6_addr_t dest,
10813431Scarlsonj stop_func_t *stop, uint_t irt, uint_t mrt)
10823431Scarlsonj {
10833431Scarlsonj dsmp->dsm_packet_sent = 0;
10843431Scarlsonj
10853431Scarlsonj (void) memset(&dsmp->dsm_send_dest.v6, 0,
10863431Scarlsonj sizeof (dsmp->dsm_send_dest.v6));
10873431Scarlsonj dsmp->dsm_send_dest.v6.sin6_addr = dest;
10883431Scarlsonj dsmp->dsm_send_dest.v6.sin6_family = AF_INET6;
10893431Scarlsonj dsmp->dsm_send_dest.v6.sin6_port = htons(IPPORT_DHCPV6S);
10903431Scarlsonj dsmp->dsm_send_stop_func = stop;
10913431Scarlsonj
10923431Scarlsonj /*
10933431Scarlsonj * TODO: dispose of this gruesome assumption (there's no real
10943431Scarlsonj * technical gain from doing so, but it would be cleaner)
10953431Scarlsonj */
10963431Scarlsonj
10973431Scarlsonj assert(dpkt == &dsmp->dsm_send_pkt);
10983431Scarlsonj
10993431Scarlsonj /*
11003431Scarlsonj * clear out any packets which had been previously received
11013431Scarlsonj * but not pulled off of the recv_packet queue.
11023431Scarlsonj */
11033431Scarlsonj
11043431Scarlsonj free_pkt_list(&dsmp->dsm_recv_pkt_list);
11050Sstevel@tonic-gate
11060Sstevel@tonic-gate if (stop == NULL) {
11073431Scarlsonj dsmp->dsm_send_timeout = 0; /* prevents retransmissions */
11083431Scarlsonj } else {
11093431Scarlsonj dsmp->dsm_send_timeout = irt;
11103431Scarlsonj dsmp->dsm_send_tcenter = mrt;
11113431Scarlsonj /*
11123431Scarlsonj * This is quite ugly, but RFC 3315 section 17.1.2 requires
11133431Scarlsonj * that the RAND value for the very first retransmission of a
11143431Scarlsonj * Solicit message is strictly greater than zero.
11153431Scarlsonj */
11163431Scarlsonj next_retransmission(dsmp, B_TRUE,
11173431Scarlsonj pkt_send_type(dpkt) == DHCPV6_MSG_SOLICIT);
11183431Scarlsonj }
11190Sstevel@tonic-gate
11203431Scarlsonj return (send_pkt_internal(dsmp));
11210Sstevel@tonic-gate }
11220Sstevel@tonic-gate
11230Sstevel@tonic-gate /*
11240Sstevel@tonic-gate * retransmit(): retransmits the current packet on an interface
11250Sstevel@tonic-gate *
11260Sstevel@tonic-gate * input: iu_tq_t *: unused
11273431Scarlsonj * void *: the dhcp_smach_t * (state machine) sending a packet
11280Sstevel@tonic-gate * output: void
11290Sstevel@tonic-gate */
11300Sstevel@tonic-gate
11310Sstevel@tonic-gate /* ARGSUSED */
11320Sstevel@tonic-gate static void
retransmit(iu_tq_t * tqp,void * arg)11330Sstevel@tonic-gate retransmit(iu_tq_t *tqp, void *arg)
11340Sstevel@tonic-gate {
11353431Scarlsonj dhcp_smach_t *dsmp = arg;
11360Sstevel@tonic-gate
11373431Scarlsonj dsmp->dsm_retrans_timer = -1;
11383431Scarlsonj
11393431Scarlsonj if (!verify_smach(dsmp))
11400Sstevel@tonic-gate return;
11410Sstevel@tonic-gate
11420Sstevel@tonic-gate /*
11433431Scarlsonj * Check the callback to see if we should keep sending retransmissions.
11443431Scarlsonj * Compute the next retransmission time first, so that the callback can
11453431Scarlsonj * cap the value if need be. (Required for DHCPv6 Confirm messages.)
11463431Scarlsonj *
11473431Scarlsonj * Hold the state machine across the callback so that the called
11483431Scarlsonj * function can remove the state machine from the system without
11493431Scarlsonj * disturbing the string used subsequently for verbose logging. The
11503431Scarlsonj * Release function destroys the state machine when the retry count
11513431Scarlsonj * expires.
11520Sstevel@tonic-gate */
11530Sstevel@tonic-gate
11543431Scarlsonj next_retransmission(dsmp, B_FALSE, B_FALSE);
11553431Scarlsonj hold_smach(dsmp);
11563431Scarlsonj if (dsmp->dsm_send_stop_func(dsmp, dsmp->dsm_packet_sent)) {
11573431Scarlsonj dhcpmsg(MSG_VERBOSE, "retransmit: time to stop on %s",
11583431Scarlsonj dsmp->dsm_name);
11593431Scarlsonj } else {
11603431Scarlsonj dhcpmsg(MSG_VERBOSE, "retransmit: sending another on %s",
11613431Scarlsonj dsmp->dsm_name);
11623431Scarlsonj (void) send_pkt_internal(dsmp);
11633431Scarlsonj }
11643431Scarlsonj release_smach(dsmp);
11650Sstevel@tonic-gate }
11660Sstevel@tonic-gate
11670Sstevel@tonic-gate /*
11680Sstevel@tonic-gate * stop_pkt_retransmission(): stops retransmission of last sent packet
11690Sstevel@tonic-gate *
11703431Scarlsonj * input: dhcp_smach_t *: the state machine to stop retransmission on
11710Sstevel@tonic-gate * output: void
11720Sstevel@tonic-gate */
11730Sstevel@tonic-gate
11740Sstevel@tonic-gate void
stop_pkt_retransmission(dhcp_smach_t * dsmp)11753431Scarlsonj stop_pkt_retransmission(dhcp_smach_t *dsmp)
11760Sstevel@tonic-gate {
11773431Scarlsonj if (dsmp->dsm_retrans_timer != -1 &&
11783431Scarlsonj iu_cancel_timer(tq, dsmp->dsm_retrans_timer, NULL) == 1) {
11793431Scarlsonj dhcpmsg(MSG_VERBOSE, "stop_pkt_retransmission: stopped on %s",
11803431Scarlsonj dsmp->dsm_name);
11813431Scarlsonj dsmp->dsm_retrans_timer = -1;
11823431Scarlsonj release_smach(dsmp);
11830Sstevel@tonic-gate }
11840Sstevel@tonic-gate }
11850Sstevel@tonic-gate
11860Sstevel@tonic-gate /*
11873431Scarlsonj * retransmit_now(): force a packet retransmission right now. Used only with
11883431Scarlsonj * the DHCPv6 UseMulticast status code. Use with caution;
11893431Scarlsonj * triggered retransmissions can cause packet storms.
11900Sstevel@tonic-gate *
11913431Scarlsonj * input: dhcp_smach_t *: the state machine to force retransmission on
11923431Scarlsonj * output: void
11930Sstevel@tonic-gate */
11940Sstevel@tonic-gate
11953431Scarlsonj void
retransmit_now(dhcp_smach_t * dsmp)11963431Scarlsonj retransmit_now(dhcp_smach_t *dsmp)
11973431Scarlsonj {
11983431Scarlsonj stop_pkt_retransmission(dsmp);
11993431Scarlsonj (void) send_pkt_internal(dsmp);
12003431Scarlsonj }
12013431Scarlsonj
12023431Scarlsonj /*
12033431Scarlsonj * alloc_pkt_entry(): Allocates a packet list entry with a given data area
12043431Scarlsonj * size.
12053431Scarlsonj *
12063431Scarlsonj * input: size_t: size of data area for packet
12073431Scarlsonj * boolean_t: B_TRUE for IPv6
12083431Scarlsonj * output: PKT_LIST *: allocated packet list entry
12093431Scarlsonj */
12103431Scarlsonj
12113431Scarlsonj PKT_LIST *
alloc_pkt_entry(size_t psize,boolean_t isv6)12123431Scarlsonj alloc_pkt_entry(size_t psize, boolean_t isv6)
12130Sstevel@tonic-gate {
12140Sstevel@tonic-gate PKT_LIST *plp;
12150Sstevel@tonic-gate
12163431Scarlsonj if ((plp = calloc(1, sizeof (*plp))) == NULL ||
12173431Scarlsonj (plp->pkt = malloc(psize)) == NULL) {
12183431Scarlsonj free(plp);
12193431Scarlsonj plp = NULL;
12203431Scarlsonj } else {
12213431Scarlsonj plp->len = psize;
12223431Scarlsonj plp->isv6 = isv6;
12230Sstevel@tonic-gate }
12240Sstevel@tonic-gate
12253431Scarlsonj return (plp);
12263431Scarlsonj }
12270Sstevel@tonic-gate
12283431Scarlsonj /*
12293431Scarlsonj * sock_recvpkt(): read from the given socket into an allocated buffer and
12303431Scarlsonj * handles any ancillary data options.
12313431Scarlsonj *
12323431Scarlsonj * input: int: file descriptor to read
12333431Scarlsonj * PKT_LIST *: allocated buffer
12343431Scarlsonj * output: ssize_t: number of bytes read, or -1 on error
12353431Scarlsonj */
12363431Scarlsonj
12373431Scarlsonj static ssize_t
sock_recvpkt(int fd,PKT_LIST * plp)12383431Scarlsonj sock_recvpkt(int fd, PKT_LIST *plp)
12393431Scarlsonj {
12403431Scarlsonj struct iovec iov;
12413431Scarlsonj struct msghdr msg;
12423476Scarlsonj int64_t ctrl[8192 / sizeof (int64_t)];
12433431Scarlsonj ssize_t msglen;
12440Sstevel@tonic-gate
12453431Scarlsonj (void) memset(&iov, 0, sizeof (iov));
12463431Scarlsonj iov.iov_base = (caddr_t)plp->pkt;
12473431Scarlsonj iov.iov_len = plp->len;
12483431Scarlsonj
12493431Scarlsonj (void) memset(&msg, 0, sizeof (msg));
12503431Scarlsonj msg.msg_name = &plp->pktfrom;
12513431Scarlsonj msg.msg_namelen = sizeof (plp->pktfrom);
12523431Scarlsonj msg.msg_iov = &iov;
12533431Scarlsonj msg.msg_iovlen = 1;
12543431Scarlsonj msg.msg_control = ctrl;
12553431Scarlsonj msg.msg_controllen = sizeof (ctrl);
12563431Scarlsonj
12573431Scarlsonj if ((msglen = recvmsg(fd, &msg, 0)) != -1) {
12583431Scarlsonj struct cmsghdr *cmsg;
12593431Scarlsonj
12603431Scarlsonj for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
12613431Scarlsonj cmsg = CMSG_NXTHDR(&msg, cmsg)) {
12623431Scarlsonj struct sockaddr_in *sinp;
12633431Scarlsonj struct sockaddr_in6 *sin6;
12643431Scarlsonj struct in6_pktinfo *ipi6;
12650Sstevel@tonic-gate
12663431Scarlsonj switch (cmsg->cmsg_level) {
12673431Scarlsonj case IPPROTO_IP:
12683431Scarlsonj switch (cmsg->cmsg_type) {
12693431Scarlsonj case IP_RECVDSTADDR:
12703431Scarlsonj sinp = (struct sockaddr_in *)
12713431Scarlsonj &plp->pktto;
12723431Scarlsonj sinp->sin_family = AF_INET;
12733431Scarlsonj (void) memcpy(&sinp->sin_addr.s_addr,
12743431Scarlsonj CMSG_DATA(cmsg),
12753431Scarlsonj sizeof (ipaddr_t));
12763431Scarlsonj break;
12773431Scarlsonj
12783431Scarlsonj case IP_RECVIF:
12793431Scarlsonj (void) memcpy(&plp->ifindex,
12803431Scarlsonj CMSG_DATA(cmsg), sizeof (uint_t));
12813431Scarlsonj break;
12823431Scarlsonj }
12833431Scarlsonj break;
12840Sstevel@tonic-gate
12853431Scarlsonj case IPPROTO_IPV6:
12863431Scarlsonj switch (cmsg->cmsg_type) {
12873431Scarlsonj case IPV6_PKTINFO:
12883431Scarlsonj /* LINTED: alignment */
12893431Scarlsonj ipi6 = (struct in6_pktinfo *)
12903431Scarlsonj CMSG_DATA(cmsg);
12913431Scarlsonj sin6 = (struct sockaddr_in6 *)
12923431Scarlsonj &plp->pktto;
12933431Scarlsonj sin6->sin6_family = AF_INET6;
12943431Scarlsonj (void) memcpy(&sin6->sin6_addr,
12953431Scarlsonj &ipi6->ipi6_addr,
12963431Scarlsonj sizeof (ipi6->ipi6_addr));
12973431Scarlsonj (void) memcpy(&plp->ifindex,
12983431Scarlsonj &ipi6->ipi6_ifindex,
12993431Scarlsonj sizeof (uint_t));
13003431Scarlsonj break;
13013431Scarlsonj }
13023431Scarlsonj }
13033431Scarlsonj }
13043431Scarlsonj }
13053431Scarlsonj return (msglen);
13063431Scarlsonj }
13070Sstevel@tonic-gate
13083431Scarlsonj /*
13093431Scarlsonj * recv_pkt(): receives a single DHCP packet on a given file descriptor.
13103431Scarlsonj *
13115381Smeem * input: int: the file descriptor to receive the packet from
13123431Scarlsonj * int: the maximum packet size to allow
13133431Scarlsonj * boolean_t: B_TRUE for IPv6
13143431Scarlsonj * output: PKT_LIST *: the received packet
13153431Scarlsonj */
13160Sstevel@tonic-gate
13173431Scarlsonj PKT_LIST *
recv_pkt(int fd,int mtu,boolean_t isv6)13185381Smeem recv_pkt(int fd, int mtu, boolean_t isv6)
13193431Scarlsonj {
13203431Scarlsonj PKT_LIST *plp;
13213431Scarlsonj ssize_t retval;
13223431Scarlsonj
13233431Scarlsonj if ((plp = alloc_pkt_entry(mtu, isv6)) == NULL) {
13243431Scarlsonj dhcpmsg(MSG_ERROR,
13253431Scarlsonj "recv_pkt: allocation failure; dropped packet");
13263431Scarlsonj return (NULL);
13270Sstevel@tonic-gate }
13280Sstevel@tonic-gate
13295381Smeem retval = sock_recvpkt(fd, plp);
13305381Smeem if (retval == -1) {
13315381Smeem dhcpmsg(MSG_ERR, "recv_pkt: recvfrom v%d failed, dropped",
13325381Smeem isv6 ? 6 : 4);
13335381Smeem goto failure;
13345381Smeem }
13353431Scarlsonj
13365381Smeem plp->len = retval;
13373431Scarlsonj
13385381Smeem if (isv6) {
13393431Scarlsonj if (retval < sizeof (dhcpv6_message_t)) {
13403431Scarlsonj dhcpmsg(MSG_WARNING, "recv_pkt: runt message");
13413431Scarlsonj goto failure;
13423431Scarlsonj }
13433431Scarlsonj } else {
13443431Scarlsonj switch (dhcp_options_scan(plp, B_TRUE)) {
13453431Scarlsonj
13463431Scarlsonj case DHCP_WRONG_MSG_TYPE:
13473431Scarlsonj dhcpmsg(MSG_WARNING,
13483431Scarlsonj "recv_pkt: unexpected DHCP message");
13493431Scarlsonj goto failure;
13500Sstevel@tonic-gate
13513431Scarlsonj case DHCP_GARBLED_MSG_TYPE:
13523431Scarlsonj dhcpmsg(MSG_WARNING,
13533431Scarlsonj "recv_pkt: garbled DHCP message type");
13543431Scarlsonj goto failure;
13550Sstevel@tonic-gate
13563431Scarlsonj case DHCP_BAD_OPT_OVLD:
13573431Scarlsonj dhcpmsg(MSG_WARNING, "recv_pkt: bad option overload");
13583431Scarlsonj goto failure;
13593431Scarlsonj
13603431Scarlsonj case 0:
13613431Scarlsonj break;
13623431Scarlsonj
13633431Scarlsonj default:
13643431Scarlsonj dhcpmsg(MSG_WARNING,
13653431Scarlsonj "recv_pkt: packet corrupted, dropped");
13660Sstevel@tonic-gate goto failure;
13670Sstevel@tonic-gate }
13680Sstevel@tonic-gate }
13693431Scarlsonj return (plp);
13700Sstevel@tonic-gate
13710Sstevel@tonic-gate failure:
13723431Scarlsonj free_pkt_entry(plp);
13733431Scarlsonj return (NULL);
13743431Scarlsonj }
13753431Scarlsonj
13763431Scarlsonj /*
13773431Scarlsonj * pkt_v4_match(): check if a given DHCPv4 message type is in a given set
13783431Scarlsonj *
13793431Scarlsonj * input: uchar_t: packet type
13803431Scarlsonj * dhcp_message_type_t: bit-wise OR of DHCP_P* values.
13813431Scarlsonj * output: boolean_t: B_TRUE if packet type is in the set
13823431Scarlsonj */
13833431Scarlsonj
13843431Scarlsonj boolean_t
pkt_v4_match(uchar_t type,dhcp_message_type_t match_type)13853431Scarlsonj pkt_v4_match(uchar_t type, dhcp_message_type_t match_type)
13863431Scarlsonj {
13873431Scarlsonj /*
13883431Scarlsonj * note: the ordering here allows direct indexing of the table
13893431Scarlsonj * based on the RFC2131 packet type value passed in.
13903431Scarlsonj */
13913431Scarlsonj
13923431Scarlsonj static dhcp_message_type_t type_map[] = {
13933431Scarlsonj DHCP_PUNTYPED, DHCP_PDISCOVER, DHCP_POFFER, DHCP_PREQUEST,
13943431Scarlsonj DHCP_PDECLINE, DHCP_PACK, DHCP_PNAK, DHCP_PRELEASE,
13953431Scarlsonj DHCP_PINFORM
13963431Scarlsonj };
13973431Scarlsonj
13983431Scarlsonj if (type < (sizeof (type_map) / sizeof (*type_map)))
13993431Scarlsonj return ((type_map[type] & match_type) ? B_TRUE : B_FALSE);
14003431Scarlsonj else
14013431Scarlsonj return (B_FALSE);
14023431Scarlsonj }
14033431Scarlsonj
14043431Scarlsonj /*
14053431Scarlsonj * pkt_smach_enqueue(): enqueue a packet on a given state machine
14063431Scarlsonj *
14073431Scarlsonj * input: dhcp_smach_t: state machine
14083431Scarlsonj * PKT_LIST *: packet to enqueue
14093431Scarlsonj * output: none
14103431Scarlsonj */
14113431Scarlsonj
14123431Scarlsonj void
pkt_smach_enqueue(dhcp_smach_t * dsmp,PKT_LIST * plp)14133431Scarlsonj pkt_smach_enqueue(dhcp_smach_t *dsmp, PKT_LIST *plp)
14143431Scarlsonj {
14153431Scarlsonj dhcpmsg(MSG_VERBOSE, "pkt_smach_enqueue: received %s %s packet on %s",
14163431Scarlsonj pkt_type_to_string(pkt_recv_type(plp), dsmp->dsm_isv6),
14173431Scarlsonj dsmp->dsm_isv6 ? "v6" : "v4", dsmp->dsm_name);
14183431Scarlsonj
14193431Scarlsonj /* add to front of list */
14203431Scarlsonj insque(plp, &dsmp->dsm_recv_pkt_list);
14210Sstevel@tonic-gate }
14220Sstevel@tonic-gate
14230Sstevel@tonic-gate /*
14243431Scarlsonj * next_retransmission(): computes the number of seconds until the next
14253431Scarlsonj * retransmission, based on the algorithms in RFCs 2131
14263431Scarlsonj * 3315.
14270Sstevel@tonic-gate *
14283431Scarlsonj * input: dhcp_smach_t *: state machine that needs a new timer
14293431Scarlsonj * boolean_t: B_TRUE if this is the first time sending the message
14303431Scarlsonj * boolean_t: B_TRUE for positive RAND values only (RFC 3315 17.1.2)
14313431Scarlsonj * output: none
14320Sstevel@tonic-gate */
14330Sstevel@tonic-gate
14343431Scarlsonj static void
next_retransmission(dhcp_smach_t * dsmp,boolean_t first_send,boolean_t positive_only)14353431Scarlsonj next_retransmission(dhcp_smach_t *dsmp, boolean_t first_send,
14363431Scarlsonj boolean_t positive_only)
14370Sstevel@tonic-gate {
14383431Scarlsonj uint32_t timeout_ms;
14393431Scarlsonj
14403431Scarlsonj if (dsmp->dsm_isv6) {
14413431Scarlsonj double randval;
14423431Scarlsonj
14433431Scarlsonj /*
14443431Scarlsonj * The RFC specifies 0 to 10% jitter for the initial
14453431Scarlsonj * solicitation, and plus or minus 10% jitter for all others.
14463431Scarlsonj * This works out to 100 milliseconds on the shortest timer we
14473431Scarlsonj * use.
14483431Scarlsonj */
14493431Scarlsonj if (positive_only)
14503431Scarlsonj randval = drand48() / 10.0;
14513431Scarlsonj else
14523431Scarlsonj randval = (drand48() - 0.5) / 5.0;
14533431Scarlsonj
14543431Scarlsonj /* The RFC specifies doubling *after* the first transmission */
14553431Scarlsonj timeout_ms = dsmp->dsm_send_timeout;
14563431Scarlsonj if (!first_send)
14573431Scarlsonj timeout_ms *= 2;
14583431Scarlsonj timeout_ms += (int)(randval * dsmp->dsm_send_timeout);
14593431Scarlsonj
14603431Scarlsonj /* This checks the MRT (maximum retransmission time) */
14613431Scarlsonj if (dsmp->dsm_send_tcenter != 0 &&
14623431Scarlsonj timeout_ms > dsmp->dsm_send_tcenter) {
14633431Scarlsonj timeout_ms = dsmp->dsm_send_tcenter +
14643431Scarlsonj (uint_t)(randval * dsmp->dsm_send_tcenter);
14653431Scarlsonj }
14663431Scarlsonj
14673431Scarlsonj dsmp->dsm_send_timeout = timeout_ms;
14683431Scarlsonj } else {
14693431Scarlsonj if (dsmp->dsm_state == RENEWING ||
14703431Scarlsonj dsmp->dsm_state == REBINDING) {
14713431Scarlsonj monosec_t mono;
14723431Scarlsonj
14733431Scarlsonj timeout_ms = dsmp->dsm_state == RENEWING ?
14743431Scarlsonj dsmp->dsm_leases->dl_t2.dt_start :
14753431Scarlsonj dsmp->dsm_leases->dl_lifs->lif_expire.dt_start;
14763431Scarlsonj timeout_ms += dsmp->dsm_curstart_monosec;
14773431Scarlsonj mono = monosec();
14783431Scarlsonj if (mono > timeout_ms)
14793431Scarlsonj timeout_ms = 0;
14803431Scarlsonj else
14813431Scarlsonj timeout_ms -= mono;
14823431Scarlsonj timeout_ms *= MILLISEC / 2;
14833431Scarlsonj } else {
14843431Scarlsonj /*
14853431Scarlsonj * Start at 4, and increase by a factor of 2 up to 64.
14863431Scarlsonj */
14873431Scarlsonj if (first_send) {
14883431Scarlsonj timeout_ms = 4 * MILLISEC;
14893431Scarlsonj } else {
14903431Scarlsonj timeout_ms = MIN(dsmp->dsm_send_tcenter << 1,
14913431Scarlsonj 64 * MILLISEC);
14923431Scarlsonj }
14933431Scarlsonj }
14943431Scarlsonj
14953431Scarlsonj dsmp->dsm_send_tcenter = timeout_ms;
14963431Scarlsonj
14973431Scarlsonj /*
14983431Scarlsonj * At each iteration, jitter the timeout by some fraction of a
14993431Scarlsonj * second.
15003431Scarlsonj */
15013431Scarlsonj dsmp->dsm_send_timeout = timeout_ms +
15023431Scarlsonj ((lrand48() % (2 * MILLISEC)) - MILLISEC);
15033431Scarlsonj }
15043431Scarlsonj }
15050Sstevel@tonic-gate
15063431Scarlsonj /*
15073431Scarlsonj * dhcp_ip_default(): open and bind the default IP sockets used for I/O and
15083431Scarlsonj * interface control.
15093431Scarlsonj *
15103431Scarlsonj * input: none
15113431Scarlsonj * output: B_TRUE on success
15123431Scarlsonj */
15133431Scarlsonj
15143431Scarlsonj boolean_t
dhcp_ip_default(void)15153431Scarlsonj dhcp_ip_default(void)
15163431Scarlsonj {
15175123Scarlsonj int on = 1;
15183431Scarlsonj
15193431Scarlsonj if ((v4_sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
15203431Scarlsonj dhcpmsg(MSG_ERR,
15213431Scarlsonj "dhcp_ip_default: unable to create IPv4 socket");
15223431Scarlsonj return (B_FALSE);
15233431Scarlsonj }
15243431Scarlsonj
15253431Scarlsonj if (setsockopt(v4_sock_fd, IPPROTO_IP, IP_RECVDSTADDR, &on,
15263431Scarlsonj sizeof (on)) == -1) {
15273431Scarlsonj dhcpmsg(MSG_ERR,
15283431Scarlsonj "dhcp_ip_default: unable to enable IP_RECVDSTADDR");
15293431Scarlsonj return (B_FALSE);
15303431Scarlsonj }
15313431Scarlsonj
15323431Scarlsonj if (setsockopt(v4_sock_fd, IPPROTO_IP, IP_RECVIF, &on, sizeof (on)) ==
15333431Scarlsonj -1) {
15343431Scarlsonj dhcpmsg(MSG_ERR,
15353431Scarlsonj "dhcp_ip_default: unable to enable IP_RECVIF");
15363431Scarlsonj return (B_FALSE);
15373431Scarlsonj }
15380Sstevel@tonic-gate
15393431Scarlsonj if (!bind_sock(v4_sock_fd, IPPORT_BOOTPC, INADDR_ANY)) {
15403431Scarlsonj dhcpmsg(MSG_ERROR,
15413431Scarlsonj "dhcp_ip_default: unable to bind IPv4 socket to port %d",
15423431Scarlsonj IPPORT_BOOTPC);
15433431Scarlsonj return (B_FALSE);
15443431Scarlsonj }
15453431Scarlsonj
15465381Smeem if (iu_register_event(eh, v4_sock_fd, POLLIN, dhcp_acknak_global,
15473431Scarlsonj NULL) == -1) {
15483431Scarlsonj dhcpmsg(MSG_WARNING, "dhcp_ip_default: cannot register to "
15493431Scarlsonj "receive IPv4 broadcasts");
15503431Scarlsonj return (B_FALSE);
15513431Scarlsonj }
15523431Scarlsonj
15533431Scarlsonj if ((v6_sock_fd = socket(AF_INET6, SOCK_DGRAM, 0)) == -1) {
15543431Scarlsonj dhcpmsg(MSG_ERR,
15553431Scarlsonj "dhcp_ip_default: unable to create IPv6 socket");
15563431Scarlsonj return (B_FALSE);
15573431Scarlsonj }
15583431Scarlsonj
15593431Scarlsonj if (setsockopt(v6_sock_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on,
15603431Scarlsonj sizeof (on)) == -1) {
15613431Scarlsonj dhcpmsg(MSG_ERR,
15623431Scarlsonj "dhcp_ip_default: unable to enable IPV6_RECVPKTINFO");
15633431Scarlsonj return (B_FALSE);
15643431Scarlsonj }
15653431Scarlsonj
15663431Scarlsonj if (!bind_sock_v6(v6_sock_fd, IPPORT_DHCPV6C, NULL)) {
15673431Scarlsonj dhcpmsg(MSG_ERROR,
15683431Scarlsonj "dhcp_ip_default: unable to bind IPv6 socket to port %d",
15693431Scarlsonj IPPORT_DHCPV6C);
15703431Scarlsonj return (B_FALSE);
15713431Scarlsonj }
15723431Scarlsonj
15735381Smeem if (iu_register_event(eh, v6_sock_fd, POLLIN, dhcp_acknak_global,
15743431Scarlsonj NULL) == -1) {
15753431Scarlsonj dhcpmsg(MSG_WARNING, "dhcp_ip_default: cannot register to "
15763431Scarlsonj "receive IPv6 packets");
15773431Scarlsonj return (B_FALSE);
15783431Scarlsonj }
15793431Scarlsonj
15803431Scarlsonj return (B_TRUE);
15810Sstevel@tonic-gate }
1582