ip_rcm.c revision 64639aaf7beb84086b88f186ea1fa9ccf0be8c57
/*
* 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) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
*/
/*
* This RCM module adds support to the RCM framework for IP managed
* interfaces.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <synch.h>
#include <libintl.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stropts.h>
#include <strings.h>
#include <sys/sysmacros.h>
#include <inet/ip.h>
#include <libinetutil.h>
#include <libdllink.h>
#include <libgen.h>
#include <ipmp_admin.h>
#include <libipadm.h>
#include "rcm_module.h"
/*
* Definitions
*/
#ifndef lint
#define _(x) gettext(x)
#else
#define _(x) x
#endif
/* Some generic well-knowns and defaults used in this module */
#define ARP_MOD_NAME "arp" /* arp module */
#define IP_MAX_MODS 9 /* max modules pushed on intr */
#define MAX_RECONFIG_SIZE 1024 /* Max. reconfig string size */
#define RCM_LINK_PREFIX "SUNW_datalink" /* RCM datalink name prefix */
#define RCM_LINK_RESOURCE_MAX (13 + LINKID_STR_WIDTH)
#define RCM_STR_SUNW_IP "SUNW_ip/" /* IP address export prefix */
#define SBIN_IFCONFIG "/sbin/ifconfig" /* ifconfig command */
#define SBIN_IFPARSE "/sbin/ifparse" /* ifparse command */
#define DHCPFILE_FMT "/etc/dhcp.%s" /* DHCP config file */
#define CFGFILE_FMT_IPV4 "/etc/hostname.%s" /* IPV4 config file */
#define CFGFILE_FMT_IPV6 "/etc/hostname6.%s" /* IPV6 config file */
#define CFG_CMDS_STD " netmask + broadcast + up" /* Normal config string */
#define CFG_DHCP_CMD "dhcp wait 0" /* command to start DHCP */
/* Some useful macros */
#define ISSPACE(c) ((c) == ' ' || (c) == '\t')
#define ISEOL(c) ((c) == '\n' || (c) == '\r' || (c) == '\0')
#define STREQ(a, b) (*(a) == *(b) && strcmp((a), (b)) == 0)
/* Interface Cache state flags */
#define CACHE_IF_STALE 0x1 /* stale cached data */
#define CACHE_IF_NEW 0x2 /* new cached interface */
#define CACHE_IF_OFFLINED 0x4 /* interface offlined */
#define CACHE_IF_IGNORE 0x8 /* state held elsewhere */
/* Network Cache lookup options */
#define CACHE_NO_REFRESH 0x1 /* cache refresh not needed */
#define CACHE_REFRESH 0x2 /* refresh cache */
/* RCM IPMP Module specific property definitions */
#define RCM_IPMP_MIN_REDUNDANCY 1 /* default min. redundancy */
/* Stream module operations */
#define MOD_INSERT 0 /* Insert a mid-stream module */
#define MOD_REMOVE 1 /* Remove a mid-stream module */
#define MOD_CHECK 2 /* Check mid-stream module safety */
/*
* IP module data types
*/
/* Physical interface representation */
typedef struct ip_pif {
char pi_ifname[LIFNAMSIZ]; /* interface name */
char pi_grname[LIFGRNAMSIZ]; /* IPMP group name */
struct ip_lif *pi_lifs; /* ptr to logical interfaces */
} ip_pif_t;
/* Logical interface representation */
typedef struct ip_lif
{
struct ip_lif *li_next; /* ptr to next lif */
struct ip_lif *li_prev; /* previous next ptr */
ip_pif_t *li_pif; /* back ptr to phy int */
ushort_t li_ifnum; /* interface number */
union {
sa_family_t family;
struct sockaddr_storage storage;
struct sockaddr_in ip4; /* IPv4 */
struct sockaddr_in6 ip6; /* IPv6 */
} li_addr;
uint64_t li_ifflags; /* current IFF_* flags */
int li_modcnt; /* # of modules */
char *li_modules[IP_MAX_MODS]; /* module list pushed */
char *li_reconfig; /* Reconfiguration string */
int32_t li_cachestate; /* cache state flags */
} ip_lif_t;
/* Cache element */
typedef struct ip_cache
{
struct ip_cache *ip_next; /* next cached resource */
struct ip_cache *ip_prev; /* prev cached resource */
char *ip_resource; /* resource name */
ip_pif_t *ip_pif; /* ptr to phy int */
int32_t ip_ifred; /* min. redundancy */
int ip_cachestate; /* cache state flags */
} ip_cache_t;
/*
* Global cache for network interfaces
*/
static ip_cache_t cache_head;
static ip_cache_t cache_tail;
static mutex_t cache_lock;
static int events_registered = 0;
static dladm_handle_t dld_handle = NULL;
static ipadm_handle_t ip_handle = NULL;
/*
* RCM module interface prototypes
*/
static int ip_register(rcm_handle_t *);
static int ip_unregister(rcm_handle_t *);
static int ip_get_info(rcm_handle_t *, char *, id_t, uint_t,
char **, char **, nvlist_t *, rcm_info_t **);
static int ip_suspend(rcm_handle_t *, char *, id_t,
timespec_t *, uint_t, char **, rcm_info_t **);
static int ip_resume(rcm_handle_t *, char *, id_t, uint_t,
char **, rcm_info_t **);
static int ip_offline(rcm_handle_t *, char *, id_t, uint_t,
char **, rcm_info_t **);
static int ip_undo_offline(rcm_handle_t *, char *, id_t, uint_t,
char **, rcm_info_t **);
static int ip_remove(rcm_handle_t *, char *, id_t, uint_t,
char **, rcm_info_t **);
static int ip_notify_event(rcm_handle_t *, char *, id_t, uint_t,
char **, nvlist_t *, rcm_info_t **);
/* Module private routines */
static void free_cache();
static int update_cache(rcm_handle_t *);
static void cache_remove(ip_cache_t *);
static ip_cache_t *cache_lookup(rcm_handle_t *, char *, char);
static void free_node(ip_cache_t *);
static void cache_insert(ip_cache_t *);
static char *ip_usage(ip_cache_t *);
static int update_pif(rcm_handle_t *, int, int, struct ifaddrs *);
static int ip_ipmp_offline(ip_cache_t *);
static int ip_ipmp_undo_offline(ip_cache_t *);
static int if_cfginfo(ip_cache_t *, uint_t);
static int if_unplumb(ip_cache_t *);
static int if_replumb(ip_cache_t *);
static void ip_log_err(ip_cache_t *, char **, char *);
static char *get_link_resource(const char *);
static void clr_cfg_state(ip_pif_t *);
static int modop(char *, char *, int, char);
static int get_modlist(char *, ip_lif_t *);
static int ip_domux2fd(int *, int *, int *, struct lifreq *);
static int ip_plink(int, int, int, struct lifreq *);
static int ip_onlinelist(rcm_handle_t *, ip_cache_t *, char **, uint_t,
rcm_info_t **);
static int ip_offlinelist(rcm_handle_t *, ip_cache_t *, char **, uint_t,
rcm_info_t **);
static char **ip_get_addrlist(ip_cache_t *);
static void ip_free_addrlist(char **);
static void ip_consumer_notify(rcm_handle_t *, datalink_id_t, char **,
uint_t, rcm_info_t **);
static boolean_t ip_addrstr(ip_lif_t *, char *, size_t);
static int if_configure_hostname(datalink_id_t);
static int if_configure_ipadm(datalink_id_t);
static boolean_t if_hostname_exists(char *, sa_family_t);
static boolean_t isgrouped(const char *);
static int if_config_inst(const char *, FILE *, int, boolean_t);
static uint_t ntok(const char *cp);
static boolean_t ifconfig(const char *, const char *, const char *, boolean_t);
/* Module-Private data */
static struct rcm_mod_ops ip_ops =
{
RCM_MOD_OPS_VERSION,
ip_register,
ip_unregister,
ip_get_info,
ip_suspend,
ip_resume,
ip_offline,
ip_undo_offline,
ip_remove,
NULL,
NULL,
ip_notify_event
};
/*
* rcm_mod_init() - Update registrations, and return the ops structure.
*/
struct rcm_mod_ops *
rcm_mod_init(void)
{
char errmsg[DLADM_STRSIZE];
dladm_status_t status;
ipadm_status_t iph_status;
rcm_log_message(RCM_TRACE1, "IP: mod_init\n");
cache_head.ip_next = &cache_tail;
cache_head.ip_prev = NULL;
cache_tail.ip_prev = &cache_head;
cache_tail.ip_next = NULL;
(void) mutex_init(&cache_lock, NULL, NULL);
if ((status = dladm_open(&dld_handle)) != DLADM_STATUS_OK) {
rcm_log_message(RCM_WARNING,
"IP: mod_init failed: cannot get datalink handle: %s\n",
dladm_status2str(status, errmsg));
return (NULL);
}
if ((iph_status = ipadm_open(&ip_handle, 0)) != IPADM_SUCCESS) {
rcm_log_message(RCM_ERROR,
"IP: mod_init failed: cannot get IP handle: %s\n",
ipadm_status2str(iph_status));
dladm_close(dld_handle);
dld_handle = NULL;
return (NULL);
}
/* Return the ops vectors */
return (&ip_ops);
}
/*
* rcm_mod_info() - Return a string describing this module.
*/
const char *
rcm_mod_info(void)
{
rcm_log_message(RCM_TRACE1, "IP: mod_info\n");
return ("IP Multipathing module version 1.23");
}
/*
* rcm_mod_fini() - Destroy the network interfaces cache.
*/
int
rcm_mod_fini(void)
{
rcm_log_message(RCM_TRACE1, "IP: mod_fini\n");
free_cache();
(void) mutex_destroy(&cache_lock);
dladm_close(dld_handle);
ipadm_close(ip_handle);
return (RCM_SUCCESS);
}
/*
* ip_register() - Make sure the cache is properly sync'ed, and its
* registrations are in order.
*/
static int
ip_register(rcm_handle_t *hd)
{
rcm_log_message(RCM_TRACE1, "IP: register\n");
/* Guard against bad arguments */
assert(hd != NULL);
if (update_cache(hd) < 0)
return (RCM_FAILURE);
/*
* Need to register interest in all new resources
* getting attached, so we get attach event notifications
*/
if (!events_registered) {
if (rcm_register_event(hd, RCM_RESOURCE_LINK_NEW, 0, NULL)
!= RCM_SUCCESS) {
rcm_log_message(RCM_ERROR,
_("IP: failed to register %s\n"),
RCM_RESOURCE_LINK_NEW);
return (RCM_FAILURE);
} else {
rcm_log_message(RCM_DEBUG, "IP: registered %s\n",
RCM_RESOURCE_LINK_NEW);
events_registered++;
}
}
return (RCM_SUCCESS);
}
/*
* ip_unregister() - Walk the cache, unregistering all the networks.
*/
static int
ip_unregister(rcm_handle_t *hd)
{
ip_cache_t *probe;
rcm_log_message(RCM_TRACE1, "IP: unregister\n");
/* Guard against bad arguments */
assert(hd != NULL);
/* Walk the cache, unregistering everything */
(void) mutex_lock(&cache_lock);
probe = cache_head.ip_next;
while (probe != &cache_tail) {
if (rcm_unregister_interest(hd, probe->ip_resource, 0)
!= RCM_SUCCESS) {
/* unregister failed for whatever reason */
(void) mutex_unlock(&cache_lock);
return (RCM_FAILURE);
}
cache_remove(probe);
free_node(probe);
probe = cache_head.ip_next;
}
(void) mutex_unlock(&cache_lock);
/*
* Need to unregister interest in all new resources
*/
if (events_registered) {
if (rcm_unregister_event(hd, RCM_RESOURCE_LINK_NEW, 0)
!= RCM_SUCCESS) {
rcm_log_message(RCM_ERROR,
_("IP: failed to unregister %s\n"),
RCM_RESOURCE_LINK_NEW);
return (RCM_FAILURE);
} else {
rcm_log_message(RCM_DEBUG, "IP: unregistered %s\n",
RCM_RESOURCE_LINK_NEW);
events_registered--;
}
}
return (RCM_SUCCESS);
}
/*
* ip_offline() - Offline an interface.
*/
static int
ip_offline(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flags,
char **errorp, rcm_info_t **depend_info)
{
ip_cache_t *node;
ip_pif_t *pif;
boolean_t detachable = B_FALSE;
boolean_t ipmp;
int retval;
rcm_log_message(RCM_TRACE1, "IP: offline(%s)\n", rsrc);
/* Guard against bad arguments */
assert(hd != NULL);
assert(rsrc != NULL);
assert(id == (id_t)0);
assert(errorp != NULL);
assert(depend_info != NULL);
/* Lock the cache and lookup the resource */
(void) mutex_lock(&cache_lock);
node = cache_lookup(hd, rsrc, CACHE_REFRESH);
if (node == NULL) {
ip_log_err(node, errorp, "Unrecognized resource");
errno = ENOENT;
(void) mutex_unlock(&cache_lock);
return (RCM_SUCCESS);
}
pif = node->ip_pif;
/* Establish default detachability criteria */
if (flags & RCM_FORCE)
detachable = B_TRUE;
/* Check if the interface is under IPMP */
ipmp = (pif->pi_grname[0] != '\0');
/*
* Even if the interface is not under IPMP, it's possible that it's
* still okay to offline it as long as there are higher-level failover
* mechanisms for the addresses it owns (e.g., clustering). In this
* case, ip_offlinelist() will return RCM_SUCCESS, and we charge on.
*/
if (!ipmp && !detachable) {
/* Inform consumers of IP addresses being offlined */
if (ip_offlinelist(hd, node, errorp, flags, depend_info) ==
RCM_SUCCESS) {
rcm_log_message(RCM_DEBUG,
"IP: consumers agree on detach");
} else {
ip_log_err(node, errorp,
"Device consumers prohibit offline");
(void) mutex_unlock(&cache_lock);
return (RCM_FAILURE);
}
}
/* Check if it's a query */
if (flags & RCM_QUERY) {
rcm_log_message(RCM_TRACE1, "IP: offline query success(%s)\n",
rsrc);
(void) mutex_unlock(&cache_lock);
return (RCM_SUCCESS);
}
/* Check detachability, save configuration if detachable */
if (if_cfginfo(node, (flags & RCM_FORCE)) < 0) {
node->ip_cachestate |= CACHE_IF_IGNORE;
rcm_log_message(RCM_TRACE1, "IP: Ignoring node(%s)\n", rsrc);
(void) mutex_unlock(&cache_lock);
return (RCM_SUCCESS);
}
/* standalone detachable device */
if (!ipmp) {
if (if_unplumb(node) < 0) {
ip_log_err(node, errorp,
"Failed to unplumb the device");
errno = EIO;
(void) mutex_unlock(&cache_lock);
return (RCM_FAILURE);
}
node->ip_cachestate |= CACHE_IF_OFFLINED;
rcm_log_message(RCM_TRACE1, "IP: Offline success(%s)\n", rsrc);
(void) mutex_unlock(&cache_lock);
return (RCM_SUCCESS);
}
/*
* This is an IPMP interface that can be offlined.
* Request in.mpathd(1M) to offline the physical interface.
*/
if ((retval = ip_ipmp_offline(node)) != IPMP_SUCCESS)
ip_log_err(node, errorp, "in.mpathd offline failed");
if (retval == IPMP_EMINRED && !detachable) {
/*
* in.mpathd(1M) could not offline the device because it was
* the last interface in the group. However, it's possible
* that it's still okay to offline it as long as there are
* higher-level failover mechanisms for the addresses it owns
* (e.g., clustering). In this case, ip_offlinelist() will
* return RCM_SUCCESS, and we charge on.
*/
/* Inform consumers of IP addresses being offlined */
if (ip_offlinelist(hd, node, errorp, flags,
depend_info) == RCM_SUCCESS) {
rcm_log_message(RCM_DEBUG,
"IP: consumers agree on detach");
} else {
ip_log_err(node, errorp,
"Device consumers prohibit offline");
(void) mutex_unlock(&cache_lock);
errno = EBUSY;
return (RCM_FAILURE);
}
}
if (if_unplumb(node) < 0) {
rcm_log_message(RCM_ERROR,
_("IP: Unplumb failed (%s)\n"),
pif->pi_ifname);
/* Request in.mpathd to undo the offline */
if (ip_ipmp_undo_offline(node) != IPMP_SUCCESS) {
ip_log_err(node, errorp, "Undo offline failed");
(void) mutex_unlock(&cache_lock);
return (RCM_FAILURE);
}
(void) mutex_unlock(&cache_lock);
return (RCM_FAILURE);
}
node->ip_cachestate |= CACHE_IF_OFFLINED;
rcm_log_message(RCM_TRACE1, "IP: offline success(%s)\n", rsrc);
(void) mutex_unlock(&cache_lock);
return (RCM_SUCCESS);
}
/*
* ip_undo_offline() - Undo offline of a previously offlined device.
*/
/*ARGSUSED*/
static int
ip_undo_offline(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flags,
char **errorp, rcm_info_t **depend_info)
{
ip_cache_t *node;
rcm_log_message(RCM_TRACE1, "IP: online(%s)\n", rsrc);
/* Guard against bad arguments */
assert(hd != NULL);
assert(rsrc != NULL);
assert(id == (id_t)0);
assert(errorp != NULL);
assert(depend_info != NULL);
(void) mutex_lock(&cache_lock);
node = cache_lookup(hd, rsrc, CACHE_NO_REFRESH);
if (node == NULL) {
ip_log_err(node, errorp, "No such device");
(void) mutex_unlock(&cache_lock);
errno = ENOENT;
return (RCM_FAILURE);
}
/* Check if no attempt should be made to online the device here */
if (node->ip_cachestate & CACHE_IF_IGNORE) {
node->ip_cachestate &= ~(CACHE_IF_IGNORE);
(void) mutex_unlock(&cache_lock);
return (RCM_SUCCESS);
}
/* Check if the interface was previously offlined */
if (!(node->ip_cachestate & CACHE_IF_OFFLINED)) {
ip_log_err(node, errorp, "Device not offlined");
(void) mutex_unlock(&cache_lock);
errno = ENOTSUP;
return (RCM_FAILURE);
}
if (if_replumb(node) == -1) {
/* re-plumb failed */
ip_log_err(node, errorp, "Replumb failed");
(void) mutex_unlock(&cache_lock);
errno = EIO;
return (RCM_FAILURE);
}
/* Inform consumers about IP addresses being un-offlined */
(void) ip_onlinelist(hd, node, errorp, flags, depend_info);
node->ip_cachestate &= ~(CACHE_IF_OFFLINED);
rcm_log_message(RCM_TRACE1, "IP: online success(%s)\n", rsrc);
(void) mutex_unlock(&cache_lock);
return (RCM_SUCCESS);
}
/*
* ip_get_info() - Gather usage information for this resource.
*/
/*ARGSUSED*/
int
ip_get_info(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flags,
char **usagep, char **errorp, nvlist_t *props, rcm_info_t **depend_info)
{
ip_cache_t *node;
char *infostr;
/* Guard against bad arguments */
assert(hd != NULL);
assert(rsrc != NULL);
assert(id == (id_t)0);
assert(usagep != NULL);
assert(errorp != NULL);
assert(depend_info != NULL);
rcm_log_message(RCM_TRACE1, "IP: get_info(%s)\n", rsrc);
(void) mutex_lock(&cache_lock);
node = cache_lookup(hd, rsrc, CACHE_REFRESH);
if (!node) {
rcm_log_message(RCM_INFO,
_("IP: get_info(%s) unrecognized resource\n"), rsrc);
(void) mutex_unlock(&cache_lock);
errno = ENOENT;
return (RCM_FAILURE);
}
infostr = ip_usage(node);
if (infostr == NULL) {
/* most likely malloc failure */
rcm_log_message(RCM_ERROR,
_("IP: get_info(%s) malloc failure\n"), rsrc);
(void) mutex_unlock(&cache_lock);
errno = ENOMEM;
*errorp = NULL;
return (RCM_FAILURE);
}
/* Set client/role properties */
(void) nvlist_add_string(props, RCM_CLIENT_NAME, "IP");
/* Set usage property, infostr will be freed by caller */
*usagep = infostr;
rcm_log_message(RCM_TRACE1, "IP: get_info(%s) info = %s \n",
rsrc, infostr);
(void) mutex_unlock(&cache_lock);
return (RCM_SUCCESS);
}
/*
* ip_suspend() - Nothing to do, always okay
*/
/*ARGSUSED*/
static int
ip_suspend(rcm_handle_t *hd, char *rsrc, id_t id, timespec_t *interval,
uint_t flags, char **errorp, rcm_info_t **depend_info)
{
/* Guard against bad arguments */
assert(hd != NULL);
assert(rsrc != NULL);
assert(id == (id_t)0);
assert(interval != NULL);
assert(errorp != NULL);
assert(depend_info != NULL);
rcm_log_message(RCM_TRACE1, "IP: suspend(%s)\n", rsrc);
return (RCM_SUCCESS);
}
/*
* ip_resume() - Nothing to do, always okay
*/
/*ARGSUSED*/
static int
ip_resume(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flags,
char **errorp, rcm_info_t ** depend_info)
{
/* Guard against bad arguments */
assert(hd != NULL);
assert(rsrc != NULL);
assert(id == (id_t)0);
assert(errorp != NULL);
assert(depend_info != NULL);
rcm_log_message(RCM_TRACE1, "IP: resume(%s)\n", rsrc);
return (RCM_SUCCESS);
}
/*
* ip_remove() - remove a resource from cache
*/
/*ARGSUSED*/
static int
ip_remove(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flags,
char **errorp, rcm_info_t **depend_info)
{
ip_cache_t *node;
/* Guard against bad arguments */
assert(hd != NULL);
assert(rsrc != NULL);
assert(id == (id_t)0);
assert(errorp != NULL);
assert(depend_info != NULL);
rcm_log_message(RCM_TRACE1, "IP: remove(%s)\n", rsrc);
(void) mutex_lock(&cache_lock);
node = cache_lookup(hd, rsrc, CACHE_NO_REFRESH);
if (!node) {
rcm_log_message(RCM_INFO,
_("IP: remove(%s) unrecognized resource\n"), rsrc);
(void) mutex_unlock(&cache_lock);
errno = ENOENT;
return (RCM_FAILURE);
}
/* remove the cached entry for the resource */
cache_remove(node);
free_node(node);
(void) mutex_unlock(&cache_lock);
return (RCM_SUCCESS);
}
/*
* ip_notify_event - Project private implementation to receive new resource
* events. It intercepts all new resource events. If the
* new resource is a network resource, pass up a notify
* for it too. The new resource need not be cached, since
* it is done at register again.
*/
/*ARGSUSED*/
static int
ip_notify_event(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flags,
char **errorp, nvlist_t *nvl, rcm_info_t **depend_info)
{
datalink_id_t linkid;
nvpair_t *nvp = NULL;
uint64_t id64;
assert(hd != NULL);
assert(rsrc != NULL);
assert(id == (id_t)0);
assert(nvl != NULL);
rcm_log_message(RCM_TRACE1, "IP: notify_event(%s)\n", rsrc);
if (!STREQ(rsrc, RCM_RESOURCE_LINK_NEW)) {
rcm_log_message(RCM_INFO,
_("IP: unrecognized event for %s\n"), rsrc);
ip_log_err(NULL, errorp, "unrecognized event");
errno = EINVAL;
return (RCM_FAILURE);
}
/* Update cache to reflect latest interfaces */
if (update_cache(hd) < 0) {
rcm_log_message(RCM_ERROR, _("IP: update_cache failed\n"));
ip_log_err(NULL, errorp, "Private Cache update failed");
return (RCM_FAILURE);
}
rcm_log_message(RCM_TRACE1, "IP: process_nvlist\n");
while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) {
if (STREQ(nvpair_name(nvp), RCM_NV_LINKID)) {
if (nvpair_value_uint64(nvp, &id64) != 0) {
rcm_log_message(RCM_WARNING,
_("IP: cannot get linkid\n"));
return (RCM_FAILURE);
}
linkid = (datalink_id_t)id64;
/*
* Grovel through /etc/hostname* files and configure
* interface in the same way that they would be handled
* by network/physical.
*/
if (if_configure_hostname(linkid) != 0) {
rcm_log_message(RCM_ERROR,
_("IP: Configuration failed (%u)\n"),
linkid);
ip_log_err(NULL, errorp,
"Failed configuring one or more IP "
"addresses");
}
/*
* Query libipadm for persistent configuration info
* and resurrect that persistent configuration.
*/
if (if_configure_ipadm(linkid) != 0) {
rcm_log_message(RCM_ERROR,
_("IP: Configuration failed (%u)\n"),
linkid);
ip_log_err(NULL, errorp,
"Failed configuring one or more IP "
"addresses");
}
/* Notify all IP address consumers */
ip_consumer_notify(hd, linkid, errorp, flags,
depend_info);
}
}
rcm_log_message(RCM_TRACE1,
"IP: notify_event: device configuration complete\n");
return (RCM_SUCCESS);
}
/*
* ip_usage - Determine the usage of a device. Call with cache_lock held.
* The returned buffer is owned by caller, and the caller
* must free it up when done.
*/
static char *
ip_usage(ip_cache_t *node)
{
ip_lif_t *lif;
uint_t numup;
char *sep, *buf, *linkidstr;
datalink_id_t linkid;
const char *msg;
char link[MAXLINKNAMELEN];
char addrstr[INET6_ADDRSTRLEN];
char errmsg[DLADM_STRSIZE];
dladm_status_t status;
boolean_t offline, ipmp;
size_t bufsz = 0;
rcm_log_message(RCM_TRACE2, "IP: usage(%s)\n", node->ip_resource);
/*
* Note that node->ip_resource is in the form of SUNW_datalink/<linkid>
*/
linkidstr = strchr(node->ip_resource, '/');
assert(linkidstr != NULL);
linkidstr = linkidstr ? linkidstr + 1 : node->ip_resource;
errno = 0;
linkid = strtol(linkidstr, &buf, 10);
if (errno != 0 || *buf != '\0') {
rcm_log_message(RCM_ERROR,
_("IP: usage(%s) parse linkid failure (%s)\n"),
node->ip_resource, strerror(errno));
return (NULL);
}
if ((status = dladm_datalink_id2info(dld_handle, linkid, NULL, NULL,
NULL, link, sizeof (link))) != DLADM_STATUS_OK) {
rcm_log_message(RCM_ERROR,
_("IP: usage(%s) get link name failure(%s)\n"),
node->ip_resource, dladm_status2str(status, errmsg));
return (NULL);
}
/* TRANSLATION_NOTE: separator used between IP addresses */
sep = _(", ");
numup = 0;
for (lif = node->ip_pif->pi_lifs; lif != NULL; lif = lif->li_next)
if (lif->li_ifflags & IFF_UP)
numup++;
ipmp = (node->ip_pif->pi_grname[0] != '\0');
offline = ((node->ip_cachestate & CACHE_IF_OFFLINED) != 0);
if (offline) {
msg = _("offlined");
} else if (numup == 0) {
msg = _("plumbed but down");
} else {
if (ipmp) {
msg = _("providing connectivity for IPMP group ");
bufsz += LIFGRNAMSIZ;
} else {
msg = _("hosts IP addresses: ");
bufsz += (numup * (INET6_ADDRSTRLEN + strlen(sep)));
}
}
bufsz += strlen(link) + strlen(msg) + 1;
if ((buf = malloc(bufsz)) == NULL) {
rcm_log_message(RCM_ERROR,
_("IP: usage(%s) malloc failure(%s)\n"),
node->ip_resource, strerror(errno));
return (NULL);
}
(void) snprintf(buf, bufsz, "%s: %s", link, msg);
if (!offline && numup > 0) {
if (ipmp) {
(void) strlcat(buf, node->ip_pif->pi_grname, bufsz);
} else {
lif = node->ip_pif->pi_lifs;
for (; lif != NULL; lif = lif->li_next) {
if (!(lif->li_ifflags & IFF_UP))
continue;
if (!ip_addrstr(lif, addrstr, sizeof (addrstr)))
continue;
(void) strlcat(buf, addrstr, bufsz);
if (--numup > 0)
(void) strlcat(buf, sep, bufsz);
}
}
}
rcm_log_message(RCM_TRACE2, "IP: usage (%s) info = %s\n",
node->ip_resource, buf);
return (buf);
}
static boolean_t
ip_addrstr(ip_lif_t *lif, char *addrstr, size_t addrsize)
{
int af = lif->li_addr.family;
void *addr;
if (af == AF_INET6) {
addr = &lif->li_addr.ip6.sin6_addr;
} else if (af == AF_INET) {
addr = &lif->li_addr.ip4.sin_addr;
} else {
rcm_log_message(RCM_DEBUG,
"IP: unknown addr family %d, assuming AF_INET\n", af);
af = AF_INET;
addr = &lif->li_addr.ip4.sin_addr;
}
if (inet_ntop(af, addr, addrstr, addrsize) == NULL) {
rcm_log_message(RCM_ERROR,
_("IP: inet_ntop: %s\n"), strerror(errno));
return (B_FALSE);
}
rcm_log_message(RCM_DEBUG, "IP addr := %s\n", addrstr);
return (B_TRUE);
}
/*
* Cache management routines, all cache management functions should be
* be called with cache_lock held.
*/
/*
* cache_lookup() - Get a cache node for a resource.
* Call with cache lock held.
*
* This ensures that the cache is consistent with the system state and
* returns a pointer to the cache element corresponding to the resource.
*/
static ip_cache_t *
cache_lookup(rcm_handle_t *hd, char *rsrc, char options)
{
ip_cache_t *probe;
rcm_log_message(RCM_TRACE2, "IP: cache lookup(%s)\n", rsrc);
if ((options & CACHE_REFRESH) && (hd != NULL)) {
/* drop lock since update locks cache again */
(void) mutex_unlock(&cache_lock);
(void) update_cache(hd);
(void) mutex_lock(&cache_lock);
}
probe = cache_head.ip_next;
while (probe != &cache_tail) {
if (probe->ip_resource &&
STREQ(rsrc, probe->ip_resource)) {
rcm_log_message(RCM_TRACE2,
"IP: cache lookup success(%s)\n", rsrc);
return (probe);
}
probe = probe->ip_next;
}
return (NULL);
}
/*
* free_node - Free a node from the cache
* Call with cache_lock held.
*/
static void
free_node(ip_cache_t *node)
{
ip_pif_t *pif;
ip_lif_t *lif, *tmplif;
if (node) {
if (node->ip_resource) {
free(node->ip_resource);
}
/* free the pif */
pif = node->ip_pif;
if (pif) {
/* free logical interfaces */
lif = pif->pi_lifs;
while (lif) {
tmplif = lif->li_next;
free(lif);
lif = tmplif;
}
free(pif);
}
free(node);
}
}
/*
* cache_insert - Insert a resource node in cache
* Call with the cache_lock held.
*/
static void
cache_insert(ip_cache_t *node)
{
rcm_log_message(RCM_TRACE2, "IP: cache insert(%s)\n",
node->ip_resource);
/* insert at the head for best performance */
node->ip_next = cache_head.ip_next;
node->ip_prev = &cache_head;
node->ip_next->ip_prev = node;
node->ip_prev->ip_next = node;
}
/*
* cache_remove() - Remove a resource node from cache.
* Call with the cache_lock held.
*/
static void
cache_remove(ip_cache_t *node)
{
rcm_log_message(RCM_TRACE2, "IP: cache remove(%s)\n",
node->ip_resource);
node->ip_next->ip_prev = node->ip_prev;
node->ip_prev->ip_next = node->ip_next;
node->ip_next = NULL;
node->ip_prev = NULL;
}
/*
* update_pif() - Update physical interface properties
* Call with cache_lock held
*/
int
update_pif(rcm_handle_t *hd, int af, int sock, struct ifaddrs *ifa)
{
char *rsrc;
ifspec_t ifspec;
ushort_t ifnumber = 0;
ip_cache_t *probe;
ip_pif_t pif;
ip_pif_t *probepif;
ip_lif_t *probelif;
struct lifreq lifreq;
struct sockaddr_storage ifaddr;
uint64_t ifflags;
int lif_listed = 0;
rcm_log_message(RCM_TRACE1, "IP: update_pif(%s)\n", ifa->ifa_name);
if (!ifparse_ifspec(ifa->ifa_name, &ifspec)) {
rcm_log_message(RCM_ERROR, _("IP: bad network interface: %s\n"),
ifa->ifa_name);
return (-1);
}
(void) snprintf(pif.pi_ifname, sizeof (pif.pi_ifname), "%s%d",
ifspec.ifsp_devnm, ifspec.ifsp_ppa);
if (ifspec.ifsp_lunvalid)
ifnumber = ifspec.ifsp_lun;
/* Get the interface flags */
ifflags = ifa->ifa_flags;
/*
* Ignore interfaces that are always incapable of DR:
* - IFF_VIRTUAL: e.g., loopback and vni
* - IFF_POINTOPOINT: e.g., sppp and ip.tun
* - !IFF_MULTICAST: e.g., ip.6to4tun
* - IFF_IPMP: IPMP meta-interfaces
*
* Note: The !IFF_MULTICAST check can be removed once iptun is
* implemented as a datalink.
*/
if (!(ifflags & IFF_MULTICAST) ||
(ifflags & (IFF_POINTOPOINT | IFF_VIRTUAL | IFF_IPMP))) {
rcm_log_message(RCM_TRACE3, "IP: if ignored (%s)\n",
pif.pi_ifname);
return (0);
}
/* Get the interface group name for this interface */
bzero(&lifreq, sizeof (lifreq));
(void) strncpy(lifreq.lifr_name, ifa->ifa_name, LIFNAMSIZ);
if (ioctl(sock, SIOCGLIFGROUPNAME, (char *)&lifreq) < 0) {
if (errno != ENXIO) {
rcm_log_message(RCM_ERROR,
_("IP: SIOCGLIFGROUPNAME(%s): %s\n"),
lifreq.lifr_name, strerror(errno));
}
return (-1);
}
/* copy the group name */
(void) strlcpy(pif.pi_grname, lifreq.lifr_groupname,
sizeof (pif.pi_grname));
/* Get the interface address for this interface */
(void) memcpy(&ifaddr, ifa->ifa_addr, sizeof (ifaddr));
rsrc = get_link_resource(pif.pi_ifname);
if (rsrc == NULL) {
rcm_log_message(RCM_ERROR,
_("IP: get_link_resource(%s) failed\n"),
lifreq.lifr_name);
return (-1);
}
probe = cache_lookup(hd, rsrc, CACHE_NO_REFRESH);
if (probe != NULL) {
free(rsrc);
probe->ip_cachestate &= ~(CACHE_IF_STALE);
} else {
if ((probe = calloc(1, sizeof (ip_cache_t))) == NULL) {
/* malloc errors are bad */
free(rsrc);
rcm_log_message(RCM_ERROR, _("IP: calloc: %s\n"),
strerror(errno));
return (-1);
}
probe->ip_resource = rsrc;
probe->ip_pif = NULL;
probe->ip_ifred = RCM_IPMP_MIN_REDUNDANCY;
probe->ip_cachestate |= CACHE_IF_NEW;
cache_insert(probe);
}
probepif = probe->ip_pif;
if (probepif != NULL) {
/* Check if lifs need to be updated */
probelif = probepif->pi_lifs;
while (probelif != NULL) {
if ((probelif->li_ifnum == ifnumber) &&
(probelif->li_addr.family == ifaddr.ss_family)) {
rcm_log_message(RCM_TRACE2,
"IP: refreshing lifs for %s, ifnum=%d\n",
pif.pi_ifname, probelif->li_ifnum);
/* refresh lif properties */
(void) memcpy(&probelif->li_addr, &ifaddr,
sizeof (probelif->li_addr));
probelif->li_ifflags = ifflags;
lif_listed++;
probelif->li_cachestate &= ~(CACHE_IF_STALE);
break;
}
probelif = probelif->li_next;
}
}
if (probepif == NULL) {
if ((probepif = calloc(1, sizeof (ip_pif_t))) == NULL) {
rcm_log_message(RCM_ERROR, _("IP: malloc: %s\n"),
strerror(errno));
if (probe->ip_pif == NULL) {
/* we created it, so clean it up */
free(probe);
}
return (-1);
}
probe->ip_pif = probepif;
/* Save interface name */
(void) memcpy(&probepif->pi_ifname, &pif.pi_ifname,
sizeof (pif.pi_ifname));
}
/* save the group name */
(void) strlcpy(probepif->pi_grname, pif.pi_grname,
sizeof (pif.pi_grname));
/* add lif, if this is a lif and it is not in cache */
if (!lif_listed) {
rcm_log_message(RCM_TRACE2, "IP: adding lifs to %s\n",
pif.pi_ifname);
if ((probelif = calloc(1, sizeof (ip_lif_t))) == NULL) {
rcm_log_message(RCM_ERROR, _("IP: malloc: %s\n"),
strerror(errno));
return (-1);
}
/* save lif properties */
(void) memcpy(&probelif->li_addr, &ifaddr,
sizeof (probelif->li_addr));
probelif->li_ifnum = ifnumber;
probelif->li_ifflags = ifflags;
/* insert us at the head of the lif list */
probelif->li_next = probepif->pi_lifs;
if (probelif->li_next != NULL) {
probelif->li_next->li_prev = probelif;
}
probelif->li_prev = NULL;
probelif->li_pif = probepif;
probepif->pi_lifs = probelif;
}
rcm_log_message(RCM_TRACE3, "IP: update_pif: (%s) success\n",
probe->ip_resource);
return (0);
}
/*
* update_ipifs() - Determine all network interfaces in the system
* Call with cache_lock held
*/
static int
update_ipifs(rcm_handle_t *hd, int af)
{
struct ifaddrs *ifa;
ipadm_addr_info_t *ainfo;
ipadm_addr_info_t *ptr;
ipadm_status_t status;
int sock;
if ((sock = socket(af, SOCK_DGRAM, 0)) == -1) {
rcm_log_message(RCM_ERROR,
_("IP: failure opening %s socket: %s\n"),
af == AF_INET6 ? "IPv6" : "IPv4", strerror(errno));
return (-1);
}
status = ipadm_addr_info(ip_handle, NULL, &ainfo, IPADM_OPT_ZEROADDR,
LIFC_UNDER_IPMP);
if (status != IPADM_SUCCESS) {
(void) close(sock);
return (-1);
}
for (ptr = ainfo; ptr; ptr = IA_NEXT(ptr)) {
ifa = &ptr->ia_ifa;
if (ptr->ia_state != IFA_DISABLED &&
af == ifa->ifa_addr->sa_family)
(void) update_pif(hd, af, sock, ifa);
}
(void) close(sock);
ipadm_free_addr_info(ainfo);
return (0);
}
/*
* update_cache() - Update cache with latest interface info
*/
static int
update_cache(rcm_handle_t *hd)
{
ip_cache_t *probe;
struct ip_lif *lif;
struct ip_lif *nextlif;
int rv;
int i;
rcm_log_message(RCM_TRACE2, "IP: update_cache\n");
(void) mutex_lock(&cache_lock);
/* first we walk the entire cache, marking each entry stale */
probe = cache_head.ip_next;
while (probe != &cache_tail) {
probe->ip_cachestate |= CACHE_IF_STALE;
if ((probe->ip_pif != NULL) &&
((lif = probe->ip_pif->pi_lifs) != NULL)) {
while (lif != NULL) {
lif->li_cachestate |= CACHE_IF_STALE;
lif = lif->li_next;
}
}
probe = probe->ip_next;
}
rcm_log_message(RCM_TRACE2, "IP: scanning IPv4 interfaces\n");
if (update_ipifs(hd, AF_INET) < 0) {
(void) mutex_unlock(&cache_lock);
return (-1);
}
rcm_log_message(RCM_TRACE2, "IP: scanning IPv6 interfaces\n");
if (update_ipifs(hd, AF_INET6) < 0) {
(void) mutex_unlock(&cache_lock);
return (-1);
}
probe = cache_head.ip_next;
/* unregister devices that are not offlined and still in cache */
while (probe != &cache_tail) {
ip_cache_t *freeit;
if ((probe->ip_pif != NULL) &&
((lif = probe->ip_pif->pi_lifs) != NULL)) {
/* clear stale lifs */
while (lif != NULL) {
if (lif->li_cachestate & CACHE_IF_STALE) {
nextlif = lif->li_next;
if (lif->li_prev != NULL)
lif->li_prev->li_next = nextlif;
if (nextlif != NULL)
nextlif->li_prev = lif->li_prev;
if (probe->ip_pif->pi_lifs == lif)
probe->ip_pif->pi_lifs =
nextlif;
for (i = 0; i < IP_MAX_MODS; i++) {
free(lif->li_modules[i]);
}
free(lif->li_reconfig);
free(lif);
lif = nextlif;
} else {
lif = lif->li_next;
}
}
}
if ((probe->ip_cachestate & CACHE_IF_STALE) &&
!(probe->ip_cachestate & CACHE_IF_OFFLINED)) {
(void) rcm_unregister_interest(hd, probe->ip_resource,
0);
rcm_log_message(RCM_DEBUG, "IP: unregistered %s\n",
probe->ip_resource);
freeit = probe;
probe = probe->ip_next;
cache_remove(freeit);
free_node(freeit);
continue;
}
if (!(probe->ip_cachestate & CACHE_IF_NEW)) {
probe = probe->ip_next;
continue;
}
rv = rcm_register_interest(hd, probe->ip_resource, 0, NULL);
if (rv != RCM_SUCCESS) {
rcm_log_message(RCM_ERROR,
_("IP: failed to register %s\n"),
probe->ip_resource);
(void) mutex_unlock(&cache_lock);
return (-1);
} else {
rcm_log_message(RCM_DEBUG, "IP: registered %s\n",
probe->ip_resource);
probe->ip_cachestate &= ~(CACHE_IF_NEW);
}
probe = probe->ip_next;
}
(void) mutex_unlock(&cache_lock);
return (0);
}
/*
* free_cache() - Empty the cache
*/
static void
free_cache()
{
ip_cache_t *probe;
rcm_log_message(RCM_TRACE2, "IP: free_cache\n");
(void) mutex_lock(&cache_lock);
probe = cache_head.ip_next;
while (probe != &cache_tail) {
cache_remove(probe);
free_node(probe);
probe = cache_head.ip_next;
}
(void) mutex_unlock(&cache_lock);
}
/*
* ip_log_err() - RCM error log wrapper
*/
static void
ip_log_err(ip_cache_t *node, char **errorp, char *errmsg)
{
char *ifname = NULL;
int size;
const char *errfmt;
char *error = NULL;
if ((node != NULL) && (node->ip_pif != NULL) &&
(node->ip_pif->pi_ifname != NULL)) {
ifname = node->ip_pif->pi_ifname;
}
if (ifname == NULL) {
rcm_log_message(RCM_ERROR, _("IP: %s\n"), errmsg);
errfmt = _("IP: %s");
size = strlen(errfmt) + strlen(errmsg) + 1;
if (errorp != NULL && (error = malloc(size)) != NULL)
(void) snprintf(error, size, errfmt, errmsg);
} else {
rcm_log_message(RCM_ERROR, _("IP: %s(%s)\n"), errmsg, ifname);
errfmt = _("IP: %s(%s)");
size = strlen(errfmt) + strlen(errmsg) + strlen(ifname) + 1;
if (errorp != NULL && (error = malloc(size)) != NULL)
(void) snprintf(error, size, errfmt, errmsg, ifname);
}
if (errorp != NULL)
*errorp = error;
}
/*
* if_cfginfo() - Save off the config info for all interfaces
*/
static int
if_cfginfo(ip_cache_t *node, uint_t force)
{
ip_lif_t *lif;
ip_pif_t *pif;
int i;
FILE *fp;
char syscmd[MAX_RECONFIG_SIZE + LIFNAMSIZ];
char buf[MAX_RECONFIG_SIZE];
rcm_log_message(RCM_TRACE2, "IP: if_cfginfo(%s)\n", node->ip_resource);
pif = node->ip_pif;
lif = pif->pi_lifs;
while (lif != NULL) {
/* Make a list of modules pushed and save */
if (lif->li_ifnum == 0) { /* physical instance */
if (get_modlist(pif->pi_ifname, lif) == -1) {
rcm_log_message(RCM_ERROR,
_("IP: get modlist error (%s) %s\n"),
pif->pi_ifname, strerror(errno));
clr_cfg_state(pif);
return (-1);
}
if (!force) {
/* Look if unknown modules have been inserted */
for (i = (lif->li_modcnt - 2); i > 0; i--) {
if (modop(pif->pi_ifname,
lif->li_modules[i],
i, MOD_CHECK) == -1) {
rcm_log_message(RCM_ERROR,
_("IP: module %s@%d\n"),
lif->li_modules[i], i);
clr_cfg_state(pif);
return (-1);
}
}
}
/* Last module is the device driver, so ignore that */
for (i = (lif->li_modcnt - 2); i > 0; i--) {
rcm_log_message(RCM_TRACE2,
"IP: modremove Pos = %d, Module = %s \n",
i, lif->li_modules[i]);
if (modop(pif->pi_ifname, lif->li_modules[i],
i, MOD_REMOVE) == -1) {
while (i != (lif->li_modcnt - 2)) {
if (modop(pif->pi_ifname,
lif->li_modules[i],
i, MOD_INSERT) == -1) {
/* Gross error */
rcm_log_message(
RCM_ERROR,
_("IP: if_cfginfo"
"(%s) %s\n"),
pif->pi_ifname,
strerror(errno));
clr_cfg_state(pif);
return (-1);
}
i++;
}
rcm_log_message(
RCM_ERROR,
_("IP: if_cfginfo(%s): modremove "
"%s failed: %s\n"), pif->pi_ifname,
lif->li_modules[i],
strerror(errno));
clr_cfg_state(pif);
return (-1);
}
}
}
/* Save reconfiguration information */
if (lif->li_ifflags & IFF_IPV4) {
(void) snprintf(syscmd, sizeof (syscmd),
"%s %s:%d configinfo\n", SBIN_IFCONFIG,
pif->pi_ifname, lif->li_ifnum);
} else if (lif->li_ifflags & IFF_IPV6) {
(void) snprintf(syscmd, sizeof (syscmd),
"%s %s:%d inet6 configinfo\n", SBIN_IFCONFIG,
pif->pi_ifname, lif->li_ifnum);
}
rcm_log_message(RCM_TRACE2, "IP: %s\n", syscmd);
/* open a pipe to retrieve reconfiguration info */
if ((fp = popen(syscmd, "r")) == NULL) {
rcm_log_message(RCM_ERROR,
_("IP: ifconfig configinfo error (%s:%d) %s\n"),
pif->pi_ifname, lif->li_ifnum, strerror(errno));
clr_cfg_state(pif);
return (-1);
}
bzero(buf, MAX_RECONFIG_SIZE);
if (fgets(buf, MAX_RECONFIG_SIZE, fp) == NULL) {
rcm_log_message(RCM_ERROR,
_("IP: ifconfig configinfo error (%s:%d) %s\n"),
pif->pi_ifname, lif->li_ifnum, strerror(errno));
(void) pclose(fp);
clr_cfg_state(pif);
return (-1);
}
(void) pclose(fp);
if ((lif->li_reconfig = strdup(buf)) == NULL) {
rcm_log_message(RCM_ERROR,
_("IP: malloc error (%s) %s\n"),
pif->pi_ifname, strerror(errno));
clr_cfg_state(pif);
return (-1);
}
rcm_log_message(RCM_DEBUG,
"IP: if_cfginfo: reconfig string(%s:%d) = %s\n",
pif->pi_ifname, lif->li_ifnum, lif->li_reconfig);
lif = lif->li_next;
}
return (0);
}
/*
* if_unplumb() - Unplumb the interface
* Save off the modlist, ifconfig options and unplumb.
* Fail, if an unknown module lives between IP and driver and
* force is not set
* Call with cache_lock held
*/
static int
if_unplumb(ip_cache_t *node)
{
ip_lif_t *lif;
ip_pif_t *pif = node->ip_pif;
boolean_t ipv4 = B_FALSE;
boolean_t ipv6 = B_FALSE;
rcm_log_message(RCM_TRACE2, "IP: if_unplumb(%s)\n", node->ip_resource);
for (lif = pif->pi_lifs; lif != NULL; lif = lif->li_next) {
if (lif->li_ifflags & IFF_IPV4) {
ipv4 = B_TRUE;
} else if (lif->li_ifflags & IFF_IPV6) {
ipv6 = B_TRUE;
} else {
/* Unlikely case */
rcm_log_message(RCM_DEBUG,
"IP: Unplumb ignored (%s:%d)\n",
pif->pi_ifname, lif->li_ifnum);
}
}
if (ipv4 && !ifconfig(pif->pi_ifname, "inet", "unplumb", B_FALSE)) {
rcm_log_message(RCM_ERROR, _("IP: Cannot unplumb (%s) %s\n"),
pif->pi_ifname, strerror(errno));
return (-1);
}
if (ipv6 && !ifconfig(pif->pi_ifname, "inet6", "unplumb", B_FALSE)) {
rcm_log_message(RCM_ERROR, _("IP: Cannot unplumb (%s) %s\n"),
pif->pi_ifname, strerror(errno));
return (-1);
}
rcm_log_message(RCM_TRACE2, "IP: if_unplumb(%s) success\n",
node->ip_resource);
return (0);
}
/*
* if_replumb() - Undo previous unplumb i.e. plumb back the physical interface
* instances and the logical interfaces in order, restoring
* all ifconfig options
* Call with cache_lock held
*/
static int
if_replumb(ip_cache_t *node)
{
ip_lif_t *lif;
ip_pif_t *pif;
int i;
boolean_t success, ipmp;
const char *fstr;
char lifname[LIFNAMSIZ];
char buf[MAX_RECONFIG_SIZE];
int max_lifnum = 0;
rcm_log_message(RCM_TRACE2, "IP: if_replumb(%s)\n", node->ip_resource);
/*
* Be extra careful about bringing up the interfaces in the
* correct order:
* - First plumb in the physical interface instances
* - modinsert the necessary modules@pos
* - Next, add the logical interfaces being careful about
* the order, (follow the cached interface number li_ifnum order)
*/
pif = node->ip_pif;
ipmp = (node->ip_pif->pi_grname[0] != '\0');
/*
* Make a first pass to plumb in physical interfaces and get a count
* of the max logical interfaces
*/
for (lif = pif->pi_lifs; lif != NULL; lif = lif->li_next) {
max_lifnum = MAX(lif->li_ifnum, max_lifnum);
if (lif->li_ifflags & IFF_IPV4) {
fstr = "inet";
} else if (lif->li_ifflags & IFF_IPV6) {
fstr = "inet6";
} else {
/* Unlikely case */
rcm_log_message(RCM_DEBUG,
"IP: Re-plumb ignored (%s:%d)\n",
pif->pi_ifname, lif->li_ifnum);
continue;
}
/* ignore logical interface instances */
if (lif->li_ifnum != 0)
continue;
if ((lif->li_ifflags & IFF_NOFAILOVER) || !ipmp) {
success = ifconfig("", "", lif->li_reconfig, B_FALSE);
} else {
(void) snprintf(buf, sizeof (buf), "plumb group %s",
pif->pi_grname);
success = ifconfig(pif->pi_ifname, fstr, buf, B_FALSE);
}
if (!success) {
rcm_log_message(RCM_ERROR,
_("IP: Cannot plumb (%s) %s\n"), pif->pi_ifname,
strerror(errno));
return (-1);
}
/*
* Restart DHCP if necessary.
*/
if ((lif->li_ifflags & IFF_DHCPRUNNING) &&
!ifconfig(pif->pi_ifname, fstr, CFG_DHCP_CMD, B_FALSE)) {
rcm_log_message(RCM_ERROR, _("IP: Cannot start DHCP "
"(%s) %s\n"), pif->pi_ifname, strerror(errno));
return (-1);
}
rcm_log_message(RCM_TRACE2,
"IP: if_replumb: Modcnt = %d\n", lif->li_modcnt);
/* modinsert modules in order, ignore driver(last) */
for (i = 0; i < (lif->li_modcnt - 1); i++) {
rcm_log_message(RCM_TRACE2,
"IP: modinsert: Pos = %d Mod = %s\n",
i, lif->li_modules[i]);
if (modop(pif->pi_ifname, lif->li_modules[i], i,
MOD_INSERT) == -1) {
rcm_log_message(RCM_ERROR,
_("IP: modinsert error(%s)\n"),
pif->pi_ifname);
return (-1);
}
}
}
/* Now, add all the logical interfaces in the correct order */
for (i = 1; i <= max_lifnum; i++) {
(void) snprintf(lifname, LIFNAMSIZ, "%s:%d", pif->pi_ifname, i);
/* reset lif through every iteration */
for (lif = pif->pi_lifs; lif != NULL; lif = lif->li_next) {
/*
* Process entries in order. If the interface is
* using IPMP, only process test addresses.
*/
if (lif->li_ifnum != i ||
(ipmp && !(lif->li_ifflags & IFF_NOFAILOVER)))
continue;
if (!ifconfig("", "", lif->li_reconfig, B_FALSE)) {
rcm_log_message(RCM_ERROR,
_("IP: Cannot addif (%s) %s\n"), lifname,
strerror(errno));
return (-1);
}
/*
* Restart DHCP if necessary.
*/
if ((lif->li_ifflags & IFF_DHCPRUNNING) &&
!ifconfig(lifname, fstr, CFG_DHCP_CMD, B_FALSE)) {
rcm_log_message(RCM_ERROR,
_("IP: Cannot start DHCP (%s) %s\n"),
lifname, strerror(errno));
return (-1);
}
}
}
rcm_log_message(RCM_TRACE2, "IP: if_replumb(%s) success \n",
node->ip_resource);
return (0);
}
/*
* clr_cfg_state() - Cleanup after errors in unplumb
*/
static void
clr_cfg_state(ip_pif_t *pif)
{
ip_lif_t *lif;
int i;
lif = pif->pi_lifs;
while (lif != NULL) {
lif->li_modcnt = 0;
free(lif->li_reconfig);
lif->li_reconfig = NULL;
for (i = 0; i < IP_MAX_MODS; i++) {
free(lif->li_modules[i]);
lif->li_modules[i] = NULL;
}
lif = lif->li_next;
}
}
/*
* Attempt to offline ip_cache_t `node'; returns an IPMP error code.
*/
static int
ip_ipmp_offline(ip_cache_t *node)
{
int retval;
ipmp_handle_t handle;
rcm_log_message(RCM_TRACE1, "IP: ip_ipmp_offline\n");
if ((retval = ipmp_open(&handle)) != IPMP_SUCCESS) {
rcm_log_message(RCM_ERROR,
_("IP: cannot create ipmp handle: %s\n"),
ipmp_errmsg(retval));
return (retval);
}
retval = ipmp_offline(handle, node->ip_pif->pi_ifname, node->ip_ifred);
if (retval != IPMP_SUCCESS) {
rcm_log_message(RCM_ERROR, _("IP: ipmp_offline error: %s\n"),
ipmp_errmsg(retval));
} else {
rcm_log_message(RCM_TRACE1, "IP: ipmp_offline success\n");
}
ipmp_close(handle);
return (retval);
}
/*
* Attempt to undo the offline ip_cache_t `node'; returns an IPMP error code.
*/
static int
ip_ipmp_undo_offline(ip_cache_t *node)
{
int retval;
ipmp_handle_t handle;
rcm_log_message(RCM_TRACE1, "IP: ip_ipmp_undo_offline\n");
if ((retval = ipmp_open(&handle)) != IPMP_SUCCESS) {
rcm_log_message(RCM_ERROR,
_("IP: cannot create ipmp handle: %s\n"),
ipmp_errmsg(retval));
return (retval);
}
retval = ipmp_undo_offline(handle, node->ip_pif->pi_ifname);
if (retval != IPMP_SUCCESS) {
rcm_log_message(RCM_ERROR,
_("IP: ipmp_undo_offline error: %s\n"),
ipmp_errmsg(retval));
} else {
rcm_log_message(RCM_TRACE1, "IP: ipmp_undo_offline success\n");
}
ipmp_close(handle);
return (retval);
}
/*
* get_link_resource() - Convert a link name (e.g., net0, hme1000) into a
* dynamically allocated string containing the associated link resource
* name ("SUNW_datalink/<linkid>").
*/
static char *
get_link_resource(const char *link)
{
char errmsg[DLADM_STRSIZE];
datalink_id_t linkid;
uint32_t flags;
char *resource;
dladm_status_t status;
status = dladm_name2info(dld_handle, link, &linkid, &flags, NULL, NULL);
if (status != DLADM_STATUS_OK)
goto fail;
if (!(flags & DLADM_OPT_ACTIVE)) {
status = DLADM_STATUS_FAILED;
goto fail;
}
resource = malloc(RCM_LINK_RESOURCE_MAX);
if (resource == NULL) {
rcm_log_message(RCM_ERROR, _("IP: malloc error(%s): %s\n"),
strerror(errno), link);
return (NULL);
}
(void) snprintf(resource, RCM_LINK_RESOURCE_MAX, "%s/%u",
RCM_LINK_PREFIX, linkid);
return (resource);
fail:
rcm_log_message(RCM_ERROR,
_("IP: get_link_resource for %s error(%s)\n"),
link, dladm_status2str(status, errmsg));
return (NULL);
}
/*
* modop() - Remove/insert a module
*/
static int
modop(char *name, char *arg, int pos, char op)
{
char syscmd[LIFNAMSIZ+MAXPATHLEN]; /* must be big enough */
rcm_log_message(RCM_TRACE1, "IP: modop(%s)\n", name);
/* Nothing to do with "ip", "arp" */
if ((arg == NULL) || (strcmp(arg, "") == 0) ||
STREQ(arg, IP_MOD_NAME) || STREQ(arg, ARP_MOD_NAME)) {
rcm_log_message(RCM_TRACE1, "IP: modop success\n");
return (0);
}
if (op == MOD_CHECK) {
/*
* No known good modules (yet) apart from ip and arp
* which are handled above
*/
return (-1);
}
if (op == MOD_REMOVE) {
(void) snprintf(syscmd, sizeof (syscmd),
"%s %s modremove %s@%d\n", SBIN_IFCONFIG, name, arg, pos);
} else if (op == MOD_INSERT) {
(void) snprintf(syscmd, sizeof (syscmd),
"%s %s modinsert %s@%d\n", SBIN_IFCONFIG, name, arg, pos);
} else {
rcm_log_message(RCM_ERROR,
_("IP: modop(%s): unknown operation\n"), name);
return (-1);
}
rcm_log_message(RCM_TRACE1, "IP: modop(%s): %s\n", name, syscmd);
if (rcm_exec_cmd(syscmd) == -1) {
rcm_log_message(RCM_ERROR,
_("IP: modop(%s): %s\n"), name, strerror(errno));
return (-1);
}
rcm_log_message(RCM_TRACE1, "IP: modop success\n");
return (0);
}
/*
* get_modlist() - return a list of pushed mid-stream modules
* Required memory is malloced to construct the list,
* Caller must free this memory list
* Call with cache_lock held
*/
static int
get_modlist(char *name, ip_lif_t *lif)
{
int mux_fd;
int muxid_fd;
int fd;
int i;
int num_mods;
struct lifreq lifr;
struct str_list strlist = { 0 };
rcm_log_message(RCM_TRACE1, "IP: getmodlist(%s)\n", name);
(void) strlcpy(lifr.lifr_name, name, sizeof (lifr.lifr_name));
lifr.lifr_flags = lif->li_ifflags;
if (ip_domux2fd(&mux_fd, &muxid_fd, &fd, &lifr) < 0) {
rcm_log_message(RCM_ERROR, _("IP: ip_domux2fd(%s)\n"), name);
return (-1);
}
if ((num_mods = ioctl(fd, I_LIST, NULL)) < 0) {
rcm_log_message(RCM_ERROR,
_("IP: get_modlist(%s): I_LIST(%s) \n"),
name, strerror(errno));
goto fail;
}
strlist.sl_nmods = num_mods;
strlist.sl_modlist = malloc(sizeof (struct str_mlist) * num_mods);
if (strlist.sl_modlist == NULL) {
rcm_log_message(RCM_ERROR, _("IP: get_modlist(%s): %s\n"),
name, strerror(errno));
goto fail;
}
if (ioctl(fd, I_LIST, (caddr_t)&strlist) < 0) {
rcm_log_message(RCM_ERROR,
_("IP: get_modlist(%s): I_LIST error: %s\n"),
name, strerror(errno));
goto fail;
}
for (i = 0; i < strlist.sl_nmods; i++) {
lif->li_modules[i] = strdup(strlist.sl_modlist[i].l_name);
if (lif->li_modules[i] == NULL) {
rcm_log_message(RCM_ERROR,
_("IP: get_modlist(%s): %s\n"),
name, strerror(errno));
while (i > 0)
free(lif->li_modules[--i]);
goto fail;
}
}
lif->li_modcnt = strlist.sl_nmods;
free(strlist.sl_modlist);
rcm_log_message(RCM_TRACE1, "IP: getmodlist(%s) success\n", name);
return (ip_plink(mux_fd, muxid_fd, fd, &lifr));
fail:
free(strlist.sl_modlist);
(void) ip_plink(mux_fd, muxid_fd, fd, &lifr);
return (-1);
}
/*
* ip_domux2fd() - Helper function for mod*() functions
* Stolen from ifconfig.c
*/
static int
ip_domux2fd(int *mux_fd, int *muxid_fdp, int *fd, struct lifreq *lifr)
{
int muxid_fd;
char *udp_dev_name;
if (lifr->lifr_flags & IFF_IPV6) {
udp_dev_name = UDP6_DEV_NAME;
} else {
udp_dev_name = UDP_DEV_NAME;
}
if ((muxid_fd = open(udp_dev_name, O_RDWR)) < 0) {
rcm_log_message(RCM_ERROR, _("IP: ip_domux2fd: open(%s) %s\n"),
udp_dev_name, strerror(errno));
return (-1);
}
if ((*mux_fd = open(udp_dev_name, O_RDWR)) < 0) {
rcm_log_message(RCM_ERROR, _("IP: ip_domux2fd: open(%s) %s\n"),
udp_dev_name, strerror(errno));
(void) close(muxid_fd);
return (-1);
}
if (ioctl(muxid_fd, SIOCGLIFMUXID, (caddr_t)lifr) < 0) {
rcm_log_message(RCM_ERROR,
_("IP: ip_domux2fd: SIOCGLIFMUXID(%s): %s\n"),
udp_dev_name, strerror(errno));
(void) close(*mux_fd);
(void) close(muxid_fd);
return (-1);
}
rcm_log_message(RCM_TRACE2,
"IP: ip_domux2fd: ARP_muxid %d IP_muxid %d\n",
lifr->lifr_arp_muxid, lifr->lifr_ip_muxid);
if ((*fd = ioctl(*mux_fd, _I_MUXID2FD, lifr->lifr_ip_muxid)) < 0) {
rcm_log_message(RCM_ERROR,
_("IP: ip_domux2fd: _I_MUXID2FD(%s): %s\n"),
udp_dev_name, strerror(errno));
(void) close(*mux_fd);
(void) close(muxid_fd);
return (-1);
}
if (ioctl(*mux_fd, I_PUNLINK, lifr->lifr_ip_muxid) < 0) {
rcm_log_message(RCM_ERROR,
_("IP: ip_domux2fd: I_PUNLINK(%s): %s\n"),
udp_dev_name, strerror(errno));
(void) close(*mux_fd);
(void) close(muxid_fd);
return (-1);
}
/* Note: mux_fd and muxid_fd are closed in ip_plink below */
*muxid_fdp = muxid_fd;
return (0);
}
/*
* ip_plink() - Helper function for mod*() functions.
* Stolen from ifconfig.c
*/
static int
ip_plink(int mux_fd, int muxid_fd, int fd, struct lifreq *lifr)
{
int mux_id;
if ((mux_id = ioctl(mux_fd, I_PLINK, fd)) < 0) {
rcm_log_message(RCM_ERROR, _("IP: ip_plink I_PLINK(%s): %s\n"),
UDP_DEV_NAME, strerror(errno));
(void) close(mux_fd);
(void) close(muxid_fd);
(void) close(fd);
return (-1);
}
lifr->lifr_ip_muxid = mux_id;
if (ioctl(muxid_fd, SIOCSLIFMUXID, (caddr_t)lifr) < 0) {
rcm_log_message(RCM_ERROR,
_("IP: ip_plink SIOCSLIFMUXID(%s): %s\n"),
UDP_DEV_NAME, strerror(errno));
(void) close(mux_fd);
(void) close(muxid_fd);
(void) close(fd);
return (-1);
}
(void) close(mux_fd);
(void) close(muxid_fd);
(void) close(fd);
return (0);
}
/*
* ip_onlinelist()
*
* Notify online to IP address consumers.
*/
/*ARGSUSED*/
static int
ip_onlinelist(rcm_handle_t *hd, ip_cache_t *node, char **errorp, uint_t flags,
rcm_info_t **depend_info)
{
char **addrlist;
int ret = RCM_SUCCESS;
rcm_log_message(RCM_TRACE2, "IP: ip_onlinelist\n");
addrlist = ip_get_addrlist(node);
if (addrlist == NULL || addrlist[0] == NULL) {
rcm_log_message(RCM_TRACE2, "IP: ip_onlinelist none\n");
ip_free_addrlist(addrlist);
return (ret);
}
ret = rcm_notify_online_list(hd, addrlist, 0, depend_info);
ip_free_addrlist(addrlist);
rcm_log_message(RCM_TRACE2, "IP: ip_onlinelist done\n");
return (ret);
}
/*
* ip_offlinelist()
*
* Offline IP address consumers.
*/
/*ARGSUSED*/
static int
ip_offlinelist(rcm_handle_t *hd, ip_cache_t *node, char **errorp, uint_t flags,
rcm_info_t **depend_info)
{
char **addrlist;
int ret = RCM_SUCCESS;
rcm_log_message(RCM_TRACE2, "IP: ip_offlinelist\n");
addrlist = ip_get_addrlist(node);
if (addrlist == NULL || addrlist[0] == NULL) {
rcm_log_message(RCM_TRACE2, "IP: ip_offlinelist none\n");
ip_free_addrlist(addrlist);
return (RCM_SUCCESS);
}
if ((ret = rcm_request_offline_list(hd, addrlist, flags, depend_info))
!= RCM_SUCCESS) {
if (ret == RCM_FAILURE)
(void) rcm_notify_online_list(hd, addrlist, 0, NULL);
ret = RCM_FAILURE;
}
ip_free_addrlist(addrlist);
rcm_log_message(RCM_TRACE2, "IP: ip_offlinelist done\n");
return (ret);
}
/*
* ip_get_addrlist() - Get the list of IP addresses on this interface (node);
* This routine malloc()s required memory for the list.
* Returns the list on success, NULL on failure.
* Call with cache_lock held.
*/
static char **
ip_get_addrlist(ip_cache_t *node)
{
ip_lif_t *lif;
char **addrlist = NULL;
int i, numifs;
size_t addrlistsize;
char addrstr[INET6_ADDRSTRLEN];
rcm_log_message(RCM_TRACE2, "IP: ip_get_addrlist(%s)\n",
node->ip_resource);
numifs = 0;
for (lif = node->ip_pif->pi_lifs; lif != NULL; lif = lif->li_next) {
numifs++;
}
/*
* Allocate space for resource names list; add 1 and use calloc()
* so that the list is NULL-terminated.
*/
if ((addrlist = calloc(numifs + 1, sizeof (char *))) == NULL) {
rcm_log_message(RCM_ERROR,
_("IP: ip_get_addrlist(%s) malloc failure(%s)\n"),
node->ip_resource, strerror(errno));
return (NULL);
}
for (lif = node->ip_pif->pi_lifs, i = 0; lif != NULL;
lif = lif->li_next, i++) {
if (!ip_addrstr(lif, addrstr, sizeof (addrstr))) {
ip_free_addrlist(addrlist);
return (NULL);
}
addrlistsize = strlen(addrstr) + sizeof (RCM_STR_SUNW_IP);
if ((addrlist[i] = malloc(addrlistsize)) == NULL) {
rcm_log_message(RCM_ERROR,
_("IP: ip_get_addrlist(%s) malloc failure(%s)\n"),
node->ip_resource, strerror(errno));
ip_free_addrlist(addrlist);
return (NULL);
}
(void) snprintf(addrlist[i], addrlistsize, "%s%s",
RCM_STR_SUNW_IP, addrstr);
rcm_log_message(RCM_DEBUG, "Anon Address: %s\n", addrlist[i]);
}
rcm_log_message(RCM_TRACE2, "IP: get_addrlist (%s) done\n",
node->ip_resource);
return (addrlist);
}
static void
ip_free_addrlist(char **addrlist)
{
int i;
if (addrlist == NULL)
return;
for (i = 0; addrlist[i] != NULL; i++)
free(addrlist[i]);
free(addrlist);
}
/*
* ip_consumer_notify() - Notify consumers of IP addresses coming back online.
*/
static void
ip_consumer_notify(rcm_handle_t *hd, datalink_id_t linkid, char **errorp,
uint_t flags, rcm_info_t **depend_info)
{
char cached_name[RCM_LINK_RESOURCE_MAX];
ip_cache_t *node;
assert(linkid != DATALINK_INVALID_LINKID);
rcm_log_message(RCM_TRACE1, _("IP: ip_consumer_notify(%u)\n"), linkid);
/* Check for the interface in the cache */
(void) snprintf(cached_name, sizeof (cached_name), "%s/%u",
RCM_LINK_PREFIX, linkid);
(void) mutex_lock(&cache_lock);
if ((node = cache_lookup(hd, cached_name, CACHE_REFRESH)) == NULL) {
rcm_log_message(RCM_TRACE1, _("IP: Skipping interface(%u)\n"),
linkid);
(void) mutex_unlock(&cache_lock);
return;
}
/*
* Inform anonymous consumers about IP addresses being onlined.
*/
(void) ip_onlinelist(hd, node, errorp, flags, depend_info);
(void) mutex_unlock(&cache_lock);
rcm_log_message(RCM_TRACE2, "IP: ip_consumer_notify success\n");
}
/*
* Gets the interface name for the given linkid. Returns -1 if there is
* any error. It fills in the interface name in `ifinst' if the interface
* is not already configured. Otherwise, it puts a null string in `ifinst'.
*/
static int
if_configure_get_linkid(datalink_id_t linkid, char *ifinst, size_t len)
{
char cached_name[RCM_LINK_RESOURCE_MAX];
ip_cache_t *node;
/* Check for the interface in the cache */
(void) snprintf(cached_name, sizeof (cached_name), "%s/%u",
RCM_LINK_PREFIX, linkid);
/* Check if the interface is new or was not previously offlined */
(void) mutex_lock(&cache_lock);
if (((node = cache_lookup(NULL, cached_name, CACHE_REFRESH)) != NULL) &&
(!(node->ip_cachestate & CACHE_IF_OFFLINED))) {
rcm_log_message(RCM_TRACE1,
_("IP: Skipping configured interface(%u)\n"), linkid);
(void) mutex_unlock(&cache_lock);
*ifinst = '\0';
return (0);
}
(void) mutex_unlock(&cache_lock);
if (dladm_datalink_id2info(dld_handle, linkid, NULL, NULL, NULL, ifinst,
len) != DLADM_STATUS_OK) {
rcm_log_message(RCM_ERROR,
_("IP: get %u link name failed\n"), linkid);
return (-1);
}
return (0);
}
/*
* if_configure_hostname() - Configure a physical interface after attach
* based on the information in /etc/hostname.*
*/
static int
if_configure_hostname(datalink_id_t linkid)
{
FILE *hostfp, *host6fp;
boolean_t ipmp = B_FALSE;
char ifinst[MAXLINKNAMELEN];
char cfgfile[MAXPATHLEN];
assert(linkid != DATALINK_INVALID_LINKID);
rcm_log_message(RCM_TRACE1, _("IP: if_configure_hostname(%u)\n"),
linkid);
if (if_configure_get_linkid(linkid, ifinst, sizeof (ifinst)) != 0)
return (-1);
/* Check if the interface is already configured. */
if (ifinst[0] == '\0')
return (0);
/*
* Scan the IPv4 and IPv6 hostname files to see if (a) they exist
* and (b) if either one places the interface into an IPMP group.
*/
(void) snprintf(cfgfile, MAXPATHLEN, CFGFILE_FMT_IPV4, ifinst);
rcm_log_message(RCM_TRACE1, "IP: Scanning %s\n", cfgfile);
if ((hostfp = fopen(cfgfile, "r")) != NULL) {
if (isgrouped(cfgfile))
ipmp = B_TRUE;
}
(void) snprintf(cfgfile, MAXPATHLEN, CFGFILE_FMT_IPV6, ifinst);
rcm_log_message(RCM_TRACE1, "IP: Scanning %s\n", cfgfile);
if ((host6fp = fopen(cfgfile, "r")) != NULL) {
if (!ipmp && isgrouped(cfgfile))
ipmp = B_TRUE;
}
/*
* Configure the interface according to its hostname files.
*/
if (hostfp != NULL &&
if_config_inst(ifinst, hostfp, AF_INET, ipmp) == -1) {
rcm_log_message(RCM_ERROR,
_("IP: IPv4 Post-attach failed (%s)\n"), ifinst);
goto fail;
}
if (host6fp != NULL &&
if_config_inst(ifinst, host6fp, AF_INET6, ipmp) == -1) {
rcm_log_message(RCM_ERROR,
_("IP: IPv6 Post-attach failed (%s)\n"), ifinst);
goto fail;
}
(void) fclose(hostfp);
(void) fclose(host6fp);
rcm_log_message(RCM_TRACE1, "IP: if_configure_hostname(%s) success\n",
ifinst);
return (0);
fail:
(void) fclose(hostfp);
(void) fclose(host6fp);
return (-1);
}
/*
* if_configure_ipadm() - Configure a physical interface after attach
* Queries libipadm for persistent configuration information and then
* resurrects that persistent configuration.
*/
static int
if_configure_ipadm(datalink_id_t linkid)
{
char ifinst[MAXLINKNAMELEN];
boolean_t found;
ipadm_if_info_t *ifinfo, *ptr;
ipadm_status_t status;
assert(linkid != DATALINK_INVALID_LINKID);
rcm_log_message(RCM_TRACE1, _("IP: if_configure_ipadm(%u)\n"),
linkid);
if (if_configure_get_linkid(linkid, ifinst, sizeof (ifinst)) != 0)
return (-1);
/* Check if the interface is already configured. */
if (ifinst[0] == '\0')
return (0);
status = ipadm_if_info(ip_handle, ifinst, &ifinfo, 0, LIFC_UNDER_IPMP);
if (status == IPADM_ENXIO)
goto done;
if (status != IPADM_SUCCESS) {
rcm_log_message(RCM_ERROR,
_("IP: IPv4 Post-attach failed (%s) Error %s\n"),
ifinst, ipadm_status2str(status));
goto fail;
}
if (ifinfo != NULL) {
found = B_FALSE;
for (ptr = ifinfo; ptr; ptr = ptr->ifi_next) {
if (strncmp(ptr->ifi_name, ifinst,
sizeof (ifinst)) == 0) {
found = B_TRUE;
break;
}
}
free(ifinfo);
if (!found) {
return (0);
}
if (if_hostname_exists(ifinst, AF_INET) ||
if_hostname_exists(ifinst, AF_INET6)) {
rcm_log_message(RCM_WARNING,
_("IP: IPv4 Post-attach (%s) found both "
"/etc/hostname and ipadm persistent configuration. "
"Ignoring ipadm config\n"), ifinst);
return (0);
}
status = ipadm_enable_if(ip_handle, ifinst, IPADM_OPT_ACTIVE);
if (status != IPADM_SUCCESS) {
rcm_log_message(RCM_ERROR,
_("IP: Post-attach failed (%s) Error %s\n"),
ifinst, ipadm_status2str(status));
goto fail;
}
}
done:
rcm_log_message(RCM_TRACE1, "IP: if_configure_ipadm(%s) success\n",
ifinst);
return (0);
fail:
return (-1);
}
/*
* isgrouped() - Scans the given config file to see if this interface is
* using IPMP. Returns B_TRUE or B_FALSE.
*/
static boolean_t
isgrouped(const char *cfgfile)
{
FILE *fp;
struct stat statb;
char *nlp, *line, *token, *lasts, *buf;
boolean_t grouped = B_FALSE;
rcm_log_message(RCM_TRACE1, "IP: isgrouped(%s)\n", cfgfile);
if (stat(cfgfile, &statb) != 0) {
rcm_log_message(RCM_TRACE1,
_("IP: No config file(%s)\n"), cfgfile);
return (B_FALSE);
}
/*
* We also ignore single-byte config files because the file should
* always be newline-terminated, so we know there's nothing of
* interest. Further, a single-byte file would cause the fgets() loop
* below to spin forever.
*/
if (statb.st_size <= 1) {
rcm_log_message(RCM_TRACE1,
_("IP: Empty config file(%s)\n"), cfgfile);
return (B_FALSE);
}
if ((fp = fopen(cfgfile, "r")) == NULL) {
rcm_log_message(RCM_ERROR,
_("IP: Cannot open configuration file(%s): %s\n"), cfgfile,
strerror(errno));
return (B_FALSE);
}
if ((buf = malloc(statb.st_size)) == NULL) {
rcm_log_message(RCM_ERROR,
_("IP: malloc failure(%s): %s\n"), cfgfile,
strerror(errno));
goto out;
}
while (fgets(buf, statb.st_size, fp) != NULL) {
if ((nlp = strrchr(buf, '\n')) != NULL)
*nlp = '\0';
line = buf;
while ((token = strtok_r(line, " \t", &lasts)) != NULL) {
line = NULL;
if (STREQ("group", token) &&
strtok_r(NULL, " \t", &lasts) != NULL) {
grouped = B_TRUE;
goto out;
}
}
}
out:
free(buf);
(void) fclose(fp);
rcm_log_message(RCM_TRACE1, "IP: isgrouped(%s): %d\n", cfgfile,
grouped);
return (grouped);
}
/*
* if_config_inst() - Configure an interface instance as specified by the
* address family af and if it is grouped (ipmp).
*/
static int
if_config_inst(const char *ifinst, FILE *hfp, int af, boolean_t ipmp)
{
FILE *ifparsefp;
struct stat statb;
char *buf = NULL;
char *ifparsebuf = NULL;
uint_t ifparsebufsize;
const char *fstr; /* address family string */
boolean_t stdif = B_FALSE;
rcm_log_message(RCM_TRACE1, "IP: if_config_inst(%s) ipmp = %d\n",
ifinst, ipmp);
if (fstat(fileno(hfp), &statb) != 0) {
rcm_log_message(RCM_ERROR,
_("IP: Cannot fstat file(%s)\n"), ifinst);
goto fail;
}
switch (af) {
case AF_INET:
fstr = "inet";
break;
case AF_INET6:
fstr = "inet6";
break;
default:
assert(0);
}
/*
* The hostname file exists; plumb the physical interface.
*/
if (!ifconfig(ifinst, fstr, "plumb", B_FALSE))
goto fail;
/* Skip static configuration if the hostname file is empty */
if (statb.st_size <= 1) {
rcm_log_message(RCM_TRACE1,
_("IP: Zero size hostname file(%s)\n"), ifinst);
goto configured;
}
if (fseek(hfp, 0, SEEK_SET) == -1) {
rcm_log_message(RCM_ERROR,
_("IP: Cannot rewind hostname file(%s): %s\n"), ifinst,
strerror(errno));
goto fail;
}
/*
* Allocate the worst-case single-line buffer sizes. A bit skanky,
* but since hostname files are small, this should suffice.
*/
if ((buf = calloc(1, statb.st_size)) == NULL) {
rcm_log_message(RCM_ERROR,
_("IP: calloc(%s): %s\n"), ifinst, strerror(errno));
goto fail;
}
ifparsebufsize = statb.st_size + sizeof (SBIN_IFPARSE " -s inet6 ");
if ((ifparsebuf = calloc(1, ifparsebufsize)) == NULL) {
rcm_log_message(RCM_ERROR,
_("IP: calloc(%s): %s\n"), ifinst, strerror(errno));
goto fail;
}
/*
* For IPv4, determine whether the hostname file consists of a single
* line. We need to handle these specially since they should
* automatically be suffixed with "netmask + broadcast + up".
*/
if (af == AF_INET &&
fgets(buf, statb.st_size, hfp) != NULL &&
fgets(buf, statb.st_size, hfp) == NULL) {
rcm_log_message(RCM_TRACE1, "IP: one-line hostname file\n");
stdif = B_TRUE;
}
if (fseek(hfp, 0L, SEEK_SET) == -1) {
rcm_log_message(RCM_ERROR,
_("IP: Cannot rewind hostname file(%s): %s\n"), ifinst,
strerror(errno));
goto fail;
}
/*
* Loop through the file one line at a time and feed it to ifconfig.
* If the interface is using IPMP, then we use /sbin/ifparse -s to
* weed out all of the data addresses, since those are already on the
* IPMP meta-interface.
*/
while (fgets(buf, statb.st_size, hfp) != NULL) {
if (ntok(buf) == 0)
continue;
if (!ipmp) {
(void) ifconfig(ifinst, fstr, buf, stdif);
continue;
}
(void) snprintf(ifparsebuf, ifparsebufsize, SBIN_IFPARSE
" -s %s %s", fstr, buf);
if ((ifparsefp = popen(ifparsebuf, "r")) == NULL) {
rcm_log_message(RCM_ERROR,
_("IP: cannot configure %s: popen \"%s\" "
"failed: %s\n"), ifinst, buf, strerror(errno));
goto fail;
}
while (fgets(buf, statb.st_size, ifparsefp) != NULL) {
if (ntok(buf) > 0)
(void) ifconfig(ifinst, fstr, buf, stdif);
}
if (pclose(ifparsefp) == -1) {
rcm_log_message(RCM_ERROR,
_("IP: cannot configure %s: pclose \"%s\" "
"failed: %s\n"), ifinst, buf, strerror(errno));
goto fail;
}
}
configured:
/*
* Bring up the interface (it may already be up)
*
* Technically, since the boot scripts only unconditionally bring up
* IPv6 interfaces, we should only unconditionally bring up IPv6 here.
* However, if we don't bring up IPv4, and a legacy IPMP configuration
* without test addresses is being used, we will never bring the
* interface up even though we would've at boot. One fix is to check
* if the IPv4 hostname file contains data addresses that we would've
* brought up, but there's no simple way to do that. Given that it's
* rare to have persistent IP configuration for an interface that
* leaves it down, we cheap out and always bring it up for IPMP.
*/
if ((af == AF_INET6 || ipmp) && !ifconfig(ifinst, fstr, "up", B_FALSE))
goto fail;
/*
* For IPv4, if a DHCP configuration file exists, have DHCP configure
* the interface. As with the boot scripts, this is done after the
* hostname files are processed so that configuration in those files
* (such as IPMP group names) will be applied first.
*/
if (af == AF_INET) {
char dhcpfile[MAXPATHLEN];
char *dhcpbuf;
off_t i, dhcpsize;
(void) snprintf(dhcpfile, MAXPATHLEN, DHCPFILE_FMT, ifinst);
if (stat(dhcpfile, &statb) == -1)
goto out;
if ((dhcpbuf = copylist(dhcpfile, &dhcpsize)) == NULL) {
rcm_log_message(RCM_ERROR, _("IP: cannot read "
"(%s): %s\n"), dhcpfile, strerror(errno));
goto fail;
}
/*
* The copylist() API converts \n's to \0's, but we want them
* to be spaces.
*/
if (dhcpsize > 0) {
for (i = 0; i < dhcpsize; i++)
if (dhcpbuf[i] == '\0')
dhcpbuf[i] = ' ';
dhcpbuf[dhcpsize - 1] = '\0';
}
(void) ifconfig(ifinst, CFG_DHCP_CMD, dhcpbuf, B_FALSE);
free(dhcpbuf);
}
out:
free(ifparsebuf);
free(buf);
rcm_log_message(RCM_TRACE1, "IP: if_config_inst(%s) success\n", ifinst);
return (0);
fail:
free(ifparsebuf);
free(buf);
rcm_log_message(RCM_ERROR, "IP: if_config_inst(%s) failure\n", ifinst);
return (-1);
}
/*
* ntok() - count the number of tokens in the provided buffer.
*/
static uint_t
ntok(const char *cp)
{
uint_t ntok = 0;
for (;;) {
while (ISSPACE(*cp))
cp++;
if (ISEOL(*cp))
break;
do {
cp++;
} while (!ISSPACE(*cp) && !ISEOL(*cp));
ntok++;
}
return (ntok);
}
static boolean_t
ifconfig(const char *ifinst, const char *fstr, const char *buf, boolean_t stdif)
{
char syscmd[MAX_RECONFIG_SIZE + MAXPATHLEN + 1];
int status;
(void) snprintf(syscmd, sizeof (syscmd), SBIN_IFCONFIG " %s %s %s",
ifinst, fstr, buf);
if (stdif)
(void) strlcat(syscmd, CFG_CMDS_STD, sizeof (syscmd));
rcm_log_message(RCM_TRACE1, "IP: Exec: %s\n", syscmd);
if ((status = rcm_exec_cmd(syscmd)) != 0) {
if (WIFEXITED(status)) {
rcm_log_message(RCM_ERROR, _("IP: \"%s\" failed with "
"exit status %d\n"), syscmd, WEXITSTATUS(status));
} else {
rcm_log_message(RCM_ERROR, _("IP: Error: %s: %s\n"),
syscmd, strerror(errno));
}
return (B_FALSE);
}
return (B_TRUE);
}
/*
* Return TRUE if a writeable /etc/hostname[6].ifname file exists.
*/
static boolean_t
if_hostname_exists(char *ifname, sa_family_t af)
{
char cfgfile[MAXPATHLEN];
if (af == AF_INET) {
(void) snprintf(cfgfile, MAXPATHLEN, CFGFILE_FMT_IPV4, ifname);
if (access(cfgfile, W_OK|F_OK) == 0)
return (B_TRUE);
} else if (af == AF_INET6) {
(void) snprintf(cfgfile, MAXPATHLEN, CFGFILE_FMT_IPV6, ifname);
if (access(cfgfile, W_OK|F_OK) == 0)
return (B_TRUE);
}
return (B_FALSE);
}