diff --git a/src/network/connection.c b/src/network/connection.c index 23ce0eb..3828d54 100644 --- a/src/network/connection.c +++ b/src/network/connection.c @@ -1,6 +1,6 @@ /* * uhub - A tiny ADC p2p connection hub - * Copyright (C) 2007-2012, Jan Vidar Krey + * Copyright (C) 2007-2013, 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 @@ -145,3 +145,364 @@ void net_con_callback(struct net_connection* con, int events) 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_in6) : sizeof(struct sockaddr_in)); + if (ret == 0 || (ret == -1 && net_error() == EISCONN)) + { + LOG_TRACE("net_connect_job_check(): Socket connected!"); + job->con = NULL; + 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) +{ + int ret; + 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 int 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); +} + + +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; + int ret; + + 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); +} diff --git a/src/network/connection.h b/src/network/connection.h index 84cc96a..f48b794 100644 --- a/src/network/connection.h +++ b/src/network/connection.h @@ -1,6 +1,6 @@ /* * uhub - A tiny ADC p2p connection hub - * Copyright (C) 2007-2012, Jan Vidar Krey + * Copyright (C) 2007-2013, 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 @@ -34,10 +34,41 @@ struct net_connection NET_CON_STRUCT_COMMON }; +struct net_connect_handle; + +enum net_connect_status +{ + net_connect_status_ok = 0, + net_connect_status_host_not_found = -1, + net_connect_status_no_address = -2, + net_connect_status_dns_error = -3, + net_connect_status_refused = -4, + net_connect_status_unreachable = -5, + net_connect_status_timeout = -6, + net_connect_status_socket_error = -7, +}; + +typedef void (*net_connect_cb)(struct net_connect_handle*, enum net_connect_status status, struct net_connection* con, void* ptr); + extern int net_con_get_sd(struct net_connection* con); extern void* net_con_get_ptr(struct net_connection* con); extern struct net_connection* net_con_create(); + +/** + * Establish an outbound TCP connection. + * This will resolve the IP-addresses, and connect to + * either an IPv4 or IPv6 address depending if it is supported, + * and using the happy eyeballs algorithm. + * + * @param address Hostname, IPv4 or IPv6 address + * @param port TCP port number + * @param callback A callback to be called once the connection is established, or failed. + * @returns a handle to the connection establishment job, or NULL if an immediate error. + */ +extern struct net_connect_handle* net_con_connect(const char* address, uint16_t port, net_connect_cb callback, void* ptr); +extern void net_connect_destroy(struct net_connect_handle* handle); + extern void net_con_destroy(struct net_connection*); extern void net_con_initialize(struct net_connection* con, int sd, net_connection_cb callback, const void* ptr, int events); extern void net_con_reinitialize(struct net_connection* con, net_connection_cb callback, const void* ptr, int events); diff --git a/src/network/dnsresolver.c b/src/network/dnsresolver.c index 65a4465..77f17ef 100644 --- a/src/network/dnsresolver.c +++ b/src/network/dnsresolver.c @@ -1,6 +1,6 @@ /* * uhub - A tiny ADC p2p connection hub - * Copyright (C) 2007-2012, Jan Vidar Krey + * Copyright (C) 2007-2013, 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 @@ -366,21 +366,24 @@ extern size_t net_dns_result_size(const struct net_dns_result* res) extern struct ip_addr_encap* net_dns_result_first(const struct net_dns_result* res) { struct ip_addr_encap* ipaddr = list_get_first(res->addr_list); - LOG_TRACE("net_dns_result_first() - Address: %s", ip_convert_to_string(ipaddr)); + LOG_TRACE("net_dns_result_first() - Address: %s", ipaddr ? ip_convert_to_string(ipaddr) : "(no address)"); return ipaddr; } extern struct ip_addr_encap* net_dns_result_next(const struct net_dns_result* res) { struct ip_addr_encap* ipaddr = list_get_next(res->addr_list); - LOG_TRACE("net_dns_result_next() - Address: %s", ip_convert_to_string(ipaddr)); + LOG_TRACE("net_dns_result_next() - Address: %s", ipaddr ? ip_convert_to_string(ipaddr) : "(no more addresses)"); return ipaddr; } -extern void net_dns_result_free(struct net_dns_result* res) +extern void net_dns_result_free(const struct net_dns_result* res) { + if (!res) + return; + list_clear(res->addr_list, &hub_free); list_destroy(res->addr_list); free_job(res->job); - hub_free(res); + hub_free((struct net_dns_result*) res); } diff --git a/src/network/dnsresolver.h b/src/network/dnsresolver.h index 6937422..7d1d2b0 100644 --- a/src/network/dnsresolver.h +++ b/src/network/dnsresolver.h @@ -1,6 +1,6 @@ /* * uhub - A tiny ADC p2p connection hub - * Copyright (C) 2007-2012, Jan Vidar Krey + * Copyright (C) 2007-2013, 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 @@ -114,6 +114,6 @@ extern struct ip_addr_encap* net_dns_result_first(const struct net_dns_result*); extern struct ip_addr_encap* net_dns_result_next(const struct net_dns_result*); /// When finished with the results -extern void net_dns_result_free(struct net_dns_result*); +extern void net_dns_result_free(const struct net_dns_result*); #endif /* HAVE_UHUB_NETWORK_DNS_RESOLVER_H */