1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <sys/types.h>
30 #include <unistd.h>
31 #include <stdlib.h>
32 #include <stdio.h>
33 #include <sys/socket.h>
34 #include <netinet/in.h>
35 #include <netinet/tcp.h>
36 #include <sys/sockio.h>
37 #include <net/if.h>
38 #include <errno.h>
39 #include <strings.h>
40 #include <ipmp_mpathd.h>
41 #include <libintl.h>
42 
43 static int		if_down(int ifsock, struct lifreq *lifr);
44 static int		if_up(int ifsock, struct lifreq *lifr);
45 static void		send_cmd(int cmd, char *ifname);
46 static int		connect_to_mpathd(sa_family_t family);
47 static void		do_offline(char *ifname);
48 static void		undo_offline(char *ifname);
49 static boolean_t	offline_set(char *ifname);
50 
51 #define	IF_SEPARATOR	':'
52 #define	MAX_RETRIES	3
53 
54 static void
55 usage()
56 {
57 	(void) fprintf(stderr, "Usage : if_mpadm [-d | -r] <interface_name>\n");
58 }
59 
60 static void
61 print_mpathd_error_msg(uint32_t error)
62 {
63 	switch (error) {
64 	case MPATHD_MIN_RED_ERROR:
65 		(void) fprintf(stderr, gettext(
66 			"Offline failed as there is no other functional "
67 			"interface available in the multipathing group "
68 			"for failing over the network access.\n"));
69 		break;
70 
71 	case MPATHD_FAILBACK_PARTIAL:
72 		(void) fprintf(stderr, gettext(
73 			"Offline cannot be undone because multipathing "
74 			"configuration is not consistent across all the "
75 			"interfaces in the group.\n"));
76 		break;
77 
78 	default:
79 		/*
80 		 * We shouldn't get here.  All errors should have a
81 		 * meaningful error message, as shown in the above
82 		 * cases.  If we get here, someone has made a mistake.
83 		 */
84 		(void) fprintf(stderr, gettext(
85 			"Operation returned an unrecognized error: %u\n"),
86 			error);
87 		break;
88 	}
89 }
90 
91 int
92 main(int argc, char **argv)
93 {
94 	char *ifname;
95 	int cmd = 0;
96 	int c;
97 
98 #if !defined(TEXT_DOMAIN)
99 #define	TEXT_DOMAIN "SYS_TEST"
100 #endif
101 	(void) textdomain(TEXT_DOMAIN);
102 
103 	while ((c = getopt(argc, argv, "d:r:")) != EOF) {
104 		switch (c) {
105 		case 'd':
106 			ifname = optarg;
107 			cmd = MI_OFFLINE;
108 			if (offline_set(ifname)) {
109 				(void) fprintf(stderr, gettext("Interface "
110 				    "already offlined\n"));
111 				exit(1);
112 			}
113 			break;
114 		case 'r':
115 			ifname = optarg;
116 			cmd = MI_UNDO_OFFLINE;
117 			if (!offline_set(ifname)) {
118 				(void) fprintf(stderr, gettext("Interface not "
119 				    "offlined\n"));
120 				exit(1);
121 			}
122 			break;
123 		default :
124 			usage();
125 			exit(1);
126 		}
127 	}
128 
129 	if (cmd == 0) {
130 		usage();
131 		exit(1);
132 	}
133 
134 	/*
135 	 * Send the command to in.mpathd which is generic to
136 	 * both the commands. send_cmd returns only if there
137 	 * is no error.
138 	 */
139 	send_cmd(cmd, ifname);
140 	if (cmd == MI_OFFLINE) {
141 		do_offline(ifname);
142 	} else {
143 		undo_offline(ifname);
144 	}
145 
146 	return (0);
147 }
148 
149 /*
150  * Is IFF_OFFLINE set ?
151  * Returns B_FALSE on failure and B_TRUE on success.
152  */
153 boolean_t
154 offline_set(char *ifname)
155 {
156 	struct lifreq lifr;
157 	int s4;
158 	int s6;
159 	int ret;
160 
161 	s4 = socket(AF_INET, SOCK_DGRAM, 0);
162 	if (s4 < 0) {
163 		perror("socket");
164 		exit(1);
165 	}
166 	s6 = socket(AF_INET6, SOCK_DGRAM, 0);
167 	if (s6 < 0) {
168 		perror("socket");
169 		exit(1);
170 	}
171 	(void) strncpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name));
172 	ret = ioctl(s4, SIOCGLIFFLAGS, (caddr_t)&lifr);
173 	if (ret < 0) {
174 		if (errno != ENXIO) {
175 			perror("ioctl: SIOCGLIFFLAGS");
176 			exit(1);
177 		}
178 		ret = ioctl(s6, SIOCGLIFFLAGS, (caddr_t)&lifr);
179 		if (ret < 0) {
180 			perror("ioctl: SIOCGLIFFLAGS");
181 			exit(1);
182 		}
183 	}
184 	(void) close(s4);
185 	(void) close(s6);
186 	if (lifr.lifr_flags & IFF_OFFLINE)
187 		return (B_TRUE);
188 	else
189 		return (B_FALSE);
190 }
191 
192 /*
193  * Sends the command to in.mpathd. If not successful, prints
194  * an error message and exits.
195  */
196 void
197 send_cmd(int cmd, char *ifname)
198 {
199 	struct mi_offline mio;
200 	struct mi_undo_offline miu;
201 	struct mi_result me;
202 	int ret;
203 	int cmd_len;
204 	int i;
205 	int s;
206 
207 	for (i = 0; i < MAX_RETRIES; i++) {
208 		s = connect_to_mpathd(AF_INET);
209 		if (s == -1) {
210 			s = connect_to_mpathd(AF_INET6);
211 			if (s == -1) {
212 				(void) fprintf(stderr, gettext("Cannot "
213 				    "establish communication with "
214 				    "in.mpathd.\n"));
215 				exit(1);
216 			}
217 		}
218 		switch (cmd) {
219 		case MI_OFFLINE :
220 			cmd_len = sizeof (struct mi_offline);
221 			bzero(&mio, cmd_len);
222 			mio.mio_command = cmd;
223 			(void) strncpy(mio.mio_ifname, ifname, LIFNAMSIZ);
224 			mio.mio_min_redundancy = 1;
225 			ret = write(s, &mio, cmd_len);
226 			if (ret != cmd_len) {
227 				/* errno is set only when ret is -1 */
228 				if (ret == -1)
229 					perror("write");
230 				(void) fprintf(stderr, gettext("Failed to "
231 				    "successfully send command to "
232 				    "in.mpathd.\n"));
233 				exit(1);
234 			}
235 			break;
236 		case MI_UNDO_OFFLINE:
237 			cmd_len = sizeof (struct mi_undo_offline);
238 			bzero(&miu, cmd_len);
239 			miu.miu_command = cmd;
240 			(void) strncpy(miu.miu_ifname, ifname, LIFNAMSIZ);
241 			ret = write(s, &miu, cmd_len);
242 			if (ret != cmd_len) {
243 				/* errno is set only when ret is -1 */
244 				if (ret == -1)
245 					perror("write");
246 				(void) fprintf(stderr, gettext("Failed to "
247 				    "successfully send command to "
248 				    "in.mpathd.\n"));
249 				exit(1);
250 			}
251 			break;
252 		default :
253 			(void) fprintf(stderr, "Unknown command \n");
254 			exit(1);
255 		}
256 
257 		/* Read the result from mpathd */
258 		ret = read(s, &me, sizeof (me));
259 		if (ret != sizeof (me)) {
260 			/* errno is set only when ret is -1 */
261 			if (ret == -1)
262 				perror("read");
263 			(void) fprintf(stderr, gettext("Failed to successfully "
264 			    "read result from in.mpathd.\n"));
265 			exit(1);
266 		}
267 		if (me.me_mpathd_error == 0) {
268 			if (i != 0) {
269 				/*
270 				 * We retried at least once. Tell the user
271 				 * that things succeeded now.
272 				 */
273 				(void) fprintf(stderr,
274 				    gettext("Retry Successful.\n"));
275 			}
276 			return;			/* Successful */
277 		}
278 
279 		if (me.me_mpathd_error == MPATHD_SYS_ERROR) {
280 			if (me.me_sys_error == EAGAIN) {
281 				(void) close(s);
282 				(void) sleep(1);
283 				(void) fprintf(stderr,
284 				    gettext("Retrying ...\n"));
285 				continue;		/* Retry */
286 			}
287 			errno = me.me_sys_error;
288 			perror("if_mpadm");
289 		} else {
290 			print_mpathd_error_msg(me.me_mpathd_error);
291 		}
292 		exit(1);
293 	}
294 	/*
295 	 * We come here only if we retry the operation multiple
296 	 * times and did not succeed. Let the user try it again
297 	 * later.
298 	 */
299 	(void) fprintf(stderr,
300 	    gettext("Device busy. Retry the operation later.\n"));
301 	exit(1);
302 }
303 
304 static void
305 do_offline(char *ifname)
306 {
307 	struct lifreq lifr;
308 	struct lifreq *lifcr;
309 	struct lifnum	lifn;
310 	struct lifconf	lifc;
311 	char *buf;
312 	int numifs;
313 	int n;
314 	char	pi_name[LIFNAMSIZ + 1];
315 	char	*cp;
316 	int ifsock_v4;
317 	int ifsock_v6;
318 	int af;
319 	int ret;
320 
321 	/*
322 	 * Verify whether IFF_OFFLINE is not set as a sanity check.
323 	 */
324 	if (!offline_set(ifname)) {
325 		(void) fprintf(stderr, gettext("Operation failed : in.mpathd "
326 		    "has not set IFF_OFFLINE on %s\n"), ifname);
327 		exit(1);
328 	}
329 	/*
330 	 * Get both the sockets as we may need to bring both
331 	 * IPv4 and IPv6 interfaces down.
332 	 */
333 	ifsock_v4 = socket(AF_INET, SOCK_DGRAM, 0);
334 	if (ifsock_v4 < 0) {
335 		perror("socket");
336 		exit(1);
337 	}
338 	ifsock_v6 = socket(AF_INET6, SOCK_DGRAM, 0);
339 	if (ifsock_v6 < 0) {
340 		perror("socket");
341 		exit(1);
342 	}
343 	/*
344 	 * Get all the logicals for "ifname" and mark them down.
345 	 * There is no easy way of doing this. We get all the
346 	 * interfaces in the system using SICGLIFCONF and mark the
347 	 * ones matching the name down.
348 	 */
349 	lifn.lifn_family = AF_UNSPEC;
350 	lifn.lifn_flags = 0;
351 	if (ioctl(ifsock_v4, SIOCGLIFNUM, (char *)&lifn) < 0) {
352 		perror("ioctl : SIOCGLIFNUM");
353 		exit(1);
354 	}
355 	numifs = lifn.lifn_count;
356 
357 	buf = calloc(numifs, sizeof (struct lifreq));
358 	if (buf == NULL) {
359 		perror("calloc");
360 		exit(1);
361 	}
362 
363 	lifc.lifc_family = AF_UNSPEC;
364 	lifc.lifc_flags = 0;
365 	lifc.lifc_len = numifs * sizeof (struct lifreq);
366 	lifc.lifc_buf = buf;
367 
368 	if (ioctl(ifsock_v4, SIOCGLIFCONF, (char *)&lifc) < 0) {
369 		perror("ioctl : SIOCGLIFCONF");
370 		exit(1);
371 	}
372 
373 	lifcr = (struct lifreq *)lifc.lifc_req;
374 	for (n = lifc.lifc_len / sizeof (struct lifreq); n > 0; n--, lifcr++) {
375 		af = lifcr->lifr_addr.ss_family;
376 		(void) strncpy(pi_name, lifcr->lifr_name,
377 		    sizeof (pi_name));
378 		pi_name[sizeof (pi_name) - 1] = '\0';
379 		if ((cp = strchr(pi_name, IF_SEPARATOR)) != NULL)
380 			*cp = '\0';
381 		if (strcmp(pi_name, ifname) == 0) {
382 			/* It matches the interface name that was offlined */
383 			(void) strncpy(lifr.lifr_name, lifcr->lifr_name,
384 			    sizeof (lifr.lifr_name));
385 			if (af == AF_INET)
386 				ret = if_down(ifsock_v4, &lifr);
387 			else
388 				ret = if_down(ifsock_v6, &lifr);
389 			if (ret != 0) {
390 				(void) fprintf(stderr, gettext("Bringing down "
391 				    "the interfaces failed.\n"));
392 				exit(1);
393 			}
394 		}
395 	}
396 }
397 
398 static void
399 undo_offline(char *ifname)
400 {
401 	struct lifreq lifr;
402 	struct lifreq *lifcr;
403 	struct lifnum	lifn;
404 	struct lifconf	lifc;
405 	char *buf;
406 	int numifs;
407 	int n;
408 	char	pi_name[LIFNAMSIZ + 1];
409 	char	*cp;
410 	int ifsock_v4;
411 	int ifsock_v6;
412 	int af;
413 	int ret;
414 
415 	/*
416 	 * Verify whether IFF_OFFLINE is set as a sanity check.
417 	 */
418 	if (offline_set(ifname)) {
419 		(void) fprintf(stderr, gettext("Operation failed : in.mpathd "
420 		    "has not cleared IFF_OFFLINE on %s\n"), ifname);
421 		exit(1);
422 	}
423 	/*
424 	 * Get both the sockets as we may need to bring both
425 	 * IPv4 and IPv6 interfaces UP.
426 	 */
427 	ifsock_v4 = socket(AF_INET, SOCK_DGRAM, 0);
428 	if (ifsock_v4 < 0) {
429 		perror("socket");
430 		exit(1);
431 	}
432 	ifsock_v6 = socket(AF_INET6, SOCK_DGRAM, 0);
433 	if (ifsock_v6 < 0) {
434 		perror("socket");
435 		exit(1);
436 	}
437 	/*
438 	 * Get all the logicals for "ifname" and mark them up.
439 	 * There is no easy way of doing this. We get all the
440 	 * interfaces in the system using SICGLIFCONF and mark the
441 	 * ones matching the name up.
442 	 */
443 	lifn.lifn_family = AF_UNSPEC;
444 	lifn.lifn_flags = 0;
445 	if (ioctl(ifsock_v4, SIOCGLIFNUM, (char *)&lifn) < 0) {
446 		perror("ioctl : SIOCGLIFNUM");
447 		exit(1);
448 	}
449 	numifs = lifn.lifn_count;
450 
451 	buf = calloc(numifs, sizeof (struct lifreq));
452 	if (buf == NULL) {
453 		perror("calloc");
454 		exit(1);
455 	}
456 
457 	lifc.lifc_family = AF_UNSPEC;
458 	lifc.lifc_flags = 0;
459 	lifc.lifc_len = numifs * sizeof (struct lifreq);
460 	lifc.lifc_buf = buf;
461 
462 	if (ioctl(ifsock_v4, SIOCGLIFCONF, (char *)&lifc) < 0) {
463 		perror("ioctl : SIOCGLIFCONF");
464 		exit(1);
465 	}
466 
467 	lifcr = (struct lifreq *)lifc.lifc_req;
468 	for (n = lifc.lifc_len / sizeof (struct lifreq); n > 0; n--, lifcr++) {
469 		af = lifcr->lifr_addr.ss_family;
470 		(void) strncpy(pi_name, lifcr->lifr_name,
471 		    sizeof (pi_name));
472 		pi_name[sizeof (pi_name) - 1] = '\0';
473 		if ((cp = strchr(pi_name, IF_SEPARATOR)) != NULL)
474 			*cp = '\0';
475 
476 		if (strcmp(pi_name, ifname) == 0) {
477 			/* It matches the interface name that was offlined */
478 			(void) strncpy(lifr.lifr_name, lifcr->lifr_name,
479 			    sizeof (lifr.lifr_name));
480 			if (af == AF_INET)
481 				ret = if_up(ifsock_v4, &lifr);
482 			else
483 				ret = if_up(ifsock_v6, &lifr);
484 			if (ret != 0) {
485 				(void) fprintf(stderr, gettext("Bringing up "
486 				    "the interfaces failed.\n"));
487 				exit(1);
488 			}
489 		}
490 	}
491 }
492 
493 /*
494  * Returns -1 on failure. Returns the socket file descriptor on
495  * success.
496  */
497 static int
498 connect_to_mpathd(sa_family_t family)
499 {
500 	int s;
501 	struct sockaddr_storage ss;
502 	struct sockaddr_in *sin = (struct sockaddr_in *)&ss;
503 	struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&ss;
504 	struct in6_addr loopback_addr = IN6ADDR_LOOPBACK_INIT;
505 	int addrlen;
506 	int ret;
507 	int on;
508 
509 	s = socket(family, SOCK_STREAM, 0);
510 	if (s < 0) {
511 		perror("socket");
512 		return (-1);
513 	}
514 	bzero((char *)&ss, sizeof (ss));
515 	ss.ss_family = family;
516 	/*
517 	 * Need to bind to a privileged port. For non-root, this
518 	 * will fail. in.mpathd verifies that only commands coming
519 	 * from privileged ports succeed so that the ordinary user
520 	 * can't issue offline commands.
521 	 */
522 	on = 1;
523 	if (setsockopt(s, IPPROTO_TCP, TCP_ANONPRIVBIND, &on,
524 	    sizeof (on)) < 0) {
525 		perror("setsockopt : TCP_ANONPRIVBIND");
526 		exit(1);
527 	}
528 	switch (family) {
529 	case AF_INET:
530 		sin->sin_port = 0;
531 		sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
532 		addrlen = sizeof (struct sockaddr_in);
533 		break;
534 	case AF_INET6:
535 		sin6->sin6_port = 0;
536 		sin6->sin6_addr = loopback_addr;
537 		addrlen = sizeof (struct sockaddr_in6);
538 		break;
539 	}
540 	ret = bind(s, (struct sockaddr *)&ss, addrlen);
541 	if (ret != 0) {
542 		perror("bind");
543 		return (-1);
544 	}
545 	switch (family) {
546 	case AF_INET:
547 		sin->sin_port = htons(MPATHD_PORT);
548 		break;
549 	case AF_INET6:
550 		sin6->sin6_port = htons(MPATHD_PORT);
551 		break;
552 	}
553 	ret = connect(s, (struct sockaddr *)&ss, addrlen);
554 	if (ret != 0) {
555 		perror("connect");
556 		return (-1);
557 	}
558 	on = 0;
559 	if (setsockopt(s, IPPROTO_TCP, TCP_ANONPRIVBIND, &on,
560 	    sizeof (on)) < 0) {
561 		perror("setsockopt : TCP_ANONPRIVBIND");
562 		return (-1);
563 	}
564 	return (s);
565 }
566 
567 /*
568  * Bring down the interface specified by the name lifr->lifr_name.
569  *
570  * Returns -1 on failure. Returns 0 on success.
571  */
572 static int
573 if_down(int ifsock, struct lifreq *lifr)
574 {
575 	int ret;
576 
577 	ret = ioctl(ifsock, SIOCGLIFFLAGS, (caddr_t)lifr);
578 	if (ret < 0) {
579 		perror("ioctl: SIOCGLIFFLAGS");
580 		return (-1);
581 	}
582 
583 	/* IFF_OFFLINE was set to start with. Is it still there ? */
584 	if (!(lifr->lifr_flags & (IFF_OFFLINE))) {
585 		(void) fprintf(stderr, gettext("IFF_OFFLINE disappeared on "
586 		    "%s\n"), lifr->lifr_name);
587 		return (-1);
588 	}
589 	lifr->lifr_flags &= ~IFF_UP;
590 	ret = ioctl(ifsock, SIOCSLIFFLAGS, (caddr_t)lifr);
591 	if (ret < 0) {
592 		perror("ioctl: SIOCSLIFFLAGS");
593 		return (-1);
594 	}
595 	return (0);
596 }
597 
598 /*
599  * Bring up the interface specified by the name lifr->lifr_name.
600  *
601  * Returns -1 on failure. Returns 0 on success.
602  */
603 static int
604 if_up(int ifsock, struct lifreq *lifr)
605 {
606 	int ret;
607 	boolean_t zeroaddr = B_FALSE;
608 	struct sockaddr_in *addr;
609 
610 	ret = ioctl(ifsock, SIOCGLIFADDR, lifr);
611 	if (ret < 0) {
612 		perror("ioctl: SIOCGLIFADDR");
613 		return (-1);
614 	}
615 
616 	addr = (struct sockaddr_in *)&lifr->lifr_addr;
617 	switch (addr->sin_family) {
618 	case AF_INET:
619 		zeroaddr = (addr->sin_addr.s_addr == INADDR_ANY);
620 		break;
621 
622 	case AF_INET6:
623 		zeroaddr = IN6_IS_ADDR_UNSPECIFIED(
624 		    &((struct sockaddr_in6 *)addr)->sin6_addr);
625 		break;
626 
627 	default:
628 		break;
629 	}
630 
631 	ret = ioctl(ifsock, SIOCGLIFFLAGS, lifr);
632 	if (ret < 0) {
633 		perror("ioctl: SIOCGLIFFLAGS");
634 		return (-1);
635 	}
636 	/*
637 	 * Don't affect the state of addresses that failed back.
638 	 *
639 	 * XXX Link local addresses that are not marked IFF_NOFAILOVER
640 	 * will not be brought up. Link local addresses never failover.
641 	 * When the interface was offlined, we brought the link local
642 	 * address down. We will not bring it up now if IFF_NOFAILOVER
643 	 * is not marked. We check for IFF_NOFAILOVER below so that
644 	 * we want to maintain the state of all other addresses as it
645 	 * was before offline. Normally link local addresses are marked
646 	 * IFF_NOFAILOVER and hence this is not an issue. These can
647 	 * be fixed in future with RCM and it is beyond the scope
648 	 * of if_mpadm to maintain state and do this correctly.
649 	 */
650 	if (!(lifr->lifr_flags & IFF_NOFAILOVER))
651 		return (0);
652 
653 	/*
654 	 * When a data address associated with the physical interface itself
655 	 * is failed over (e.g., qfe0, rather than qfe0:1), the kernel must
656 	 * fill the ipif data structure for qfe0 with a placeholder entry (the
657 	 * "replacement ipif").	 Replacement ipif's cannot be brought IFF_UP
658 	 * (nor would it make any sense to do so), so we must be careful to
659 	 * skip them; thankfully they can be easily identified since they
660 	 * all have a zeroed address.
661 	 */
662 	if (zeroaddr)
663 		return (0);
664 
665 	/* IFF_OFFLINE was not set to start with. Is it there ? */
666 	if (lifr->lifr_flags & IFF_OFFLINE) {
667 		(void) fprintf(stderr,
668 		    gettext("IFF_OFFLINE set wrongly on %s\n"),
669 		    lifr->lifr_name);
670 		return (-1);
671 	}
672 	lifr->lifr_flags |= IFF_UP;
673 	ret = ioctl(ifsock, SIOCSLIFFLAGS, lifr);
674 	if (ret < 0) {
675 		perror("ioctl: SIOCSLIFFLAGS");
676 		return (-1);
677 	}
678 	return (0);
679 }
680