/* silcunixnet.c Author: Pekka Riikonen Copyright (C) 1997 - 2005 Pekka Riikonen The contents of this file are subject to one of the Licenses specified in the COPYING file; You may not use this file except in compliance with the License. The software distributed under the License is distributed on an "AS IS" basis, in the hope that it will be useful, but WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the COPYING file for more information. */ /* $Id: silcunixnet.c,v 1.16.2.2 2005/04/30 15:31:27 priikone Exp $ */ #include "silcincludes.h" #include "silcnet.h" #ifdef HAVE_IPV6 #define SIZEOF_SOCKADDR(so) ((so).sa.sa_family == AF_INET6 ? \ sizeof(so.sin6) : sizeof(so.sin)) #else #define SIZEOF_SOCKADDR(so) (sizeof(so.sin)) #endif typedef union { struct sockaddr sa; struct sockaddr_in sin; #ifdef HAVE_IPV6 struct sockaddr_in6 sin6; #endif } SilcSockaddr; static bool silc_net_set_sockaddr(SilcSockaddr *addr, const char *ip_addr, int port) { int len; memset(addr, 0, sizeof(*addr)); /* Check for IPv4 and IPv6 addresses */ if (ip_addr) { if (!silc_net_is_ip(ip_addr)) { SILC_LOG_ERROR(("%s is not IP address", ip_addr)); return FALSE; } if (silc_net_is_ip4(ip_addr)) { /* IPv4 address */ len = sizeof(addr->sin.sin_addr); silc_net_addr2bin(ip_addr, (unsigned char *)&addr->sin.sin_addr.s_addr, len); addr->sin.sin_family = AF_INET; addr->sin.sin_port = port ? htons(port) : 0; } else { #ifdef HAVE_IPV6 /* IPv6 address */ len = sizeof(addr->sin6.sin6_addr); silc_net_addr2bin(ip_addr, (unsigned char *)&addr->sin6.sin6_addr, len); addr->sin6.sin6_family = AF_INET6; addr->sin6.sin6_port = port ? htons(port) : 0; #else SILC_LOG_ERROR(("IPv6 support is not compiled in")); return FALSE; #endif } } else { /* Any address */ addr->sin.sin_family = AF_INET; addr->sin.sin_addr.s_addr = INADDR_ANY; if (port) addr->sin.sin_port = htons(port); } return TRUE; } /* This function creates server or daemon or listener or what ever. This does not fork a new process, it must be done by the caller if caller wants to create a child process. This is used by the SILC server. If argument `ip_addr' is NULL `any' address will be used. Returns the created socket or -1 on error. */ int silc_net_create_server(int port, const char *ip_addr) { int sock, rval; SilcSockaddr server; SILC_LOG_DEBUG(("Creating a new server listener")); /* Set sockaddr for server */ if (!silc_net_set_sockaddr(&server, ip_addr, port)) return -1; /* Create the socket */ sock = socket(server.sin.sin_family, SOCK_STREAM, 0); if (sock < 0) { SILC_LOG_ERROR(("Cannot create socket: %s", strerror(errno))); return -1; } /* Set the socket options */ rval = silc_net_set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 1); if (rval < 0) { SILC_LOG_ERROR(("Cannot set socket options: %s", strerror(errno))); return -1; } /* Bind the server socket */ rval = bind(sock, &server.sa, SIZEOF_SOCKADDR(server)); if (rval < 0) { SILC_LOG_DEBUG(("Cannot bind socket: %s", strerror(errno))); return -1; } /* Specify that we are listenning */ rval = listen(sock, 5); if (rval < 0) { SILC_LOG_ERROR(("Cannot set socket listenning: %s", strerror(errno))); return -1; } /* Set the server socket to non-blocking mode */ silc_net_set_socket_nonblock(sock); SILC_LOG_DEBUG(("Server listener created, fd=%d", sock)); return sock; } /* Closes the server by closing the socket connection. */ void silc_net_close_server(int sock) { shutdown(sock, 2); close(sock); SILC_LOG_DEBUG(("Server socket closed")); } /* Creates a connection (TCP/IP) to a remote host. Returns the connection socket or -1 on error. This blocks the process while trying to create the connection. */ int silc_net_create_connection(const char *local_ip, int port, const char *host) { int sock, rval; char ip_addr[64]; SilcSockaddr desthost; bool prefer_ipv6 = TRUE; SILC_LOG_DEBUG(("Creating connection to host %s port %d", host, port)); /* Do host lookup */ retry: if (!silc_net_gethostbyname(host, prefer_ipv6, ip_addr, sizeof(ip_addr))) { SILC_LOG_ERROR(("Network (%s) unreachable: could not resolve the " "IP address", host)); return -1; } /* Set sockaddr for this connection */ if (!silc_net_set_sockaddr(&desthost, ip_addr, port)) return -1; /* Create the connection socket */ sock = socket(desthost.sin.sin_family, SOCK_STREAM, 0); if (sock < 0) { /* If address is IPv6, then fallback to IPv4 and see whether we can do better with that on socket creation. */ if (prefer_ipv6 && silc_net_is_ip6(ip_addr)) { prefer_ipv6 = FALSE; goto retry; } SILC_LOG_ERROR(("Cannot create socket: %s", strerror(errno))); return -1; } /* Bind to the local address if provided */ if (local_ip) { SilcSockaddr local; /* Set sockaddr for local listener, and try to bind it. */ if (silc_net_set_sockaddr(&local, local_ip, 0)) bind(sock, &local.sa, SIZEOF_SOCKADDR(local)); } /* Connect to the host */ rval = connect(sock, &desthost.sa, SIZEOF_SOCKADDR(desthost)); if (rval < 0) { /* retry using an IPv4 adress, if IPv6 didn't work */ if (prefer_ipv6 && silc_net_is_ip6(ip_addr)) { shutdown(sock, 2); close(sock); prefer_ipv6 = FALSE; goto retry; } SILC_LOG_ERROR(("Cannot connect to remote host: %s", strerror(errno))); shutdown(sock, 2); close(sock); return -1; } /* Set appropriate options */ #if defined(TCP_NODELAY) silc_net_set_socket_opt(sock, IPPROTO_TCP, TCP_NODELAY, 1); #endif silc_net_set_socket_opt(sock, SOL_SOCKET, SO_KEEPALIVE, 1); SILC_LOG_DEBUG(("Connection created")); return sock; } /* Creates a connection (TCP/IP) to a remote host. Returns the connection socket or -1 on error. This creates non-blocking socket hence the connection returns directly. To get the result of the connect() one must select() the socket and read the result after it's ready. */ int silc_net_create_connection_async(const char *local_ip, int port, const char *host) { int sock, rval; char ip_addr[64]; SilcSockaddr desthost; bool prefer_ipv6 = TRUE; SILC_LOG_DEBUG(("Creating connection (async) to host %s port %d", host, port)); /* Do host lookup */ retry: if (!silc_net_gethostbyname(host, prefer_ipv6, ip_addr, sizeof(ip_addr))) { SILC_LOG_ERROR(("Network (%s) unreachable: could not resolve the " "IP address", host)); return -1; } /* Set sockaddr for this connection */ if (!silc_net_set_sockaddr(&desthost, ip_addr, port)) return -1; /* Create the connection socket */ sock = socket(desthost.sin.sin_family, SOCK_STREAM, 0); if (sock < 0) { /* If address is IPv6, then fallback to IPv4 and see whether we can do better with that on socket creation. */ if (prefer_ipv6 && silc_net_is_ip6(ip_addr)) { prefer_ipv6 = FALSE; goto retry; } SILC_LOG_ERROR(("Cannot create socket: %s", strerror(errno))); return -1; } /* Bind to the local address if provided */ if (local_ip) { SilcSockaddr local; /* Set sockaddr for local listener, and try to bind it. */ if (silc_net_set_sockaddr(&local, local_ip, 0)) bind(sock, &local.sa, SIZEOF_SOCKADDR(local)); } /* Set the socket to non-blocking mode */ silc_net_set_socket_nonblock(sock); /* Connect to the host */ rval = connect(sock, &desthost.sa, SIZEOF_SOCKADDR(desthost)); if (rval < 0) { if (errno != EINPROGRESS) { /* retry using an IPv4 adress, if IPv6 didn't work */ if (prefer_ipv6 && silc_net_is_ip6(ip_addr)) { shutdown(sock, 2); close(sock); prefer_ipv6 = FALSE; goto retry; } SILC_LOG_ERROR(("Cannot connect to remote host: %s", strerror(errno))); shutdown(sock, 2); close(sock); return -1; } } /* Set appropriate options */ #if defined(TCP_NODELAY) silc_net_set_socket_opt(sock, IPPROTO_TCP, TCP_NODELAY, 1); #endif silc_net_set_socket_opt(sock, SOL_SOCKET, SO_KEEPALIVE, 1); SILC_LOG_DEBUG(("Connection operation in progress")); return sock; } /* Closes the connection by closing the socket connection. */ void silc_net_close_connection(int sock) { close(sock); } /* Set's the socket to non-blocking mode. */ int silc_net_set_socket_nonblock(int sock) { return fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0) | O_NONBLOCK); } /* Converts the IP number string from numbers-and-dots notation to binary form. */ bool silc_net_addr2bin(const char *addr, void *bin, SilcUInt32 bin_len) { int ret = 0; if (silc_net_is_ip4(addr)) { /* IPv4 address */ struct in_addr tmp; ret = inet_aton(addr, &tmp); if (bin_len < 4) return FALSE; memcpy(bin, (unsigned char *)&tmp.s_addr, 4); #ifdef HAVE_IPV6 } else { struct addrinfo hints, *ai; SilcSockaddr *s; /* IPv6 address */ if (bin_len < 16) return FALSE; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET6; if (getaddrinfo(addr, NULL, &hints, &ai)) return FALSE; if (ai) { s = (SilcSockaddr *)ai->ai_addr; memcpy(bin, &s->sin6.sin6_addr, sizeof(s->sin6.sin6_addr)); freeaddrinfo(ai); } ret = TRUE; #endif /* HAVE_IPV6 */ } return ret != 0; }