[gnet-dev] [PATCH] bind outgoing connections to local interface
- From: Michael Alger <gnet mm quex org>
- To: gnet-dev lists gnetlibrary org
- Subject: [gnet-dev] [PATCH] bind outgoing connections to local interface
- Date: Fri, 5 Dec 2003 18:25:53 +0800
Attached is a patch (against the CVS version) that adds the ability to
bind outgoing connections from a GTcpSocket to a specific interface.
I've just implemented what I needed for my own application, so the API
is not yet quite complete. My program uses gnet_conn_new(), so that's
where I started from. I'll add the missing calls when/if this part of
the modification is accepted (I'm not convinced I picked the best name
for the new functions, for one thing).
I've implemented it by adding new functions with "_bind_addr" appended
to the old function name and an extra GInetAddr pointer which provides
the local interface name to bind to. The old functions are stubs that
call the new _bind_addr functions with a NULL local address. I used a
GInetAddr for the local interface name so I didn't have to do an async
lookup on that in case the user provides a hostname; it would probably
be easier to use in many cases with a gchar* for the local address.
Functions added:
gnet_conn_new_bind_addr()
gnet_tcp_socket_connect_async_bind_addr()
gnet_tcp_socket_new_async_bind_addr()
gnet_tcp_socket_new_async_direct_bind_addr()
It also adds a GInetAddr ptr, called 'localaddr' to:
GTcpSocketAsyncState
GTcpSocketConnectState
This is cloned by some of the _bind_addr() functions and deleted when
the state is freed.
The primary change is in _new_async_direct; instead of using socket()
directly the socket is created by gnet_private_create_listen_socket()
and then just before connecting calling bind() on the socket. I took
the code for this from _server_new_full().
Despite what the comments say, the port part of the GInetAddr used to
set the local interface address is ignored. I don't actually need to
connect from a particular port and I couldn't figure out how to do it
so I gave up on this part.
Example of the usage, taken from my program:
{
GInetAddr *addr = gnet_inetaddr_new ("192.168.7.8", 0);
connection = gnet_conn_new_bind_addr (host, port, addr,
IRCNetwork::gconn_callback,
this);
gnet_inetaddr_unref (addr);
}
There's two unrelated tweaks made by the patch, too:
inetaddr.c: gnet_gethostbyname()
Call of inet_pton() was checked for success with != 0; I've changed
it to > 0, since the manpage says it can return a negative value if
an unsupported protocol family is specified. While it's very, very
unlikely that that would actually happen, I was feeling pedantic.
gnet-private.h
Two minor typos of "async" as "asymc" in the comments.
Mike.
diff -u5rb gnet-cvs/src/conn.c gnet-bind/src/conn.c
--- gnet-cvs/src/conn.c 2003-10-29 08:06:18.000000000 +0800
+++ gnet-bind/src/conn.c 2003-12-05 17:08:54.000000000 +0800
@@ -125,10 +125,52 @@
conn->user_data = user_data;
return conn;
}
+/**
+ * gnet_conn_new_bind_addr
+ * @hostname: name of host to connect to
+ * @port: port to connect to
+ * @local: local address to connect from
+ * @func: function to call on #GConn events
+ * @user_data: data to pass to @func on callbacks
+ *
+ * Creates a #GConn. A connection is not made until
+ * gnet_conn_connect() is called. The callback @func is called when
+ * events occur.
+ *
+ * If @local is not NULL, the socket will be bound to the address
+ * and port specified before the connection is made. If the socket
+ * cannot be bound to the requested address, the function returns
+ * NULL. If the port specified in @local is 0, then an arbitrary
+ * local port is chosen.
+ *
+ * Returns: a #GConn.
+ *
+ **/
+GConn*
+gnet_conn_new_bind_addr (const gchar* hostname, gint port,
+ const GInetAddr* local,
+ GConnFunc func, gpointer user_data)
+{
+ GConn* conn;
+
+ g_return_val_if_fail (hostname, NULL);
+
+ conn = g_new0 (GConn, 1);
+ conn->ref_count = 1;
+ conn->hostname = g_strdup(hostname);
+ conn->port = port;
+ conn->inetaddr = gnet_inetaddr_new_nonblock (hostname, port);
+ conn->func = func;
+ conn->user_data = user_data;
+ if (local)
+ conn->localaddr = gnet_inetaddr_clone (local);
+
+ return conn;
+}
/**
* gnet_conn_new_inetaddr
* @inetaddr: address of host to connect to
@@ -253,10 +295,13 @@
g_free (conn->hostname);
if (conn->inetaddr)
gnet_inetaddr_delete (conn->inetaddr);
+ if (conn->localaddr)
+ gnet_inetaddr_delete (conn->localaddr);
+
if (conn->buffer)
g_free (conn->buffer);
g_free (conn);
}
@@ -331,17 +376,19 @@
/* Make asynchronous connection */
if (conn->inetaddr)
{
conn->new_id =
- gnet_tcp_socket_new_async (conn->inetaddr, conn_new_cb, conn);
+ gnet_tcp_socket_new_async_bind_addr (conn->inetaddr, conn->localaddr,
+ conn_new_cb, conn);
}
else if (conn->hostname)
{
conn->connect_id =
- gnet_tcp_socket_connect_async (conn->hostname, conn->port,
+ gnet_tcp_socket_connect_async_bind_addr (conn->hostname, conn->port,
+ conn->localaddr,
conn_connect_cb, conn);
}
else
g_return_if_fail (FALSE);
}
diff -u5rb gnet-cvs/src/conn.h gnet-bind/src/conn.h
--- gnet-cvs/src/conn.h 2003-02-06 07:40:40.000000000 +0800
+++ gnet-bind/src/conn.h 2003-12-05 14:42:49.000000000 +0800
@@ -81,11 +81,12 @@
* GConn
* @hostname: host name
* @port: port
* @iochannel: IO channel
* @socket: socket
- * @inetaddr: address
+ * @inetaddr: address of remote host
+ * @localaddr: local address
* @ref_count: [private]
* @ref_count_internal: [private]
* @connect_id: [private]
* @new_id: [private]
* @write_queue: [private]
@@ -156,10 +157,11 @@
GIOChannel* iochannel;
GTcpSocket* socket;
GInetAddr* inetaddr;
+ GInetAddr* localaddr;
/* Private */
guint ref_count;
guint ref_count_internal;
@@ -200,10 +202,13 @@
/* ********** */
GConn* gnet_conn_new (const gchar* hostname, gint port,
GConnFunc func, gpointer user_data);
+GConn* gnet_conn_new_bind_addr (const gchar* hostname, gint port,
+ const GInetAddr* local,
+ GConnFunc func, gpointer user_data);
GConn* gnet_conn_new_inetaddr (const GInetAddr* inetaddr,
GConnFunc func, gpointer user_data);
GConn* gnet_conn_new_socket (GTcpSocket* socket,
GConnFunc func, gpointer user_data);
diff -u5rb gnet-cvs/src/gnet-private.h gnet-bind/src/gnet-private.h
--- gnet-cvs/src/gnet-private.h 2003-10-05 02:11:50.000000000 +0800
+++ gnet-bind/src/gnet-private.h 2003-12-05 14:42:49.000000000 +0800
@@ -372,17 +372,18 @@
GIOChannel* iochannel;
guint connect_watch;
#ifdef GNET_WIN32
gint errorcode;
#endif
+ GInetAddr* localaddr;
} GTcpSocketAsyncState;
#ifdef GNET_WIN32
/*
Used for:
-gnet_inetaddr_new_async
--gnet_inetaddr_get_name_asymc
+-gnet_inetaddr_get_name_async
*/
typedef struct _SocketWatchAsyncState
{
GIOChannel *channel;
gint fd;
@@ -403,10 +404,12 @@
GList* ia_next;
gpointer inetaddr_id;
gpointer tcp_id;
+ GInetAddr *localaddr;
+
gboolean in_callback;
GTcpSocketConnectAsyncFunc func;
gpointer data;
@@ -421,11 +424,11 @@
extern HWND gnet_hWnd;
extern GHashTable *gnet_hash;
extern HANDLE gnet_Mutex;
extern HANDLE gnet_hostent_Mutex;
#define IA_NEW_MSG 100 /* gnet_inetaddr_new_async */
-#define GET_NAME_MSG 101 /* gnet_inetaddr_get_name_asymc */
+#define GET_NAME_MSG 101 /* gnet_inetaddr_get_name_async */
/* Name-mangled IPv6 structures for primarily Mingw and a few VC .NET 2002 */
#ifndef MAX_ADAPTER_ADDRESS_LENGTH
#define MAX_ADAPTER_ADDRESS_LENGTH 8
#endif
diff -u5rb gnet-cvs/src/inetaddr.c gnet-bind/src/inetaddr.c
--- gnet-cvs/src/inetaddr.c 2003-10-29 08:06:18.000000000 +0800
+++ gnet-bind/src/inetaddr.c 2003-12-05 16:31:29.000000000 +0800
@@ -134,11 +134,11 @@
struct in_addr inaddr;
#ifdef HAVE_IPV6
struct in6_addr in6addr;
#endif
- if (inet_pton(AF_INET, hostname, &inaddr) != 0)
+ if (inet_pton(AF_INET, hostname, &inaddr) > 0)
{
GInetAddr* ia;
struct sockaddr_in* sa;
ia = g_new0(GInetAddr, 1);
@@ -151,11 +151,11 @@
list = g_list_prepend (list, ia);
return list;
}
#ifdef HAVE_IPV6
- else if (inet_pton(AF_INET6, hostname, &in6addr) != 0)
+ else if (inet_pton(AF_INET6, hostname, &in6addr) > 0)
{
GInetAddr* ia;
struct sockaddr_in6* sa;
ia = g_new0(GInetAddr, 1);
diff -u5rb gnet-cvs/src/tcp.c gnet-bind/src/tcp.c
--- gnet-cvs/src/tcp.c 2003-08-22 12:29:50.000000000 +0800
+++ gnet-bind/src/tcp.c 2003-12-05 17:08:36.000000000 +0800
@@ -83,28 +83,65 @@
GTcpSocketConnectAsyncID
gnet_tcp_socket_connect_async (const gchar* hostname, gint port,
GTcpSocketConnectAsyncFunc func,
gpointer data)
{
+ return gnet_tcp_socket_connect_async_bind_addr (hostname, port, NULL,
+ func, data);
+}
+
+/**
+ * gnet_tcp_socket_connect_async_bind_addr
+ * @hostname: host name
+ * @port: port
+ * @local: address to connect from
+ * @func: callback function
+ * @data: data to pass to @func on callback
+ *
+ * Asynchronously creates a #GTcpSocket and connects to
+ * @hostname:@port. The callback is called when the connection is
+ * made or an error occurs. The callback will not be called during
+ * the call to this function.
+ *
+ * If @local is not NULL, the socket will be bound to the specified
+ * address and port before the outgoing connection is made. If the
+ * port given is 0, then an arbitrary local port will be chosen. If
+ * the function cannot bind to the requested address, it will failure
+ * and return NULL. If SOCKS is in use, @local is ignored.
+ *
+ * Returns: the ID of the connection; NULL on failure. The ID can be
+ * used with gnet_tcp_socket_connect_async_cancel() to cancel the
+ * connection.
+ *
+ **/
+GTcpSocketConnectAsyncID
+gnet_tcp_socket_connect_async_bind_addr (const gchar* hostname, gint port,
+ const GInetAddr* local,
+ GTcpSocketConnectAsyncFunc func,
+ gpointer data)
+{
GTcpSocketConnectState* state;
g_return_val_if_fail(hostname != NULL, NULL);
g_return_val_if_fail(func != NULL, NULL);
state = g_new0(GTcpSocketConnectState, 1);
state->func = func;
state->data = data;
+ state->localaddr = local ? gnet_inetaddr_clone (local) : NULL;
state->inetaddr_id =
gnet_inetaddr_new_list_async (hostname, port,
gnet_tcp_socket_connect_inetaddr_cb,
state);
/* On failure, gnet_inetaddr_new_list_async() returns NULL. It will
not call the callback before it returns. */
if (state->inetaddr_id == NULL)
{
+ if (state->localaddr)
+ gnet_inetaddr_delete (state->localaddr);
g_free (state);
return NULL;
}
return state;
@@ -131,11 +168,11 @@
gpointer tcp_id;
ia = (GInetAddr*) state->ia_next->data;
state->ia_next = state->ia_next->next;
- tcp_id = gnet_tcp_socket_new_async (ia,
+ tcp_id = gnet_tcp_socket_new_async_bind_addr (ia, state->localaddr,
gnet_tcp_socket_connect_tcp_cb,
state);
if (tcp_id) /* Success */
{
state->tcp_id = tcp_id;
@@ -194,11 +231,11 @@
gpointer tcp_id = NULL;
ia = (GInetAddr*) state->ia_next->data;
state->ia_next = state->ia_next->next;
- tcp_id = gnet_tcp_socket_new_async (ia,
+ tcp_id = gnet_tcp_socket_new_async_bind_addr (ia, state->localaddr,
gnet_tcp_socket_connect_tcp_cb,
state);
if (tcp_id) /* Success */
{
state->tcp_id = tcp_id;
@@ -251,10 +288,12 @@
if (state->tcp_id)
{
gnet_tcp_socket_new_async_cancel (state->tcp_id);
}
+ if (state->localaddr)
+ gnet_inetaddr_delete (state->localaddr);
g_free (state);
}
/* **************************************** */
@@ -357,28 +396,61 @@
GTcpSocketNewAsyncID
gnet_tcp_socket_new_async (const GInetAddr* addr,
GTcpSocketNewAsyncFunc func,
gpointer data)
{
+ return gnet_tcp_socket_new_async_bind_addr (addr, NULL, func, data);
+}
+
+/**
+ * gnet_tcp_socket_new_async_bind_addr
+ * @addr: address to connect to
+ * @local: address to connect from
+ * @func: callback function
+ * @data: data to pass to @func on callback
+ *
+ * Asynchronously creates a #GTcpSocket and connects to @addr. The
+ * callback is called once the connection is made or an error occurs.
+ * The callback will not be called during the call to this function.
+ *
+ * SOCKS is used if SOCKS is enabled. The SOCKS negotiation will
+ * block.
+ *
+ * If @local is not NULL, the socket will be bound to the specified
+ * address and port before the outgoing connection is made. If the
+ * port in @local is 0, then an arbitrary port will be chosen. If
+ * the socket cannot be bound to the requested address, the function
+ * fails and NULL is returned. If SOCKS is in use, @local is ignored.
+ *
+ * Returns: the ID of the connection; NULL on failure. The ID can be
+ * used with gnet_tcp_socket_new_async_cancel() to cancel the
+ * connection.
+ *
+ **/
+GTcpSocketNewAsyncID
+gnet_tcp_socket_new_async_bind_addr (const GInetAddr* addr,
+ const GInetAddr* local,
+ GTcpSocketNewAsyncFunc func,
+ gpointer data)
+{
g_return_val_if_fail (addr != NULL, NULL);
g_return_val_if_fail (func != NULL, NULL);
/* Use SOCKS if enabled */
if (gnet_socks_get_enabled())
return gnet_private_socks_tcp_socket_new_async (addr, func, data);
/* Otherwise, connect directly to the address */
- return gnet_tcp_socket_new_async_direct (addr, func, data);
+ return gnet_tcp_socket_new_async_direct_bind_addr (addr, local, func, data);
}
#ifndef GNET_WIN32 /*********** Unix code ***********/
-
/**
* gnet_tcp_socket_new_async_direct
- * @addr: address
+ * @addr: address to connect to
* @func: callback function
* @data: data to pass to @func on callback
*
* Asynchronously creates a #GTcpSocket and connects to @addr without
* using SOCKS. Most users should use gnet_tcp_socket_new_async()
@@ -394,20 +466,55 @@
GTcpSocketNewAsyncID
gnet_tcp_socket_new_async_direct (const GInetAddr* addr,
GTcpSocketNewAsyncFunc func,
gpointer data)
{
+ return gnet_tcp_socket_new_async_direct_bind_addr (addr, NULL, func, data);
+}
+
+/**
+ * gnet_tcp_socket_new_async_direct_bind_addr
+ * @addr: address to connect to
+ * @local: address to connect from
+ * @func: callback function
+ * @data: data to pass to @func on callback
+ *
+ * Asynchronously creates a #GTcpSocket and connects to @addr without
+ * using SOCKS. Most users should use gnet_tcp_socket_new_async_bind_addr()
+ * instead. The callback is called once the connection is made or an
+ * error occurs. The callback will not be called during the call to
+ * this function.
+ *
+ * If @local is not NULL, then the socket will be bound to the given
+ * address and port; failure to bind is an error. If the port given
+ * in the local address is 0, then an arbitrary port will be chosen.
+ * If @local is NULL, then the connection will be made from whichever
+ * address is deemed most appropriate by the operating system.
+ *
+ * Returns: the ID of the connection; NULL on failure. The ID can be
+ * used with gnet_tcp_socket_new_async_cancel() to cancel the
+ * connection.
+ *
+ **/
+GTcpSocketNewAsyncID
+gnet_tcp_socket_new_async_direct_bind_addr (const GInetAddr* addr,
+ const GInetAddr* local,
+ GTcpSocketNewAsyncFunc func,
+ gpointer data)
+{
gint sockfd;
gint flags;
GTcpSocket* s;
GTcpSocketAsyncState* state;
+ struct sockaddr_storage sa;
g_return_val_if_fail(addr != NULL, NULL);
g_return_val_if_fail(func != NULL, NULL);
/* Create socket */
- sockfd = socket(GNET_INETADDR_FAMILY(addr), SOCK_STREAM, 0);
+ sockfd = gnet_private_create_listen_socket (SOCK_STREAM, local,
+ local == NULL ? 0 : GNET_INETADDR_PORT(local), &sa);
if (sockfd < 0)
{
g_warning ("socket() failed");
return NULL;
}
@@ -419,17 +526,26 @@
g_warning ("fcntl() failed");
GNET_CLOSE_SOCKET(sockfd);
return NULL;
}
+ /* Request non-blocking I/O */
if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) == -1)
{
g_warning ("fcntl() failed");
GNET_CLOSE_SOCKET(sockfd);
return NULL;
}
+ /* bind to requested address */
+ if (local && bind(sockfd, &GNET_SOCKADDR_SA(sa), GNET_SOCKADDR_LEN(sa)) != 0)
+ {
+ g_warning ("bind() failed");
+ GNET_CLOSE_SOCKET(sockfd);
+ return NULL;
+ }
+
/* Create our structure */
s = g_new0(GTcpSocket, 1);
s->ref_count = 1;
s->sockfd = sockfd;
@@ -462,10 +578,12 @@
state->iochannel = gnet_private_io_channel_new(s->sockfd);
state->connect_watch = g_io_add_watch(state->iochannel,
GNET_ANY_IO_CONDITION,
gnet_tcp_socket_new_async_cb,
state);
+ if (local)
+ state->localaddr = gnet_inetaddr_clone (local);
return state;
}
@@ -506,17 +624,21 @@
goto error;
}
/* Success */
(*state->func)(state->socket, state->data);
+ if (state->localaddr)
+ gnet_inetaddr_delete (state->localaddr);
g_free(state);
return FALSE;
/* Error */
error:
(*state->func)(NULL, state->data);
gnet_tcp_socket_delete (state->socket);
+ if (state->localaddr)
+ gnet_inetaddr_delete (state->localaddr);
g_free(state);
return FALSE;
}
@@ -537,10 +659,12 @@
if (state->connect_watch)
g_source_remove(state->connect_watch);
if (state->iochannel)
g_io_channel_unref (state->iochannel);
gnet_tcp_socket_delete (state->socket);
+ if (state->localaddr)
+ gnet_inetaddr_unref (state->localaddr);
g_free (state);
}
#else /*********** Windows code ***********/
@@ -621,16 +745,20 @@
if (condition & G_IO_ERR)
goto error;
(*state->func)(state->socket, state->data);
+ if (state->localaddr)
+ gnet_inetaddr_delete (state->localaddr);
g_free (state);
return FALSE;
error:
(*state->func)(NULL, state->data);
gnet_tcp_socket_delete (state->socket);
+ if (state->localaddr)
+ gnet_inetaddr_delete (state->localaddr);
g_free (state);
return FALSE;
}
@@ -639,10 +767,12 @@
{
GTcpSocketAsyncState* state = (GTcpSocketAsyncState*) id;
g_source_remove(state->connect_watch);
gnet_tcp_socket_delete(state->socket);
+ if (state->localaddr)
+ gnet_inetaddr_delete (state->localaddr);
g_free (state);
}
#endif /*********** End Windows code ***********/
diff -u5rb gnet-cvs/src/tcp.h gnet-bind/src/tcp.h
--- gnet-cvs/src/tcp.h 2003-02-05 04:22:58.000000000 +0800
+++ gnet-bind/src/tcp.h 2003-12-05 14:42:49.000000000 +0800
@@ -146,10 +146,15 @@
GTcpSocketConnectAsyncID
gnet_tcp_socket_connect_async (const gchar* hostname, gint port,
GTcpSocketConnectAsyncFunc func,
gpointer data);
+GTcpSocketConnectAsyncID
+gnet_tcp_socket_connect_async_bind_addr (const gchar* hostname, gint port,
+ const GInetAddr* local,
+ GTcpSocketConnectAsyncFunc func,
+ gpointer data);
void gnet_tcp_socket_connect_async_cancel (GTcpSocketConnectAsyncID id);
/* ********** */
@@ -180,17 +185,27 @@
GTcpSocketNewAsyncID
gnet_tcp_socket_new_async (const GInetAddr* addr,
GTcpSocketNewAsyncFunc func,
gpointer data);
+GTcpSocketNewAsyncID
+gnet_tcp_socket_new_async_bind_addr (const GInetAddr* addr,
+ const GInetAddr* local,
+ GTcpSocketNewAsyncFunc func,
+ gpointer data);
void gnet_tcp_socket_new_async_cancel (GTcpSocketNewAsyncID id);
GTcpSocketNewAsyncID
gnet_tcp_socket_new_async_direct (const GInetAddr* addr,
GTcpSocketNewAsyncFunc func,
gpointer data);
+GTcpSocketNewAsyncID
+gnet_tcp_socket_new_async_direct_bind_addr (const GInetAddr* addr,
+ const GInetAddr* local,
+ GTcpSocketNewAsyncFunc func,
+ gpointer data);
/* ********** */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]