1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 /*
  22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  23  * Use is subject to license terms.
  24  * Copyright (c) 2016, Chris Fraire <cfraire@me.com>.
  25  */
  26 
  27 #include <string.h>
  28 #include <sys/types.h>
  29 #include <stdlib.h>
  30 #include <dhcpmsg.h>
  31 #include <stddef.h>
  32 #include <assert.h>
  33 #include <search.h>
  34 #include <alloca.h>
  35 #include <limits.h>
  36 #include <stropts.h>
  37 #include <netinet/dhcp6.h>
  38 #include <arpa/inet.h>
  39 #include <sys/sysmacros.h>
  40 #include <sys/sockio.h>
  41 #include <inet/ip6_asp.h>
  42 
  43 #include "states.h"
  44 #include "interface.h"
  45 #include "agent.h"
  46 #include "packet.h"
  47 #include "util.h"
  48 
  49 int v6_sock_fd = -1;
  50 int v4_sock_fd = -1;
  51 
  52 const in6_addr_t ipv6_all_dhcp_relay_and_servers = {
  53         0xff, 0x02, 0x00, 0x00,
  54         0x00, 0x00, 0x00, 0x00,
  55         0x00, 0x00, 0x00, 0x00,
  56         0x00, 0x01, 0x00, 0x02
  57 };
  58 
  59 /*
  60  * We have our own version of this constant because dhcpagent is compiled with
  61  * -lxnet.
  62  */
  63 const in6_addr_t my_in6addr_any = IN6ADDR_ANY_INIT;
  64 
  65 static void     retransmit(iu_tq_t *, void *);
  66 static void     next_retransmission(dhcp_smach_t *, boolean_t, boolean_t);
  67 static boolean_t send_pkt_internal(dhcp_smach_t *);
  68 
  69 /*
  70  * pkt_send_type(): returns an integer representing the packet's type; only
  71  *                  for use with outbound packets.
  72  *
  73  *   input: dhcp_pkt_t *: the packet to examine
  74  *  output: uchar_t: the packet type (0 if unknown)
  75  */
  76 
  77 static uchar_t
  78 pkt_send_type(const dhcp_pkt_t *dpkt)
  79 {
  80         const uchar_t *option;
  81 
  82         if (dpkt->pkt_isv6)
  83                 return (((const dhcpv6_message_t *)dpkt->pkt)->d6m_msg_type);
  84 
  85         /*
  86          * this is a little dirty but it should get the job done.
  87          * assumes that the type is in the statically allocated part
  88          * of the options field.
  89          */
  90 
  91         option = dpkt->pkt->options;
  92         for (;;) {
  93                 if (*option == CD_PAD) {
  94                         option++;
  95                         continue;
  96                 }
  97                 if (*option == CD_END ||
  98                     option + 2 - dpkt->pkt->options >=
  99                     sizeof (dpkt->pkt->options))
 100                         return (0);
 101                 if (*option == CD_DHCP_TYPE)
 102                         break;
 103                 option++;
 104                 option += *option + 1;
 105         }
 106 
 107         return (option[2]);
 108 }
 109 
 110 /*
 111  * pkt_recv_type(): returns an integer representing the packet's type; only
 112  *                  for use with inbound packets.
 113  *
 114  *   input: dhcp_pkt_t *: the packet to examine
 115  *  output: uchar_t: the packet type (0 if unknown)
 116  */
 117 
 118 uchar_t
 119 pkt_recv_type(const PKT_LIST *plp)
 120 {
 121         if (plp->isv6)
 122                 return (((const dhcpv6_message_t *)plp->pkt)->d6m_msg_type);
 123         else if (plp->opts[CD_DHCP_TYPE] != NULL)
 124                 return (plp->opts[CD_DHCP_TYPE]->value[0]);
 125         else
 126                 return (0);
 127 }
 128 
 129 /*
 130  * pkt_get_xid(): returns transaction ID from a DHCP packet.
 131  *
 132  *   input: const PKT *: the packet to examine
 133  *  output: uint_t: the transaction ID (0 if unknown)
 134  */
 135 
 136 uint_t
 137 pkt_get_xid(const PKT *pkt, boolean_t isv6)
 138 {
 139         if (pkt == NULL)
 140                 return (0);
 141         if (isv6)
 142                 return (DHCPV6_GET_TRANSID((const dhcpv6_message_t *)pkt));
 143         else
 144                 return (pkt->xid);
 145 }
 146 
 147 /*
 148  * init_pkt(): initializes and returns a packet of a given type
 149  *
 150  *   input: dhcp_smach_t *: the state machine that will send the packet
 151  *          uchar_t: the packet type (DHCP message type)
 152  *  output: dhcp_pkt_t *: a pointer to the initialized packet; may be NULL
 153  */
 154 
 155 dhcp_pkt_t *
 156 init_pkt(dhcp_smach_t *dsmp, uchar_t type)
 157 {
 158         dhcp_pkt_t      *dpkt = &dsmp->dsm_send_pkt;
 159         dhcp_lif_t      *lif = dsmp->dsm_lif;
 160         dhcp_pif_t      *pif = lif->lif_pif;
 161         uint_t          mtu = lif->lif_max;
 162         uint32_t        xid;
 163         boolean_t       isv6;
 164 
 165         dpkt->pkt_isv6 = isv6 = pif->pif_isv6;
 166 
 167         /*
 168          * Since multiple dhcp leases may be maintained over the same pif
 169          * (e.g. "hme0" and "hme0:1"), make sure the xid is unique.
 170          *
 171          * Note that transaction ID zero is intentionally never assigned.
 172          * That's used to represent "no ID."  Also note that transaction IDs
 173          * are only 24 bits long in DHCPv6.
 174          */
 175 
 176         do {
 177                 xid = mrand48();
 178                 if (isv6)
 179                         xid &= 0xFFFFFF;
 180         } while (xid == 0 ||
 181             lookup_smach_by_xid(xid, NULL, dpkt->pkt_isv6) != NULL);
 182 
 183         if (isv6) {
 184                 dhcpv6_message_t *v6;
 185 
 186                 if (mtu != dpkt->pkt_max_len &&
 187                     (v6 = realloc(dpkt->pkt, mtu)) != NULL) {
 188                         /* LINTED: alignment known to be correct */
 189                         dpkt->pkt = (PKT *)v6;
 190                         dpkt->pkt_max_len = mtu;
 191                 }
 192 
 193                 if (sizeof (*v6) > dpkt->pkt_max_len) {
 194                         dhcpmsg(MSG_ERR, "init_pkt: cannot allocate v6 pkt: %u",
 195                             mtu);
 196                         return (NULL);
 197                 }
 198 
 199                 v6 = (dhcpv6_message_t *)dpkt->pkt;
 200                 dpkt->pkt_cur_len = sizeof (*v6);
 201 
 202                 (void) memset(v6, 0, dpkt->pkt_max_len);
 203 
 204                 v6->d6m_msg_type = type;
 205                 DHCPV6_SET_TRANSID(v6, xid);
 206 
 207                 if (dsmp->dsm_cidlen > 0 &&
 208                     add_pkt_opt(dpkt, DHCPV6_OPT_CLIENTID, dsmp->dsm_cid,
 209                     dsmp->dsm_cidlen) == NULL) {
 210                         dhcpmsg(MSG_WARNING,
 211                             "init_pkt: cannot insert client ID");
 212                         return (NULL);
 213                 }
 214 
 215                 /* For v6, time starts with the creation of a transaction */
 216                 dsmp->dsm_neg_hrtime = gethrtime();
 217                 dsmp->dsm_newstart_monosec = monosec();
 218         } else {
 219                 static uint8_t bootmagic[] = BOOTMAGIC;
 220                 PKT *v4;
 221 
 222                 if (mtu != dpkt->pkt_max_len &&
 223                     (v4 = realloc(dpkt->pkt, mtu)) != NULL) {
 224                         dpkt->pkt = v4;
 225                         dpkt->pkt_max_len = mtu;
 226                 }
 227 
 228                 if (offsetof(PKT, options) > dpkt->pkt_max_len) {
 229                         dhcpmsg(MSG_ERR, "init_pkt: cannot allocate v4 pkt: %u",
 230                             mtu);
 231                         return (NULL);
 232                 }
 233 
 234                 v4 = dpkt->pkt;
 235                 dpkt->pkt_cur_len = offsetof(PKT, options);
 236 
 237                 (void) memset(v4, 0, dpkt->pkt_max_len);
 238                 (void) memcpy(v4->cookie, bootmagic, sizeof (bootmagic));
 239                 if (pif->pif_hwlen <= sizeof (v4->chaddr)) {
 240                         v4->hlen  = pif->pif_hwlen;
 241                         (void) memcpy(v4->chaddr, pif->pif_hwaddr,
 242                             pif->pif_hwlen);
 243                 } else {
 244                         /*
 245                          * The mac address does not fit in the chaddr
 246                          * field, thus it can not be sent to the server,
 247                          * thus server can not unicast the reply. Per
 248                          * RFC 2131 4.4.1, client can set this bit in
 249                          * DISCOVER/REQUEST. If the client is already
 250                          * in a bound state, do not set this bit, as it
 251                          * can respond to unicast responses from server
 252                          * using the 'ciaddr' address.
 253                          */
 254                         if (type == DISCOVER || (type == REQUEST &&
 255                             !is_bound_state(dsmp->dsm_state)))
 256                                 v4->flags = htons(BCAST_MASK);
 257                 }
 258 
 259                 v4->xid   = xid;
 260                 v4->op    = BOOTREQUEST;
 261                 v4->htype = pif->pif_hwtype;
 262 
 263                 if (add_pkt_opt(dpkt, CD_DHCP_TYPE, &type, 1) == NULL) {
 264                         dhcpmsg(MSG_WARNING,
 265                             "init_pkt: cannot set DHCP packet type");
 266                         return (NULL);
 267                 }
 268 
 269                 if (dsmp->dsm_cidlen > 0 &&
 270                     add_pkt_opt(dpkt, CD_CLIENT_ID, dsmp->dsm_cid,
 271                     dsmp->dsm_cidlen) == NULL) {
 272                         dhcpmsg(MSG_WARNING,
 273                             "init_pkt: cannot insert client ID");
 274                         return (NULL);
 275                 }
 276         }
 277 
 278         return (dpkt);
 279 }
 280 
 281 /*
 282  * remove_pkt_opt(): removes the first instance of an option from a dhcp_pkt_t
 283  *
 284  *   input: dhcp_pkt_t *: the packet to remove the option from
 285  *          uint_t: the type of option being added
 286  *  output: boolean_t: B_TRUE on success, B_FALSE on failure
 287  *    note: currently does not work with DHCPv6 suboptions, or to remove
 288  *          arbitrary option instances.
 289  */
 290 
 291 boolean_t
 292 remove_pkt_opt(dhcp_pkt_t *dpkt, uint_t opt_type)
 293 {
 294         uchar_t         *raw_pkt, *raw_end, *next;
 295         uint_t          len;
 296 
 297         raw_pkt = (uchar_t *)dpkt->pkt;
 298         raw_end = raw_pkt + dpkt->pkt_cur_len;
 299         if (dpkt->pkt_isv6) {
 300                 dhcpv6_option_t d6o;
 301 
 302                 raw_pkt += sizeof (dhcpv6_message_t);
 303 
 304                 opt_type = htons(opt_type);
 305                 while (raw_pkt + sizeof (d6o) <= raw_end) {
 306                         (void) memcpy(&d6o, raw_pkt, sizeof (d6o));
 307                         len = ntohs(d6o.d6o_len) + sizeof (d6o);
 308                         if (len > raw_end - raw_pkt)
 309                                 break;
 310                         next = raw_pkt + len;
 311                         if (d6o.d6o_code == opt_type) {
 312                                 if (next < raw_end) {
 313                                         (void) memmove(raw_pkt, next,
 314                                             raw_end - next);
 315                                 }
 316                                 dpkt->pkt_cur_len -= len;
 317                                 return (B_TRUE);
 318                         }
 319                         raw_pkt = next;
 320                 }
 321         } else {
 322                 uchar_t *pstart, *padrun;
 323 
 324                 raw_pkt += offsetof(PKT, options);
 325                 pstart = raw_pkt;
 326 
 327                 if (opt_type == CD_END || opt_type == CD_PAD)
 328                         return (B_FALSE);
 329 
 330                 padrun = NULL;
 331                 while (raw_pkt + 1 <= raw_end) {
 332                         if (*raw_pkt == CD_END)
 333                                 break;
 334                         if (*raw_pkt == CD_PAD) {
 335                                 if (padrun == NULL)
 336                                         padrun = raw_pkt;
 337                                 raw_pkt++;
 338                                 continue;
 339                         }
 340                         if (raw_pkt + 2 > raw_end)
 341                                 break;
 342                         len = raw_pkt[1];
 343                         if (len > raw_end - raw_pkt || len < 2)
 344                                 break;
 345                         next = raw_pkt + len;
 346                         if (*raw_pkt == opt_type) {
 347                                 if (next < raw_end) {
 348                                         int toadd = (4 + ((next-pstart)&3) -
 349                                             ((raw_pkt-pstart)&3)) & 3;
 350                                         int torem = 4 - toadd;
 351 
 352                                         if (torem != 4 && padrun != NULL &&
 353                                             (raw_pkt - padrun) >= torem) {
 354                                                 raw_pkt -= torem;
 355                                                 dpkt->pkt_cur_len -= torem;
 356                                         } else if (toadd > 0) {
 357                                                 (void) memset(raw_pkt, CD_PAD,
 358                                                     toadd);
 359                                                 raw_pkt += toadd;
 360                                                 /* max is not an issue here */
 361                                                 dpkt->pkt_cur_len += toadd;
 362                                         }
 363                                         if (raw_pkt != next) {
 364                                                 (void) memmove(raw_pkt, next,
 365                                                     raw_end - next);
 366                                         }
 367                                 }
 368                                 dpkt->pkt_cur_len -= len;
 369                                 return (B_TRUE);
 370                         }
 371                         padrun = NULL;
 372                         raw_pkt = next;
 373                 }
 374         }
 375         return (B_FALSE);
 376 }
 377 
 378 /*
 379  * update_v6opt_len(): updates the length field of a DHCPv6 option.
 380  *
 381  *   input: dhcpv6_option_t *: option to be updated
 382  *          int: number of octets to add or subtract
 383  *  output: boolean_t: B_TRUE on success, B_FALSE on failure
 384  */
 385 
 386 boolean_t
 387 update_v6opt_len(dhcpv6_option_t *opt, int adjust)
 388 {
 389         dhcpv6_option_t optval;
 390 
 391         (void) memcpy(&optval, opt, sizeof (optval));
 392         adjust += ntohs(optval.d6o_len);
 393         if (adjust < 0 || adjust > UINT16_MAX) {
 394                 return (B_FALSE);
 395         } else {
 396                 optval.d6o_len = htons(adjust);
 397                 (void) memcpy(opt, &optval, sizeof (optval));
 398                 return (B_TRUE);
 399         }
 400 }
 401 
 402 /*
 403  * add_pkt_opt(): adds an option to a dhcp_pkt_t
 404  *
 405  *   input: dhcp_pkt_t *: the packet to add the option to
 406  *          uint_t: the type of option being added
 407  *          const void *: the value of that option
 408  *          uint_t: the length of the value of the option
 409  *  output: void *: pointer to the option that was added, or NULL on failure.
 410  */
 411 
 412 void *
 413 add_pkt_opt(dhcp_pkt_t *dpkt, uint_t opt_type, const void *opt_val,
 414     uint_t opt_len)
 415 {
 416         uchar_t         *raw_pkt;
 417         size_t          req_len;
 418         void            *optr;
 419 
 420         raw_pkt = (uchar_t *)dpkt->pkt;
 421         optr = raw_pkt + dpkt->pkt_cur_len;
 422         if (dpkt->pkt_isv6) {
 423                 req_len = opt_len + sizeof (dhcpv6_option_t);
 424 
 425                 if (dpkt->pkt_cur_len + req_len > dpkt->pkt_max_len) {
 426                         dhcpmsg(MSG_WARNING,
 427                             "add_pkt_opt: not enough room for v6 option %u in "
 428                             "packet (%u + %u > %u)", opt_type,
 429                             dpkt->pkt_cur_len, req_len, dpkt->pkt_max_len);
 430                         return (NULL);
 431                 }
 432         } else {
 433                 req_len = opt_len + DHCP_OPT_META_LEN;
 434 
 435                 /* CD_END and CD_PAD options don't have a length field */
 436                 if (opt_type == CD_END || opt_type == CD_PAD) {
 437                         req_len = 1;
 438                 } else if (opt_val == NULL) {
 439                         dhcpmsg(MSG_ERROR, "add_pkt_opt: option type %d is "
 440                             "missing required value", opt_type);
 441                         return (NULL);
 442                 }
 443 
 444                 if ((dpkt->pkt_cur_len + req_len) > dpkt->pkt_max_len) {
 445                         dhcpmsg(MSG_WARNING,
 446                             "add_pkt_opt: not enough room for v4 option %u in "
 447                             "packet", opt_type);
 448                         return (NULL);
 449                 }
 450         }
 451 
 452         req_len = encode_dhcp_opt(&raw_pkt[dpkt->pkt_cur_len], dpkt->pkt_isv6,
 453             opt_type, opt_val, opt_len);
 454         dpkt->pkt_cur_len += req_len;
 455 
 456         return (optr);
 457 }
 458 
 459 /*
 460  * encode_dhcp_opt(): sets the fields of an allocated DHCP option buffer
 461  *
 462  *   input: void *: the buffer allocated for enough space for
 463  *                      (DHCPv6) dhcpv6_option_t and value, or for (DHCPv4) opt_type +
 464  *                      length + value (length/value are skipped for CD_END or
 465  *                      CD_PAD);
 466  *          boolean_t: a value indicating whether DHCPv6 or not;
 467  *          uint_t: the type of option being added;
 468  *          const void *: the value of that option;
 469  *          uint_t: the length of the value of the option
 470  *  output: size_t: the number of bytes written starting at opt.
 471  */
 472 
 473 size_t
 474 encode_dhcp_opt(void *dopt, boolean_t isv6, uint_t opt_type,
 475     const void *opt_val, uint_t opt_len)
 476 {
 477         boolean_t do_copy_value = B_FALSE;
 478         size_t res_len = 0;
 479         uint8_t *pval;
 480 
 481         if (isv6) {
 482                 dhcpv6_option_t d6o;
 483                 d6o.d6o_code = htons(opt_type);
 484                 d6o.d6o_len = htons(opt_len);
 485                 (void) memcpy(dopt, &d6o, sizeof (d6o));
 486                 res_len += sizeof (d6o);
 487 
 488                 do_copy_value = B_TRUE;
 489         } else {
 490                 pval = (uint8_t *)dopt;
 491                 pval[res_len++] = opt_type;
 492 
 493                 if (opt_type != CD_END && opt_type != CD_PAD) {
 494                         pval[res_len++] = opt_len;
 495                         do_copy_value = B_TRUE;
 496                 }
 497         }
 498 
 499         pval = (uint8_t *)dopt + res_len;
 500         if (do_copy_value && opt_len > 0) {
 501                 (void) memcpy(pval, opt_val, opt_len);
 502                 res_len += opt_len;
 503         }
 504 
 505         return (res_len);
 506 }
 507 
 508 /*
 509  * add_pkt_subopt(): adds an option to a dhcp_pkt_t option.  DHCPv6-specific,
 510  *                   but could be extended to IPv4 DHCP if necessary.  Assumes
 511  *                   that if the parent isn't a top-level option, the caller
 512  *                   will adjust any upper-level options recursively using
 513  *                   update_v6opt_len.
 514  *
 515  *   input: dhcp_pkt_t *: the packet to add the suboption to
 516  *          dhcpv6_option_t *: the start of the option to that should contain
 517  *                             it (parent)
 518  *          uint_t: the type of suboption being added
 519  *          const void *: the value of that option
 520  *          uint_t: the length of the value of the option
 521  *  output: void *: pointer to the suboption that was added, or NULL on
 522  *                  failure.
 523  */
 524 
 525 void *
 526 add_pkt_subopt(dhcp_pkt_t *dpkt, dhcpv6_option_t *parentopt, uint_t opt_type,
 527     const void *opt_val, uint_t opt_len)
 528 {
 529         uchar_t         *raw_pkt;
 530         int             req_len;
 531         void            *optr;
 532         dhcpv6_option_t d6o;
 533         uchar_t         *optend;
 534         int             olen;
 535 
 536         if (!dpkt->pkt_isv6)
 537                 return (NULL);
 538 
 539         raw_pkt = (uchar_t *)dpkt->pkt;
 540         req_len = opt_len + sizeof (d6o);
 541 
 542         if (dpkt->pkt_cur_len + req_len > dpkt->pkt_max_len) {
 543                 dhcpmsg(MSG_WARNING,
 544                     "add_pkt_subopt: not enough room for v6 suboption %u in "
 545                     "packet (%u + %u > %u)", opt_type,
 546                     dpkt->pkt_cur_len, req_len, dpkt->pkt_max_len);
 547                 return (NULL);
 548         }
 549 
 550         /*
 551          * Update the parent option to include room for this option,
 552          * and compute the insertion point.
 553          */
 554         (void) memcpy(&d6o, parentopt, sizeof (d6o));
 555         olen = ntohs(d6o.d6o_len);
 556         optend = (uchar_t *)(parentopt + 1) + olen;
 557         olen += req_len;
 558         d6o.d6o_len = htons(olen);
 559         (void) memcpy(parentopt, &d6o, sizeof (d6o));
 560 
 561         /*
 562          * If there's anything at the end to move, then move it.  Also bump up
 563          * the packet size.
 564          */
 565         if (optend < raw_pkt + dpkt->pkt_cur_len) {
 566                 (void) memmove(optend + req_len, optend,
 567                     (raw_pkt + dpkt->pkt_cur_len) - optend);
 568         }
 569         dpkt->pkt_cur_len += req_len;
 570 
 571         /*
 572          * Now format the suboption and add it in.
 573          */
 574         optr = optend;
 575         d6o.d6o_code = htons(opt_type);
 576         d6o.d6o_len = htons(opt_len);
 577         (void) memcpy(optend, &d6o, sizeof (d6o));
 578         if (opt_len > 0)
 579                 (void) memcpy(optend + sizeof (d6o), opt_val, opt_len);
 580         return (optr);
 581 }
 582 
 583 /*
 584  * add_pkt_opt16(): adds an option with a 16-bit value to a dhcp_pkt_t
 585  *
 586  *   input: dhcp_pkt_t *: the packet to add the option to
 587  *          uint_t: the type of option being added
 588  *          uint16_t: the value of that option
 589  *  output: void *: pointer to the option that was added, or NULL on failure.
 590  */
 591 
 592 void *
 593 add_pkt_opt16(dhcp_pkt_t *dpkt, uint_t opt_type, uint16_t opt_value)
 594 {
 595         return (add_pkt_opt(dpkt, opt_type, &opt_value, 2));
 596 }
 597 
 598 /*
 599  * add_pkt_opt32(): adds an option with a 32-bit value to a dhcp_pkt_t
 600  *
 601  *   input: dhcp_pkt_t *: the packet to add the option to
 602  *          uint_t: the type of option being added
 603  *          uint32_t: the value of that option
 604  *  output: void *: pointer to the option that was added, or NULL on failure.
 605  */
 606 
 607 void *
 608 add_pkt_opt32(dhcp_pkt_t *dpkt, uint_t opt_type, uint32_t opt_value)
 609 {
 610         return (add_pkt_opt(dpkt, opt_type, &opt_value, 4));
 611 }
 612 
 613 /*
 614  * add_pkt_prl(): adds the parameter request option to the packet
 615  *
 616  *   input: dhcp_pkt_t *: the packet to add the option to
 617  *          dhcp_smach_t *: state machine with request option
 618  *  output: void *: pointer to the option that was added, or NULL on failure.
 619  */
 620 
 621 void *
 622 add_pkt_prl(dhcp_pkt_t *dpkt, dhcp_smach_t *dsmp)
 623 {
 624         uint_t len;
 625 
 626         if (dsmp->dsm_prllen == 0)
 627                 return (0);
 628 
 629         if (dpkt->pkt_isv6) {
 630                 uint16_t *prl;
 631 
 632                 /*
 633                  * RFC 3315 requires that we include the option, even if we
 634                  * have nothing to request.
 635                  */
 636                 if (dsmp->dsm_prllen == 0)
 637                         prl = NULL;
 638                 else
 639                         prl = alloca(dsmp->dsm_prllen * sizeof (uint16_t));
 640 
 641                 for (len = 0; len < dsmp->dsm_prllen; len++)
 642                         prl[len] = htons(dsmp->dsm_prl[len]);
 643                 return (add_pkt_opt(dpkt, DHCPV6_OPT_ORO, prl,
 644                     len * sizeof (uint16_t)));
 645         } else {
 646                 uint8_t *prl = alloca(dsmp->dsm_prllen);
 647 
 648                 for (len = 0; len < dsmp->dsm_prllen; len++)
 649                         prl[len] = dsmp->dsm_prl[len];
 650                 return (add_pkt_opt(dpkt, CD_REQUEST_LIST, prl, len));
 651         }
 652 }
 653 
 654 /*
 655  * add_pkt_lif(): Adds CD_REQUESTED_IP_ADDR (IPv4 DHCP) or IA_NA and IAADDR
 656  *                (DHCPv6) options to the packet to represent the given LIF.
 657  *
 658  *   input: dhcp_pkt_t *: the packet to add the options to
 659  *          dhcp_lif_t *: the logical interface to represent
 660  *          int: status code (unused for IPv4 DHCP)
 661  *          const char *: message to include with status option, or NULL
 662  *  output: boolean_t: B_TRUE on success, B_FALSE on failure
 663  */
 664 
 665 boolean_t
 666 add_pkt_lif(dhcp_pkt_t *dpkt, dhcp_lif_t *lif, int status, const char *msg)
 667 {
 668         if (lif->lif_pif->pif_isv6) {
 669                 dhcp_smach_t *dsmp;
 670                 dhcpv6_message_t *d6m;
 671                 dhcpv6_ia_na_t d6in;
 672                 dhcpv6_iaaddr_t d6ia;
 673                 uint32_t iaid;
 674                 uint16_t *statusopt;
 675                 dhcpv6_option_t *d6o, *d6so;
 676                 uint_t olen;
 677 
 678                 /*
 679                  * Currently, we support just one IAID related to the primary
 680                  * LIF on the state machine.
 681                  */
 682                 dsmp = lif->lif_lease->dl_smach;
 683                 iaid = dsmp->dsm_lif->lif_iaid;
 684                 iaid = htonl(iaid);
 685 
 686                 d6m = (dhcpv6_message_t *)dpkt->pkt;
 687 
 688                 /*
 689                  * Find or create the IA_NA needed for this LIF.  If we
 690                  * supported IA_TA, we'd check the IFF_TEMPORARY bit here.
 691                  */
 692                 d6o = NULL;
 693                 while ((d6o = dhcpv6_find_option(d6m + 1,
 694                     dpkt->pkt_cur_len - sizeof (*d6m), d6o, DHCPV6_OPT_IA_NA,
 695                     &olen)) != NULL) {
 696                         if (olen < sizeof (d6in))
 697                                 continue;
 698                         (void) memcpy(&d6in, d6o, sizeof (d6in));
 699                         if (d6in.d6in_iaid == iaid)
 700                                 break;
 701                 }
 702                 if (d6o == NULL) {
 703                         d6in.d6in_iaid = iaid;
 704                         d6in.d6in_t1 = 0;
 705                         d6in.d6in_t2 = 0;
 706                         d6o = add_pkt_opt(dpkt, DHCPV6_OPT_IA_NA,
 707                             (dhcpv6_option_t *)&d6in + 1,
 708                             sizeof (d6in) - sizeof (*d6o));
 709                         if (d6o == NULL)
 710                                 return (B_FALSE);
 711                 }
 712 
 713                 /*
 714                  * Now add the IAADDR suboption for this LIF.  No need to
 715                  * search here, as we know that this is unique.
 716                  */
 717                 d6ia.d6ia_addr = lif->lif_v6addr;
 718 
 719                 /*
 720                  * For Release and Decline, we zero out the lifetime.  For
 721                  * Renew and Rebind, we report the original time as the
 722                  * preferred and valid lifetimes.
 723                  */
 724                 if (d6m->d6m_msg_type == DHCPV6_MSG_RELEASE ||
 725                     d6m->d6m_msg_type == DHCPV6_MSG_DECLINE) {
 726                         d6ia.d6ia_preflife = 0;
 727                         d6ia.d6ia_vallife = 0;
 728                 } else {
 729                         d6ia.d6ia_preflife = htonl(lif->lif_preferred.dt_start);
 730                         d6ia.d6ia_vallife = htonl(lif->lif_expire.dt_start);
 731                 }
 732                 d6so = add_pkt_subopt(dpkt, d6o, DHCPV6_OPT_IAADDR,
 733                     (dhcpv6_option_t *)&d6ia + 1,
 734                     sizeof (d6ia) - sizeof (*d6o));
 735                 if (d6so == NULL)
 736                         return (B_FALSE);
 737 
 738                 /*
 739                  * Add a status code suboption to the IAADDR to tell the server
 740                  * why we're declining the address.  Note that we must manually
 741                  * update the enclosing IA_NA, as add_pkt_subopt doesn't know
 742                  * how to do that.
 743                  */
 744                 if (status != DHCPV6_STAT_SUCCESS || msg != NULL) {
 745                         olen = sizeof (*statusopt) +
 746                             (msg == NULL ? 0 : strlen(msg));
 747                         statusopt = alloca(olen);
 748                         *statusopt = htons(status);
 749                         if (msg != NULL) {
 750                                 (void) memcpy((char *)(statusopt + 1), msg,
 751                                     olen - sizeof (*statusopt));
 752                         }
 753                         d6so = add_pkt_subopt(dpkt, d6so,
 754                             DHCPV6_OPT_STATUS_CODE, statusopt, olen);
 755                         if (d6so != NULL) {
 756                                 /*
 757                                  * Update for length of suboption header and
 758                                  * suboption contents.
 759                                  */
 760                                 (void) update_v6opt_len(d6o, sizeof (*d6so) +
 761                                     olen);
 762                         }
 763                 }
 764         } else {
 765                 /*
 766                  * For DECLINE, we need to add the CD_REQUESTED_IP_ADDR option.
 767                  * In all other cases (RELEASE and REQUEST), we need to set
 768                  * ciadr.
 769                  */
 770                 if (pkt_send_type(dpkt) == DECLINE) {
 771                         if (!add_pkt_opt32(dpkt, CD_REQUESTED_IP_ADDR,
 772                             lif->lif_addr))
 773                                 return (B_FALSE);
 774                 } else {
 775                         dpkt->pkt->ciaddr.s_addr = lif->lif_addr;
 776                 }
 777 
 778                 /*
 779                  * It's not too worrisome if the message fails to fit in the
 780                  * packet.  The result will still be valid.
 781                  */
 782                 if (msg != NULL)
 783                         (void) add_pkt_opt(dpkt, CD_MESSAGE, msg,
 784                             strlen(msg) + 1);
 785         }
 786         return (B_TRUE);
 787 }
 788 
 789 /*
 790  * free_pkt_entry(): frees a packet list list entry
 791  *
 792  *   input: PKT_LIST *: the packet list entry to free
 793  *  output: void
 794  */
 795 void
 796 free_pkt_entry(PKT_LIST *plp)
 797 {
 798         if (plp != NULL) {
 799                 free(plp->pkt);
 800                 free(plp);
 801         }
 802 }
 803 
 804 /*
 805  * free_pkt_list(): frees an entire packet list
 806  *
 807  *   input: PKT_LIST **: the packet list to free
 808  *  output: void
 809  */
 810 
 811 void
 812 free_pkt_list(PKT_LIST **head)
 813 {
 814         PKT_LIST *plp;
 815 
 816         while ((plp = *head) != NULL) {
 817                 remque(plp);
 818                 free_pkt_entry(plp);
 819         }
 820 }
 821 
 822 /*
 823  * send_pkt_internal(): sends a packet out on an interface
 824  *
 825  *   input: dhcp_smach_t *: the state machine with a packet to send
 826  *  output: boolean_t: B_TRUE if the packet is sent, B_FALSE otherwise
 827  */
 828 
 829 static boolean_t
 830 send_pkt_internal(dhcp_smach_t *dsmp)
 831 {
 832         ssize_t         n_bytes;
 833         dhcp_lif_t      *lif = dsmp->dsm_lif;
 834         dhcp_pkt_t      *dpkt = &dsmp->dsm_send_pkt;
 835         uchar_t         ptype = pkt_send_type(dpkt);
 836         const char      *pkt_name;
 837         struct iovec    iov;
 838         struct msghdr   msg;
 839         struct cmsghdr  *cmsg;
 840         struct in6_pktinfo *ipi6;
 841         boolean_t       ismcast;
 842         int             msgtype;
 843 
 844         /*
 845          * Timer should not be running at the point we go to send a packet.
 846          */
 847         if (dsmp->dsm_retrans_timer != -1) {
 848                 dhcpmsg(MSG_CRIT, "send_pkt_internal: unexpected retransmit "
 849                     "timer on %s", dsmp->dsm_name);
 850                 stop_pkt_retransmission(dsmp);
 851         }
 852 
 853         pkt_name = pkt_type_to_string(ptype, dpkt->pkt_isv6);
 854 
 855         /*
 856          * if needed, schedule a retransmission timer, then attempt to
 857          * send the packet.  if we fail, then log the error.  our
 858          * return value should indicate whether or not we were
 859          * successful in sending the request, independent of whether
 860          * we could schedule a timer.
 861          */
 862 
 863         if (dsmp->dsm_send_timeout != 0) {
 864                 if ((dsmp->dsm_retrans_timer = iu_schedule_timer_ms(tq,
 865                     dsmp->dsm_send_timeout, retransmit, dsmp)) == -1)
 866                         dhcpmsg(MSG_WARNING, "send_pkt_internal: cannot "
 867                             "schedule retransmit timer for %s packet",
 868                             pkt_name);
 869                 else
 870                         hold_smach(dsmp);
 871         }
 872 
 873         if (dpkt->pkt_isv6) {
 874                 hrtime_t delta;
 875 
 876                 /*
 877                  * Convert current time into centiseconds since transaction
 878                  * started.  This is what DHCPv6 expects to see in the Elapsed
 879                  * Time option.
 880                  */
 881                 delta = (gethrtime() - dsmp->dsm_neg_hrtime) /
 882                     (NANOSEC / 100);
 883                 if (delta > DHCPV6_FOREVER)
 884                         delta = DHCPV6_FOREVER;
 885                 (void) remove_pkt_opt(dpkt, DHCPV6_OPT_ELAPSED_TIME);
 886                 (void) add_pkt_opt16(dpkt, DHCPV6_OPT_ELAPSED_TIME,
 887                     htons(delta));
 888         } else {
 889                 /*
 890                  * set the `pkt->secs' field depending on the type of packet.
 891                  * it should be zero, except in the following cases:
 892                  *
 893                  * DISCOVER:    set to the number of seconds since we started
 894                  *              trying to obtain a lease.
 895                  *
 896                  * INFORM:      set to the number of seconds since we started
 897                  *              trying to get configuration parameters.
 898                  *
 899                  * REQUEST:     if in the REQUESTING state, then same value as
 900                  *              DISCOVER, otherwise the number of seconds
 901                  *              since we started trying to obtain a lease.
 902                  *
 903                  * we also set `dsm_newstart_monosec', to the time we sent a
 904                  * REQUEST or DISCOVER packet, so we know the lease start
 905                  * time (the DISCOVER case is for handling BOOTP servers).
 906                  */
 907 
 908                 switch (ptype) {
 909 
 910                 case DISCOVER:
 911                         dsmp->dsm_newstart_monosec = monosec();
 912                         dsmp->dsm_disc_secs = dsmp->dsm_newstart_monosec -
 913                             hrtime_to_monosec(dsmp->dsm_neg_hrtime);
 914                         dpkt->pkt->secs = htons(dsmp->dsm_disc_secs);
 915                         break;
 916 
 917                 case INFORM:
 918                         dpkt->pkt->secs = htons(monosec() -
 919                             hrtime_to_monosec(dsmp->dsm_neg_hrtime));
 920                         break;
 921 
 922                 case REQUEST:
 923                         dsmp->dsm_newstart_monosec = monosec();
 924 
 925                         if (dsmp->dsm_state == REQUESTING) {
 926                                 dpkt->pkt->secs = htons(dsmp->dsm_disc_secs);
 927                                 break;
 928                         }
 929 
 930                         dpkt->pkt->secs = htons(monosec() -
 931                             hrtime_to_monosec(dsmp->dsm_neg_hrtime));
 932                         break;
 933 
 934                 default:
 935                         dpkt->pkt->secs = htons(0);
 936                         break;
 937                 }
 938         }
 939 
 940         if (dpkt->pkt_isv6) {
 941                 struct sockaddr_in6 sin6;
 942 
 943                 (void) memset(&iov, 0, sizeof (iov));
 944                 iov.iov_base = dpkt->pkt;
 945                 iov.iov_len = dpkt->pkt_cur_len;
 946 
 947                 (void) memset(&msg, 0, sizeof (msg));
 948                 msg.msg_name = &dsmp->dsm_send_dest.v6;
 949                 msg.msg_namelen = sizeof (struct sockaddr_in6);
 950                 msg.msg_iov = &iov;
 951                 msg.msg_iovlen = 1;
 952 
 953                 /*
 954                  * If the address that's requested cannot be reached, then fall
 955                  * back to the multcast address.
 956                  */
 957                 if (IN6_IS_ADDR_MULTICAST(&dsmp->dsm_send_dest.v6.sin6_addr)) {
 958                         ismcast = B_TRUE;
 959                 } else {
 960                         struct dstinforeq dinfo;
 961                         struct strioctl str;
 962 
 963                         ismcast = B_FALSE;
 964                         (void) memset(&dinfo, 0, sizeof (dinfo));
 965                         dinfo.dir_daddr = dsmp->dsm_send_dest.v6.sin6_addr;
 966                         str.ic_cmd = SIOCGDSTINFO;
 967                         str.ic_timout = 0;
 968                         str.ic_len = sizeof (dinfo);
 969                         str.ic_dp = (char *)&dinfo;
 970                         if (ioctl(v6_sock_fd, I_STR, &str) == -1) {
 971                                 dhcpmsg(MSG_ERR,
 972                                     "send_pkt_internal: ioctl SIOCGDSTINFO");
 973                         } else if (!dinfo.dir_dreachable) {
 974                                 char abuf[INET6_ADDRSTRLEN];
 975 
 976                                 dhcpmsg(MSG_DEBUG, "send_pkt_internal: %s is "
 977                                     "not reachable; using multicast instead",
 978                                     inet_ntop(AF_INET6, &dinfo.dir_daddr, abuf,
 979                                     sizeof (abuf)));
 980                                 sin6 = dsmp->dsm_send_dest.v6;
 981                                 sin6.sin6_addr =
 982                                     ipv6_all_dhcp_relay_and_servers;
 983                                 msg.msg_name = &sin6;
 984                                 ismcast = B_TRUE;
 985                         }
 986                 }
 987 
 988                 /*
 989                  * Make room for our ancillary data option as well as a dummy
 990                  * option used by CMSG_NXTHDR.
 991                  */
 992                 msg.msg_controllen = sizeof (*cmsg) + _MAX_ALIGNMENT +
 993                     sizeof (*ipi6) + _MAX_ALIGNMENT + sizeof (*cmsg);
 994                 msg.msg_control = alloca(msg.msg_controllen);
 995                 cmsg = CMSG_FIRSTHDR(&msg);
 996                 cmsg->cmsg_level = IPPROTO_IPV6;
 997                 cmsg->cmsg_type = IPV6_PKTINFO;
 998                 /* LINTED: alignment */
 999                 ipi6 = (struct in6_pktinfo *)CMSG_DATA(cmsg);
1000                 if (ismcast)
1001                         ipi6->ipi6_addr = lif->lif_v6addr;
1002                 else
1003                         ipi6->ipi6_addr = my_in6addr_any;
1004                 if (lif->lif_pif->pif_under_ipmp)
1005                         ipi6->ipi6_ifindex = lif->lif_pif->pif_grindex;
1006                 else
1007                         ipi6->ipi6_ifindex = lif->lif_pif->pif_index;
1008                 cmsg->cmsg_len = (char *)(ipi6 + 1) - (char *)cmsg;
1009 
1010                 /*
1011                  * Now correct the control message length.
1012                  */
1013                 cmsg = CMSG_NXTHDR(&msg, cmsg);
1014                 msg.msg_controllen = (char *)cmsg - (char *)msg.msg_control;
1015 
1016                 n_bytes = sendmsg(v6_sock_fd, &msg, 0);
1017         } else {
1018                 n_bytes = sendto(lif->lif_sock_ip_fd, dpkt->pkt,
1019                     dpkt->pkt_cur_len, 0,
1020                     (struct sockaddr *)&dsmp->dsm_send_dest.v4,
1021                     sizeof (struct sockaddr_in));
1022         }
1023 
1024         if (n_bytes != dpkt->pkt_cur_len) {
1025                 msgtype = (n_bytes == -1) ? MSG_ERR : MSG_WARNING;
1026                 if (dsmp->dsm_retrans_timer == -1)
1027                         dhcpmsg(msgtype, "send_pkt_internal: cannot send "
1028                             "%s packet to server", pkt_name);
1029                 else
1030                         dhcpmsg(msgtype, "send_pkt_internal: cannot send "
1031                             "%s packet to server (will retry in %u seconds)",
1032                             pkt_name, dsmp->dsm_send_timeout / MILLISEC);
1033                 return (B_FALSE);
1034         }
1035 
1036         dhcpmsg(MSG_VERBOSE, "sent %s xid %x packet out %s", pkt_name,
1037             pkt_get_xid(dpkt->pkt, dpkt->pkt_isv6), dsmp->dsm_name);
1038 
1039         dsmp->dsm_packet_sent++;
1040         dsmp->dsm_sent++;
1041         return (B_TRUE);
1042 }
1043 
1044 /*
1045  * send_pkt(): sends a packet out
1046  *
1047  *   input: dhcp_smach_t *: the state machine sending the packet
1048  *          dhcp_pkt_t *: the packet to send out
1049  *          in_addr_t: the destination IP address for the packet
1050  *          stop_func_t *: a pointer to function to indicate when to stop
1051  *                         retransmitting the packet (if NULL, packet is
1052  *                         not retransmitted)
1053  *  output: boolean_t: B_TRUE if the packet was sent, B_FALSE otherwise
1054  */
1055 
1056 boolean_t
1057 send_pkt(dhcp_smach_t *dsmp, dhcp_pkt_t *dpkt, in_addr_t dest,
1058     stop_func_t *stop)
1059 {
1060         /*
1061          * packets must be at least sizeof (PKT) or they may be dropped
1062          * by routers.  pad out the packet in this case.
1063          */
1064 
1065         dpkt->pkt_cur_len = MAX(dpkt->pkt_cur_len, sizeof (PKT));
1066 
1067         dsmp->dsm_packet_sent = 0;
1068 
1069         (void) memset(&dsmp->dsm_send_dest.v4, 0,
1070             sizeof (dsmp->dsm_send_dest.v4));
1071         dsmp->dsm_send_dest.v4.sin_addr.s_addr       = dest;
1072         dsmp->dsm_send_dest.v4.sin_family    = AF_INET;
1073         dsmp->dsm_send_dest.v4.sin_port              = htons(IPPORT_BOOTPS);
1074         dsmp->dsm_send_stop_func             = stop;
1075 
1076         /*
1077          * TODO: dispose of this gruesome assumption (there's no real
1078          * technical gain from doing so, but it would be cleaner)
1079          */
1080 
1081         assert(dpkt == &dsmp->dsm_send_pkt);
1082 
1083         /*
1084          * clear out any packets which had been previously received
1085          * but not pulled off of the recv_packet queue.
1086          */
1087 
1088         free_pkt_list(&dsmp->dsm_recv_pkt_list);
1089 
1090         if (stop == NULL)
1091                 dsmp->dsm_send_timeout = 0;  /* prevents retransmissions */
1092         else
1093                 next_retransmission(dsmp, B_TRUE, B_FALSE);
1094 
1095         return (send_pkt_internal(dsmp));
1096 }
1097 
1098 /*
1099  * send_pkt_v6(): sends a DHCPv6 packet out
1100  *
1101  *   input: dhcp_smach_t *: the state machine sending the packet
1102  *          dhcp_pkt_t *: the packet to send out
1103  *          in6_addr_t: the destination IPv6 address for the packet
1104  *          stop_func_t *: a pointer to function to indicate when to stop
1105  *                         retransmitting the packet (if NULL, packet is
1106  *                         not retransmitted)
1107  *          uint_t: Initial Retransmit Timer value
1108  *          uint_t: Maximum Retransmit Timer value, zero if none
1109  *  output: boolean_t: B_TRUE if the packet was sent, B_FALSE otherwise
1110  */
1111 
1112 boolean_t
1113 send_pkt_v6(dhcp_smach_t *dsmp, dhcp_pkt_t *dpkt, in6_addr_t dest,
1114     stop_func_t *stop, uint_t irt, uint_t mrt)
1115 {
1116         dsmp->dsm_packet_sent = 0;
1117 
1118         (void) memset(&dsmp->dsm_send_dest.v6, 0,
1119             sizeof (dsmp->dsm_send_dest.v6));
1120         dsmp->dsm_send_dest.v6.sin6_addr     = dest;
1121         dsmp->dsm_send_dest.v6.sin6_family   = AF_INET6;
1122         dsmp->dsm_send_dest.v6.sin6_port     = htons(IPPORT_DHCPV6S);
1123         dsmp->dsm_send_stop_func             = stop;
1124 
1125         /*
1126          * TODO: dispose of this gruesome assumption (there's no real
1127          * technical gain from doing so, but it would be cleaner)
1128          */
1129 
1130         assert(dpkt == &dsmp->dsm_send_pkt);
1131 
1132         /*
1133          * clear out any packets which had been previously received
1134          * but not pulled off of the recv_packet queue.
1135          */
1136 
1137         free_pkt_list(&dsmp->dsm_recv_pkt_list);
1138 
1139         if (stop == NULL) {
1140                 dsmp->dsm_send_timeout = 0;  /* prevents retransmissions */
1141         } else {
1142                 dsmp->dsm_send_timeout = irt;
1143                 dsmp->dsm_send_tcenter = mrt;
1144                 /*
1145                  * This is quite ugly, but RFC 3315 section 17.1.2 requires
1146                  * that the RAND value for the very first retransmission of a
1147                  * Solicit message is strictly greater than zero.
1148                  */
1149                 next_retransmission(dsmp, B_TRUE,
1150                     pkt_send_type(dpkt) == DHCPV6_MSG_SOLICIT);
1151         }
1152 
1153         return (send_pkt_internal(dsmp));
1154 }
1155 
1156 /*
1157  * retransmit(): retransmits the current packet on an interface
1158  *
1159  *   input: iu_tq_t *: unused
1160  *          void *: the dhcp_smach_t * (state machine) sending a packet
1161  *  output: void
1162  */
1163 
1164 /* ARGSUSED */
1165 static void
1166 retransmit(iu_tq_t *tqp, void *arg)
1167 {
1168         dhcp_smach_t    *dsmp = arg;
1169 
1170         dsmp->dsm_retrans_timer = -1;
1171 
1172         if (!verify_smach(dsmp))
1173                 return;
1174 
1175         /*
1176          * Check the callback to see if we should keep sending retransmissions.
1177          * Compute the next retransmission time first, so that the callback can
1178          * cap the value if need be.  (Required for DHCPv6 Confirm messages.)
1179          *
1180          * Hold the state machine across the callback so that the called
1181          * function can remove the state machine from the system without
1182          * disturbing the string used subsequently for verbose logging.  The
1183          * Release function destroys the state machine when the retry count
1184          * expires.
1185          */
1186 
1187         next_retransmission(dsmp, B_FALSE, B_FALSE);
1188         hold_smach(dsmp);
1189         if (dsmp->dsm_send_stop_func(dsmp, dsmp->dsm_packet_sent)) {
1190                 dhcpmsg(MSG_VERBOSE, "retransmit: time to stop on %s",
1191                     dsmp->dsm_name);
1192         } else {
1193                 dhcpmsg(MSG_VERBOSE, "retransmit: sending another on %s",
1194                     dsmp->dsm_name);
1195                 (void) send_pkt_internal(dsmp);
1196         }
1197         release_smach(dsmp);
1198 }
1199 
1200 /*
1201  * stop_pkt_retransmission(): stops retransmission of last sent packet
1202  *
1203  *   input: dhcp_smach_t *: the state machine to stop retransmission on
1204  *  output: void
1205  */
1206 
1207 void
1208 stop_pkt_retransmission(dhcp_smach_t *dsmp)
1209 {
1210         if (dsmp->dsm_retrans_timer != -1 &&
1211             iu_cancel_timer(tq, dsmp->dsm_retrans_timer, NULL) == 1) {
1212                 dhcpmsg(MSG_VERBOSE, "stop_pkt_retransmission: stopped on %s",
1213                     dsmp->dsm_name);
1214                 dsmp->dsm_retrans_timer = -1;
1215                 release_smach(dsmp);
1216         }
1217 }
1218 
1219 /*
1220  * retransmit_now(): force a packet retransmission right now.  Used only with
1221  *                   the DHCPv6 UseMulticast status code.  Use with caution;
1222  *                   triggered retransmissions can cause packet storms.
1223  *
1224  *   input: dhcp_smach_t *: the state machine to force retransmission on
1225  *  output: void
1226  */
1227 
1228 void
1229 retransmit_now(dhcp_smach_t *dsmp)
1230 {
1231         stop_pkt_retransmission(dsmp);
1232         (void) send_pkt_internal(dsmp);
1233 }
1234 
1235 /*
1236  * alloc_pkt_entry(): Allocates a packet list entry with a given data area
1237  *                    size.
1238  *
1239  *   input: size_t: size of data area for packet
1240  *          boolean_t: B_TRUE for IPv6
1241  *  output: PKT_LIST *: allocated packet list entry
1242  */
1243 
1244 PKT_LIST *
1245 alloc_pkt_entry(size_t psize, boolean_t isv6)
1246 {
1247         PKT_LIST        *plp;
1248 
1249         if ((plp = calloc(1, sizeof (*plp))) == NULL ||
1250             (plp->pkt = malloc(psize)) == NULL) {
1251                 free(plp);
1252                 plp = NULL;
1253         } else {
1254                 plp->len = psize;
1255                 plp->isv6 = isv6;
1256         }
1257 
1258         return (plp);
1259 }
1260 
1261 /*
1262  * sock_recvpkt(): read from the given socket into an allocated buffer and
1263  *                 handles any ancillary data options.
1264  *
1265  *   input: int: file descriptor to read
1266  *          PKT_LIST *: allocated buffer
1267  *  output: ssize_t: number of bytes read, or -1 on error
1268  */
1269 
1270 static ssize_t
1271 sock_recvpkt(int fd, PKT_LIST *plp)
1272 {
1273         struct iovec iov;
1274         struct msghdr msg;
1275         int64_t ctrl[8192 / sizeof (int64_t)];
1276         ssize_t msglen;
1277 
1278         (void) memset(&iov, 0, sizeof (iov));
1279         iov.iov_base = (caddr_t)plp->pkt;
1280         iov.iov_len = plp->len;
1281 
1282         (void) memset(&msg, 0, sizeof (msg));
1283         msg.msg_name = &plp->pktfrom;
1284         msg.msg_namelen = sizeof (plp->pktfrom);
1285         msg.msg_iov = &iov;
1286         msg.msg_iovlen = 1;
1287         msg.msg_control = ctrl;
1288         msg.msg_controllen = sizeof (ctrl);
1289 
1290         if ((msglen = recvmsg(fd, &msg, 0)) != -1) {
1291                 struct cmsghdr *cmsg;
1292 
1293                 for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
1294                     cmsg = CMSG_NXTHDR(&msg, cmsg)) {
1295                         struct sockaddr_in *sinp;
1296                         struct sockaddr_in6 *sin6;
1297                         struct in6_pktinfo *ipi6;
1298 
1299                         switch (cmsg->cmsg_level) {
1300                         case IPPROTO_IP:
1301                                 switch (cmsg->cmsg_type) {
1302                                 case IP_RECVDSTADDR:
1303                                         sinp = (struct sockaddr_in *)
1304                                             &plp->pktto;
1305                                         sinp->sin_family = AF_INET;
1306                                         (void) memcpy(&sinp->sin_addr.s_addr,
1307                                             CMSG_DATA(cmsg),
1308                                             sizeof (ipaddr_t));
1309                                         break;
1310 
1311                                 case IP_RECVIF:
1312                                         (void) memcpy(&plp->ifindex,
1313                                             CMSG_DATA(cmsg), sizeof (uint_t));
1314                                         break;
1315                                 }
1316                                 break;
1317 
1318                         case IPPROTO_IPV6:
1319                                 switch (cmsg->cmsg_type) {
1320                                 case IPV6_PKTINFO:
1321                                         /* LINTED: alignment */
1322                                         ipi6 = (struct in6_pktinfo *)
1323                                             CMSG_DATA(cmsg);
1324                                         sin6 = (struct sockaddr_in6 *)
1325                                             &plp->pktto;
1326                                         sin6->sin6_family = AF_INET6;
1327                                         (void) memcpy(&sin6->sin6_addr,
1328                                             &ipi6->ipi6_addr,
1329                                             sizeof (ipi6->ipi6_addr));
1330                                         (void) memcpy(&plp->ifindex,
1331                                             &ipi6->ipi6_ifindex,
1332                                             sizeof (uint_t));
1333                                         break;
1334                                 }
1335                         }
1336                 }
1337         }
1338         return (msglen);
1339 }
1340 
1341 /*
1342  * recv_pkt(): receives a single DHCP packet on a given file descriptor.
1343  *
1344  *   input: int: the file descriptor to receive the packet from
1345  *          int: the maximum packet size to allow
1346  *          boolean_t: B_TRUE for IPv6
1347  *  output: PKT_LIST *: the received packet
1348  */
1349 
1350 PKT_LIST *
1351 recv_pkt(int fd, int mtu, boolean_t isv6)
1352 {
1353         PKT_LIST        *plp;
1354         ssize_t         retval;
1355 
1356         if ((plp = alloc_pkt_entry(mtu, isv6)) == NULL) {
1357                 dhcpmsg(MSG_ERROR,
1358                     "recv_pkt: allocation failure; dropped packet");
1359                 return (NULL);
1360         }
1361 
1362         retval = sock_recvpkt(fd, plp);
1363         if (retval == -1) {
1364                 dhcpmsg(MSG_ERR, "recv_pkt: recvfrom v%d failed, dropped",
1365                     isv6 ? 6 : 4);
1366                 goto failure;
1367         }
1368 
1369         plp->len = retval;
1370 
1371         if (isv6) {
1372                 if (retval < sizeof (dhcpv6_message_t)) {
1373                         dhcpmsg(MSG_WARNING, "recv_pkt: runt message");
1374                         goto failure;
1375                 }
1376         } else {
1377                 switch (dhcp_options_scan(plp, B_TRUE)) {
1378 
1379                 case DHCP_WRONG_MSG_TYPE:
1380                         dhcpmsg(MSG_WARNING,
1381                             "recv_pkt: unexpected DHCP message");
1382                         goto failure;
1383 
1384                 case DHCP_GARBLED_MSG_TYPE:
1385                         dhcpmsg(MSG_WARNING,
1386                             "recv_pkt: garbled DHCP message type");
1387                         goto failure;
1388 
1389                 case DHCP_BAD_OPT_OVLD:
1390                         dhcpmsg(MSG_WARNING, "recv_pkt: bad option overload");
1391                         goto failure;
1392 
1393                 case 0:
1394                         break;
1395 
1396                 default:
1397                         dhcpmsg(MSG_WARNING,
1398                             "recv_pkt: packet corrupted, dropped");
1399                         goto failure;
1400                 }
1401         }
1402         return (plp);
1403 
1404 failure:
1405         free_pkt_entry(plp);
1406         return (NULL);
1407 }
1408 
1409 /*
1410  * pkt_v4_match(): check if a given DHCPv4 message type is in a given set
1411  *
1412  *   input: uchar_t: packet type
1413  *          dhcp_message_type_t: bit-wise OR of DHCP_P* values.
1414  *  output: boolean_t: B_TRUE if packet type is in the set
1415  */
1416 
1417 boolean_t
1418 pkt_v4_match(uchar_t type, dhcp_message_type_t match_type)
1419 {
1420         /*
1421          * note: the ordering here allows direct indexing of the table
1422          *       based on the RFC2131 packet type value passed in.
1423          */
1424 
1425         static dhcp_message_type_t type_map[] = {
1426                 DHCP_PUNTYPED, DHCP_PDISCOVER, DHCP_POFFER, DHCP_PREQUEST,
1427                 DHCP_PDECLINE, DHCP_PACK, DHCP_PNAK, DHCP_PRELEASE,
1428                 DHCP_PINFORM
1429         };
1430 
1431         if (type < (sizeof (type_map) / sizeof (*type_map)))
1432                 return ((type_map[type] & match_type) ? B_TRUE : B_FALSE);
1433         else
1434                 return (B_FALSE);
1435 }
1436 
1437 /*
1438  * pkt_smach_enqueue(): enqueue a packet on a given state machine
1439  *
1440  *   input: dhcp_smach_t: state machine
1441  *          PKT_LIST *: packet to enqueue
1442  *  output: none
1443  */
1444 
1445 void
1446 pkt_smach_enqueue(dhcp_smach_t *dsmp, PKT_LIST *plp)
1447 {
1448         dhcpmsg(MSG_VERBOSE, "pkt_smach_enqueue: received %s %s packet on %s",
1449             pkt_type_to_string(pkt_recv_type(plp), dsmp->dsm_isv6),
1450             dsmp->dsm_isv6 ? "v6" : "v4", dsmp->dsm_name);
1451 
1452         /* add to front of list */
1453         insque(plp, &dsmp->dsm_recv_pkt_list);
1454 }
1455 
1456 /*
1457  * next_retransmission(): computes the number of seconds until the next
1458  *                        retransmission, based on the algorithms in RFCs 2131
1459  *                        3315.
1460  *
1461  *   input: dhcp_smach_t *: state machine that needs a new timer
1462  *          boolean_t: B_TRUE if this is the first time sending the message
1463  *          boolean_t: B_TRUE for positive RAND values only (RFC 3315 17.1.2)
1464  *  output: none
1465  */
1466 
1467 static void
1468 next_retransmission(dhcp_smach_t *dsmp, boolean_t first_send,
1469     boolean_t positive_only)
1470 {
1471         uint32_t timeout_ms;
1472 
1473         if (dsmp->dsm_isv6) {
1474                 double randval;
1475 
1476                 /*
1477                  * The RFC specifies 0 to 10% jitter for the initial
1478                  * solicitation, and plus or minus 10% jitter for all others.
1479                  * This works out to 100 milliseconds on the shortest timer we
1480                  * use.
1481                  */
1482                 if (positive_only)
1483                         randval = drand48() / 10.0;
1484                 else
1485                         randval = (drand48() - 0.5) / 5.0;
1486 
1487                 /* The RFC specifies doubling *after* the first transmission */
1488                 timeout_ms = dsmp->dsm_send_timeout;
1489                 if (!first_send)
1490                         timeout_ms *= 2;
1491                 timeout_ms += (int)(randval * dsmp->dsm_send_timeout);
1492 
1493                 /* This checks the MRT (maximum retransmission time) */
1494                 if (dsmp->dsm_send_tcenter != 0 &&
1495                     timeout_ms > dsmp->dsm_send_tcenter) {
1496                         timeout_ms = dsmp->dsm_send_tcenter +
1497                             (uint_t)(randval * dsmp->dsm_send_tcenter);
1498                 }
1499 
1500                 dsmp->dsm_send_timeout = timeout_ms;
1501         } else {
1502                 if (dsmp->dsm_state == RENEWING ||
1503                     dsmp->dsm_state == REBINDING) {
1504                         monosec_t mono;
1505 
1506                         timeout_ms = dsmp->dsm_state == RENEWING ?
1507                             dsmp->dsm_leases->dl_t2.dt_start :
1508                             dsmp->dsm_leases->dl_lifs->lif_expire.dt_start;
1509                         timeout_ms += dsmp->dsm_curstart_monosec;
1510                         mono = monosec();
1511                         if (mono > timeout_ms)
1512                                 timeout_ms = 0;
1513                         else
1514                                 timeout_ms -= mono;
1515                         timeout_ms *= MILLISEC / 2;
1516                 } else {
1517                         /*
1518                          * Start at 4, and increase by a factor of 2 up to 64.
1519                          */
1520                         if (first_send) {
1521                                 timeout_ms = 4 * MILLISEC;
1522                         } else {
1523                                 timeout_ms = MIN(dsmp->dsm_send_tcenter << 1,
1524                                     64 * MILLISEC);
1525                         }
1526                 }
1527 
1528                 dsmp->dsm_send_tcenter = timeout_ms;
1529 
1530                 /*
1531                  * At each iteration, jitter the timeout by some fraction of a
1532                  * second.
1533                  */
1534                 dsmp->dsm_send_timeout = timeout_ms +
1535                     ((lrand48() % (2 * MILLISEC)) - MILLISEC);
1536         }
1537 }
1538 
1539 /*
1540  * dhcp_ip_default(): open and bind the default IP sockets used for I/O and
1541  *                    interface control.
1542  *
1543  *   input: none
1544  *  output: B_TRUE on success
1545  */
1546 
1547 boolean_t
1548 dhcp_ip_default(void)
1549 {
1550         int on = 1;
1551 
1552         if ((v4_sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
1553                 dhcpmsg(MSG_ERR,
1554                     "dhcp_ip_default: unable to create IPv4 socket");
1555                 return (B_FALSE);
1556         }
1557 
1558         if (setsockopt(v4_sock_fd, IPPROTO_IP, IP_RECVDSTADDR, &on,
1559             sizeof (on)) == -1) {
1560                 dhcpmsg(MSG_ERR,
1561                     "dhcp_ip_default: unable to enable IP_RECVDSTADDR");
1562                 return (B_FALSE);
1563         }
1564 
1565         if (setsockopt(v4_sock_fd, IPPROTO_IP, IP_RECVIF, &on, sizeof (on)) ==
1566             -1) {
1567                 dhcpmsg(MSG_ERR,
1568                     "dhcp_ip_default: unable to enable IP_RECVIF");
1569                 return (B_FALSE);
1570         }
1571 
1572         if (!bind_sock(v4_sock_fd, IPPORT_BOOTPC, INADDR_ANY)) {
1573                 dhcpmsg(MSG_ERROR,
1574                     "dhcp_ip_default: unable to bind IPv4 socket to port %d",
1575                     IPPORT_BOOTPC);
1576                 return (B_FALSE);
1577         }
1578 
1579         if (iu_register_event(eh, v4_sock_fd, POLLIN, dhcp_acknak_global,
1580             NULL) == -1) {
1581                 dhcpmsg(MSG_WARNING, "dhcp_ip_default: cannot register to "
1582                     "receive IPv4 broadcasts");
1583                 return (B_FALSE);
1584         }
1585 
1586         if ((v6_sock_fd = socket(AF_INET6, SOCK_DGRAM, 0)) == -1) {
1587                 dhcpmsg(MSG_ERR,
1588                     "dhcp_ip_default: unable to create IPv6 socket");
1589                 return (B_FALSE);
1590         }
1591 
1592         if (setsockopt(v6_sock_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on,
1593             sizeof (on)) == -1) {
1594                 dhcpmsg(MSG_ERR,
1595                     "dhcp_ip_default: unable to enable IPV6_RECVPKTINFO");
1596                 return (B_FALSE);
1597         }
1598 
1599         if (!bind_sock_v6(v6_sock_fd, IPPORT_DHCPV6C, NULL)) {
1600                 dhcpmsg(MSG_ERROR,
1601                     "dhcp_ip_default: unable to bind IPv6 socket to port %d",
1602                     IPPORT_DHCPV6C);
1603                 return (B_FALSE);
1604         }
1605 
1606         if (iu_register_event(eh, v6_sock_fd, POLLIN, dhcp_acknak_global,
1607             NULL) == -1) {
1608                 dhcpmsg(MSG_WARNING, "dhcp_ip_default: cannot register to "
1609                     "receive IPv6 packets");
1610                 return (B_FALSE);
1611         }
1612 
1613         return (B_TRUE);
1614 }