/* * uhub - A tiny ADC p2p connection hub * Copyright (C) 2007-2014, Jan Vidar Krey * * 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 3 of the License, 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. If not, see . * */ #include "uhub.h" #include "network/common.h" #include "network/backend.h" static int is_blocked_or_interrupted() { int err = net_error(); return #ifdef WINSOCK err == WSAEWOULDBLOCK #else err == EWOULDBLOCK #endif || err == EINTR; } ssize_t net_con_send(struct net_connection* con, const void* buf, size_t len) { int ret; #ifdef SSL_SUPPORT if (!con->ssl) { #endif ret = net_send(con->sd, buf, len, UHUB_SEND_SIGNAL); if (ret == -1) { if (is_blocked_or_interrupted()) return 0; return -1; } #ifdef SSL_SUPPORT } else { ret = net_ssl_send(con, buf, len); } #endif /* SSL_SUPPORT */ return ret; } ssize_t net_con_recv(struct net_connection* con, void* buf, size_t len) { int ret; #ifdef SSL_SUPPORT if (!con->ssl) { #endif ret = net_recv(con->sd, buf, len, 0); if (ret == -1) { if (is_blocked_or_interrupted()) return 0; return -net_error(); } else if (ret == 0) { return -1; } #ifdef SSL_SUPPORT } else { ret = net_ssl_recv(con, buf, len); } #endif /* SSL_SUPPORT */ return ret; } ssize_t net_con_peek(struct net_connection* con, void* buf, size_t len) { int ret = net_recv(con->sd, buf, len, MSG_PEEK); if (ret == -1) { if (is_blocked_or_interrupted()) return 0; return -net_error(); } else if (ret == 0) return -1; return ret; } #ifdef SSL_SUPPORT int net_con_is_ssl(struct net_connection* con) { return !!con->ssl; } #endif /* SSL_SUPPORT */ int net_con_get_sd(struct net_connection* con) { return con->sd; } void* net_con_get_ptr(struct net_connection* con) { return con->ptr; } void net_con_update(struct net_connection* con, int events) { #ifdef SSL_SUPPORT if (con->ssl) net_ssl_update(con, events); else #endif net_backend_update(con, events); } void net_con_reinitialize(struct net_connection* con, net_connection_cb callback, const void* ptr, int events) { con->callback = callback; con->ptr = (void*) ptr; net_con_update(con, events); } void net_con_destroy(struct net_connection* con) { #ifdef SSL_SUPPORT if (con && con->ssl) net_ssl_destroy(con); #endif hub_free(con); } void net_con_callback(struct net_connection* con, int events) { if (con->flags & NET_CLEANUP) return; if (events == NET_EVENT_TIMEOUT) { LOG_TRACE("net_con_callback(%p, TIMEOUT)", con); con->callback(con, events, con->ptr); return; } #ifdef SSL_SUPPORT if (con->ssl) net_ssl_callback(con, events); else #endif con->callback(con, events, con->ptr); } struct net_connect_job { struct net_connection* con; struct net_connect_handle* handle; struct sockaddr_storage addr; struct net_connect_job* next; }; struct net_connect_handle { const char* address; uint16_t port; void* ptr; net_connect_cb callback; struct net_dns_job* dns; const struct net_dns_result* result; struct net_connect_job* job4; struct net_connect_job* job6; }; static void net_connect_callback(struct net_connect_handle* handle, enum net_connect_status status, struct net_connection* con); static void net_connect_job_internal_cb(struct net_connection* con, int event, void* ptr); /** * Check if a connection job is completed. * @return -1 on completed with an error, 0 on not yet completed, or 1 if completed successfully (connected). */ static int net_connect_job_check(struct net_connect_job* job) { struct net_connection* con = job->con; int af = job->addr.ss_family; enum net_connect_status status; int ret = net_connect(net_con_get_sd(con), (struct sockaddr*) &job->addr, af == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)); if (ret == 0 || (ret == -1 && net_error() == EISCONN)) { LOG_TRACE("net_connect_job_check(): Socket connected!"); job->con = NULL; net_con_clear_timeout(con); net_connect_callback(job->handle, net_connect_status_ok, con); return 1; } else if (ret == -1 && (net_error() == EALREADY || net_error() == EINPROGRESS || net_error() == EWOULDBLOCK || net_error() == EINTR)) { return 0; } LOG_TRACE("net_connect_job_check(): Socket error!"); switch (net_error()) { case ECONNREFUSED: status = net_connect_status_refused; break; case ENETUNREACH: status = net_connect_status_unreachable; break; default: status = net_connect_status_socket_error; } net_connect_callback(job->handle, status, NULL); return -1; } static void net_connect_job_free(struct net_connect_job* job) { if (job->con) net_con_close(job->con); job->handle = NULL; job->next = NULL; hub_free(job); } static void net_connect_job_stop(struct net_connect_job* job) { if (job->addr.ss_family == AF_INET6) { job->handle->job6 = job->next; } else { job->handle->job4 = job->next; } net_connect_job_free(job); } static int net_connect_depleted(struct net_connect_handle* handle) { return (!handle->job6 && !handle->job4); } static int net_connect_job_process(struct net_connect_job* job) { int sd; if (!job->con) { sd = net_socket_create(job->addr.ss_family, SOCK_STREAM, IPPROTO_TCP); if (sd == -1) { LOG_DEBUG("net_connect_job_process: Unable to create socket!"); net_connect_callback(job->handle, net_connect_status_socket_error, NULL); return -1; // FIXME } job->con = net_con_create(); net_con_initialize(job->con, sd, net_connect_job_internal_cb, job, NET_EVENT_WRITE); net_con_set_timeout(job->con, TIMEOUT_CONNECTED); // FIXME: Use a proper timeout value! } return net_connect_job_check(job); } /* * Internal callback used to establish an outbound connection. */ static void net_connect_job_internal_cb(struct net_connection* con, int event, void* ptr) { struct net_connect_job* job = net_con_get_ptr(con); struct net_connect_job* next_job = job->next; struct net_connect_handle* handle = job->handle; if (event == NET_EVENT_TIMEOUT) { // FIXME: Try next address, or if no more addresses left declare failure to connect. if (job->addr.ss_family == AF_INET6) { net_connect_job_stop(job); if (!next_job) { LOG_TRACE("No more IPv6 addresses to try!"); } } else { net_connect_job_stop(job); if (!next_job) { LOG_TRACE("No more IPv4 addresses to try!"); } } if (net_connect_depleted(handle)) { LOG_TRACE("No more addresses left. Unable to connect!"); net_connect_callback(handle, net_connect_status_timeout, NULL); } return; } if (event == NET_EVENT_WRITE) { net_connect_job_process(job); } } static void net_connect_cancel(struct net_connect_handle* handle) { struct net_connect_job* job; job = handle->job6; while (job) { job = job->next; net_connect_job_free(handle->job6); handle->job6 = job; } job = handle->job4; while (job) { job = job->next; net_connect_job_free(handle->job4); handle->job4 = job; } } static int net_connect_process_queue(struct net_connect_handle* handle, struct net_connect_job* job) { int ret; while (job) { ret = net_connect_job_process(job); if (ret < 0) { net_connect_job_stop(job); continue; } else if (ret == 0) { // Need to process again return 0; } else { // FIXME: Success! return 1; } } return -1; } static int net_connect_process(struct net_connect_handle* handle) { int ret4, ret6; ret6 = net_connect_process_queue(handle, handle->job6); if (ret6 == 1) return 1; // Connected - cool! net_connect_process_queue(handle, handle->job4); return 0; } static int net_connect_job_schedule(struct net_connect_handle* handle, struct ip_addr_encap* addr) { struct net_connect_job* job; struct sockaddr_in* addr4; struct sockaddr_in6* addr6; if (addr->af == AF_INET6 && !net_is_ipv6_supported()) { LOG_TRACE("net_connect_job_schedule(): Skipping IPv6 support since IPv6 is not supported."); return 0; } else { job = hub_malloc_zero(sizeof(struct net_connect_job)); job->handle = handle; if (addr->af == AF_INET6) { addr6 = (struct sockaddr_in6*) &job->addr; LOG_TRACE("net_connect_job_schedule(): Scheduling IPv6 connect job."); addr6->sin6_family = AF_INET6; addr6->sin6_port = htons(handle->port); memcpy(&addr6->sin6_addr, &addr->internal_ip_data.in6, sizeof(struct in6_addr)); // prepend job->next = handle->job6; handle->job6 = job; } else { addr4 = (struct sockaddr_in*) &job->addr; LOG_TRACE("net_connect_job_schedule(): Scheduling IPv4 connect job."); addr4->sin_family = AF_INET; addr4->sin_port = htons(handle->port); memcpy(&addr4->sin_addr, &addr->internal_ip_data.in, sizeof(struct in_addr)); // prepend job->next = handle->job4; handle->job4 = job; } } return 1; } /* * Callback when the DNS results are ready. * Create a list of IPv6 and IPv4 addresses, then * start connecting to them one by one until one succeeds. */ static int net_con_connect_dns_callback(struct net_dns_job* job, const struct net_dns_result* result) { struct ip_addr_encap* addr; struct net_connect_handle* handle = (struct net_connect_handle*) net_dns_job_get_ptr(job); handle->dns = NULL; size_t usable = 0; LOG_TRACE("net_con_connect(): async - Got DNS results"); if (!result) { LOG_DEBUG("net_con_connect() - Unable to lookup host!"); net_connect_callback(handle, net_connect_status_dns_error, NULL); return 1; } if (!net_dns_result_size(result)) { LOG_DEBUG("net_con_connect() - Host not found!"); net_connect_callback(handle, net_connect_status_host_not_found, NULL); return 1; } handle->result = result; // Extract results into a separate list of IPv4 and IPv6 addresses. addr = net_dns_result_first(result); while (addr) { if (net_connect_job_schedule(handle, addr)) usable++; addr = net_dns_result_next(result); } net_connect_process(handle); return 0; } // typedef void (*net_connect_cb)(struct net_connect_handle*, enum net_connect_handle_code, struct net_connection* con); struct net_connect_handle* net_con_connect(const char* address, uint16_t port, net_connect_cb callback, void* ptr) { struct net_connect_handle* handle = hub_malloc_zero(sizeof(struct net_connect_handle)); handle->address = hub_strdup(address); handle->port = port; handle->ptr = ptr; handle->callback = callback; // FIXME: Check if DNS resolving is necessary ? handle->dns = net_dns_gethostbyname(address, AF_UNSPEC, net_con_connect_dns_callback, handle); if (!handle->dns) { LOG_TRACE("net_con_connect(): Unable to create DNS lookup job."); hub_free((char*) handle->address); hub_free(handle); return NULL; } return handle; } void net_connect_destroy(struct net_connect_handle* handle) { hub_free((char*) handle->address); // cancel DNS job if pending if (handle->dns) net_dns_job_cancel(handle->dns); // Stop any connect jobs. net_connect_cancel(handle); // free any DNS results net_dns_result_free(handle->result); hub_free(handle); } static void net_connect_callback(struct net_connect_handle* handle, enum net_connect_status status, struct net_connection* con) { uhub_assert(handle->callback != NULL); // Call the callback handle->callback(handle, status, con, handle->ptr); handle->callback = NULL; // Cleanup net_connect_destroy(handle); } static void timeout_callback(struct timeout_evt* evt) { net_con_callback((struct net_connection*) evt->ptr, NET_EVENT_TIMEOUT); } void net_con_set_timeout(struct net_connection* con, int seconds) { if (!con->timeout) { con->timeout = hub_malloc_zero(sizeof(struct timeout_evt)); timeout_evt_initialize(con->timeout, timeout_callback, con); timeout_queue_insert(net_backend_get_timeout_queue(), con->timeout, seconds); } else { timeout_queue_reschedule(net_backend_get_timeout_queue(), con->timeout, seconds); } } void net_con_clear_timeout(struct net_connection* con) { if (con->timeout && timeout_evt_is_scheduled(con->timeout)) { timeout_queue_remove(net_backend_get_timeout_queue(), con->timeout); hub_free(con->timeout); con->timeout = 0; } }