packet.c revision e704a8f24a369484ba8f4a1cf49d4db00dd91166
/*
* 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
* 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 <stdlib.h>
#include <dhcpmsg.h>
#include <stddef.h>
#include <assert.h>
#include <search.h>
#include <alloca.h>
#include <limits.h>
#include <stropts.h>
#include <sys/sysmacros.h>
#include "states.h"
#include "interface.h"
#include "agent.h"
#include "packet.h"
#include "util.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.
*/
static void retransmit(iu_tq_t *, void *);
/*
* 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
{
/*
* 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.
*/
for (;;) {
option++;
continue;
}
return (0);
if (*option == CD_DHCP_TYPE)
break;
option++;
}
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)
*/
{
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)
*/
{
return (0);
if (isv6)
else
}
/*
* 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
*/
{
/*
* Since multiple dhcp leases may be maintained over the same pif
* (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 {
if (isv6)
xid &= 0xFFFFFF;
} while (xid == 0 ||
if (isv6) {
/* LINTED: alignment known to be correct */
}
mtu);
return (NULL);
}
if (dsmp->dsm_cidlen > 0 &&
"init_pkt: cannot insert client ID");
return (NULL);
}
/* For v6, time starts with the creation of a transaction */
} else {
}
mtu);
return (NULL);
}
} 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
* in a bound state, do not set this bit, as it
* can respond to unicast responses from server
* using the 'ciaddr' address.
*/
}
"init_pkt: cannot set DHCP packet type");
return (NULL);
}
if (dsmp->dsm_cidlen > 0 &&
"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.
*/
{
raw_pkt += sizeof (dhcpv6_message_t);
break;
}
return (B_TRUE);
}
}
} else {
return (B_FALSE);
break;
raw_pkt++;
continue;
}
break;
break;
} else if (toadd > 0) {
toadd);
/* max is not an issue here */
}
}
}
return (B_TRUE);
}
}
}
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
*/
{
return (B_FALSE);
} else {
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 *
{
int req_len;
void *optr;
"add_pkt_opt: not enough room for v6 option %u in "
"packet (%u + %u > %u)", opt_type,
return (NULL);
}
if (opt_len > 0) {
opt_len);
}
} else {
/* CD_END and CD_PAD options don't have a length field */
req_len = 1;
"missing required value", opt_type);
return (NULL);
}
"add_pkt_opt: not enough room for v4 option %u in "
"packet", opt_type);
return (NULL);
}
if (req_len > 1) {
if (opt_len > 0) {
}
}
}
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 *
{
int req_len;
void *optr;
int olen;
return (NULL);
"add_pkt_subopt: not enough room for v6 suboption %u in "
"packet (%u + %u > %u)", opt_type,
return (NULL);
}
/*
* Update the parent option to include room for this option,
* and compute the insertion point.
*/
/*
* If there's anything at the end to move, then move it. Also bump up
* the packet size.
*/
}
/*
* Now format the suboption and add it in.
*/
if (opt_len > 0)
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_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_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 *
{
if (dsmp->dsm_prllen == 0)
return (0);
/*
* RFC 3315 requires that we include the option, even if we
* have nothing to request.
*/
if (dsmp->dsm_prllen == 0)
else
} else {
}
}
/*
* 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
*/
{
/*
* Currently, we support just one IAID related to the primary
* LIF on the state machine.
*/
/*
* Find or create the IA_NA needed for this LIF. If we
* supported IA_TA, we'd check the IFF_TEMPORARY bit here.
*/
continue;
break;
}
return (B_FALSE);
}
/*
* Now add the IAADDR suboption for this LIF. No need to
* search here, as we know that this is unique.
*/
/*
* For Release and Decline, we zero out the lifetime. For
* Renew and Rebind, we report the original time as the
* preferred and valid lifetimes.
*/
d6ia.d6ia_preflife = 0;
d6ia.d6ia_vallife = 0;
} else {
}
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.
*/
}
/*
* Update for length of suboption header and
* suboption contents.
*/
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.
*/
return (B_FALSE);
} else {
}
/*
* It's not too worrisome if the message fails to fit in the
* packet. The result will still be valid.
*/
}
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_list(): frees an entire packet list
*
* input: PKT_LIST **: the packet list to free
* output: void
*/
void
{
}
}
/*
* 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
{
const char *pkt_name;
struct in6_pktinfo *ipi6;
int msgtype;
/*
* Timer should not be running at the point we go to send a packet.
*/
}
/*
* 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) {
"schedule retransmit timer for %s packet",
pkt_name);
else
}
/*
* Convert current time into centiseconds since transaction
* started. This is what DHCPv6 expects to see in the Elapsed
* Time option.
*/
(NANOSEC / 100);
if (delta > DHCPV6_FOREVER)
} 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:
break;
case INFORM:
break;
case REQUEST:
break;
}
break;
default:
break;
}
}
struct sockaddr_in6 sin6;
/*
* If the address that's requested cannot be reached, then fall
* back to the multcast address.
*/
} else {
struct dstinforeq dinfo;
"send_pkt_internal: ioctl SIOCGDSTINFO");
} else if (!dinfo.dir_dreachable) {
char abuf[INET6_ADDRSTRLEN];
"not reachable; using multicast instead",
sizeof (abuf)));
}
}
/*
* Make room for our ancillary data option as well as a dummy
* option used by CMSG_NXTHDR.
*/
/* LINTED: alignment */
if (ismcast)
else
/*
* Now correct the control message length.
*/
} else {
dpkt->pkt_cur_len, 0,
sizeof (struct sockaddr_in));
}
"%s packet to server", pkt_name);
else
"%s packet to server (will retry in %u seconds)",
return (B_FALSE);
}
dsmp->dsm_packet_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
*/
{
/*
* packets must be at least sizeof (PKT) or they may be dropped
* by routers. pad out the packet in this case.
*/
dsmp->dsm_packet_sent = 0;
/*
* TODO: dispose of this gruesome assumption (there's no real
* technical gain from doing so, but it would be cleaner)
*/
/*
* clear out any packets which had been previously received
* but not pulled off of the recv_packet queue.
*/
else
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
*/
{
dsmp->dsm_packet_sent = 0;
/*
* TODO: dispose of this gruesome assumption (there's no real
* technical gain from doing so, but it would be cleaner)
*/
/*
* clear out any packets which had been previously received
* but not pulled off of the recv_packet queue.
*/
} else {
/*
* 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.
*/
}
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
{
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.
*/
} else {
(void) send_pkt_internal(dsmp);
}
}
/*
* stop_pkt_retransmission(): stops retransmission of last sent packet
*
* input: dhcp_smach_t *: the state machine to stop retransmission on
* output: void
*/
void
{
}
}
/*
* 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
{
(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 *
{
} else {
}
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
{
struct sockaddr_in *sinp;
struct sockaddr_in6 *sin6;
struct in6_pktinfo *ipi6;
switch (cmsg->cmsg_level) {
case IPPROTO_IP:
case IP_RECVDSTADDR:
sinp = (struct sockaddr_in *)
sizeof (ipaddr_t));
break;
case IP_RECVIF:
break;
}
break;
case IPPROTO_IPV6:
case IPV6_PKTINFO:
/* LINTED: alignment */
ipi6 = (struct in6_pktinfo *)
sin6 = (struct sockaddr_in6 *)
&ipi6->ipi6_ifindex,
sizeof (uint_t));
break;
}
}
}
}
return (msglen);
}
/*
* recv_pkt(): receives a single DHCP packet on a given file descriptor.
*
* input: int: the file descriptor to receive the packet from
* int: the maximum packet size to allow
* boolean_t: B_TRUE for IPv6
* output: PKT_LIST *: the received packet
*/
PKT_LIST *
{
"recv_pkt: allocation failure; dropped packet");
return (NULL);
}
if (retval == -1) {
goto failure;
}
if (isv6) {
if (retval < sizeof (dhcpv6_message_t)) {
goto failure;
}
} else {
case DHCP_WRONG_MSG_TYPE:
"recv_pkt: unexpected DHCP message");
goto failure;
case DHCP_GARBLED_MSG_TYPE:
"recv_pkt: garbled DHCP message type");
goto failure;
case DHCP_BAD_OPT_OVLD:
goto failure;
case 0:
break;
default:
"recv_pkt: packet corrupted, dropped");
goto failure;
}
}
return (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
*/
{
/*
* 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[] = {
};
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
{
/* add to front of 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
{
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)
else
/* The RFC specifies doubling *after* the first transmission */
if (!first_send)
timeout_ms *= 2;
/* This checks the MRT (maximum retransmission time) */
if (dsmp->dsm_send_tcenter != 0 &&
}
} else {
if (mono > timeout_ms)
timeout_ms = 0;
else
timeout_ms -= mono;
} else {
/*
* Start at 4, and increase by a factor of 2 up to 64.
*/
if (first_send) {
} else {
64 * MILLISEC);
}
}
/*
* At each iteration, jitter the timeout by some fraction of a
* second.
*/
}
}
/*
* dhcp_ip_default(): open and bind the default IP sockets used for I/O and
* interface control.
*
* input: none
* output: B_TRUE on success
*/
dhcp_ip_default(void)
{
int on = 1;
"dhcp_ip_default: unable to create IPv4 socket");
return (B_FALSE);
}
sizeof (on)) == -1) {
"dhcp_ip_default: unable to enable IP_RECVDSTADDR");
return (B_FALSE);
}
-1) {
"dhcp_ip_default: unable to enable IP_RECVIF");
return (B_FALSE);
}
"dhcp_ip_default: unable to bind IPv4 socket to port %d",
return (B_FALSE);
}
NULL) == -1) {
"receive IPv4 broadcasts");
return (B_FALSE);
}
"dhcp_ip_default: unable to create IPv6 socket");
return (B_FALSE);
}
sizeof (on)) == -1) {
"dhcp_ip_default: unable to enable IPV6_RECVPKTINFO");
return (B_FALSE);
}
"dhcp_ip_default: unable to bind IPv6 socket to port %d",
return (B_FALSE);
}
NULL) == -1) {
"receive IPv6 packets");
return (B_FALSE);
}
return (B_TRUE);
}