summaryrefslogtreecommitdiffstats
path: root/src/lib/socket.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lib/socket.c394
1 files changed, 394 insertions, 0 deletions
diff --git a/src/lib/socket.c b/src/lib/socket.c
new file mode 100644
index 0000000..3c88b78
--- /dev/null
+++ b/src/lib/socket.c
@@ -0,0 +1,394 @@
+/*
+ * This program is is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ * @file socket.c
+ * @brief Functions for establishing and managing low level sockets.
+ *
+ * @author Arran Cudbard-Bell <a.cudbardb@freeradius.org>
+ * @author Alan DeKok <aland@freeradius.org>
+ *
+ * @copyright 2015 The FreeRADIUS project
+ */
+ #include <freeradius-devel/libradius.h>
+
+#ifdef HAVE_SYS_UN_H
+# include <sys/un.h>
+# ifndef SUN_LEN
+# define SUN_LEN(su) (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
+# endif
+
+/** Open a Unix socket
+ *
+ * @note If the file doesn't exist then errno will be set to ENOENT.
+ *
+ * The following code demonstrates using this function with a connection timeout:
+ @code {.c}
+ sockfd = fr_socket_client_unix(path, true);
+ if (sockfd < 0) {
+ fr_perror();
+ exit(1);
+}
+ if ((errno == EINPROGRESS) && (fr_socket_wait_for_connect(sockfd, timeout) < 0)) {
+ error:
+ fr_perror();
+ close(sockfd);
+ goto error;
+}
+//Optionally, if blocking operation is required
+ if (fr_blocking(sockfd) < 0) goto error;
+ @endcode
+ *
+ * @param path to the file bound to the unix socket.
+ * @param async Whether to set the socket to nonblocking, allowing use of
+ * #fr_socket_wait_for_connect.
+ * @return socket FD on success, -1 on error.
+ */
+int fr_socket_client_unix(char const *path, bool async)
+{
+ int sockfd = -1;
+ size_t len;
+ socklen_t socklen;
+ struct sockaddr_un saremote;
+
+ len = strlen(path);
+ if (len >= sizeof(saremote.sun_path)) {
+ fr_strerror_printf("Path too long, maximum length is %zu", sizeof(saremote.sun_path) - 1);
+ errno = EINVAL;
+ return -1;
+ }
+
+ sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (sockfd < 0) {
+ fr_strerror_printf("Failed creating UNIX socket: %s", fr_syserror(errno));
+ return -1;
+ }
+
+ if (async && (fr_nonblock(sockfd) < 0)) {
+ close(sockfd);
+ return -1;
+ }
+
+ saremote.sun_family = AF_UNIX;
+ memcpy(saremote.sun_path, path, len + 1); /* SUN_LEN does strlen */
+
+ socklen = SUN_LEN(&saremote);
+
+ /*
+ * Although we ignore SIGPIPE, some operating systems
+ * like BSD and OSX ignore the ignoring.
+ *
+ * Fortunately, those operating systems usually support
+ * SO_NOSIGPIPE, to prevent them raising the signal in
+ * the first place.
+ */
+#ifdef SO_NOSIGPIPE
+ {
+ int set = 1;
+
+ setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));
+ }
+#endif
+
+ if (connect(sockfd, (struct sockaddr *)&saremote, socklen) < 0) {
+ /*
+ * POSIX says the only time we will get this,
+ * is if the socket has been marked as
+ * nonblocking. This is not an error, the caller
+ * must check the state of errno, and wait for
+ * the connection to complete.
+ */
+ if (errno == EINPROGRESS) return sockfd;
+
+ close(sockfd);
+ fr_strerror_printf("Failed connecting to %s: %s", path, fr_syserror(errno));
+
+ return -1;
+ }
+ return sockfd;
+}
+#else
+int fr_socket_client_unix(UNUSED char const *path, UNUSED bool async)
+{
+ fprintf(stderr, "Unix domain sockets not supported on this system");
+ return -1;
+}
+#endif /* WITH_SYS_UN_H */
+
+/** Establish a connected TCP socket
+ *
+ * The following code demonstrates using this function with a connection timeout:
+ @code {.c}
+ sockfd = fr_socket_client_tcp(NULL, ipaddr, port, true);
+ if (sockfd < 0) {
+ fr_perror();
+ exit(1);
+}
+ if ((errno == EINPROGRESS) && (fr_socket_wait_for_connect(sockfd, timeout) < 0)) {
+ error:
+ fr_perror();
+ close(sockfd);
+ goto error;
+}
+//Optionally, if blocking operation is required
+ if (fr_blocking(sockfd) < 0) goto error;
+ @endcode
+ *
+ * @param src_ipaddr to bind socket to, may be NULL if socket is not bound to any specific
+ * address.
+ * @param dst_ipaddr Where to connect to.
+ * @param dst_port Where to connect to.
+ * @param async Whether to set the socket to nonblocking, allowing use of
+ * #fr_socket_wait_for_connect.
+ * @return FD on success, -1 on failure.
+ */
+int fr_socket_client_tcp(fr_ipaddr_t *src_ipaddr, fr_ipaddr_t *dst_ipaddr, uint16_t dst_port, bool async)
+{
+ int sockfd;
+ struct sockaddr_storage salocal;
+ socklen_t salen;
+
+ if (!dst_ipaddr) return -1;
+
+ sockfd = socket(dst_ipaddr->af, SOCK_STREAM, 0);
+ if (sockfd < 0) {
+ fr_strerror_printf("Error creating TCP socket: %s", fr_syserror(errno));
+ return sockfd;
+ }
+
+ if (async && (fr_nonblock(sockfd) < 0)) {
+ close(sockfd);
+ return -1;
+ }
+
+ /*
+ * Allow the caller to bind us to a specific source IP.
+ */
+ if (src_ipaddr && (src_ipaddr->af != AF_UNSPEC)) {
+ if (!fr_ipaddr2sockaddr(src_ipaddr, 0, &salocal, &salen)) {
+ close(sockfd);
+ return -1;
+ }
+
+ if (bind(sockfd, (struct sockaddr *) &salocal, salen) < 0) {
+ fr_strerror_printf("Failure binding to IP: %s", fr_syserror(errno));
+ close(sockfd);
+ return -1;
+ }
+ }
+
+ if (!fr_ipaddr2sockaddr(dst_ipaddr, dst_port, &salocal, &salen)) {
+ close(sockfd);
+ return -1;
+ }
+
+ /*
+ * Although we ignore SIGPIPE, some operating systems
+ * like BSD and OSX ignore the ignoring.
+ *
+ * Fortunately, those operating systems usually support
+ * SO_NOSIGPIPE, to prevent them raising the signal in
+ * the first place.
+ */
+#ifdef SO_NOSIGPIPE
+ {
+ int set = 1;
+
+ setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));
+ }
+#endif
+
+ if (connect(sockfd, (struct sockaddr *) &salocal, salen) < 0) {
+ /*
+ * POSIX says the only time we will get this,
+ * is if the socket has been marked as
+ * nonblocking. This is not an error, the caller
+ * must check the state of errno, and wait for
+ * the connection to complete.
+ */
+ if (errno == EINPROGRESS) return sockfd;
+
+ fr_strerror_printf("Failed connecting socket: %s", fr_syserror(errno));
+ close(sockfd);
+ return -1;
+ }
+
+ return sockfd;
+}
+
+/** Establish a connected UDP socket
+ *
+ * Connected UDP sockets can be used with write(), unlike unconnected sockets
+ * which must be used with sendto and recvfrom.
+ *
+ * The following code demonstrates using this function with a connection timeout:
+ @code {.c}
+ sockfd = fr_socket_client_udp(NULL, ipaddr, port, true);
+ if (sockfd < 0) {
+ fr_perror();
+ exit(1);
+}
+ if ((errno == EINPROGRESS) && (fr_socket_wait_for_connect(sockfd, timeout) < 0)) {
+ error:
+ fr_perror();
+ close(sockfd);
+ goto error;
+}
+//Optionally, if blocking operation is required
+ if (fr_blocking(sockfd) < 0) goto error;
+ @endcode
+ *
+ * @param src_ipaddr to bind socket to, may be NULL if socket is not bound to any specific
+ * address.
+ * @param dst_ipaddr Where to send datagrams.
+ * @param dst_port Where to send datagrams.
+ * @param async Whether to set the socket to nonblocking, allowing use of
+ * #fr_socket_wait_for_connect.
+ * @return FD on success, -1 on failure.
+ */
+int fr_socket_client_udp(fr_ipaddr_t *src_ipaddr, fr_ipaddr_t *dst_ipaddr, uint16_t dst_port, bool async)
+{
+ int sockfd;
+ struct sockaddr_storage salocal;
+ socklen_t salen;
+
+ if (!dst_ipaddr) return -1;
+
+ sockfd = socket(dst_ipaddr->af, SOCK_DGRAM, 0);
+ if (sockfd < 0) {
+ fr_strerror_printf("Error creating UDP socket: %s", fr_syserror(errno));
+ return sockfd;
+ }
+
+ if (async && (fr_nonblock(sockfd) < 0)) {
+ close(sockfd);
+ return -1;
+ }
+
+ /*
+ * Allow the caller to bind us to a specific source IP.
+ */
+ if (src_ipaddr && (src_ipaddr->af != AF_UNSPEC)) {
+ if (!fr_ipaddr2sockaddr(src_ipaddr, 0, &salocal, &salen)) {
+ close(sockfd);
+ return -1;
+ }
+
+ if (bind(sockfd, (struct sockaddr *) &salocal, salen) < 0) {
+ fr_strerror_printf("Failure binding to IP: %s", fr_syserror(errno));
+ close(sockfd);
+ return -1;
+ }
+ }
+
+ if (!fr_ipaddr2sockaddr(dst_ipaddr, dst_port, &salocal, &salen)) {
+ close(sockfd);
+ return -1;
+ }
+
+ /*
+ * Although we ignore SIGPIPE, some operating systems
+ * like BSD and OSX ignore the ignoring.
+ *
+ * Fortunately, those operating systems usually support
+ * SO_NOSIGPIPE, to prevent them raising the signal in
+ * the first place.
+ */
+#ifdef SO_NOSIGPIPE
+ {
+ int set = 1;
+
+ setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));
+ }
+#endif
+
+ if (connect(sockfd, (struct sockaddr *) &salocal, salen) < 0) {
+ /*
+ * POSIX says the only time we will get this,
+ * is if the socket has been marked as
+ * nonblocking. This is not an error, the caller
+ * must check the state of errno, and wait for
+ * the connection to complete.
+ */
+ if (errno == EINPROGRESS) return sockfd;
+
+ fr_strerror_printf("Failed connecting socket: %s", fr_syserror(errno));
+ close(sockfd);
+ return -1;
+ }
+
+ return sockfd;
+}
+
+/** Wait for a socket to be connected, with an optional timeout
+ *
+ * @note On error the caller is expected to ``close(sockfd)``.
+ *
+ * @param sockfd the socket to wait on.
+ * @param timeout How long to wait for socket to open.
+ * @return 0 on success, -1 on connection error, -2 on timeout, -3 on select error.
+ */
+int fr_socket_wait_for_connect(int sockfd, struct timeval *timeout)
+{
+ int ret;
+ fd_set error_set;
+ fd_set write_set; /* POSIX says sockets are open when they become writeable */
+
+ FD_ZERO(&error_set);
+ FD_ZERO(&write_set);
+
+ FD_SET(sockfd, &error_set);
+ FD_SET(sockfd, &write_set);
+
+ /* Don't let signals mess up the select */
+ do {
+ ret = select(sockfd + 1, NULL, &write_set, &error_set, timeout);
+ } while ((ret == -1) && (errno == EINTR));
+
+ switch (ret) {
+ case 1: /* ok (maybe) */
+ {
+ int error;
+ socklen_t socklen = sizeof(error);
+
+ if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *)&error, &socklen)) {
+ fr_strerror_printf("Failed connecting socket: %s", fr_syserror(errno));
+ return -1;
+ }
+
+ if (FD_ISSET(sockfd, &error_set)) {
+ fr_strerror_printf("Failed connecting socket: Unknown error");
+ return -1;
+ }
+ }
+ return 0;
+
+ case 0: /* timeout */
+ if (!fr_assert(timeout)) return -1;
+ fr_strerror_printf("Connection timed out after %" PRIu64"ms",
+ (timeout->tv_sec * (uint64_t)1000) + (timeout->tv_usec / 1000));
+ return -2;
+
+ case -1: /* select error */
+ fr_strerror_printf("Failed waiting for connection: %s", fr_syserror(errno));
+ return -3;
+
+ default:
+ fr_assert(0);
+ return -1;
+ }
+}