[gnet-dev] [PATCH] bind outgoing connections to local interface



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]