diff options
Diffstat (limited to 'agents/virt/common/tcp.c')
-rw-r--r-- | agents/virt/common/tcp.c | 386 |
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; +} |