xref: /freebsd/sbin/hastd/proto_common.c (revision e0455434b4f7568ba25a07e2d64b94b70a50150b)
132115b10SPawel Jakub Dawidek /*-
232115b10SPawel Jakub Dawidek  * Copyright (c) 2009-2010 The FreeBSD Foundation
3a7ebb3ebSPawel Jakub Dawidek  * Copyright (c) 2011 Pawel Jakub Dawidek <pawel@dawidek.net>
432115b10SPawel Jakub Dawidek  * All rights reserved.
532115b10SPawel Jakub Dawidek  *
632115b10SPawel Jakub Dawidek  * This software was developed by Pawel Jakub Dawidek under sponsorship from
732115b10SPawel Jakub Dawidek  * the FreeBSD Foundation.
832115b10SPawel Jakub Dawidek  *
932115b10SPawel Jakub Dawidek  * Redistribution and use in source and binary forms, with or without
1032115b10SPawel Jakub Dawidek  * modification, are permitted provided that the following conditions
1132115b10SPawel Jakub Dawidek  * are met:
1232115b10SPawel Jakub Dawidek  * 1. Redistributions of source code must retain the above copyright
1332115b10SPawel Jakub Dawidek  *    notice, this list of conditions and the following disclaimer.
1432115b10SPawel Jakub Dawidek  * 2. Redistributions in binary form must reproduce the above copyright
1532115b10SPawel Jakub Dawidek  *    notice, this list of conditions and the following disclaimer in the
1632115b10SPawel Jakub Dawidek  *    documentation and/or other materials provided with the distribution.
1732115b10SPawel Jakub Dawidek  *
1832115b10SPawel Jakub Dawidek  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
1932115b10SPawel Jakub Dawidek  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2032115b10SPawel Jakub Dawidek  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2132115b10SPawel Jakub Dawidek  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
2232115b10SPawel Jakub Dawidek  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2332115b10SPawel Jakub Dawidek  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2432115b10SPawel Jakub Dawidek  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2532115b10SPawel Jakub Dawidek  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2632115b10SPawel Jakub Dawidek  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2732115b10SPawel Jakub Dawidek  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2832115b10SPawel Jakub Dawidek  * SUCH DAMAGE.
2932115b10SPawel Jakub Dawidek  */
3032115b10SPawel Jakub Dawidek 
3132115b10SPawel Jakub Dawidek #include <sys/cdefs.h>
3232115b10SPawel Jakub Dawidek __FBSDID("$FreeBSD$");
3332115b10SPawel Jakub Dawidek 
3432115b10SPawel Jakub Dawidek #include <sys/types.h>
3532115b10SPawel Jakub Dawidek #include <sys/socket.h>
3632115b10SPawel Jakub Dawidek 
3732115b10SPawel Jakub Dawidek #include <errno.h>
38a7ebb3ebSPawel Jakub Dawidek #include <fcntl.h>
39a7ebb3ebSPawel Jakub Dawidek #include <stdbool.h>
4032115b10SPawel Jakub Dawidek #include <stdlib.h>
4132115b10SPawel Jakub Dawidek #include <strings.h>
42a7ebb3ebSPawel Jakub Dawidek #include <unistd.h>
4332115b10SPawel Jakub Dawidek 
442ec483c5SPawel Jakub Dawidek #include "pjdlog.h"
4532115b10SPawel Jakub Dawidek #include "proto_impl.h"
4632115b10SPawel Jakub Dawidek 
4732115b10SPawel Jakub Dawidek /* Maximum size of packet we want to use when sending data. */
4832115b10SPawel Jakub Dawidek #ifndef MAX_SEND_SIZE
4928df1f23SPawel Jakub Dawidek #define	MAX_SEND_SIZE	32768
5032115b10SPawel Jakub Dawidek #endif
5132115b10SPawel Jakub Dawidek 
52a7ebb3ebSPawel Jakub Dawidek static bool
53a7ebb3ebSPawel Jakub Dawidek blocking_socket(int sock)
54a7ebb3ebSPawel Jakub Dawidek {
55a7ebb3ebSPawel Jakub Dawidek 	int flags;
56a7ebb3ebSPawel Jakub Dawidek 
57a7ebb3ebSPawel Jakub Dawidek 	flags = fcntl(sock, F_GETFL);
58a7ebb3ebSPawel Jakub Dawidek 	PJDLOG_ASSERT(flags >= 0);
59a7ebb3ebSPawel Jakub Dawidek 	return ((flags & O_NONBLOCK) == 0);
60a7ebb3ebSPawel Jakub Dawidek }
61a7ebb3ebSPawel Jakub Dawidek 
6201ab52c0SPawel Jakub Dawidek static int
6301ab52c0SPawel Jakub Dawidek proto_descriptor_send(int sock, int fd)
648046c499SPawel Jakub Dawidek {
658046c499SPawel Jakub Dawidek 	unsigned char ctrl[CMSG_SPACE(sizeof(fd))];
668046c499SPawel Jakub Dawidek 	struct msghdr msg;
678046c499SPawel Jakub Dawidek 	struct cmsghdr *cmsg;
688046c499SPawel Jakub Dawidek 
698046c499SPawel Jakub Dawidek 	PJDLOG_ASSERT(sock >= 0);
708046c499SPawel Jakub Dawidek 	PJDLOG_ASSERT(fd >= 0);
718046c499SPawel Jakub Dawidek 
728046c499SPawel Jakub Dawidek 	bzero(&msg, sizeof(msg));
738046c499SPawel Jakub Dawidek 	bzero(&ctrl, sizeof(ctrl));
748046c499SPawel Jakub Dawidek 
758046c499SPawel Jakub Dawidek 	msg.msg_iov = NULL;
768046c499SPawel Jakub Dawidek 	msg.msg_iovlen = 0;
778046c499SPawel Jakub Dawidek 	msg.msg_control = ctrl;
788046c499SPawel Jakub Dawidek 	msg.msg_controllen = sizeof(ctrl);
798046c499SPawel Jakub Dawidek 
808046c499SPawel Jakub Dawidek 	cmsg = CMSG_FIRSTHDR(&msg);
818046c499SPawel Jakub Dawidek 	cmsg->cmsg_level = SOL_SOCKET;
828046c499SPawel Jakub Dawidek 	cmsg->cmsg_type = SCM_RIGHTS;
838046c499SPawel Jakub Dawidek 	cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
8494486ae2SPawel Jakub Dawidek 	bcopy(&fd, CMSG_DATA(cmsg), sizeof(fd));
858046c499SPawel Jakub Dawidek 
868046c499SPawel Jakub Dawidek 	if (sendmsg(sock, &msg, 0) == -1)
878046c499SPawel Jakub Dawidek 		return (errno);
888046c499SPawel Jakub Dawidek 
898046c499SPawel Jakub Dawidek 	return (0);
908046c499SPawel Jakub Dawidek }
918046c499SPawel Jakub Dawidek 
928046c499SPawel Jakub Dawidek int
9301ab52c0SPawel Jakub Dawidek proto_common_send(int sock, const unsigned char *data, size_t size, int fd)
9401ab52c0SPawel Jakub Dawidek {
9501ab52c0SPawel Jakub Dawidek 	ssize_t done;
9601ab52c0SPawel Jakub Dawidek 	size_t sendsize;
9741bb8514SPawel Jakub Dawidek 	int errcount = 0;
9801ab52c0SPawel Jakub Dawidek 
9901ab52c0SPawel Jakub Dawidek 	PJDLOG_ASSERT(sock >= 0);
1003a0b818fSPawel Jakub Dawidek 
1013a0b818fSPawel Jakub Dawidek 	if (data == NULL) {
1023a0b818fSPawel Jakub Dawidek 		/* The caller is just trying to decide about direction. */
1033a0b818fSPawel Jakub Dawidek 
1043a0b818fSPawel Jakub Dawidek 		PJDLOG_ASSERT(size == 0);
1053a0b818fSPawel Jakub Dawidek 
1063a0b818fSPawel Jakub Dawidek 		if (shutdown(sock, SHUT_RD) == -1)
1073a0b818fSPawel Jakub Dawidek 			return (errno);
1083a0b818fSPawel Jakub Dawidek 		return (0);
1093a0b818fSPawel Jakub Dawidek 	}
1103a0b818fSPawel Jakub Dawidek 
11101ab52c0SPawel Jakub Dawidek 	PJDLOG_ASSERT(data != NULL);
11201ab52c0SPawel Jakub Dawidek 	PJDLOG_ASSERT(size > 0);
11301ab52c0SPawel Jakub Dawidek 
11401ab52c0SPawel Jakub Dawidek 	do {
11501ab52c0SPawel Jakub Dawidek 		sendsize = size < MAX_SEND_SIZE ? size : MAX_SEND_SIZE;
11601ab52c0SPawel Jakub Dawidek 		done = send(sock, data, sendsize, MSG_NOSIGNAL);
117a7ebb3ebSPawel Jakub Dawidek 		if (done == 0) {
11801ab52c0SPawel Jakub Dawidek 			return (ENOTCONN);
119a7ebb3ebSPawel Jakub Dawidek 		} else if (done < 0) {
12001ab52c0SPawel Jakub Dawidek 			if (errno == EINTR)
12101ab52c0SPawel Jakub Dawidek 				continue;
12241bb8514SPawel Jakub Dawidek 			if (errno == ENOBUFS) {
12341bb8514SPawel Jakub Dawidek 				/*
12441bb8514SPawel Jakub Dawidek 				 * If there are no buffers we retry.
12541bb8514SPawel Jakub Dawidek 				 * After each try we increase delay before the
12641bb8514SPawel Jakub Dawidek 				 * next one and we give up after fifteen times.
12741bb8514SPawel Jakub Dawidek 				 * This gives 11s of total wait time.
12841bb8514SPawel Jakub Dawidek 				 */
12941bb8514SPawel Jakub Dawidek 				if (errcount == 15) {
13041bb8514SPawel Jakub Dawidek 					pjdlog_warning("Getting ENOBUFS errors for 11s on send(), giving up.");
13141bb8514SPawel Jakub Dawidek 				} else {
13241bb8514SPawel Jakub Dawidek 					if (errcount == 0)
13341bb8514SPawel Jakub Dawidek 						pjdlog_warning("Got ENOBUFS error on send(), retrying for a bit.");
13441bb8514SPawel Jakub Dawidek 					errcount++;
13541bb8514SPawel Jakub Dawidek 					usleep(100000 * errcount);
13641bb8514SPawel Jakub Dawidek 					continue;
13741bb8514SPawel Jakub Dawidek 				}
13841bb8514SPawel Jakub Dawidek 			}
139a7ebb3ebSPawel Jakub Dawidek 			/*
140a7ebb3ebSPawel Jakub Dawidek 			 * If this is blocking socket and we got EAGAIN, this
141a7ebb3ebSPawel Jakub Dawidek 			 * means the request timed out. Translate errno to
142a7ebb3ebSPawel Jakub Dawidek 			 * ETIMEDOUT, to give administrator a hint to
143a7ebb3ebSPawel Jakub Dawidek 			 * eventually increase timeout.
144a7ebb3ebSPawel Jakub Dawidek 			 */
145a7ebb3ebSPawel Jakub Dawidek 			if (errno == EAGAIN && blocking_socket(sock))
146a7ebb3ebSPawel Jakub Dawidek 				errno = ETIMEDOUT;
14701ab52c0SPawel Jakub Dawidek 			return (errno);
14801ab52c0SPawel Jakub Dawidek 		}
14901ab52c0SPawel Jakub Dawidek 		data += done;
15001ab52c0SPawel Jakub Dawidek 		size -= done;
15101ab52c0SPawel Jakub Dawidek 	} while (size > 0);
15241bb8514SPawel Jakub Dawidek 	if (errcount > 0) {
15341bb8514SPawel Jakub Dawidek 		pjdlog_info("Data sent successfully after %d ENOBUFS error%s.",
15441bb8514SPawel Jakub Dawidek 		    errcount, errcount == 1 ? "" : "s");
15541bb8514SPawel Jakub Dawidek 	}
15601ab52c0SPawel Jakub Dawidek 
15701ab52c0SPawel Jakub Dawidek 	if (fd == -1)
15801ab52c0SPawel Jakub Dawidek 		return (0);
15901ab52c0SPawel Jakub Dawidek 	return (proto_descriptor_send(sock, fd));
16001ab52c0SPawel Jakub Dawidek }
16101ab52c0SPawel Jakub Dawidek 
16201ab52c0SPawel Jakub Dawidek static int
16301ab52c0SPawel Jakub Dawidek proto_descriptor_recv(int sock, int *fdp)
1648046c499SPawel Jakub Dawidek {
1658046c499SPawel Jakub Dawidek 	unsigned char ctrl[CMSG_SPACE(sizeof(*fdp))];
1668046c499SPawel Jakub Dawidek 	struct msghdr msg;
1678046c499SPawel Jakub Dawidek 	struct cmsghdr *cmsg;
1688046c499SPawel Jakub Dawidek 
1698046c499SPawel Jakub Dawidek 	PJDLOG_ASSERT(sock >= 0);
1708046c499SPawel Jakub Dawidek 	PJDLOG_ASSERT(fdp != NULL);
1718046c499SPawel Jakub Dawidek 
1728046c499SPawel Jakub Dawidek 	bzero(&msg, sizeof(msg));
1738046c499SPawel Jakub Dawidek 	bzero(&ctrl, sizeof(ctrl));
1748046c499SPawel Jakub Dawidek 
1758046c499SPawel Jakub Dawidek 	msg.msg_iov = NULL;
1768046c499SPawel Jakub Dawidek 	msg.msg_iovlen = 0;
1778046c499SPawel Jakub Dawidek 	msg.msg_control = ctrl;
1788046c499SPawel Jakub Dawidek 	msg.msg_controllen = sizeof(ctrl);
1798046c499SPawel Jakub Dawidek 
1808046c499SPawel Jakub Dawidek 	if (recvmsg(sock, &msg, 0) == -1)
1818046c499SPawel Jakub Dawidek 		return (errno);
1828046c499SPawel Jakub Dawidek 
18394486ae2SPawel Jakub Dawidek 	cmsg = CMSG_FIRSTHDR(&msg);
18494486ae2SPawel Jakub Dawidek 	if (cmsg->cmsg_level != SOL_SOCKET ||
18501ab52c0SPawel Jakub Dawidek 	    cmsg->cmsg_type != SCM_RIGHTS) {
18694486ae2SPawel Jakub Dawidek 		return (EINVAL);
1878046c499SPawel Jakub Dawidek 	}
18894486ae2SPawel Jakub Dawidek 	bcopy(CMSG_DATA(cmsg), fdp, sizeof(*fdp));
1898046c499SPawel Jakub Dawidek 
19094486ae2SPawel Jakub Dawidek 	return (0);
1918046c499SPawel Jakub Dawidek }
19201ab52c0SPawel Jakub Dawidek 
19301ab52c0SPawel Jakub Dawidek int
19401ab52c0SPawel Jakub Dawidek proto_common_recv(int sock, unsigned char *data, size_t size, int *fdp)
19501ab52c0SPawel Jakub Dawidek {
19601ab52c0SPawel Jakub Dawidek 	ssize_t done;
19701ab52c0SPawel Jakub Dawidek 
19801ab52c0SPawel Jakub Dawidek 	PJDLOG_ASSERT(sock >= 0);
1993a0b818fSPawel Jakub Dawidek 
2003a0b818fSPawel Jakub Dawidek 	if (data == NULL) {
2013a0b818fSPawel Jakub Dawidek 		/* The caller is just trying to decide about direction. */
2023a0b818fSPawel Jakub Dawidek 
2033a0b818fSPawel Jakub Dawidek 		PJDLOG_ASSERT(size == 0);
2043a0b818fSPawel Jakub Dawidek 
2053a0b818fSPawel Jakub Dawidek 		if (shutdown(sock, SHUT_WR) == -1)
2063a0b818fSPawel Jakub Dawidek 			return (errno);
2073a0b818fSPawel Jakub Dawidek 		return (0);
2083a0b818fSPawel Jakub Dawidek 	}
2093a0b818fSPawel Jakub Dawidek 
21001ab52c0SPawel Jakub Dawidek 	PJDLOG_ASSERT(data != NULL);
21101ab52c0SPawel Jakub Dawidek 	PJDLOG_ASSERT(size > 0);
21201ab52c0SPawel Jakub Dawidek 
21301ab52c0SPawel Jakub Dawidek 	do {
214*e0455434SMaxim Sobolev 		done = recv(sock, data, size, MSG_WAITALL);
215*e0455434SMaxim Sobolev 	} while (done == -1 && errno == EINTR);
216a7ebb3ebSPawel Jakub Dawidek 	if (done == 0) {
21701ab52c0SPawel Jakub Dawidek 		return (ENOTCONN);
218a7ebb3ebSPawel Jakub Dawidek 	} else if (done < 0) {
219a7ebb3ebSPawel Jakub Dawidek 		/*
220a7ebb3ebSPawel Jakub Dawidek 		 * If this is blocking socket and we got EAGAIN, this
221a7ebb3ebSPawel Jakub Dawidek 		 * means the request timed out. Translate errno to
222a7ebb3ebSPawel Jakub Dawidek 		 * ETIMEDOUT, to give administrator a hint to
223a7ebb3ebSPawel Jakub Dawidek 		 * eventually increase timeout.
224a7ebb3ebSPawel Jakub Dawidek 		 */
225a7ebb3ebSPawel Jakub Dawidek 		if (errno == EAGAIN && blocking_socket(sock))
226a7ebb3ebSPawel Jakub Dawidek 			errno = ETIMEDOUT;
22701ab52c0SPawel Jakub Dawidek 		return (errno);
228a7ebb3ebSPawel Jakub Dawidek 	}
22901ab52c0SPawel Jakub Dawidek 	if (fdp == NULL)
23001ab52c0SPawel Jakub Dawidek 		return (0);
23101ab52c0SPawel Jakub Dawidek 	return (proto_descriptor_recv(sock, fdp));
23201ab52c0SPawel Jakub Dawidek }
233