if_mpadm.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (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 2000-2003 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/sockio.h>
#include <net/if.h>
#include <errno.h>
#include <strings.h>
#include <ipmp_mpathd.h>
#include <libintl.h>
static int if_down(int ifsock, struct lifreq *lifr);
static int if_up(int ifsock, struct lifreq *lifr);
static void send_cmd(int cmd, char *ifname);
static int connect_to_mpathd(sa_family_t family);
static void do_offline(char *ifname);
static void undo_offline(char *ifname);
static boolean_t offline_set(char *ifname);
#define IF_SEPARATOR ':'
#define MAX_RETRIES 3
static void
usage()
{
fprintf(stderr, "Usage : if_mpadm [-d | -r] <interface_name>\n");
}
static void
print_mpathd_error_msg(uint32_t error)
{
switch (error) {
case MPATHD_MIN_RED_ERROR:
(void) fprintf(stderr, gettext(
"Offline failed as there is no other functional "
"interface available in the multipathing group "
"for failing over the network access.\n"));
break;
case MPATHD_FAILBACK_PARTIAL:
(void) fprintf(stderr, gettext(
"Offline cannot be undone because multipathing "
"configuration is not consistent across all the "
"interfaces in the group.\n"));
break;
default:
/*
* We shouldn't get here. All errors should have a
* meaningful error message, as shown in the above
* cases. If we get here, someone has made a mistake.
*/
(void) fprintf(stderr, gettext(
"Operation returned an unrecognized error: %u\n"),
error);
break;
}
}
main(int argc, char **argv)
{
char *ifname;
int cmd = 0;
int c;
#if !defined(TEXT_DOMAIN)
#define TEXT_DOMAIN "SYS_TEST"
#endif
(void) textdomain(TEXT_DOMAIN);
while ((c = getopt(argc, argv, "d:r:")) != EOF) {
switch (c) {
case 'd':
ifname = optarg;
cmd = MI_OFFLINE;
if (offline_set(ifname)) {
fprintf(stderr, gettext("Interface already "
"offlined\n"));
exit(1);
}
break;
case 'r':
ifname = optarg;
cmd = MI_UNDO_OFFLINE;
if (!offline_set(ifname)) {
fprintf(stderr, gettext("Interface not "
"offlined\n"));
exit(1);
}
break;
default :
usage();
exit(1);
}
}
if (cmd == 0) {
usage();
exit(1);
}
/*
* Send the command to in.mpathd which is generic to
* both the commands. send_cmd returns only if there
* is no error.
*/
send_cmd(cmd, ifname);
if (cmd == MI_OFFLINE) {
do_offline(ifname);
} else {
undo_offline(ifname);
}
return (0);
}
/*
* Is IFF_OFFLINE set ?
* Returns B_FALSE on failure and B_TRUE on success.
*/
boolean_t
offline_set(char *ifname)
{
struct lifreq lifr;
int s4;
int s6;
int ret;
s4 = socket(AF_INET, SOCK_DGRAM, 0);
if (s4 < 0) {
perror("socket");
exit(1);
}
s6 = socket(AF_INET6, SOCK_DGRAM, 0);
if (s6 < 0) {
perror("socket");
exit(1);
}
(void) strncpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name));
ret = ioctl(s4, SIOCGLIFFLAGS, (caddr_t)&lifr);
if (ret < 0) {
if (errno != ENXIO) {
perror("ioctl: SIOCGLIFFLAGS");
exit(1);
}
ret = ioctl(s6, SIOCGLIFFLAGS, (caddr_t)&lifr);
if (ret < 0) {
perror("ioctl: SIOCGLIFFLAGS");
exit(1);
}
}
(void) close(s4);
(void) close(s6);
if (lifr.lifr_flags & IFF_OFFLINE)
return (B_TRUE);
else
return (B_FALSE);
}
/*
* Sends the command to in.mpathd. If not successful, prints
* an error message and exits.
*/
void
send_cmd(int cmd, char *ifname)
{
struct mi_offline mio;
struct mi_undo_offline miu;
struct mi_result me;
int ret;
int cmd_len;
int i;
int s;
for (i = 0; i < MAX_RETRIES; i++) {
s = connect_to_mpathd(AF_INET);
if (s == -1) {
s = connect_to_mpathd(AF_INET6);
if (s == -1) {
fprintf(stderr, gettext("Cannot establish "
"communication with in.mpathd.\n"));
exit(1);
}
}
switch (cmd) {
case MI_OFFLINE :
cmd_len = sizeof (struct mi_offline);
bzero(&mio, cmd_len);
mio.mio_command = cmd;
(void) strncpy(mio.mio_ifname, ifname, LIFNAMSIZ);
mio.mio_min_redundancy = 1;
ret = write(s, &mio, cmd_len);
if (ret != cmd_len) {
/* errno is set only when ret is -1 */
if (ret == -1)
perror("write");
fprintf(stderr, gettext("Failed to "
"successfully send command to "
"in.mpathd.\n"));
exit(1);
}
break;
case MI_UNDO_OFFLINE:
cmd_len = sizeof (struct mi_undo_offline);
bzero(&miu, cmd_len);
miu.miu_command = cmd;
(void) strncpy(miu.miu_ifname, ifname, LIFNAMSIZ);
ret = write(s, &miu, cmd_len);
if (ret != cmd_len) {
/* errno is set only when ret is -1 */
if (ret == -1)
perror("write");
fprintf(stderr, gettext("Failed to "
"successfully send command to "
"in.mpathd.\n"));
exit(1);
}
break;
default :
fprintf(stderr, "Unknown command \n");
exit(1);
}
/* Read the result from mpathd */
ret = read(s, &me, sizeof (me));
if (ret != sizeof (me)) {
/* errno is set only when ret is -1 */
if (ret == -1)
perror("read");
fprintf(stderr, gettext("Failed to successfully read "
"result from in.mpathd.\n"));
exit(1);
}
if (me.me_mpathd_error == 0) {
if (i != 0) {
/*
* We retried at least once. Tell the user
* that things succeeded now.
*/
fprintf(stderr, gettext("Retry Successful.\n"));
}
return; /* Successful */
}
if (me.me_mpathd_error == MPATHD_SYS_ERROR) {
if (me.me_sys_error == EAGAIN) {
(void) close(s);
(void) sleep(1);
fprintf(stderr, gettext("Retrying ...\n"));
continue; /* Retry */
}
errno = me.me_sys_error;
perror("if_mpadm");
} else {
print_mpathd_error_msg(me.me_mpathd_error);
}
exit(1);
}
/*
* We come here only if we retry the operation multiple
* times and did not succeed. Let the user try it again
* later.
*/
fprintf(stderr, gettext("Device busy. Retry the operation later.\n"));
exit(1);
}
static void
do_offline(char *ifname)
{
struct lifreq lifr;
struct lifreq *lifcr;
struct lifnum lifn;
struct lifconf lifc;
char *buf;
int numifs;
int n;
char pi_name[LIFNAMSIZ + 1];
char *cp;
int ifsock_v4;
int ifsock_v6;
int af;
int ret;
/*
* Verify whether IFF_OFFLINE is not set as a sanity check.
*/
if (!offline_set(ifname)) {
fprintf(stderr, gettext("Operation failed : in.mpathd has "
"not set IFF_OFFLINE on %s\n"), ifname);
exit(1);
}
/*
* Get both the sockets as we may need to bring both
* IPv4 and IPv6 interfaces down.
*/
ifsock_v4 = socket(AF_INET, SOCK_DGRAM, 0);
if (ifsock_v4 < 0) {
perror("socket");
exit(1);
}
ifsock_v6 = socket(AF_INET6, SOCK_DGRAM, 0);
if (ifsock_v6 < 0) {
perror("socket");
exit(1);
}
/*
* Get all the logicals for "ifname" and mark them down.
* There is no easy way of doing this. We get all the
* interfaces in the system using SICGLIFCONF and mark the
* ones matching the name down.
*/
lifn.lifn_family = AF_UNSPEC;
lifn.lifn_flags = 0;
if (ioctl(ifsock_v4, SIOCGLIFNUM, (char *)&lifn) < 0) {
perror("ioctl : SIOCGLIFNUM");
exit(1);
}
numifs = lifn.lifn_count;
buf = (char *)calloc(numifs, sizeof (struct lifreq));
if (buf == NULL) {
perror("calloc");
exit(1);
}
lifc.lifc_family = AF_UNSPEC;
lifc.lifc_flags = 0;
lifc.lifc_len = numifs * sizeof (struct lifreq);
lifc.lifc_buf = buf;
if (ioctl(ifsock_v4, SIOCGLIFCONF, (char *)&lifc) < 0) {
perror("ioctl : SIOCGLIFCONF");
exit(1);
}
lifcr = (struct lifreq *)lifc.lifc_req;
for (n = lifc.lifc_len / sizeof (struct lifreq); n > 0; n--, lifcr++) {
af = lifcr->lifr_addr.ss_family;
(void) strncpy(pi_name, lifcr->lifr_name,
sizeof (pi_name));
pi_name[sizeof (pi_name) - 1] = '\0';
if ((cp = strchr(pi_name, IF_SEPARATOR)) != NULL)
*cp = '\0';
if (strcmp(pi_name, ifname) == 0) {
/* It matches the interface name that was offlined */
(void) strncpy(lifr.lifr_name, lifcr->lifr_name,
sizeof (lifr.lifr_name));
if (af == AF_INET)
ret = if_down(ifsock_v4, &lifr);
else
ret = if_down(ifsock_v6, &lifr);
if (ret != 0) {
fprintf(stderr, gettext("Bringing down the "
"interfaces failed.\n"));
exit(1);
}
}
}
}
static void
undo_offline(char *ifname)
{
struct lifreq lifr;
struct lifreq *lifcr;
struct lifnum lifn;
struct lifconf lifc;
char *buf;
int numifs;
int n;
char pi_name[LIFNAMSIZ + 1];
char *cp;
int ifsock_v4;
int ifsock_v6;
int af;
int ret;
/*
* Verify whether IFF_OFFLINE is set as a sanity check.
*/
if (offline_set(ifname)) {
fprintf(stderr, gettext("Operation failed : in.mpathd has "
"not cleared IFF_OFFLINE on %s\n"), ifname);
exit(1);
}
/*
* Get both the sockets as we may need to bring both
* IPv4 and IPv6 interfaces UP.
*/
ifsock_v4 = socket(AF_INET, SOCK_DGRAM, 0);
if (ifsock_v4 < 0) {
perror("socket");
exit(1);
}
ifsock_v6 = socket(AF_INET6, SOCK_DGRAM, 0);
if (ifsock_v6 < 0) {
perror("socket");
exit(1);
}
/*
* Get all the logicals for "ifname" and mark them up.
* There is no easy way of doing this. We get all the
* interfaces in the system using SICGLIFCONF and mark the
* ones matching the name up.
*/
lifn.lifn_family = AF_UNSPEC;
lifn.lifn_flags = 0;
if (ioctl(ifsock_v4, SIOCGLIFNUM, (char *)&lifn) < 0) {
perror("ioctl : SIOCGLIFNUM");
exit(1);
}
numifs = lifn.lifn_count;
buf = (char *)calloc(numifs, sizeof (struct lifreq));
if (buf == NULL) {
perror("calloc");
exit(1);
}
lifc.lifc_family = AF_UNSPEC;
lifc.lifc_flags = 0;
lifc.lifc_len = numifs * sizeof (struct lifreq);
lifc.lifc_buf = buf;
if (ioctl(ifsock_v4, SIOCGLIFCONF, (char *)&lifc) < 0) {
perror("ioctl : SIOCGLIFCONF");
exit(1);
}
lifcr = (struct lifreq *)lifc.lifc_req;
for (n = lifc.lifc_len / sizeof (struct lifreq); n > 0; n--, lifcr++) {
af = lifcr->lifr_addr.ss_family;
(void) strncpy(pi_name, lifcr->lifr_name,
sizeof (pi_name));
pi_name[sizeof (pi_name) - 1] = '\0';
if ((cp = strchr(pi_name, IF_SEPARATOR)) != NULL)
*cp = '\0';
if (strcmp(pi_name, ifname) == 0) {
/* It matches the interface name that was offlined */
(void) strncpy(lifr.lifr_name, lifcr->lifr_name,
sizeof (lifr.lifr_name));
if (af == AF_INET)
ret = if_up(ifsock_v4, &lifr);
else
ret = if_up(ifsock_v6, &lifr);
if (ret != 0) {
fprintf(stderr, gettext("Bringing up the "
"interfaces failed.\n"));
exit(1);
}
}
}
}
/*
* Returns -1 on failure. Returns the socket file descriptor on
* success.
*/
static int
connect_to_mpathd(sa_family_t family)
{
int s;
struct sockaddr_storage ss;
struct sockaddr_in *sin = (struct sockaddr_in *)&ss;
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&ss;
struct in6_addr loopback_addr = IN6ADDR_LOOPBACK_INIT;
int addrlen;
int ret;
int on;
s = socket(family, SOCK_STREAM, 0);
if (s < 0) {
perror("socket");
return (-1);
}
bzero((char *)&ss, sizeof (ss));
ss.ss_family = family;
/*
* Need to bind to a privileged port. For non-root, this
* will fail. in.mpathd verifies that only commands coming
* from privileged ports succeed so that the ordinary user
* can't issue offline commands.
*/
on = 1;
if (setsockopt(s, IPPROTO_TCP, TCP_ANONPRIVBIND, &on,
sizeof (on)) < 0) {
perror("setsockopt : TCP_ANONPRIVBIND");
exit(1);
}
switch (family) {
case AF_INET:
sin->sin_port = 0;
sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addrlen = sizeof (struct sockaddr_in);
break;
case AF_INET6:
sin6->sin6_port = 0;
sin6->sin6_addr = loopback_addr;
addrlen = sizeof (struct sockaddr_in6);
break;
}
ret = bind(s, (struct sockaddr *)&ss, addrlen);
if (ret != 0) {
perror("bind");
return (-1);
}
switch (family) {
case AF_INET:
sin->sin_port = htons(MPATHD_PORT);
break;
case AF_INET6:
sin6->sin6_port = htons(MPATHD_PORT);
break;
}
ret = connect(s, (struct sockaddr *)&ss, addrlen);
if (ret != 0) {
perror("connect");
return (-1);
}
on = 0;
if (setsockopt(s, IPPROTO_TCP, TCP_ANONPRIVBIND, &on,
sizeof (on)) < 0) {
perror("setsockopt : TCP_ANONPRIVBIND");
return (-1);
}
return (s);
}
/*
* Bring down the interface specified by the name lifr->lifr_name.
*
* Returns -1 on failure. Returns 0 on success.
*/
static int
if_down(int ifsock, struct lifreq *lifr)
{
int ret;
ret = ioctl(ifsock, SIOCGLIFFLAGS, (caddr_t)lifr);
if (ret < 0) {
perror("ioctl: SIOCGLIFFLAGS");
return (-1);
}
/* IFF_OFFLINE was set to start with. Is it still there ? */
if (!(lifr->lifr_flags & (IFF_OFFLINE))) {
fprintf(stderr, gettext("IFF_OFFLINE disappeared on %s\n"),
lifr->lifr_name);
return (-1);
}
lifr->lifr_flags &= ~IFF_UP;
ret = ioctl(ifsock, SIOCSLIFFLAGS, (caddr_t)lifr);
if (ret < 0) {
perror("ioctl: SIOCSLIFFLAGS");
return (-1);
}
return (0);
}
/*
* Bring up the interface specified by the name lifr->lifr_name.
*
* Returns -1 on failure. Returns 0 on success.
*/
static int
if_up(int ifsock, struct lifreq *lifr)
{
int ret;
boolean_t zeroaddr = B_FALSE;
struct sockaddr_in *addr;
ret = ioctl(ifsock, SIOCGLIFADDR, lifr);
if (ret < 0) {
perror("ioctl: SIOCGLIFADDR");
return (-1);
}
addr = (struct sockaddr_in *)&lifr->lifr_addr;
switch (addr->sin_family) {
case AF_INET:
zeroaddr = (addr->sin_addr.s_addr == INADDR_ANY);
break;
case AF_INET6:
zeroaddr = IN6_IS_ADDR_UNSPECIFIED(
&((struct sockaddr_in6 *)addr)->sin6_addr);
break;
default:
break;
}
ret = ioctl(ifsock, SIOCGLIFFLAGS, lifr);
if (ret < 0) {
perror("ioctl: SIOCGLIFFLAGS");
return (-1);
}
/*
* Don't affect the state of addresses that failed back.
*
* XXX Link local addresses that are not marked IFF_NOFAILOVER
* will not be brought up. Link local addresses never failover.
* When the interface was offlined, we brought the link local
* address down. We will not bring it up now if IFF_NOFAILOVER
* is not marked. We check for IFF_NOFAILOVER below so that
* we want to maintain the state of all other addresses as it
* was before offline. Normally link local addresses are marked
* IFF_NOFAILOVER and hence this is not an issue. These can
* be fixed in future with RCM and it is beyond the scope
* of if_mpadm to maintain state and do this correctly.
*/
if (!(lifr->lifr_flags & IFF_NOFAILOVER))
return (0);
/*
* When a data address associated with the physical interface itself
* is failed over (e.g., qfe0, rather than qfe0:1), the kernel must
* fill the ipif data structure for qfe0 with a placeholder entry (the
* "replacement ipif"). Replacement ipif's cannot be brought IFF_UP
* (nor would it make any sense to do so), so we must be careful to
* skip them; thankfully they can be easily identified since they
* all have a zeroed address.
*/
if (zeroaddr)
return (0);
/* IFF_OFFLINE was not set to start with. Is it there ? */
if (lifr->lifr_flags & IFF_OFFLINE) {
fprintf(stderr, gettext("IFF_OFFLINE set wrongly on %s\n"),
lifr->lifr_name);
return (-1);
}
lifr->lifr_flags |= IFF_UP;
ret = ioctl(ifsock, SIOCSLIFFLAGS, lifr);
if (ret < 0) {
perror("ioctl: SIOCSLIFFLAGS");
return (-1);
}
return (0);
}