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