root/usr/src/cmd/rcm_daemon/common/ip_anon_rcm.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.
 */

/*
 * RCM module to prevent plumbed IP addresses from being removed.
 */


#include <stdlib.h>
#include <ctype.h>
#include <memory.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <thread.h>
#include <synch.h>
#include <assert.h>
#include <errno.h>
#include <libintl.h>
#include <sys/param.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/cladm.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/time.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/ip6.h>
#include <inet/ip.h>
#include <inet/ip6.h>
#include <libinetutil.h>

#include "rcm_module.h"

#define SUNW_IP         "SUNW_ip/"
#define IP_REG_SIZE     (9 + INET6_ADDRSTRLEN)
#define IP_ANON_USAGE   gettext("Plumbed IP Address")
#define IP_SUSPEND_ERR  gettext("Plumbed IP Addresses cannot be suspended")
#define IP_OFFLINE_ERR  gettext("Invalid operation: IP cannot be offlined")
#define IP_REMOVE_ERR   gettext("Invalid operation: IP cannot be removed")
#define IP_REG_FAIL     gettext("Registration Failed")
#define IP_NO_CLUSTER   gettext("Could not read cluster network addresses")

#define IP_FLAG_NEW     0x00
#define IP_FLAG_REG     0x01
#define IP_FLAG_CL      0x02
#define IP_FLAG_IGNORE  0x04
#define IP_FLAG_DELETE  0x08

static int              ip_anon_register(rcm_handle_t *);
static int              ip_anon_unregister(rcm_handle_t *);
static int              ip_anon_getinfo(rcm_handle_t *, char *, id_t, uint_t,
                            char **, char **, nvlist_t *, rcm_info_t **);
static int              ip_anon_suspend(rcm_handle_t *, char *, id_t,
                            timespec_t *, uint_t, char **, rcm_info_t **);
static int              ip_anon_resume(rcm_handle_t *, char *, id_t, uint_t,
                            char **, rcm_info_t **);
static int              ip_anon_offline(rcm_handle_t *, char *, id_t, uint_t,
                            char **, rcm_info_t **);
static int              ip_anon_online(rcm_handle_t *, char *, id_t, uint_t,
                            char **, rcm_info_t **);
static int              ip_anon_remove(rcm_handle_t *, char *, id_t, uint_t,
                            char **, rcm_info_t **);

static int              exclude_ipv4(cladm_netaddrs_t exclude_addrs,
                            ipaddr_t address);
static int              exclude_ipv6(cladm_netaddrs_t exclude_addrs,
                            uint32_t address[4]);


typedef struct ip_status {
        int                     flags;
        char                    device[IP_REG_SIZE];
        struct ip_status        *next;
} ip_status_t;

static ip_status_t      *findreg(char *reg);
static ip_status_t      *addreg(char *reg);
static int              deletereg(ip_status_t *entry);

static ip_status_t      *ip_list = NULL;
static mutex_t          ip_list_lock;

static struct rcm_mod_ops ip_anon_ops =
{
        RCM_MOD_OPS_VERSION,
        ip_anon_register,
        ip_anon_unregister,
        ip_anon_getinfo,
        ip_anon_suspend,
        ip_anon_resume,
        ip_anon_offline,
        ip_anon_online,
        ip_anon_remove,
        NULL,
        NULL,
        NULL
};

struct rcm_mod_ops *
rcm_mod_init()
{
        return (&ip_anon_ops);
}

const char *
rcm_mod_info()
{
        return ("RCM IP address module 1.4");
}

int
rcm_mod_fini()
{
        ip_status_t *tlist;

        /* free the registration list */

        (void) mutex_lock(&ip_list_lock);
        while (ip_list != NULL) {
                tlist = ip_list->next;
                free(ip_list);
                ip_list = tlist;
        }
        (void) mutex_unlock(&ip_list_lock);

        (void) mutex_destroy(&ip_list_lock);
        return (RCM_SUCCESS);
}

static int
ip_anon_register(rcm_handle_t *hdl)
{
        int bootflags;
        struct ifaddrlist *al = NULL, *al6 = NULL;
        char errbuf[ERRBUFSIZE];
        char treg[IP_REG_SIZE], tstr[IP_REG_SIZE];
        cladm_netaddrs_t exclude_addrs;
        int num_ifs, num_ifs6,  i, ret;
        uint32_t num_exclude_addrs = 0;
        ip_status_t *tlist, *tentry;

        (void) mutex_lock(&ip_list_lock);

        rcm_log_message(RCM_DEBUG, "ip_anon: registration refresh.\n");

        exclude_addrs.cladm_num_netaddrs = 0;

        if (_cladm(CL_INITIALIZE, CL_GET_BOOTFLAG, &bootflags) != 0) {
                rcm_log_message(RCM_ERROR,
                    gettext("unable to check cluster status\n"));
                (void) mutex_unlock(&ip_list_lock);
                return (RCM_FAILURE);
        }

        rcm_log_message(RCM_DEBUG,
            "ip_anon: cladm bootflags=%d\n", bootflags);

        if (bootflags == 3) {

                /* build the exclusion list */

                if ((ret = _cladm(CL_CONFIG, CL_GET_NUM_NETADDRS,
                    &num_exclude_addrs)) == 0) {
                        exclude_addrs.cladm_num_netaddrs = num_exclude_addrs;

                        if (num_exclude_addrs == 0)
                                rcm_log_message(RCM_DEBUG,
                                    "ip_anon: no addresses excluded\n");
                        else {
                                if ((exclude_addrs.cladm_netaddrs_array =
                                    malloc(sizeof (cladm_netaddr_entry_t) *
                                    (num_exclude_addrs))) == NULL) {
                                        rcm_log_message(RCM_ERROR,
                                            gettext("out of memory\n"));
                                        (void) mutex_unlock(&ip_list_lock);
                                        return (RCM_FAILURE);
                                }

                                if ((ret = _cladm(CL_CONFIG,
                                    CL_GET_NETADDRS, &exclude_addrs))
                                    != 0) {
                                        rcm_log_message(RCM_ERROR,
                                            IP_NO_CLUSTER);
                                        (void) mutex_unlock(&ip_list_lock);
                                        return (RCM_FAILURE);
                                }
                        }

                } else {
                        if ((ret != 0) && (errno == EINVAL)) {
                                rcm_log_message(RCM_DEBUG,
                                    "no _cladm() backend to get addrs\n");
                        } else {
                                rcm_log_message(RCM_ERROR, IP_NO_CLUSTER);
                                (void) mutex_unlock(&ip_list_lock);
                                return (RCM_FAILURE);
                        }
                }
                rcm_log_message(RCM_DEBUG,
                    "cladm returned %d errno=%d\n", ret, errno);

                rcm_log_message(RCM_DEBUG,
                    "ip_anon: num exclude addrs: %d\n",
                    exclude_addrs.cladm_num_netaddrs);

                /* print the exclusion list for debugging purposes */

                for (i = 0; i < exclude_addrs.cladm_num_netaddrs; i++) {
                        (void) strcpy(treg, "<UNKNOWN>");
                        (void) strcpy(tstr, "<UNKNOWN>");
                        if (exclude_addrs.cladm_netaddrs_array[i].\
                            cl_ipversion == IPV4_VERSION) {
                                (void) inet_ntop(AF_INET,
                                    &exclude_addrs.cladm_netaddrs_array[i].
                                    cl_ipv_un.cl_ipv4.ipv4_netaddr,
                                    treg, INET_ADDRSTRLEN);

                                (void) inet_ntop(AF_INET,
                                    &exclude_addrs.cladm_netaddrs_array[i].
                                    cl_ipv_un.cl_ipv4.ipv4_netmask,
                                    tstr, INET_ADDRSTRLEN);
                        }

                        if (exclude_addrs.cladm_netaddrs_array[i].\
                            cl_ipversion == IPV6_VERSION) {
                                (void) inet_ntop(AF_INET6,
                                    &exclude_addrs.cladm_netaddrs_array[i].
                                    cl_ipv_un.cl_ipv6.ipv6_netaddr,
                                    treg, INET6_ADDRSTRLEN);

                                (void) inet_ntop(AF_INET6,
                                    &exclude_addrs.cladm_netaddrs_array[i].
                                    cl_ipv_un.cl_ipv6.ipv6_netmask,
                                    tstr, INET6_ADDRSTRLEN);
                        }
                        rcm_log_message(RCM_DEBUG, "IPV%d: %s %s\n",
                            exclude_addrs.cladm_netaddrs_array[i].
                            cl_ipversion, treg, tstr);

                }
        }

        /* obtain a list of all IPv4 and IPv6 addresses in the system */

        rcm_log_message(RCM_DEBUG,
            "ip_anon: obtaining list of IPv4 addresses.\n");
        num_ifs = ifaddrlist(&al, AF_INET, LIFC_UNDER_IPMP, errbuf);
        if (num_ifs == -1) {
                rcm_log_message(RCM_ERROR,
                    gettext("cannot get IPv4 address list errno=%d (%s)\n"),
                    errno, errbuf);
                (void) mutex_unlock(&ip_list_lock);
                return (RCM_FAILURE);
        }

        rcm_log_message(RCM_DEBUG,
            "ip_anon: obtaining list of IPv6 addresses.\n");

        num_ifs6 = ifaddrlist(&al6, AF_INET6, LIFC_UNDER_IPMP, errbuf);
        if (num_ifs6 == -1) {
                rcm_log_message(RCM_ERROR,
                    gettext("cannot get IPv6 address list errno=%d (%s)\n"),
                    errno, errbuf);
                free(al);
                (void) mutex_unlock(&ip_list_lock);
                return (RCM_FAILURE);
        }

        /* check the state of outstanding registrations against the list */

        rcm_log_message(RCM_DEBUG,
            "ip_anon: checking outstanding registrations.\n");

        tlist = ip_list;
        while (tlist != NULL) {
                tlist->flags |= IP_FLAG_DELETE;
                tlist = tlist->next;
        }

        /* IPv4 */

        rcm_log_message(RCM_DEBUG, "ip_anon: checking IPv4 addresses.\n");

        for (i = 0; i < num_ifs; i++) {
                (void) inet_ntop(AF_INET, &al[i].addr.addr, tstr,
                    INET_ADDRSTRLEN);
                (void) strcpy(treg, SUNW_IP);
                (void) strcat(treg, tstr);

                if ((tlist = findreg(treg)) == NULL)
                        tlist = addreg(treg);
                else
                        tlist->flags &= (~IP_FLAG_DELETE);

                if (tlist == NULL) {
                        rcm_log_message(RCM_ERROR,
                            gettext("out of memory\n"));
                        free(al);
                        free(al6);
                        (void) mutex_unlock(&ip_list_lock);
                        return (RCM_FAILURE);
                }

                if (exclude_ipv4(exclude_addrs, al[i].addr.addr.s_addr))
                        tlist->flags |= IP_FLAG_CL;
        }

        /* IPv6 */

        rcm_log_message(RCM_DEBUG, "ip_anon: checking IPv6 addresses.\n");

        for (i = 0; i < num_ifs6; i++) {
                (void) inet_ntop(AF_INET6, &al6[i].addr.addr, tstr,
                    INET6_ADDRSTRLEN);
                (void) strcpy(treg, SUNW_IP);
                (void) strcat(treg, tstr);

                if ((tlist = findreg(treg)) == NULL)
                        tlist = addreg(treg);
                else
                        tlist->flags &= (~IP_FLAG_DELETE);

                if (tlist == NULL) {
                        rcm_log_message(RCM_ERROR,
                            gettext("out of memory\n"));
                        free(al);
                        free(al6);
                        (void) mutex_unlock(&ip_list_lock);
                        return (RCM_FAILURE);
                }

                if (exclude_ipv6(exclude_addrs, al6[i].addr.addr6._S6_un.\
                    _S6_u32))
                        tlist->flags |= IP_FLAG_CL;
        }

        rcm_log_message(RCM_DEBUG, "ip_anon: updating reg. state.\n");

        /* examine the list of ip address registrations and their state */

        tlist = ip_list;
        while (tlist != NULL) {
                tentry = tlist;
                tlist = tlist->next;

                if (tentry->flags & IP_FLAG_DELETE) {
                        if (tentry->flags & IP_FLAG_REG) {
                                rcm_log_message(RCM_DEBUG,
                                    "ip_anon: unregistering interest in %s\n",
                                    tentry->device);
                                if (rcm_unregister_interest(hdl,
                                    tentry->device, 0) != 0) {
                                        rcm_log_message(RCM_ERROR,
                                            gettext("failed to unregister"));
                                }
                        }
                        (void) deletereg(tentry);
                } else if (!(tentry->flags & IP_FLAG_IGNORE)) {
                        /*
                         * If the registration is not a clustered devices and
                         * not already registered, then RCM doesn't
                         * currently know about it.
                         */
                        if (!(tentry->flags & IP_FLAG_CL) &&
                            !(tentry->flags & IP_FLAG_REG)) {
                                tentry->flags |= IP_FLAG_REG;
                                rcm_log_message(RCM_DEBUG,
                                    "ip_anon: registering interest in %s\n",
                                    tentry->device);
                                if (rcm_register_interest(hdl,
                                    tentry->device, 0, NULL) !=
                                    RCM_SUCCESS) {
                                        rcm_log_message(RCM_ERROR,
                                            IP_REG_FAIL);
                                        free(al);
                                        free(al6);
                                        (void) mutex_unlock(&ip_list_lock);
                                        return (RCM_FAILURE);
                                } else {
                                        rcm_log_message(RCM_DEBUG,
                                            "ip_anon: registered %s\n",
                                            tentry->device);
                                }
                        }

                        /*
                         * If the entry is registered and clustered, then
                         * the configuration has been changed and it
                         * should be unregistered.
                         */
                        if ((tentry->flags & IP_FLAG_REG) &
                            (tentry->flags & IP_FLAG_CL)) {
                                rcm_log_message(RCM_DEBUG,
                                    "ip_anon: unregistering in %s\n",
                                    tentry->device);
                                if (rcm_unregister_interest(hdl,
                                    tentry->device, 0) != 0) {
                                        rcm_log_message(RCM_ERROR,
                                            gettext("failed to unregister"));
                                }
                                tentry->flags &= (~IP_FLAG_REG);
                        }
                }
        }

        tlist = ip_list;
        while (tlist != NULL) {
                rcm_log_message(RCM_DEBUG, "ip_anon: %s (%Xh)\n",
                    tlist->device, tlist->flags);
                tlist = tlist->next;
        }
        rcm_log_message(RCM_DEBUG, "ip_anon: registration complete.\n");

        free(al);
        free(al6);
        (void) mutex_unlock(&ip_list_lock);
        return (RCM_SUCCESS);
}

static int
ip_anon_unregister(rcm_handle_t *hdl)
{
        ip_status_t *tlist;

        (void) mutex_lock(&ip_list_lock);

        tlist = ip_list;
        while (tlist != NULL) {
                if ((tlist->flags & IP_FLAG_REG)) {
                        if (rcm_unregister_interest(hdl,
                            tlist->device, 0) != 0) {
                                rcm_log_message(RCM_ERROR,
                                    gettext("failed to unregister"));
                        }
                        tlist->flags &= (~IP_FLAG_REG);
                }
                tlist = tlist->next;
        }

        (void) mutex_unlock(&ip_list_lock);

        return (RCM_SUCCESS);
}

/*ARGSUSED*/
static int
ip_anon_getinfo(rcm_handle_t *hdl, char *rsrcname, id_t id, uint_t flags,
    char **infostr, char **errstr, nvlist_t *props, rcm_info_t **dependent)
{

        assert(rsrcname != NULL && infostr != NULL);

        if ((*infostr = strdup(IP_ANON_USAGE)) == NULL)
                rcm_log_message(RCM_ERROR, gettext("strdup failure\n"));

        return (RCM_SUCCESS);
}

/*ARGSUSED*/
static int
ip_anon_suspend(rcm_handle_t *hdl, char *rsrcname, id_t id,
    timespec_t *interval, uint_t flags, char **errstr,
    rcm_info_t **dependent)
{
        if ((*errstr = strdup(IP_SUSPEND_ERR)) == NULL)
                rcm_log_message(RCM_ERROR, gettext("strdup failure\n"));

        return (RCM_FAILURE);
}

/*ARGSUSED*/
static int
ip_anon_resume(rcm_handle_t *hdl, char *rsrcname, id_t id, uint_t flags,
    char **errstr, rcm_info_t **dependent)
{
        return (RCM_SUCCESS);
}

/*ARGSUSED*/
static int
ip_anon_offline(rcm_handle_t *hdl, char *rsrcname, id_t id, uint_t flags,
    char **errstr, rcm_info_t **dependent)
{
        if ((*errstr = strdup(IP_OFFLINE_ERR)) == NULL)
                rcm_log_message(RCM_ERROR, gettext("strdup failure\n"));

        return (RCM_FAILURE);
}

/*ARGSUSED*/
static int
ip_anon_online(rcm_handle_t *hdl, char *rsrcname, id_t id, uint_t flags,
    char  **errstr, rcm_info_t **dependent)
{
        return (RCM_SUCCESS);
}

/*ARGSUSED*/
static int
ip_anon_remove(rcm_handle_t *hdl, char *rsrcname, id_t id, uint_t flags,
    char **errstr, rcm_info_t **dependent)
{
        if ((*errstr = strdup(IP_REMOVE_ERR)) == NULL)
                rcm_log_message(RCM_ERROR, gettext("strdup failure\n"));

        return (RCM_FAILURE);
}

/*
 * Call with ip_list_lock held.
 */

static ip_status_t *
findreg(char *reg)
{
        ip_status_t *tlist;
        int done;

        tlist = ip_list;
        done = 0;
        while ((tlist != NULL) && (!done)) {
                if (strcmp(tlist->device, reg) == 0)
                        done = 1;
                else
                        tlist = tlist->next;
        }

        return (tlist);
}

static ip_status_t *
addreg(char *reg)
{
        ip_status_t *tlist, *tentry;

        tentry = (ip_status_t *)malloc(sizeof (ip_status_t));
        if (tentry == NULL)
                return (tentry);

        tentry->flags = IP_FLAG_NEW;
        tentry->next = NULL;
        (void) strcpy(tentry->device, reg);

        if (ip_list == NULL)
                ip_list = tentry;
        else {
                tlist = ip_list;
                while (tlist->next != NULL)
                        tlist = tlist->next;
                tlist->next = tentry;
        }

        return (tentry);
}

static int
deletereg(ip_status_t *entry)
{
        ip_status_t *tlist;

        if (entry == NULL)
                return (-1);

        if (entry == ip_list) {
                ip_list = ip_list->next;
                free(entry);
        } else {
                tlist = ip_list;
                while ((tlist->next != NULL) && (tlist->next != entry))
                        tlist = tlist->next;

                if (tlist->next != entry)
                        return (-1);
                tlist->next = entry->next;
                free(entry);
        }
        return (0);
}

static int
exclude_ipv4(cladm_netaddrs_t exclude_addrs, ipaddr_t address)
{
        int i;
        char taddr[IP_REG_SIZE], tmask[IP_REG_SIZE], tmatch[IP_REG_SIZE];
        ipaddr_t ipv4_netaddr, ipv4_netmask;

        (void) inet_ntop(AF_INET, &address, taddr, INET_ADDRSTRLEN);

        rcm_log_message(RCM_DEBUG, "ip_anon: exclude_ipv4 (%s, %d)\n",
            taddr, exclude_addrs.cladm_num_netaddrs);
        /*
         * If this falls in the exclusion list, the IP_FLAG_CL
         * bit should be set for the adapter.
         */
        for (i = 0; i < exclude_addrs.cladm_num_netaddrs; i++) {
                if (exclude_addrs.cladm_netaddrs_array[i].\
                    cl_ipversion == IPV4_VERSION) {

                        ipv4_netaddr = exclude_addrs.\
                            cladm_netaddrs_array[i].cl_ipv_un.cl_ipv4.\
                            ipv4_netaddr;
                        ipv4_netmask = exclude_addrs.\
                            cladm_netaddrs_array[i].cl_ipv_un.cl_ipv4.\
                            ipv4_netmask;

                        (void) inet_ntop(AF_INET, &ipv4_netaddr, tmatch,
                            INET_ADDRSTRLEN);
                        (void) inet_ntop(AF_INET, &ipv4_netmask, tmask,
                            INET_ADDRSTRLEN);

                        if ((address & ipv4_netmask) == ipv4_netaddr) {
                                rcm_log_message(RCM_DEBUG,
                                    "ip_anon: matched %s:%s => %s\n",
                                    taddr, tmask, tmatch);
                                return (1);
                        }
                }
        }
        rcm_log_message(RCM_DEBUG, "ip_anon: no match for %s\n",
            taddr);
        return (0);
}

static int
exclude_ipv6(cladm_netaddrs_t exclude_addrs, uint32_t address[4])
{
        int i, j, numequal;
        uint32_t addr[4], ipv6_netaddr[4], ipv6_netmask[4];
        char taddr[IP_REG_SIZE], tmask[IP_REG_SIZE], tmatch[IP_REG_SIZE];

        (void) inet_ntop(AF_INET6, address, taddr, INET6_ADDRSTRLEN);

        /*
         * If this falls in the exclusion list, the IP_FLAG_CL
         * bit should be set for the adapter.
         */

        for (i = 0; i < exclude_addrs.cladm_num_netaddrs; i++) {
                if (exclude_addrs.cladm_netaddrs_array[i].\
                    cl_ipversion == IPV6_VERSION) {
                        numequal = 0;
                        for (j = 0; j < 4; j++) {
                                ipv6_netaddr[j] = exclude_addrs.\
                                    cladm_netaddrs_array[i].\
                                    cl_ipv_un.cl_ipv6.ipv6_netaddr[j];

                                ipv6_netmask[j] = exclude_addrs.\
                                    cladm_netaddrs_array[i].\
                                    cl_ipv_un.cl_ipv6.ipv6_netmask[j];

                                addr[j] = address[j] & ipv6_netmask[j];
                                if (addr[j] == ipv6_netaddr[j])
                                        numequal++;
                        }

                        (void) inet_ntop(AF_INET6, ipv6_netaddr, tmatch,
                            INET6_ADDRSTRLEN);
                        (void) inet_ntop(AF_INET6, ipv6_netmask, tmask,
                            INET6_ADDRSTRLEN);

                        if (numequal == 4)
                                return (1);
                }
        }
        rcm_log_message(RCM_DEBUG, "ip_anon: no match for %s\n",
            taddr);
        return (0);
}