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