root/usr/src/cmd/cmd-inet/sbin/dhcpagent/release.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
 *
 * DECLINE/RELEASE configuration functionality for the DHCP client.
 */

#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <net/if.h>
#include <netinet/dhcp.h>
#include <netinet/dhcp6.h>
#include <dhcpmsg.h>
#include <dhcp_hostconf.h>
#include <dhcpagent_util.h>

#include "agent.h"
#include "packet.h"
#include "interface.h"
#include "states.h"

static boolean_t stop_release_decline(dhcp_smach_t *, unsigned int);

/*
 * send_declines(): sends a DECLINE message (broadcasted for IPv4) to the
 *                  server to indicate a problem with the offered addresses.
 *                  The failing addresses are removed from the leases.
 *
 *   input: dhcp_smach_t *: the state machine sending DECLINE
 *  output: void
 */

void
send_declines(dhcp_smach_t *dsmp)
{
        dhcp_pkt_t      *dpkt;
        dhcp_lease_t    *dlp, *dlpn;
        uint_t          nlifs;
        dhcp_lif_t      *lif, *lifn;
        boolean_t       got_one;

        /*
         * Create an empty DECLINE message.  We'll stuff the information into
         * this message as we find it.
         */
        if (dsmp->dsm_isv6) {
                if ((dpkt = init_pkt(dsmp, DHCPV6_MSG_DECLINE)) == NULL)
                        return;
                (void) add_pkt_opt(dpkt, DHCPV6_OPT_SERVERID,
                    dsmp->dsm_serverid, dsmp->dsm_serveridlen);
        } else {
                ipaddr_t serverip;

                /*
                 * If this ack is from BOOTP, then there's no way to send a
                 * decline.  Note that since we haven't bound yet, we can't
                 * just check the BOOTP flag.
                 */
                if (dsmp->dsm_ack->opts[CD_DHCP_TYPE] == NULL)
                        return;

                if ((dpkt = init_pkt(dsmp, DECLINE)) == NULL)
                        return;
                IN6_V4MAPPED_TO_IPADDR(&dsmp->dsm_server, serverip);
                (void) add_pkt_opt32(dpkt, CD_SERVER_ID, serverip);
        }

        /*
         * Loop over the leases, looking for ones with now-broken LIFs.  Add
         * each one found to the DECLINE message, and remove it from the list.
         * Also remove any completely declined leases.
         */
        got_one = B_FALSE;
        for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlpn) {
                dlpn = dlp->dl_next;
                lif = dlp->dl_lifs;
                for (nlifs = dlp->dl_nlifs; nlifs > 0; nlifs--, lif = lifn) {
                        lifn = lif->lif_next;
                        if (lif->lif_declined != NULL) {
                                (void) add_pkt_lif(dpkt, lif,
                                    DHCPV6_STAT_UNSPECFAIL, lif->lif_declined);
                                unplumb_lif(lif);
                                got_one = B_TRUE;
                        }
                }
                if (dlp->dl_nlifs == 0)
                        remove_lease(dlp);
        }

        if (!got_one)
                return;

        (void) set_smach_state(dsmp, DECLINING);

        if (dsmp->dsm_isv6) {
                (void) send_pkt_v6(dsmp, dpkt, dsmp->dsm_server,
                    stop_release_decline, DHCPV6_DEC_TIMEOUT, 0);
        } else {
                (void) add_pkt_opt(dpkt, CD_END, NULL, 0);

                (void) send_pkt(dsmp, dpkt, htonl(INADDR_BROADCAST), NULL);
        }
}

/*
 * dhcp_release(): sends a RELEASE message to a DHCP server and removes
 *                 the all interfaces for the given state machine from DHCP
 *                 control.  Called back by script handler.
 *
 *   input: dhcp_smach_t *: the state machine to send the RELEASE on and remove
 *          void *: an optional text explanation to send with the message
 *  output: int: 1 on success, 0 on failure
 */

int
dhcp_release(dhcp_smach_t *dsmp, void *arg)
{
        const char      *msg = arg;
        dhcp_pkt_t      *dpkt;
        dhcp_lease_t    *dlp;
        dhcp_lif_t      *lif;
        ipaddr_t        serverip;
        uint_t          nlifs;

        if ((dsmp->dsm_dflags & DHCP_IF_BOOTP) ||
            !check_cmd_allowed(dsmp->dsm_state, DHCP_RELEASE)) {
                ipc_action_finish(dsmp, DHCP_IPC_E_INT);
                return (0);
        }

        dhcpmsg(MSG_INFO, "releasing leases for state machine %s",
            dsmp->dsm_name);
        (void) set_smach_state(dsmp, RELEASING);

        (void) remove_hostconf(dsmp->dsm_name, dsmp->dsm_isv6);

        if (dsmp->dsm_isv6) {
                dpkt = init_pkt(dsmp, DHCPV6_MSG_RELEASE);
                (void) add_pkt_opt(dpkt, DHCPV6_OPT_SERVERID,
                    dsmp->dsm_serverid, dsmp->dsm_serveridlen);

                for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) {
                        lif = dlp->dl_lifs;
                        for (nlifs = dlp->dl_nlifs; nlifs > 0;
                            nlifs--, lif = lif->lif_next) {
                                (void) add_pkt_lif(dpkt, lif,
                                    DHCPV6_STAT_SUCCESS, NULL);
                        }
                }

                /*
                 * Must kill off the leases before attempting to tell the
                 * server.
                 */
                deprecate_leases(dsmp);

                /*
                 * For DHCPv6, this is a transaction, rather than just a
                 * one-shot message.  When this transaction is done, we'll
                 * finish the invoking async operation.
                 */
                (void) send_pkt_v6(dsmp, dpkt, dsmp->dsm_server,
                    stop_release_decline, DHCPV6_REL_TIMEOUT, 0);
        } else {
                if ((dlp = dsmp->dsm_leases) != NULL && dlp->dl_nlifs > 0) {
                        dpkt = init_pkt(dsmp, RELEASE);
                        if (msg != NULL) {
                                (void) add_pkt_opt(dpkt, CD_MESSAGE, msg,
                                    strlen(msg) + 1);
                        }
                        lif = dlp->dl_lifs;
                        (void) add_pkt_lif(dpkt, dlp->dl_lifs, 0, NULL);

                        IN6_V4MAPPED_TO_IPADDR(&dsmp->dsm_server, serverip);
                        (void) add_pkt_opt32(dpkt, CD_SERVER_ID, serverip);
                        (void) add_pkt_opt(dpkt, CD_END, NULL, 0);
                        (void) send_pkt(dsmp, dpkt, serverip, NULL);
                }

                /*
                 * XXX this totally sucks, but since udp is best-effort,
                 * without this delay, there's a good chance that the packet
                 * that we just enqueued for sending will get pitched
                 * when we canonize the interface through remove_smach.
                 */

                (void) usleep(500);
                deprecate_leases(dsmp);

                finished_smach(dsmp, DHCP_IPC_SUCCESS);
        }
        return (1);
}

/*
 * dhcp_drop(): drops the interface from DHCP control; callback from script
 *              handler
 *
 *   input: dhcp_smach_t *: the state machine dropping leases
 *          void *: unused
 *  output: int: always 1
 */

/* ARGSUSED1 */
int
dhcp_drop(dhcp_smach_t *dsmp, void *arg)
{
        dhcpmsg(MSG_INFO, "dropping leases for state machine %s",
            dsmp->dsm_name);

        if (dsmp->dsm_state == PRE_BOUND || dsmp->dsm_state == BOUND ||
            dsmp->dsm_state == RENEWING || dsmp->dsm_state == REBINDING) {
                if (dsmp->dsm_dflags & DHCP_IF_BOOTP) {
                        dhcpmsg(MSG_INFO,
                            "used bootp; not writing lease file for %s",
                            dsmp->dsm_name);
                } else {
                        write_lease_to_hostconf(dsmp);
                }
        } else {
                dhcpmsg(MSG_DEBUG, "%s in state %s; not saving lease",
                    dsmp->dsm_name, dhcp_state_to_string(dsmp->dsm_state));
        }
        deprecate_leases(dsmp);
        finished_smach(dsmp, DHCP_IPC_SUCCESS);
        return (1);
}

/*
 * stop_release_decline(): decides when to stop retransmitting RELEASE/DECLINE
 *                         messages for DHCPv6.  When we stop, if there are no
 *                         more leases left, then restart the state machine.
 *
 *   input: dhcp_smach_t *: the state machine messages are being sent from
 *          unsigned int: the number of messages sent so far
 *  output: boolean_t: B_TRUE if retransmissions should stop
 */

static boolean_t
stop_release_decline(dhcp_smach_t *dsmp, unsigned int n_requests)
{
        if (dsmp->dsm_state == RELEASING) {
                if (n_requests >= DHCPV6_REL_MAX_RC) {
                        dhcpmsg(MSG_INFO, "no Reply to Release, finishing "
                            "transaction on %s", dsmp->dsm_name);
                        finished_smach(dsmp, DHCP_IPC_SUCCESS);
                        return (B_TRUE);
                } else {
                        return (B_FALSE);
                }
        } else {
                if (n_requests >= DHCPV6_DEC_MAX_RC) {
                        dhcpmsg(MSG_INFO, "no Reply to Decline on %s",
                            dsmp->dsm_name);

                        if (dsmp->dsm_leases == NULL) {
                                dhcpmsg(MSG_VERBOSE, "stop_release_decline: "
                                    "%s has no leases left", dsmp->dsm_name);
                                dhcp_restart(dsmp);
                        }
                        return (B_TRUE);
                } else {
                        return (B_FALSE);
                }
        }
}