summaryrefslogtreecommitdiffstats
path: root/agents/virt/common/tcp.c
diff options
context:
space:
mode:
Diffstat (limited to 'agents/virt/common/tcp.c')
-rw-r--r--agents/virt/common/tcp.c386
1 files changed, 386 insertions, 0 deletions
diff --git a/agents/virt/common/tcp.c b/agents/virt/common/tcp.c
new file mode 100644
index 0000000..5796770
--- /dev/null
+++ b/agents/virt/common/tcp.c
@@ -0,0 +1,386 @@
+/*
+ Copyright Red Hat, Inc. 2002-2004, 2006
+ Copyright Mission Critical Linux, 2000
+
+ This program 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, 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; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 675 Mass Ave, Cambridge,
+ MA 02139, USA.
+*/
+/** @file
+ *
+ * @author Lon H. Hohberger <lhh at redhat.com>
+ * @author Jeff Moyer <jmoyer at redhat.com>
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include "tcp.h"
+#include "debug.h"
+
+static int connect_nb(int fd, struct sockaddr *dest, socklen_t len, int timeout);
+static int get_addr(const char *hostname, int family, struct sockaddr_storage *addr);
+
+/**
+ Set close-on-exec bit option for a socket.
+
+ @param fd Socket to set CLOEXEC flag
+ @return 0 on success, -1 on failure
+ @see fcntl
+ */
+static int
+set_cloexec(int fd)
+{
+ int flags = fcntl(fd, F_GETFD, 0);
+ flags |= FD_CLOEXEC;
+ return fcntl(fd, F_SETFD, flags);
+}
+
+
+/**
+ Bind to a port on the local IPv6 stack
+
+ @param addr_str Address to listen on, NULL for inaddr6_any
+ @param port Port to bind to
+ @param backlog same as backlog for listen(2)
+ @return 0 on success, -1 on failure
+ @see ipv4_bind
+ */
+int
+ipv6_listen(const char *addr_str, uint16_t port, int backlog)
+{
+ struct sockaddr_in6 _sin6;
+ int fd, opt=1;
+
+ dbg_printf(4, "%s: Setting up ipv6 listen socket for %s:%d\n",
+ __FUNCTION__, addr_str, port);
+
+ memset(&_sin6, 0, sizeof(_sin6));
+ _sin6.sin6_family = PF_INET6;
+ _sin6.sin6_port = htons(port);
+ _sin6.sin6_flowinfo = 0;
+
+ if (addr_str == NULL) {
+ _sin6.sin6_addr = in6addr_any;
+ } else {
+ struct sockaddr_storage ss;
+
+ if (get_addr(addr_str, AF_INET6, &ss) == -1) {
+ dbg_printf(4, "%s: Can't get addr for %s\n",
+ __FUNCTION__, addr_str);
+ return -1;
+ }
+
+ memcpy(&_sin6.sin6_addr,
+ &((struct sockaddr_in6 *)&ss)->sin6_addr, sizeof(_sin6.sin6_addr));
+ }
+
+ fd = socket(PF_INET6, SOCK_STREAM, 0);
+ if (fd < 0)
+ return -1;
+
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof (opt)) < 0) {
+ close(fd);
+ return -1;
+ }
+
+ if (set_cloexec(fd) < 0) {
+ close(fd);
+ return -1;
+ }
+
+ if (bind(fd, (struct sockaddr *)&_sin6, sizeof(_sin6)) < 0) {
+ close(fd);
+ return -1;
+ }
+
+ if (listen(fd, backlog) < 0){
+ close(fd);
+ return -1;
+ }
+
+ dbg_printf(4, "%s: Success; fd = %d\n", __FUNCTION__, fd);
+ return fd;
+}
+
+
+/**
+ Bind to a port on the local IPv4 stack
+
+ @param addr_str Address to listen on, NULL for inaddr_any
+ @param port Port to bind to
+ @param backlog same as backlog for listen(2)
+ @return 0 on success, -1 on failure
+ @see ipv6_bind
+ */
+int
+ipv4_listen(const char *addr_str, uint16_t port, int backlog)
+{
+ struct sockaddr_in _sin;
+ int fd, opt=1;
+
+ dbg_printf(4, "%s: Setting up ipv4 listen socket for %s:%d\n",
+ __FUNCTION__, addr_str, port);
+
+ _sin.sin_family = PF_INET;
+ _sin.sin_port = htons(port);
+
+ if (addr_str == NULL) {
+ _sin.sin_addr.s_addr = htonl(INADDR_ANY);
+ } else {
+ struct sockaddr_storage ss;
+
+ if (get_addr(addr_str, AF_INET, &ss) == -1) {
+ dbg_printf(4, "%s: Can't get addr for %s\n",
+ __FUNCTION__, addr_str);
+ return -1;
+ }
+
+ memcpy(&_sin.sin_addr,
+ &((struct sockaddr_in *)&ss)->sin_addr, sizeof(_sin.sin_addr));
+ }
+
+ fd = socket(PF_INET, SOCK_STREAM, 0);
+ if (fd < 0)
+ return -1;
+
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof (opt)) < 0) {
+ close(fd);
+ return -1;
+ }
+
+ if (set_cloexec(fd) < 0) {
+ close(fd);
+ return -1;
+ }
+
+ if (bind(fd, (struct sockaddr *)&_sin, sizeof(_sin)) < 0) {
+ close(fd);
+ return -1;
+ }
+
+ if (listen(fd, backlog) < 0) {
+ close(fd);
+ return -1;
+ }
+
+ dbg_printf(4, "%s: Success; fd = %d\n", __FUNCTION__, fd);
+ return fd;
+}
+
+
+
+/**
+ Connect via ipv6 socket to a given IP address and port.
+
+ @param in6_addr IPv6 address to connect to
+ @param port Port to connect to
+ @param timeout Timeout, in seconds, to wait for a completed
+ connection
+ @return 0 on success, -1 on failure
+ @see connect_nb, ipv4_connect
+ */
+int
+ipv6_connect(struct in6_addr *in6_addr, uint16_t port, int timeout)
+{
+ struct sockaddr_in6 _sin6;
+ int fd, ret;
+
+ dbg_printf(4, "%s: Connecting to client\n", __FUNCTION__);
+ fd = socket(PF_INET6, SOCK_STREAM, 0);
+ if (fd < 0)
+ return -1;
+
+ memset(&_sin6, 0, sizeof(_sin6));
+ _sin6.sin6_family = PF_INET6;
+ _sin6.sin6_port = htons(port);
+ _sin6.sin6_flowinfo = 0;
+ memcpy(&_sin6.sin6_addr, in6_addr, sizeof(_sin6.sin6_addr));
+
+ ret = connect_nb(fd, (struct sockaddr *)&_sin6, sizeof(_sin6), timeout);
+ if (ret < 0) {
+ close(fd);
+ return -1;
+ }
+ dbg_printf(4, "%s: Success; fd = %d\n", __FUNCTION__, fd);
+ return fd;
+}
+
+
+/**
+ Connect via ipv4 socket to a given IP address and port.
+
+ @param in_addr IPv4 address to connect to
+ @param port Port to connect to
+ @param timeout Timeout, in seconds, to wait for a completed
+ connection
+ @return 0 on success, -1 on failure
+ @see connect_nb, ipv6_connect
+ */
+int
+ipv4_connect(struct in_addr *in_addr, uint16_t port, int timeout)
+{
+ struct sockaddr_in _sin;
+ int fd, ret;
+
+ dbg_printf(4, "%s: Connecting to client\n", __FUNCTION__);
+ fd = socket(PF_INET, SOCK_STREAM, 0);
+ if (fd < 0)
+ return -1;
+
+ _sin.sin_family = PF_INET;
+ _sin.sin_port = htons(port);
+ memcpy(&_sin.sin_addr, in_addr, sizeof(_sin.sin_addr));
+
+ ret = connect_nb(fd, (struct sockaddr *)&_sin, sizeof(_sin), timeout);
+ if (ret < 0) {
+ close(fd);
+ return -1;
+ }
+
+ dbg_printf(4, "%s: Success; fd = %d\n", __FUNCTION__, fd);
+ return fd;
+}
+
+
+/**
+ Connect in a non-blocking fashion to the designated address.
+
+ @param fd File descriptor to connect
+ @param dest sockaddr (ipv4 or ipv6) to connect to.
+ @param len Length of dest
+ @param timeout Timeout, in seconds, to wait for a completed
+ connection.
+ @return 0 on success, -1 on failure.
+ */
+static int
+connect_nb(int fd, struct sockaddr *dest, socklen_t len, int timeout)
+{
+ int ret, flags = 1, err;
+ unsigned l;
+ fd_set rfds, wfds;
+ struct timeval tv;
+
+ /*
+ * Use TCP Keepalive
+ */
+ if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&flags,
+ sizeof(flags))<0) {
+ return -1;
+ }
+
+ /*
+ Set up non-blocking connect
+ */
+ flags = fcntl(fd, F_GETFL, 0);
+ if (flags < 0) {
+ return -1;
+ }
+ if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
+ return -1;
+ }
+
+ ret = connect(fd, dest, len);
+
+ if ((ret < 0) && (errno != EINPROGRESS))
+ return -1;
+
+ if (ret != 0) {
+ FD_ZERO(&rfds);
+ FD_SET(fd, &rfds);
+ FD_ZERO(&wfds);
+ FD_SET(fd, &wfds);
+
+ tv.tv_sec = timeout;
+ tv.tv_usec = 0;
+
+ if (select(fd + 1, &rfds, &wfds, NULL, &tv) == 0) {
+ errno = ETIMEDOUT;
+ return -1;
+ }
+ /* XXX check for -1 from select */
+
+ if (FD_ISSET(fd, &rfds) || FD_ISSET(fd, &wfds)) {
+ l = sizeof(err);
+ if (getsockopt(fd, SOL_SOCKET, SO_ERROR,
+ (void *)&err, &l) < 0) {
+ close(fd);
+ return -1;
+ }
+
+ if (err != 0) {
+ close(fd);
+ errno = err;
+ return -1;
+ }
+
+ if (fcntl(fd, F_SETFL, flags) < 0) {
+ close(fd);
+ return -1;
+ }
+ return 0;
+ }
+ }
+
+ errno = EIO;
+ return -1;
+}
+
+static int
+get_addr(const char *hostname, int family, struct sockaddr_storage *addr)
+{
+ struct addrinfo *res;
+ size_t len;
+ struct addrinfo ai;
+
+ memset(&ai, 0, sizeof(ai));
+ ai.ai_family = family;
+
+ if (getaddrinfo(hostname, NULL, &ai, &res) != 0)
+ return -1;
+
+ switch (res->ai_addr->sa_family) {
+ case AF_INET:
+ len = sizeof(struct sockaddr_in);
+ break;
+ case AF_INET6:
+ len = sizeof(struct sockaddr_in6);
+ break;
+ default:
+ goto out_fail;
+ }
+
+ if (len < (size_t) res->ai_addrlen)
+ goto out_fail;
+
+ memcpy(addr, res->ai_addr, res->ai_addrlen);
+ freeaddrinfo(res);
+
+ return 0;
+
+out_fail:
+ freeaddrinfo(res);
+ return -1;
+}