/* * gibber-tcp-transport.c - Source for GibberTCPTransport * Copyright (C) 2006 Collabora Ltd. * @author Sjoerd Simons * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #ifdef HAVE_UNISTD_H # include #endif #include "gibber-sockets.h" #include "gibber-tcp-transport.h" #define DEBUG_FLAG DEBUG_NET #include "gibber-debug.h" #include #include "errno.h" G_DEFINE_TYPE(GibberTCPTransport, gibber_tcp_transport, GIBBER_TYPE_FD_TRANSPORT) /* private structure */ typedef struct _GibberTCPTransportPrivate GibberTCPTransportPrivate; struct _GibberTCPTransportPrivate { GIOChannel *channel; GList *addresses; guint16 port; guint watch_in; gboolean dispose_has_run; }; #define GIBBER_TCP_TRANSPORT_GET_PRIVATE(o) \ (G_TYPE_INSTANCE_GET_PRIVATE ((o), GIBBER_TYPE_TCP_TRANSPORT, \ GibberTCPTransportPrivate)) static void gibber_tcp_transport_init (GibberTCPTransport *obj) { /* GibberTCPTransportPrivate *priv = GIBBER_TCP_TRANSPORT_GET_PRIVATE (obj); */ /* allocate any data required by the object here */ } static void gibber_tcp_transport_dispose (GObject *object); static void gibber_tcp_transport_finalize (GObject *object); static void gibber_tcp_transport_class_init ( GibberTCPTransportClass *gibber_tcp_transport_class) { GObjectClass *object_class = G_OBJECT_CLASS (gibber_tcp_transport_class); g_type_class_add_private (gibber_tcp_transport_class, sizeof (GibberTCPTransportPrivate)); object_class->dispose = gibber_tcp_transport_dispose; object_class->finalize = gibber_tcp_transport_finalize; } static void clean_connect_attempt (GibberTCPTransport *self) { GibberTCPTransportPrivate *priv = GIBBER_TCP_TRANSPORT_GET_PRIVATE ( self); if (priv->watch_in != 0) { g_source_remove (priv->watch_in); priv->watch_in = 0; } if (priv->channel != NULL) { g_io_channel_unref (priv->channel); priv->channel = NULL; } } static void clean_all_connect_attempts (GibberTCPTransport *self) { GibberTCPTransportPrivate *priv = GIBBER_TCP_TRANSPORT_GET_PRIVATE ( self); clean_connect_attempt (self); g_resolver_free_addresses (priv->addresses); priv->addresses = NULL; } void gibber_tcp_transport_dispose (GObject *object) { GibberTCPTransport *self = GIBBER_TCP_TRANSPORT (object); GibberTCPTransportPrivate *priv = GIBBER_TCP_TRANSPORT_GET_PRIVATE (self); if (priv->dispose_has_run) return; priv->dispose_has_run = TRUE; clean_all_connect_attempts (self); /* release any references held by the object here */ if (G_OBJECT_CLASS (gibber_tcp_transport_parent_class)->dispose) G_OBJECT_CLASS (gibber_tcp_transport_parent_class)->dispose (object); } void gibber_tcp_transport_finalize (GObject *object) { /* GibberTCPTransport *self = GIBBER_TCP_TRANSPORT (object); GibberTCPTransportPrivate *priv = GIBBER_TCP_TRANSPORT_GET_PRIVATE (self); */ /* free any data held directly by the object here */ G_OBJECT_CLASS (gibber_tcp_transport_parent_class)->finalize (object); } GibberTCPTransport * gibber_tcp_transport_new () { return g_object_new (GIBBER_TYPE_TCP_TRANSPORT, NULL); } static void new_connect_attempt (GibberTCPTransport *self); static gboolean try_to_connect (GibberTCPTransport *self) { GibberTCPTransportPrivate *priv = GIBBER_TCP_TRANSPORT_GET_PRIVATE ( self); GSocketAddress *gaddr; struct sockaddr_storage addr; gssize native_size; gint fd; int ret; int err; gboolean connected = FALSE; g_assert (priv->channel != NULL); fd = g_io_channel_unix_get_fd (priv->channel); gaddr = g_inet_socket_address_new (G_INET_ADDRESS (priv->addresses->data), priv->port); native_size = g_socket_address_get_native_size (gaddr); /* _get_native_size() really shouldn't fail... */ g_return_val_if_fail (native_size > 0, FALSE); /* ...and if sockaddr_storage isn't big enough, we're basically screwed. */ g_return_val_if_fail ((gsize) native_size <= sizeof (addr), FALSE); g_socket_address_to_native (gaddr, &addr, sizeof (addr), NULL); ret = connect (fd, (struct sockaddr *)&addr, (gsize) native_size); #ifdef G_OS_WIN32 err = WSAGetLastError (); connected = (ret == 0 || err == WSAEISCONN); #else err = errno; connected = (ret == 0); #endif g_object_unref (gaddr); if (connected) { DEBUG ("connect succeeded"); clean_all_connect_attempts (self); gibber_fd_transport_set_fd (GIBBER_FD_TRANSPORT (self), fd, TRUE); return FALSE; } if (gibber_connect_errno_requires_retry (err)) { /* We have to wait longer */ return TRUE; } clean_connect_attempt (self); g_object_unref (priv->addresses->data); priv->addresses = g_list_remove_link (priv->addresses, priv->addresses); new_connect_attempt (self); return FALSE; } static gboolean _channel_io (GIOChannel *source, GIOCondition condition, gpointer data) { GibberTCPTransport *self = GIBBER_TCP_TRANSPORT (data); return try_to_connect (self); } static void new_connect_attempt (GibberTCPTransport *self) { GibberTCPTransportPrivate *priv = GIBBER_TCP_TRANSPORT_GET_PRIVATE ( self); int fd; GSocketAddress *gaddr; gchar *tmpstr; if (priv->addresses == NULL) { /* no more candidate to try */ DEBUG ("connection failed"); goto failed; } tmpstr = g_inet_address_to_string (G_INET_ADDRESS (priv->addresses->data)); DEBUG ("Trying %s port %d...", tmpstr, priv->port); g_free (tmpstr); gaddr = g_inet_socket_address_new (G_INET_ADDRESS (priv->addresses->data), priv->port); fd = socket (g_socket_address_get_family (gaddr), SOCK_STREAM, IPPROTO_TCP); g_object_unref (gaddr); if (fd < 0) { DEBUG("socket failed: #%d %s", gibber_socket_errno (), gibber_socket_strerror ()); goto failed; } gibber_socket_set_nonblocking (fd); priv->channel = gibber_io_channel_new_from_socket (fd); g_io_channel_set_close_on_unref (priv->channel, FALSE); g_io_channel_set_encoding (priv->channel, NULL, NULL); g_io_channel_set_buffered (priv->channel, FALSE); priv->watch_in = g_io_add_watch (priv->channel, G_IO_IN | G_IO_PRI | G_IO_OUT, _channel_io, self); try_to_connect (self); return; failed: clean_all_connect_attempts (self); gibber_transport_set_state (GIBBER_TRANSPORT (self), GIBBER_TRANSPORT_DISCONNECTED); } void gibber_tcp_transport_connect (GibberTCPTransport *tcp_transport, const gchar *host, guint16 port) { GibberTCPTransportPrivate *priv = GIBBER_TCP_TRANSPORT_GET_PRIVATE ( tcp_transport); GResolver *resolver = g_resolver_get_default (); GError *error = NULL; gibber_transport_set_state (GIBBER_TRANSPORT (tcp_transport), GIBBER_TRANSPORT_CONNECTING); priv->port = port; g_assert (priv->addresses == NULL); g_assert (priv->channel == NULL); priv->addresses = g_resolver_lookup_by_name (resolver, host, NULL, &error); if (priv->addresses == NULL) { DEBUG("Address lookup failed: %s", error->message); gibber_transport_set_state (GIBBER_TRANSPORT (tcp_transport), GIBBER_TRANSPORT_DISCONNECTED); g_error_free (error); goto out; } new_connect_attempt (tcp_transport); out: g_object_unref (resolver); }