X-Git-Url: http://git.silcnet.org/gitweb/?a=blobdiff_plain;f=lib%2Fsilcutil%2Fwin32%2Fsilcwin32net.c;h=0501ab82d12b62873471491773133528edfc6164;hb=e7b6c157b80152bf9fb9266e6bdd93f9fb0db776;hp=e8fcc26a2195ae834fb08658c390468e094f31b1;hpb=2dc218143c7859f7529396dc121ae08e2fd78da0;p=silc.git diff --git a/lib/silcutil/win32/silcwin32net.c b/lib/silcutil/win32/silcwin32net.c index e8fcc26a..0501ab82 100644 --- a/lib/silcutil/win32/silcwin32net.c +++ b/lib/silcutil/win32/silcwin32net.c @@ -4,13 +4,12 @@ Author: Pekka Riikonen - Copyright (C) 1997 - 2001 Pekka Riikonen + Copyright (C) 1997 - 2007 Pekka Riikonen 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 of the License, or - (at your option) any later version. - + the Free Software Foundation; version 2 of the License. + 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 @@ -19,186 +18,720 @@ */ /* $Id$ */ -#include "silcincludes.h" -#include "silcnet.h" +#include "silc.h" + +/************************** Types and definitions ***************************/ + +#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 utility functions **************************/ + +static SilcBool 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)); + silc_set_errno_reason(SILC_ERR_BAD_IP, "%s is not an IP address", + ip_addr); + return FALSE; + } + + if (silc_net_is_ip4(ip_addr)) { + /* IPv4 address */ + len = sizeof(addr->sin.sin_addr); + if (!silc_net_addr2bin(ip_addr, + (unsigned char *)&addr->sin.sin_addr.s_addr, + len)) + return FALSE; + 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); + if (!silc_net_addr2bin(ip_addr, + (unsigned char *)&addr->sin6.sin6_addr, len)) + return FALSE; + addr->sin6.sin6_family = AF_INET6; + addr->sin6.sin6_port = port ? htons(port) : 0; +#else + SILC_LOG_ERROR(("Operating System does not support IPv6")); + 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; +} + + +/****************************** TCP Listener ********************************/ + +/* Deliver new stream to upper layer */ + +static void silc_net_accept_stream(SilcResult status, + SilcStream stream, void *context) +{ + SilcNetListener listener = context; + + if (status != SILC_OK) + return; -/* 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. */ + listener->callback(SILC_OK, stream, listener->context); +} -int silc_net_create_server(int port, char *ip_addr) +/* Accept incoming connection and notify upper layer */ + +SILC_TASK_CALLBACK(silc_net_accept) { + SilcNetListener listener = context; + int sock; + + SILC_LOG_DEBUG(("Accepting new connection")); + + sock = silc_net_accept_connection(fd); + if (sock == INVALID_SOCKET) + return; + + /* Set socket options */ + silc_net_set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 1); + + /* Create socket stream */ + silc_socket_tcp_stream_create(sock, listener->lookup, + listener->require_fqdn, schedule, + silc_net_accept_stream, listener); +} + +/* Create TCP network listener */ + +SilcNetListener +silc_net_tcp_create_listener(const char **local_ip_addr, + SilcUInt32 local_ip_count, int port, + SilcBool lookup, SilcBool require_fqdn, + SilcSchedule schedule, + SilcNetCallback callback, void *context) +{ + SilcNetListener listener = NULL; SOCKET sock; - int rval; - struct sockaddr_in server; - int len = sizeof(server.sin_addr); + SilcSockaddr server; + int i, rval; + const char *ipany = "0.0.0.0"; + + SILC_LOG_DEBUG(("Creating TCP listener")); + + if (!schedule) { + schedule = silc_schedule_get_global(); + if (!schedule) { + silc_set_errno(SILC_ERR_INVALID_ARGUMENT); + goto err; + } + } - SILC_LOG_DEBUG(("Creating a new server listener")); + if (port < 0 || !callback) { + silc_set_errno(SILC_ERR_INVALID_ARGUMENT); + goto err; + } - /* Create the socket */ - sock = socket(PF_INET, SOCK_STREAM, 0); - if (sock == INVALID_SOCKET) { - SILC_LOG_ERROR(("Cannot create socket")); - return -1; + listener = silc_calloc(1, sizeof(*listener)); + if (!listener) + return NULL; + listener->schedule = schedule; + listener->callback = callback; + listener->context = context; + listener->require_fqdn = require_fqdn; + listener->lookup = lookup; + + if (local_ip_count > 0) { + listener->socks = silc_calloc(local_ip_count, sizeof(*listener->socks)); + if (!listener->socks) + return NULL; + } else { + listener->socks = silc_calloc(1, sizeof(*listener->socks)); + if (!listener->socks) + return NULL; + + local_ip_count = 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")); - closesocket(sock); - return -1; + /* Bind to local addresses */ + for (i = 0; i < local_ip_count; i++) { + SILC_LOG_DEBUG(("Binding to local address %s", + local_ip_addr ? local_ip_addr[i] : ipany)); + + /* Set sockaddr for server */ + if (!silc_net_set_sockaddr(&server, + local_ip_addr ? local_ip_addr[i] : ipany, + port)) + goto err; + + /* Create the socket */ + sock = socket(server.sin.sin_family, SOCK_STREAM, 0); + if (sock == INVALID_SOCKET) { + silc_set_errno_posix(WSAGetLastError()); + SILC_LOG_ERROR(("Cannot create socket, error %s", + silc_errno_string(silc_errno))); + goto err; + } + + /* Set the socket options */ + rval = silc_net_set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 1); + if (rval == SOCKET_ERROR) { + SILC_LOG_ERROR(("Cannot set socket options, error %s", + silc_errno_string(silc_errno))); + closesocket(sock); + goto err; + } + + /* Bind the listener socket */ + rval = bind(sock, &server.sa, SIZEOF_SOCKADDR(server)); + if (rval == SOCKET_ERROR) { + silc_set_errno_posix(WSAGetLastError()); + SILC_LOG_ERROR(("Cannot bind socket, error %s", + silc_errno_string(silc_errno))); + closesocket(sock); + goto err; + } + + /* Specify that we are listenning */ + rval = listen(sock, SOMAXCONN); + if (rval == SOCKET_ERROR) { + silc_set_errno_posix(WSAGetLastError()); + SILC_LOG_ERROR(("Cannot set socket listenning, error %s", + silc_errno_string(silc_errno))); + closesocket(sock); + goto err; + } + + /* Schedule for incoming connections */ + silc_schedule_task_add_fd(schedule, sock, silc_net_accept, listener); + + SILC_LOG_DEBUG(("TCP listener created, fd=%d", sock)); + listener->socks[i] = sock; + listener->socks_count++; } - /* Set the socket information for bind() */ - memset(&server, 0, sizeof(server)); - server.sin_family = AF_INET; - if (port) - server.sin_port = htons(port); + return listener; + + err: + if (listener) + silc_net_close_listener(listener); + return NULL; +} + +/* Create TCP network, multiple ports */ - /* Convert IP address to network byte order */ - if (ip_addr) - silc_net_addr2bin(ip_addr, (unsigned char *)&server.sin_addr.s_addr, len); - else - server.sin_addr.s_addr = INADDR_ANY; +SilcNetListener +silc_net_tcp_create_listener2(const char *local_ip_addr, int *ports, + SilcUInt32 port_count, + SilcBool ignore_port_error, + SilcBool lookup, SilcBool require_fqdn, + SilcSchedule schedule, + SilcNetCallback callback, void *context) +{ + SilcNetListener listener = NULL; + SOCKET sock; + SilcSockaddr server; + int i, rval; + const char *ipany = "0.0.0.0"; + + SILC_LOG_DEBUG(("Creating TCP listener")); + + if (!schedule) { + schedule = silc_schedule_get_global(); + if (!schedule) { + silc_set_errno(SILC_ERR_INVALID_ARGUMENT); + goto err; + } + } + + if (!callback) { + silc_set_errno(SILC_ERR_INVALID_ARGUMENT); + goto err; + } - /* Bind the server socket */ - rval = bind(sock, (struct sockaddr *)&server, sizeof(server)); - if (rval != 0) { - SILC_LOG_ERROR(("Cannot bind socket")); - closesocket(sock); - return -1; + listener = silc_calloc(1, sizeof(*listener)); + if (!listener) + return NULL; + listener->schedule = schedule; + listener->callback = callback; + listener->context = context; + listener->require_fqdn = require_fqdn; + listener->lookup = lookup; + + if (port_count > 0) { + listener->socks = silc_calloc(port_count, sizeof(*listener->socks)); + if (!listener->socks) + return NULL; + } else { + listener->socks = silc_calloc(1, sizeof(*listener->socks)); + if (!listener->socks) + return NULL; + + port_count = 1; } - /* Specify that we are listenning */ - rval = listen(sock, 5); - if (rval != 0) { - SILC_LOG_ERROR(("Cannot set socket listenning")); - closesocket(sock); - return -1; + /* Bind to local addresses */ + for (i = 0; i < local_ip_count; i++) { + SILC_LOG_DEBUG(("Binding to local address %s:%d", + local_ip_addr ? local_ip_addr : ipany, + ports ? ports[i] : 0)); + + /* Set sockaddr for server */ + if (!silc_net_set_sockaddr(&server, + local_ip_addr ? local_ip_addr : ipany, + ports ? ports[i] : 0)) { + if (ignore_port_error) + continue; + goto err; + } + + /* Create the socket */ + sock = socket(server.sin.sin_family, SOCK_STREAM, 0); + if (sock == INVALID_SOCKET) { + if (ignore_port_error) + continue; + silc_set_errno_posix(WSAGetLastError()); + SILC_LOG_ERROR(("Cannot create socket, error %s", + silc_errno_string(silc_errno))); + goto err; + } + + /* Set the socket options */ + rval = silc_net_set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 1); + if (rval == SOCKET_ERROR) { + closesocket(sock); + if (ignore_port_error) + continue; + SILC_LOG_ERROR(("Cannot set socket options, error %s", + silc_errno_string(silc_errno))); + goto err; + } + + /* Bind the listener socket */ + rval = bind(sock, &server.sa, SIZEOF_SOCKADDR(server)); + if (rval == SOCKET_ERROR) { + closesocket(sock); + if (ignore_port_error) + continue; + silc_set_errno_posix(WSAGetLastError()); + SILC_LOG_ERROR(("Cannot bind socket, error %s", + silc_errno_string(silc_errno))); + goto err; + } + + /* Specify that we are listenning */ + rval = listen(sock, SOMAXCONN); + if (rval == SOCKET_ERROR) { + closesocket(sock); + if (ignore_port_error) + continue; + silc_set_errno_posix(WSAGetLastError()); + SILC_LOG_ERROR(("Cannot set socket listenning, error %s", + silc_errno_string(silc_errno))); + goto err; + } + + /* Schedule for incoming connections */ + silc_schedule_task_add_fd(schedule, sock, silc_net_accept, listener); + + SILC_LOG_DEBUG(("TCP listener created, fd=%d", sock)); + listener->socks[i] = sock; + listener->socks_count++; } - SILC_LOG_DEBUG(("Server listener created, fd=%d", sock)); + if (ignore_port_error && !listener->socks_count) + goto err; - return sock; + return listener; + + err: + if (listener) + silc_net_close_listener(listener); + return NULL; } -/* Closes the server by closing the socket connection. */ +/* Close network listener */ -void silc_net_close_server(int sock) +void silc_net_close_listener(SilcNetListener listener) { - shutdown(sock, 2); - closesocket(sock); + int i; + + SILC_LOG_DEBUG(("Closing network listener")); - SILC_LOG_DEBUG(("Server socket closed")); + if (!listener) + return; + + for (i = 0; i < listener->socks_count; i++) { + silc_schedule_task_del_by_fd(listener->schedule, listener->socks[i]); + shutdown(listener->socks[i], 2); + closesocket(listener->socks[i]); + } + + silc_free(listener->socks); + silc_free(listener); } -/* 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. */ +/******************************* UDP Stream *********************************/ + +/* Create UDP stream */ -int silc_net_create_connection(const char *local_ip, int port, - const char *host) +SilcStream +silc_net_udp_connect(const char *local_ip_addr, int local_port, + const char *remote_ip_addr, int remote_port, + SilcSchedule schedule) { + SilcStream stream; + SilcSockaddr server; SOCKET sock; - int rval, err; - struct hostent *dest; - struct sockaddr_in desthost; + int rval; + const char *ipany = "0.0.0.0"; - SILC_LOG_DEBUG(("Creating connection to host %s port %d", host, port)); + SILC_LOG_DEBUG(("Creating UDP stream")); - /* Do host lookup */ - dest = gethostbyname(host); - if (!dest) { - SILC_LOG_ERROR(("Network (%s) unreachable: could not resolve the " - "IP address", host)); - return -1; + if (!schedule) { + schedule = silc_schedule_get_global(); + if (!schedule) { + silc_set_errno(SILC_ERR_INVALID_ARGUMENT); + goto err; + } } - /* Set socket information */ - memset(&desthost, 0, sizeof(desthost)); - desthost.sin_port = htons(port); - desthost.sin_family = AF_INET; - memcpy(&desthost.sin_addr, dest->h_addr_list[0], sizeof(desthost.sin_addr)); + /* Bind to local addresses */ + SILC_LOG_DEBUG(("Binding to local address %s", + local_ip_addr ? local_ip_addr : ipany)); - /* Create the connection socket */ - sock = socket(AF_INET, SOCK_STREAM, 0); + /* Set sockaddr for server */ + if (!silc_net_set_sockaddr(&server, local_ip_addr ? local_ip_addr : ipany, + local_port)) + goto err; + + /* Create the socket */ + sock = socket(server.sin.sin_family, SOCK_DGRAM, 0); if (sock == INVALID_SOCKET) { SILC_LOG_ERROR(("Cannot create socket")); - return -1; + silc_set_errno_posix(WSAGetLastError()); + goto err; } - /* Connect to the host */ - rval = connect(sock, (struct sockaddr *)&desthost, sizeof(desthost)); - err = WSAGetLastError(); - if (rval == SOCKET_ERROR && err != WSAEWOULDBLOCK) { - SILC_LOG_ERROR(("Cannot connect to remote host")); - shutdown(sock, 2); - closesocket(sock); - return -1; + /* Set the socket options */ + rval = silc_net_set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 1); + if (rval == SOCKET_ERROR) { + SILC_LOG_ERROR(("Cannot set socket options")); + goto err; + } +#ifdef SO_REUSEPORT + rval = silc_net_set_socket_opt(sock, SOL_SOCKET, SO_REUSEPORT, 1); + if (rval == SOCKET_ERROR) { + SILC_LOG_ERROR(("Cannot set socket options")); + goto err; + } +#endif /* SO_REUSEPORT */ + + /* Bind the listener socket */ + rval = bind(sock, &server.sa, SIZEOF_SOCKADDR(server)); + if (rval == SOCKET_ERROR) { + SILC_LOG_DEBUG(("Cannot bind socket")); + silc_set_errno_posix(WSAGetLastError()); + goto err; } - /* 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); + /* Set to connected state if remote address is provided. */ + if (remote_ip_addr && remote_port) { + if (!silc_net_set_sockaddr(&server, remote_ip_addr, remote_port)) + goto err; + + rval = connect(sock, &server.sa, SIZEOF_SOCKADDR(server)); + if (rval == SOCKET_ERROR) { + SILC_LOG_DEBUG(("Cannot connect UDP stream")); + silc_set_errno_posix(WSAGetLastError()); + goto err; + } + } - SILC_LOG_DEBUG(("Connection created")); + /* Encapsulate into socket stream */ + stream = + silc_socket_udp_stream_create(sock, local_ip_addr ? + silc_net_is_ip6(local_ip_addr) : FALSE, + remote_ip_addr ? TRUE : FALSE, schedule); + if (!stream) + goto err; + + SILC_LOG_DEBUG(("UDP stream created, fd=%d", sock)); + return stream; + + err: + if (sock != -1) + close(sock); + return NULL; +} + +/* Receive UDP packet */ + +int silc_net_udp_receive(SilcStream stream, char *remote_ip_addr, + SilcUInt32 remote_ip_addr_size, int *remote_port, + unsigned char *ret_data, SilcUInt32 data_size) +{ + SilcSocketStream sock = stream; + SilcSockaddr s; + struct sockaddr *from; + int len, flen, err; + + SILC_LOG_DEBUG(("Reading data from UDP socket %d", sock->sock)); + + if (remote_ip_addr && remote_port) { + if (sock->ipv6) { +#ifdef HAVE_IPV6 + from = (struct sockaddr *)&s.sin6; + flen = sizeof(s.sin6); +#endif /* HAVE_IPV6 */ + } else { + from = (struct sockaddr *)&s.sin; + flen = sizeof(s.sin); + } + len = recvfrom(sock->sock, ret_data, data_size, 0, from, &flen); + } else + len = recv(sock->sock, ret_data, data_size, 0); + + if (len == SOCKET_ERROR) { + err = WSAGetLastError(); + silc_set_errno_posix(err); + if (err == WSAEWOULDBLOCK) { + SILC_LOG_DEBUG(("Could not read immediately, will do it later")); + silc_schedule_set_listen_fd(sock->schedule, sock->sock, + SILC_TASK_READ, FALSE); + return -1; + } + SILC_LOG_DEBUG(("Cannot read from UDP socket: %d: %s", sock->sock, + silc_errno_string(silc_errno))); + silc_schedule_unset_listen_fd(sock->schedule, sock->sock); + return -2; + } + + SILC_LOG_DEBUG(("Read %d bytes", len)); + + if (!len) + silc_schedule_unset_listen_fd(sock->schedule, sock->sock); + + /* Return remote address */ + if (remote_ip_addr && remote_port) { + if (sock->ipv6) { +#ifdef HAVE_IPV6 + *remote_port = ntohs(s.sin6.sin6_port); + inet_ntop(AF_INET6, &s.sin6.sin6_addr, remote_ip_addr, + remote_ip_addr_size); +#endif /* HAVE_IPV6 */ + } else { + const char *ip = inet_ntoa(s.sin.sin_addr); + if (ip) + silc_snprintf(remote_ip_addr, remote_ip_addr_size, ip); + *remote_port = ntohs(s.sin.sin_port); + } + + SILC_LOG_DEBUG(("UDP packet from %s:%d", remote_ip_addr, *remote_port)); + } + + return len; +} + +/* Send UDP packet */ + +int silc_net_udp_send(SilcStream stream, + const char *remote_ip_addr, int remote_port, + const unsigned char *data, SilcUInt32 data_len) +{ + SilcSocketStream sock = stream; + SilcSockaddr remote; + int ret, err; + + SILC_LOG_DEBUG(("Sending data to UDP socket %d", sock->sock)); + + /* Set sockaddr */ + if (!silc_net_set_sockaddr(&remote, remote_ip_addr, remote_port)) + return -2; + + /* Send */ + ret = sendto(sock->sock, data, data_len, 0, &remote.sa, + SIZEOF_SOCKADDR(remote)); + if (ret == SOCKET_ERROR) { + err = WSAGetLastError(); + silc_set_errno_posix(err); + if (err == WSAEWOULDBLOCK) { + SILC_LOG_DEBUG(("Could not send immediately, will do it later")); + silc_schedule_set_listen_fd(sock->schedule, sock->sock, + SILC_TASK_READ | SILC_TASK_WRITE, FALSE); + return -1; + } + SILC_LOG_DEBUG(("Cannot send to UDP socket: %s", + silc_errno_string(silc_errno))); + silc_schedule_unset_listen_fd(sock->schedule, sock->sock); + return -2; + } + + SILC_LOG_DEBUG(("Sent data %d bytes", ret)); + if (silc_schedule_get_fd_events(sock->schedule, sock->sock) & + SILC_TASK_WRITE) + silc_schedule_set_listen_fd(sock->schedule, sock->sock, + SILC_TASK_READ, FALSE); + + return ret; +} + + +/******************************* TCP Stream *********************************/ + +typedef struct { + SilcResult status; + SilcStream stream; + SilcFSMStruct fsm; + SilcFSMThreadStruct thread; + SilcAsyncOperation op; + SilcAsyncOperation sop; + char *local_ip; + char *remote; + char ip_addr[64]; + int sock; + SilcNetCallback callback; + void *context; + unsigned int port : 24; + unsigned int retry : 7; + unsigned int aborted : 1; +} *SilcNetConnect; + +SILC_FSM_STATE(silc_net_connect_st_start); +SILC_FSM_STATE(silc_net_connect_st_stream); +SILC_FSM_STATE(silc_net_connect_st_finish); + +static void silc_net_connect_wait_stream(SilcResult status, + SilcStream stream, void *context) +{ + SilcNetConnect conn = context; + conn->sop = NULL; + conn->status = status; + conn->stream = stream; + SILC_FSM_CALL_CONTINUE(&conn->thread); +} + +/* Start connecting. Create a real thread where we connect. */ + +SILC_FSM_STATE(silc_net_connect_st_thread) +{ + SilcNetConnect conn = fsm_context; + + /* Connect in real thread so as to not block the application. */ + silc_fsm_thread_init(&conn->thread, fsm, conn, NULL, NULL, TRUE); + silc_fsm_start(&conn->thread, silc_net_connect_st_start); - return sock; + /* Wait for the thread to finish */ + silc_fsm_next(fsm, silc_net_connect_st_finish); + SILC_FSM_THREAD_WAIT(&conn->thread); } -/* 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. */ +/* Connecting thread */ -int silc_net_create_connection_async(const char *local_ip, int port, - const char *host) +SILC_FSM_STATE(silc_net_connect_st_start) { + SilcNetConnect conn = fsm_context; SOCKET sock; int rval, err; - struct hostent *dest; - struct sockaddr_in desthost; + SilcSockaddr desthost; + SilcBool prefer_ipv6 = TRUE; - SILC_LOG_DEBUG(("Creating connection (async) to host %s port %d", - host, port)); + if (conn->aborted) + return SILC_FSM_FINISH; /* Do host lookup */ - dest = gethostbyname(host); - if (!dest) { + retry: + if (!silc_net_gethostbyname(conn->remote, prefer_ipv6, + conn->ip_addr, sizeof(conn->ip_addr))) { SILC_LOG_ERROR(("Network (%s) unreachable: could not resolve the " - "IP address", host)); - return -1; + "host, error %d", conn->remote, WSAGetLastError())); + + /** Network unreachable */ + conn->status = SILC_ERR_UNREACHABLE; + return SILC_FSM_FINISH; } - /* Set socket information */ - memset(&desthost, 0, sizeof(desthost)); - desthost.sin_port = htons(port); - desthost.sin_family = AF_INET; - memcpy(&desthost.sin_addr, dest->h_addr_list[0], sizeof(desthost.sin_addr)); + /* Set sockaddr for this connection */ + if (!silc_net_set_sockaddr(&desthost, conn->ip_addr, conn->port)) + return SILC_FSM_FINISH; /* Create the connection socket */ - sock = socket(AF_INET, SOCK_STREAM, 0); + sock = socket(desthost.sin.sin_family, SOCK_STREAM, 0); if (sock == INVALID_SOCKET) { - SILC_LOG_ERROR(("Cannot create socket")); - return -1; + /* 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(conn->ip_addr)) { + prefer_ipv6 = FALSE; + goto retry; + } + + /** Cannot create socket */ + silc_set_errno_posix(err); + SILC_LOG_ERROR(("Cannot create socket, error %d", + silc_errno_string(silc_errno))); + return SILC_FSM_FINISH; + } + + /* Bind to the local address if provided */ + if (conn->local_ip) { + SilcSockaddr local; + + /* Set sockaddr for local listener, and try to bind it. */ + if (silc_net_set_sockaddr(&local, conn->local_ip, 0)) + bind(sock, &local.sa, SIZEOF_SOCKADDR(local)); } /* Connect to the host */ - rval = connect(sock, (struct sockaddr *)&desthost, sizeof(desthost)); + rval = connect(sock, &desthost.sa, SIZEOF_SOCKADDR(desthost)); err = WSAGetLastError(); - if (rval == SOCKET_ERROR && err != WSAEWOULDBLOCK) { - SILC_LOG_ERROR(("Cannot connect to remote host")); - shutdown(sock, 2); - closesocket(sock); - return -1; + if (rval == SOCKET_ERROR) { + if (err != WSAEWOULDBLOCK) { + shutdown(sock, 2); + closesocket(sock); + + /* Retry using an IPv4 address, if IPv6 didn't work */ + if (prefer_ipv6 && silc_net_is_ip6(conn->ip_addr)) { + prefer_ipv6 = FALSE; + goto retry; + } + + /* Set error */ + silc_set_errno_posix(err); + conn->status = silc_errno; + + SILC_LOG_ERROR(("Cannot connect to remote host: %s", + silc_errno_string(silc_errno))); + return SILC_FSM_FINISH; + } } - /* Set socket to nonblocking mode */ + /* Set the socket to non-blocking mode */ silc_net_set_socket_nonblock(sock); /* Set appropriate options */ @@ -207,85 +740,218 @@ int silc_net_create_connection_async(const char *local_ip, int port, #endif silc_net_set_socket_opt(sock, SOL_SOCKET, SO_KEEPALIVE, 1); - SILC_LOG_DEBUG(("Connection created")); + SILC_LOG_DEBUG(("TCP connection established")); - return sock; + conn->sock = sock; + + /** Connection created */ + silc_fsm_next(fsm, silc_net_connect_st_stream); + SILC_FSM_CALL((conn->sop = silc_socket_tcp_stream_create( + conn->sock, TRUE, FALSE, + silc_fsm_get_schedule(&conn->fsm), + silc_net_connect_wait_stream, conn))); } -/* Closes the connection by closing the socket connection. */ +/* TCP socket stream created */ -void silc_net_close_connection(int sock) +SILC_FSM_STATE(silc_net_connect_st_stream) { - closesocket(sock); -} + SilcNetConnect conn = fsm_context; -/* Converts the IP number string from numbers-and-dots notation to - binary form. */ + if (conn->aborted) + return SILC_FSM_FINISH; + + if (conn->status != SILC_OK) { + /** Stream creation failed */ + return SILC_FSM_FINISH; + } -bool silc_net_addr2bin(const char *addr, void *bin, uint32 bin_len); + /** Stream created successfully */ + SILC_LOG_DEBUG(("Connected successfully, sock %d", conn->sock)); + conn->status = SILC_OK; + return SILC_FSM_FINISH; +} + +SILC_FSM_STATE(silc_net_connect_st_finish) { - unsigned long ret; + SilcNetConnect conn = fsm_context; - ret = inet_addr(addr); + /* Deliver error or new stream */ + if (!conn->aborted) { + conn->callback(conn->status, conn->stream, conn->context); + if (conn->op) + silc_async_free(conn->op); + } - if (bin_len < 4) - return FALSE; + return SILC_FSM_FINISH; +} - memcpy(bin, (unsigned char *)&tmp.s_addr, 4); - return ret != INADDR_NONE; +static void silc_net_connect_abort(SilcAsyncOperation op, void *context) +{ + SilcNetConnect conn = context; + conn->aborted = TRUE; + + /* Abort underlaying stream creation too */ + if (conn->sop) { + silc_async_abort(conn->sop, NULL, NULL); + conn->sop = NULL; + } } -/* Converts the IP number string from numbers-and-dots notation to - binary form in network byte order. */ +static void silc_net_connect_destructor(SilcFSM fsm, void *fsm_context, + void *destructor_context) +{ + SilcNetConnect conn = fsm_context; + silc_free(conn->local_ip); + silc_free(conn->remote); + silc_free(conn); +} + +/* Create asynchronous TCP/IP connection. */ -bool silc_net_addr2bin_ne(const char *addr, unsigned char *bin, - uint32 bin_len) +SilcAsyncOperation silc_net_tcp_connect(const char *local_ip_addr, + const char *remote_ip_addr, + int remote_port, + SilcSchedule schedule, + SilcNetCallback callback, + void *context) { - unsigned long ret; + SilcNetConnect conn; + + if (!schedule) { + schedule = silc_schedule_get_global(); + if (!schedule) { + silc_set_errno(SILC_ERR_INVALID_ARGUMENT); + return NULL; + } + } - ret = inet_addr(addr); + if (!remote_ip_addr || remote_port < 1 || !callback) { + silc_set_errno(SILC_ERR_INVALID_ARGUMENT); + return NULL; + } - if (bin_len < 4) - return FALSE; + SILC_LOG_DEBUG(("Creating connection to host %s port %d", + remote_ip_addr, remote_port)); - SILC_PUT32_MSB(ret, bin); + conn = silc_calloc(1, sizeof(*conn)); + if (!conn) { + callback(SILC_ERR_OUT_OF_MEMORY, NULL, context); + return NULL; + } + + /* Start async operation */ + conn->op = silc_async_alloc(silc_net_connect_abort, NULL, conn); + if (!conn->op) { + silc_free(conn); + callback(SILC_ERR_OUT_OF_MEMORY, NULL, context); + return NULL; + } - return ret != INADDR_NONE; + if (local_ip_addr) + conn->local_ip = silc_strdup(local_ip_addr); + conn->remote = silc_strdup(remote_ip_addr); + if (!conn->remote) { + silc_async_free(conn->op); + silc_free(conn->local_ip); + silc_free(conn); + callback(SILC_ERR_OUT_OF_MEMORY, NULL, context); + return NULL; + } + conn->port = remote_port; + conn->callback = callback; + conn->context = context; + conn->retry = 1; + conn->status = SILC_ERR; + + silc_fsm_init(&conn->fsm, conn, silc_net_connect_destructor, NULL, schedule); + silc_fsm_start(&conn->fsm, silc_net_connect_st_thread); + + return conn->op; } -/* Set socket to non-blocking mode. */ +/* Closes the connection by closing the socket connection. */ -int silc_net_set_socket_nonblock(int sock) +void silc_net_close_connection(int sock) { - unsigned long on = 1; - return ioctlsocket(sock, FIONBIO, &on); + SILC_LOG_DEBUG(("Closing sock %d", sock)); + closesocket(sock); } -/* Init Winsock2. */ +/* Converts the IP number string from numbers-and-dots notation to + binary form. */ -bool silc_net_win32_init(void) +SilcBool silc_net_addr2bin(const char *addr, void *bin, SilcUInt32 bin_len) { - int ret, sopt = SO_SYNCHRONOUS_NONALERT; - WSADATA wdata; - WORD ver = MAKEWORD(1, 1); - - ret = WSAStartup(ver, &wdata); - if (ret) + if (silc_net_is_ip4(addr)) { + /* IPv4 address */ + int i = 0, c = 0, d = 0, len = strlen(addr); + unsigned char ret[4]; + + memset(ret, 0, sizeof(ret)); + while (len-- > 0) { + if (addr[i++] == '.') { + ret[c++] = d; + d = 0; + if (c > 3) + goto err; + continue; + } + + if (!isdigit((int)addr[i - 1])) + goto err; + + d = 10 * d + addr[i - 1] - '0'; + if (d > 255) + goto err; + } + if (c != 3) + goto err; + ret[c] = d; + + if (bin_len < sizeof(ret)) { + silc_set_errno(SILC_ERR_OVERFLOW); + return FALSE; + } + + memcpy(bin, ret, sizeof(ret)); + return TRUE; + + err: return FALSE; - - /* Allow using the SOCKET's as file descriptors so that we can poll - them with SILC Scheduler. */ - ret = setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE, (char *)&sopt, - sizeof(sopt)); - if (ret) + } else { +#ifdef HAVE_IPV6 + struct addrinfo hints, *ai; + SilcSockaddr *s; + + /* IPv6 address */ + if (bin_len < 16) { + silc_set_errno(SILC_ERR_INVALID_ARGUMENT); + 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); + } + + return TRUE; +#else return FALSE; - - return TRUE; +#endif /* HAVE_IPV6 */ + } } -/* Uninit Winsock2 */ +/* Set socket to non-blocking mode. */ -void silc_net_win32_uninit(void) +int silc_net_set_socket_nonblock(SilcSocket sock) { - WSACleanup(); + unsigned long on = 1; + return ioctlsocket(sock, FIONBIO, &on); }