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 (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
  23  * Copyright (c) 2016, Chris Fraire <cfraire@me.com>.
  24  */
  25 
  26 #include <sys/types.h>
  27 #include <time.h>
  28 #include <netinet/in.h>
  29 #include <netinet/dhcp.h>
  30 #include <netinet/udp.h>
  31 #include <netinet/ip_var.h>
  32 #include <netinet/udp_var.h>
  33 #include <libinetutil.h>
  34 #include <dhcpmsg.h>
  35 #include <dhcp_hostconf.h>
  36 #include <string.h>
  37 
  38 #include "packet.h"
  39 #include "agent.h"
  40 #include "script_handler.h"
  41 #include "interface.h"
  42 #include "states.h"
  43 #include "util.h"
  44 
  45 /*
  46  * Number of seconds to wait for a retry if the user is interacting with the
  47  * daemon.
  48  */
  49 #define RETRY_DELAY     10
  50 
  51 /*
  52  * If the renew timer fires within this number of seconds of the rebind timer,
  53  * then skip renew.  This prevents us from sending back-to-back renew and
  54  * rebind messages -- a pointless activity.
  55  */
  56 #define TOO_CLOSE       2
  57 
  58 static boolean_t stop_extending(dhcp_smach_t *, unsigned int);
  59 
  60 /*
  61  * dhcp_renew(): attempts to renew a DHCP lease on expiration of the T1 timer.
  62  *
  63  *   input: iu_tq_t *: unused
  64  *          void *: the lease to renew (dhcp_lease_t)
  65  *  output: void
  66  *
  67  *   notes: The primary expense involved with DHCP (like most UDP protocols) is
  68  *          with the generation and handling of packets, not the contents of
  69  *          those packets.  Thus, we try to reduce the number of packets that
  70  *          are sent.  It would be nice to just renew all leases here (each one
  71  *          added has trivial added overhead), but the DHCPv6 RFC doesn't
  72  *          explicitly allow that behavior.  Rather than having that argument,
  73  *          we settle for ones that are close in expiry to the one that fired.
  74  *          For v4, we repeatedly reschedule the T1 timer to do the
  75  *          retransmissions.  For v6, we rely on the common timer computation
  76  *          in packet.c.
  77  */
  78 
  79 /* ARGSUSED */
  80 void
  81 dhcp_renew(iu_tq_t *tqp, void *arg)
  82 {
  83         dhcp_lease_t *dlp = arg;
  84         dhcp_smach_t *dsmp = dlp->dl_smach;
  85         uint32_t        t2;
  86 
  87         dhcpmsg(MSG_VERBOSE, "dhcp_renew: T1 timer expired on %s",
  88             dsmp->dsm_name);
  89 
  90         dlp->dl_t1.dt_id = -1;
  91 
  92         if (dsmp->dsm_state == RENEWING || dsmp->dsm_state == REBINDING) {
  93                 dhcpmsg(MSG_DEBUG, "dhcp_renew: already renewing");
  94                 release_lease(dlp);
  95                 return;
  96         }
  97 
  98         /*
  99          * Sanity check: don't send packets if we're past T2, or if we're
 100          * extremely close.
 101          */
 102 
 103         t2 = dsmp->dsm_curstart_monosec + dlp->dl_t2.dt_start;
 104         if (monosec() + TOO_CLOSE >= t2) {
 105                 dhcpmsg(MSG_DEBUG, "dhcp_renew: %spast T2 on %s",
 106                     monosec() > t2 ? "" : "almost ", dsmp->dsm_name);
 107                 release_lease(dlp);
 108                 return;
 109         }
 110 
 111         /*
 112          * If there isn't an async event pending, or if we can cancel the one
 113          * that's there, then try to renew by sending an extension request.  If
 114          * that fails, we'll try again when the next timer fires.
 115          */
 116         if (!async_cancel(dsmp) || !async_start(dsmp, DHCP_EXTEND, B_FALSE) ||
 117             !dhcp_extending(dsmp)) {
 118                 if (monosec() + RETRY_DELAY < t2) {
 119                         /*
 120                          * Try again in RETRY_DELAY seconds; user command
 121                          * should be gone.
 122                          */
 123                         init_timer(&dlp->dl_t1, RETRY_DELAY);
 124                         (void) set_smach_state(dsmp, BOUND);
 125                         if (!schedule_lease_timer(dlp, &dlp->dl_t1,
 126                             dhcp_renew)) {
 127                                 dhcpmsg(MSG_INFO, "dhcp_renew: unable to "
 128                                     "reschedule renewal around user command "
 129                                     "on %s; will wait for rebind",
 130                                     dsmp->dsm_name);
 131                         }
 132                 } else {
 133                         dhcpmsg(MSG_DEBUG, "dhcp_renew: user busy on %s; will "
 134                             "wait for rebind", dsmp->dsm_name);
 135                 }
 136         }
 137         release_lease(dlp);
 138 }
 139 
 140 /*
 141  * dhcp_rebind(): attempts to renew a DHCP lease from the REBINDING state (T2
 142  *                timer expiry).
 143  *
 144  *   input: iu_tq_t *: unused
 145  *          void *: the lease to renew
 146  *  output: void
 147  *   notes: For v4, we repeatedly reschedule the T2 timer to do the
 148  *          retransmissions.  For v6, we rely on the common timer computation
 149  *          in packet.c.
 150  */
 151 
 152 /* ARGSUSED */
 153 void
 154 dhcp_rebind(iu_tq_t *tqp, void *arg)
 155 {
 156         dhcp_lease_t    *dlp = arg;
 157         dhcp_smach_t    *dsmp = dlp->dl_smach;
 158         int             nlifs;
 159         dhcp_lif_t      *lif;
 160         boolean_t       some_valid;
 161         uint32_t        expiremax;
 162         DHCPSTATE       oldstate;
 163 
 164         dhcpmsg(MSG_VERBOSE, "dhcp_rebind: T2 timer expired on %s",
 165             dsmp->dsm_name);
 166 
 167         dlp->dl_t2.dt_id = -1;
 168 
 169         if ((oldstate = dsmp->dsm_state) == REBINDING) {
 170                 dhcpmsg(MSG_DEBUG, "dhcp_renew: already rebinding");
 171                 release_lease(dlp);
 172                 return;
 173         }
 174 
 175         /*
 176          * Sanity check: don't send packets if we've already expired on all of
 177          * the addresses.  We compute the maximum expiration time here, because
 178          * it won't matter for v4 (there's only one lease) and for v6 we need
 179          * to know when the last lease ages away.
 180          */
 181 
 182         some_valid = B_FALSE;
 183         expiremax = monosec();
 184         lif = dlp->dl_lifs;
 185         for (nlifs = dlp->dl_nlifs; nlifs > 0; nlifs--, lif = lif->lif_next) {
 186                 uint32_t expire;
 187 
 188                 expire = dsmp->dsm_curstart_monosec + lif->lif_expire.dt_start;
 189                 if (expire > expiremax) {
 190                         expiremax = expire;
 191                         some_valid = B_TRUE;
 192                 }
 193         }
 194         if (!some_valid) {
 195                 dhcpmsg(MSG_DEBUG, "dhcp_rebind: all leases expired on %s",
 196                     dsmp->dsm_name);
 197                 release_lease(dlp);
 198                 return;
 199         }
 200 
 201         /*
 202          * This is our first venture into the REBINDING state, so reset the
 203          * server address.  We know the renew timer has already been cancelled
 204          * (or we wouldn't be here).
 205          */
 206         if (dsmp->dsm_isv6) {
 207                 dsmp->dsm_server = ipv6_all_dhcp_relay_and_servers;
 208         } else {
 209                 IN6_IPADDR_TO_V4MAPPED(htonl(INADDR_BROADCAST),
 210                     &dsmp->dsm_server);
 211         }
 212 
 213         /* {Bound,Renew}->rebind transitions cannot fail */
 214         (void) set_smach_state(dsmp, REBINDING);
 215 
 216         /*
 217          * If there isn't an async event pending, or if we can cancel the one
 218          * that's there, then try to rebind by sending an extension request.
 219          * If that fails, we'll clean up when the lease expires.
 220          */
 221         if (!async_cancel(dsmp) || !async_start(dsmp, DHCP_EXTEND, B_FALSE) ||
 222             !dhcp_extending(dsmp)) {
 223                 if (monosec() + RETRY_DELAY < expiremax) {
 224                         /*
 225                          * Try again in RETRY_DELAY seconds; user command
 226                          * should be gone.
 227                          */
 228                         init_timer(&dlp->dl_t2, RETRY_DELAY);
 229                         (void) set_smach_state(dsmp, oldstate);
 230                         if (!schedule_lease_timer(dlp, &dlp->dl_t2,
 231                             dhcp_rebind)) {
 232                                 dhcpmsg(MSG_INFO, "dhcp_rebind: unable to "
 233                                     "reschedule rebind around user command on "
 234                                     "%s; lease may expire", dsmp->dsm_name);
 235                         }
 236                 } else {
 237                         dhcpmsg(MSG_WARNING, "dhcp_rebind: user busy on %s; "
 238                             "will expire", dsmp->dsm_name);
 239                 }
 240         }
 241         release_lease(dlp);
 242 }
 243 
 244 /*
 245  * dhcp_finish_expire(): finish expiration of a lease after the user script
 246  *                       runs.  If this is the last lease, then restart DHCP.
 247  *                       The caller has a reference to the LIF, which will be
 248  *                       dropped.
 249  *
 250  *   input: dhcp_smach_t *: the state machine to be restarted
 251  *          void *: logical interface that has expired
 252  *  output: int: always 1
 253  */
 254 
 255 static int
 256 dhcp_finish_expire(dhcp_smach_t *dsmp, void *arg)
 257 {
 258         dhcp_lif_t *lif = arg;
 259         dhcp_lease_t *dlp;
 260 
 261         dhcpmsg(MSG_DEBUG, "lease expired on %s; removing", lif->lif_name);
 262 
 263         dlp = lif->lif_lease;
 264         unplumb_lif(lif);
 265         if (dlp->dl_nlifs == 0)
 266                 remove_lease(dlp);
 267         release_lif(lif);
 268 
 269         /* If some valid leases remain, then drive on */
 270         if (dsmp->dsm_leases != NULL) {
 271                 dhcpmsg(MSG_DEBUG,
 272                     "dhcp_finish_expire: some leases remain on %s",
 273                     dsmp->dsm_name);
 274                 return (1);
 275         }
 276 
 277         (void) remove_hostconf(dsmp->dsm_name, dsmp->dsm_isv6);
 278 
 279         dhcpmsg(MSG_INFO, "last lease expired on %s -- restarting DHCP",
 280             dsmp->dsm_name);
 281 
 282         /*
 283          * in the case where the lease is less than DHCP_REBIND_MIN
 284          * seconds, we will never enter dhcp_renew() and thus the packet
 285          * counters will not be reset.  in that case, reset them here.
 286          */
 287 
 288         if (dsmp->dsm_state == BOUND) {
 289                 dsmp->dsm_bad_offers = 0;
 290                 dsmp->dsm_sent               = 0;
 291                 dsmp->dsm_received   = 0;
 292         }
 293 
 294         deprecate_leases(dsmp);
 295 
 296         /* reset_smach() in dhcp_selecting() will clean up any leftover state */
 297         dhcp_selecting(dsmp);
 298 
 299         return (1);
 300 }
 301 
 302 /*
 303  * dhcp_deprecate(): deprecates an address on a given logical interface when
 304  *                   the preferred lifetime expires.
 305  *
 306  *   input: iu_tq_t *: unused
 307  *          void *: the logical interface whose lease is expiring
 308  *  output: void
 309  */
 310 
 311 /* ARGSUSED */
 312 void
 313 dhcp_deprecate(iu_tq_t *tqp, void *arg)
 314 {
 315         dhcp_lif_t *lif = arg;
 316 
 317         set_lif_deprecated(lif);
 318         release_lif(lif);
 319 }
 320 
 321 /*
 322  * dhcp_expire(): expires a lease on a given logical interface and, if there
 323  *                are no more leases, restarts DHCP.
 324  *
 325  *   input: iu_tq_t *: unused
 326  *          void *: the logical interface whose lease has expired
 327  *  output: void
 328  */
 329 
 330 /* ARGSUSED */
 331 void
 332 dhcp_expire(iu_tq_t *tqp, void *arg)
 333 {
 334         dhcp_lif_t      *lif = arg;
 335         dhcp_smach_t    *dsmp;
 336         const char      *event;
 337 
 338         dhcpmsg(MSG_VERBOSE, "dhcp_expire: lease timer expired on %s",
 339             lif->lif_name);
 340 
 341         lif->lif_expire.dt_id = -1;
 342         if (lif->lif_lease == NULL) {
 343                 release_lif(lif);
 344                 return;
 345         }
 346 
 347         set_lif_deprecated(lif);
 348 
 349         dsmp = lif->lif_lease->dl_smach;
 350 
 351         if (!async_cancel(dsmp)) {
 352 
 353                 dhcpmsg(MSG_WARNING,
 354                     "dhcp_expire: cannot cancel current asynchronous command "
 355                     "on %s", dsmp->dsm_name);
 356 
 357                 /*
 358                  * Try to schedule ourselves for callback.  We're really
 359                  * situation-critical here; there's not much hope for us if
 360                  * this fails.
 361                  */
 362                 init_timer(&lif->lif_expire, DHCP_EXPIRE_WAIT);
 363                 if (schedule_lif_timer(lif, &lif->lif_expire, dhcp_expire))
 364                         return;
 365 
 366                 dhcpmsg(MSG_CRIT, "dhcp_expire: cannot reschedule dhcp_expire "
 367                     "to get called back, proceeding...");
 368         }
 369 
 370         if (!async_start(dsmp, DHCP_START, B_FALSE))
 371                 dhcpmsg(MSG_WARNING, "dhcp_expire: cannot start asynchronous "
 372                     "transaction on %s, continuing...", dsmp->dsm_name);
 373 
 374         /*
 375          * Determine if this state machine has any non-expired LIFs left in it.
 376          * If it doesn't, then this is an "expire" event.  Otherwise, if some
 377          * valid leases remain, it's a "loss" event.  The SOMEEXP case can
 378          * occur only with DHCPv6.
 379          */
 380         if (expired_lif_state(dsmp) == DHCP_EXP_SOMEEXP)
 381                 event = EVENT_LOSS6;
 382         else if (dsmp->dsm_isv6)
 383                 event = EVENT_EXPIRE6;
 384         else
 385                 event = EVENT_EXPIRE;
 386 
 387         /*
 388          * just march on if this fails; at worst someone will be able
 389          * to async_start() while we're actually busy with our own
 390          * asynchronous transaction.  better than not having a lease.
 391          */
 392 
 393         (void) script_start(dsmp, event, dhcp_finish_expire, lif, NULL);
 394 }
 395 
 396 /*
 397  * dhcp_extending(): sends a REQUEST (IPv4 DHCP) or Rebind/Renew (DHCPv6) to
 398  *                   extend a lease on a given state machine
 399  *
 400  *   input: dhcp_smach_t *: the state machine to send the message from
 401  *  output: boolean_t: B_TRUE if the extension request was sent
 402  */
 403 
 404 boolean_t
 405 dhcp_extending(dhcp_smach_t *dsmp)
 406 {
 407         dhcp_pkt_t              *dpkt;
 408 
 409         stop_pkt_retransmission(dsmp);
 410 
 411         /*
 412          * We change state here because this function is also called when
 413          * adopting a lease and on demand by the user.
 414          */
 415         if (dsmp->dsm_state == BOUND) {
 416                 dsmp->dsm_neg_hrtime = gethrtime();
 417                 dsmp->dsm_bad_offers = 0;
 418                 dsmp->dsm_sent               = 0;
 419                 dsmp->dsm_received   = 0;
 420                 /* Bound->renew can't fail */
 421                 (void) set_smach_state(dsmp, RENEWING);
 422         }
 423 
 424         dhcpmsg(MSG_DEBUG, "dhcp_extending: sending request on %s",
 425             dsmp->dsm_name);
 426 
 427         if (dsmp->dsm_isv6) {
 428                 dhcp_lease_t *dlp;
 429                 dhcp_lif_t *lif;
 430                 uint_t nlifs;
 431                 uint_t irt, mrt;
 432 
 433                 /*
 434                  * Start constructing the Renew/Rebind message.  Only Renew has
 435                  * a server ID, as we still think our server might be
 436                  * reachable.
 437                  */
 438                 if (dsmp->dsm_state == RENEWING) {
 439                         dpkt = init_pkt(dsmp, DHCPV6_MSG_RENEW);
 440                         (void) add_pkt_opt(dpkt, DHCPV6_OPT_SERVERID,
 441                             dsmp->dsm_serverid, dsmp->dsm_serveridlen);
 442                         irt = DHCPV6_REN_TIMEOUT;
 443                         mrt = DHCPV6_REN_MAX_RT;
 444                 } else {
 445                         dpkt = init_pkt(dsmp, DHCPV6_MSG_REBIND);
 446                         irt = DHCPV6_REB_TIMEOUT;
 447                         mrt = DHCPV6_REB_MAX_RT;
 448                 }
 449 
 450                 /*
 451                  * Loop over the leases, and add an IA_NA for each and an
 452                  * IAADDR for each address.
 453                  */
 454                 for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) {
 455                         lif = dlp->dl_lifs;
 456                         for (nlifs = dlp->dl_nlifs; nlifs > 0;
 457                             nlifs--, lif = lif->lif_next) {
 458                                 (void) add_pkt_lif(dpkt, lif,
 459                                     DHCPV6_STAT_SUCCESS, NULL);
 460                         }
 461                 }
 462 
 463                 /* Add required Option Request option */
 464                 (void) add_pkt_prl(dpkt, dsmp);
 465 
 466                 /* Add FQDN if configured */
 467                 (void) dhcp_add_fqdn_opt(dpkt, dsmp);
 468 
 469                 return (send_pkt_v6(dsmp, dpkt, dsmp->dsm_server,
 470                     stop_extending, irt, mrt));
 471         } else {
 472                 dhcp_lif_t *lif = dsmp->dsm_lif;
 473                 ipaddr_t server;
 474 
 475                 /* assemble the DHCPREQUEST message. */
 476                 dpkt = init_pkt(dsmp, REQUEST);
 477                 dpkt->pkt->ciaddr.s_addr = lif->lif_addr;
 478 
 479                 /*
 480                  * The max dhcp message size option is set to the interface
 481                  * max, minus the size of the udp and ip headers.
 482                  */
 483                 (void) add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE,
 484                     htons(lif->lif_max - sizeof (struct udpiphdr)));
 485                 (void) add_pkt_opt32(dpkt, CD_LEASE_TIME, htonl(DHCP_PERM));
 486 
 487                 if (class_id_len != 0) {
 488                         (void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id,
 489                             class_id_len);
 490                 }
 491                 (void) add_pkt_prl(dpkt, dsmp);
 492                 /*
 493                  * dsm_reqhost was set for this state machine in
 494                  * dhcp_selecting() if the REQUEST_HOSTNAME option was set and
 495                  * a host name was found.
 496                  */
 497                 if (dhcp_add_fqdn_opt(dpkt, dsmp) != 0 &&
 498                     dsmp->dsm_reqhost != NULL) {
 499                         (void) add_pkt_opt(dpkt, CD_HOSTNAME, dsmp->dsm_reqhost,
 500                             strlen(dsmp->dsm_reqhost));
 501                 }
 502                 (void) add_pkt_opt(dpkt, CD_END, NULL, 0);
 503 
 504                 IN6_V4MAPPED_TO_IPADDR(&dsmp->dsm_server, server);
 505                 return (send_pkt(dsmp, dpkt, server, stop_extending));
 506         }
 507 }
 508 
 509 /*
 510  * stop_extending(): decides when to stop retransmitting v4 REQUEST or v6
 511  *                   Renew/Rebind messages.  If we're renewing, then stop if
 512  *                   T2 is soon approaching.
 513  *
 514  *   input: dhcp_smach_t *: the state machine REQUESTs are being sent from
 515  *          unsigned int: the number of REQUESTs sent so far
 516  *  output: boolean_t: B_TRUE if retransmissions should stop
 517  */
 518 
 519 /* ARGSUSED */
 520 static boolean_t
 521 stop_extending(dhcp_smach_t *dsmp, unsigned int n_requests)
 522 {
 523         dhcp_lease_t *dlp;
 524 
 525         /*
 526          * If we're renewing and rebind time is soon approaching, then don't
 527          * schedule
 528          */
 529         if (dsmp->dsm_state == RENEWING) {
 530                 monosec_t t2;
 531 
 532                 t2 = 0;
 533                 for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) {
 534                         if (dlp->dl_t2.dt_start > t2)
 535                                 t2 = dlp->dl_t2.dt_start;
 536                 }
 537                 t2 += dsmp->dsm_curstart_monosec;
 538                 if (monosec() + TOO_CLOSE >= t2) {
 539                         dhcpmsg(MSG_DEBUG, "stop_extending: %spast T2 on %s",
 540                             monosec() > t2 ? "" : "almost ", dsmp->dsm_name);
 541                         return (B_TRUE);
 542                 }
 543         }
 544 
 545         /*
 546          * Note that returning B_TRUE cancels both this transmission and the
 547          * one that would occur at dsm_send_timeout, and that for v4 we cut the
 548          * time in half for each retransmission.  Thus we check here against
 549          * half of the minimum.
 550          */
 551         if (!dsmp->dsm_isv6 &&
 552             dsmp->dsm_send_timeout < DHCP_REBIND_MIN * MILLISEC / 2) {
 553                 dhcpmsg(MSG_DEBUG, "stop_extending: next retry would be in "
 554                     "%d.%03d; stopping", dsmp->dsm_send_timeout / MILLISEC,
 555                     dsmp->dsm_send_timeout % MILLISEC);
 556                 return (B_TRUE);
 557         }
 558 
 559         /* Otherwise, w stop only when the next timer (rebind, expire) fires */
 560         return (B_FALSE);
 561 }