/* * 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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include 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() { (void) fprintf(stderr, "Usage : if_mpadm [-d | -r] \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; } } int 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)) { (void) fprintf(stderr, gettext("Interface " "already offlined\n")); exit(1); } break; case 'r': ifname = optarg; cmd = MI_UNDO_OFFLINE; if (!offline_set(ifname)) { (void) 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) { (void) 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"); (void) 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"); (void) fprintf(stderr, gettext("Failed to " "successfully send command to " "in.mpathd.\n")); exit(1); } break; default : (void) 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"); (void) 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. */ (void) 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); (void) 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. */ (void) 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)) { (void) 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 = 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) { (void) 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)) { (void) 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 = 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) { (void) 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))) { (void) 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) { (void) 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); }