root/usr/src/cmd/cmd-inet/usr.sbin/if_mpadm.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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <errno.h>
#include <ipmp_admin.h>
#include <libinetutil.h>
#include <locale.h>
#include <net/if.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/types.h>

typedef void            offline_func_t(const char *, ipmp_handle_t);

static const char       *progname;
static int              sioc4fd, sioc6fd;
static offline_func_t   do_offline, undo_offline;
static boolean_t        set_lifflags(const char *, uint64_t);
static boolean_t        is_offline(const char *);
static void             warn(const char *, ...);
static void             die(const char *, ...);

static void
usage(void)
{
        (void) fprintf(stderr, gettext("Usage: %s -d | -r <ifname>\n"),
            progname);
        exit(EXIT_FAILURE);
}

static const char *
mpadm_errmsg(uint32_t error)
{
        switch (error) {
        case IPMP_EUNKIF:
                return (gettext("not a physical interface or not in an "
                    "IPMP group"));
        case IPMP_EMINRED:
                return (gettext("no other functioning interfaces are in its "
                    "IPMP group"));
        default:
                return (ipmp_errmsg(error));
        }
}

int
main(int argc, char **argv)
{
        int retval;
        ipmp_handle_t handle;
        offline_func_t *ofuncp = NULL;
        const char *ifname;
        int c;

        if ((progname = strrchr(argv[0], '/')) != NULL)
                progname++;
        else
                progname = argv[0];

        (void) setlocale(LC_ALL, "");
        (void) textdomain(TEXT_DOMAIN);

        while ((c = getopt(argc, argv, "d:r:")) != EOF) {
                switch (c) {
                case 'd':
                        ifname = optarg;
                        ofuncp = do_offline;
                        break;
                case 'r':
                        ifname = optarg;
                        ofuncp = undo_offline;
                        break;
                default:
                        usage();
                }
        }

        if (ofuncp == NULL)
                usage();

        /*
         * Create the global V4 and V6 socket ioctl descriptors.
         */
        sioc4fd = socket(AF_INET, SOCK_DGRAM, 0);
        sioc6fd = socket(AF_INET6, SOCK_DGRAM, 0);
        if (sioc4fd == -1 || sioc6fd == -1)
                die("cannot create sockets");

        if ((retval = ipmp_open(&handle)) != IPMP_SUCCESS)
                die("cannot create ipmp handle: %s\n", ipmp_errmsg(retval));

        (*ofuncp)(ifname, handle);

        ipmp_close(handle);
        (void) close(sioc4fd);
        (void) close(sioc6fd);

        return (EXIT_SUCCESS);
}

/*
 * Checks whether IFF_OFFLINE is set on `ifname'.
 */
boolean_t
is_offline(const char *ifname)
{
        struct lifreq lifr = { 0 };

        (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name));
        if (ioctl(sioc4fd, SIOCGLIFFLAGS, &lifr) == -1) {
                if (errno != ENXIO ||
                    ioctl(sioc6fd, SIOCGLIFFLAGS, &lifr) == -1) {
                        die("cannot get interface flags on %s", ifname);
                }
        }

        return ((lifr.lifr_flags & IFF_OFFLINE) != 0);
}

static void
do_offline(const char *ifname, ipmp_handle_t handle)
{
        ifaddrlistx_t *ifaddrp, *ifaddrs;
        int retval;

        if (is_offline(ifname))
                die("interface %s is already offline\n", ifname);

        if ((retval = ipmp_offline(handle, ifname, 1)) != IPMP_SUCCESS)
                die("cannot offline %s: %s\n", ifname, mpadm_errmsg(retval));

        /*
         * Get all the up addresses for `ifname' and bring them down.
         */
        if (ifaddrlistx(ifname, IFF_UP, 0, &ifaddrs) == -1)
                die("cannot get addresses on %s", ifname);

        for (ifaddrp = ifaddrs; ifaddrp != NULL; ifaddrp = ifaddrp->ia_next) {
                if (!(ifaddrp->ia_flags & IFF_OFFLINE))
                        warn("IFF_OFFLINE vanished on %s\n", ifaddrp->ia_name);

                if (!set_lifflags(ifaddrp->ia_name,
                    ifaddrp->ia_flags & ~IFF_UP))
                        warn("cannot bring down address on %s",
                            ifaddrp->ia_name);
        }

        ifaddrlistx_free(ifaddrs);
}

static void
undo_offline(const char *ifname, ipmp_handle_t handle)
{
        ifaddrlistx_t *ifaddrp, *ifaddrs;
        int retval;

        if (!is_offline(ifname))
                die("interface %s is not offline\n", ifname);

        /*
         * Get all the down addresses for `ifname' and bring them up.
         */
        if (ifaddrlistx(ifname, 0, IFF_UP, &ifaddrs) == -1)
                die("cannot get addresses for %s", ifname);

        for (ifaddrp = ifaddrs; ifaddrp != NULL; ifaddrp = ifaddrp->ia_next) {
                if (!(ifaddrp->ia_flags & IFF_OFFLINE))
                        warn("IFF_OFFLINE vanished on %s\n", ifaddrp->ia_name);

                if (!set_lifflags(ifaddrp->ia_name, ifaddrp->ia_flags | IFF_UP))
                        warn("cannot bring up address on %s", ifaddrp->ia_name);
        }

        ifaddrlistx_free(ifaddrs);

        /*
         * Undo the offline.
         */
        if ((retval = ipmp_undo_offline(handle, ifname)) != IPMP_SUCCESS) {
                die("cannot undo-offline %s: %s\n", ifname,
                    mpadm_errmsg(retval));
        }

        /*
         * Verify whether IFF_OFFLINE is set as a sanity check.
         */
        if (is_offline(ifname))
                warn("in.mpathd has not cleared IFF_OFFLINE on %s\n", ifname);
}

/*
 * Change `lifname' to have `flags' set.  Returns B_TRUE on success.
 */
static boolean_t
set_lifflags(const char *lifname, uint64_t flags)
{
        struct lifreq   lifr = { 0 };
        int             fd = (flags & IFF_IPV4) ? sioc4fd : sioc6fd;

        (void) strlcpy(lifr.lifr_name, lifname, LIFNAMSIZ);
        lifr.lifr_flags = flags;

        return (ioctl(fd, SIOCSLIFFLAGS, &lifr) >= 0);
}

/* PRINTFLIKE1 */
static void
die(const char *format, ...)
{
        va_list alist;
        char *errstr = strerror(errno);

        format = gettext(format);
        (void) fprintf(stderr, gettext("%s: fatal: "), progname);

        va_start(alist, format);
        (void) vfprintf(stderr, format, alist);
        va_end(alist);

        if (strchr(format, '\n') == NULL)
                (void) fprintf(stderr, ": %s\n", errstr);

        exit(EXIT_FAILURE);
}

/* PRINTFLIKE1 */
static void
warn(const char *format, ...)
{
        va_list alist;
        char *errstr = strerror(errno);

        format = gettext(format);
        (void) fprintf(stderr, gettext("%s: warning: "), progname);

        va_start(alist, format);
        (void) vfprintf(stderr, format, alist);
        va_end(alist);

        if (strchr(format, '\n') == NULL)
                (void) fprintf(stderr, ": %s\n", errstr);
}