/* 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 * @author Jeff Moyer */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #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; }