PATCH: multiple BindAddress

Tautvydas gtautvis at gmail.com
Mon Feb 13 02:22:09 EST 2012


Hello all,
I recently have a problem with multiple addresses and address
families. Problem is simple, i have some hosts with IPv4 access only
and some with IPv6 access. This wouldn't be big problem if I had a
stable IP addresses. But sometimes I move to another network with
complete different addresses. So I created patch which on option
BindAddress accept list of addresses. With ip I solved two main
problems:
1. When using "Host *" it is possible to set address from one family
(IPv4 or IPv6), ofcource there can be introduced BindAddress4 and
BindAddress6 options
2. I can move to another net and there no need do modify ~/.ssh/config file
Of course added address called "any" witch means to use any address
(like the are no option BindAddress defined)
So my config file now looks like:

Host *
  BindAddress 2001:1010:10::1 192.168.0.4 any # means that tries both
binds, if everything fail will use any address
Host ipv4_only.host
   IdentityFile ~/.ssh/ipv4
Host ipv6_only.host
   IdentityFile ~/.ssh/ipv6
Host ipv4_ipv6.host
  IdentityFile ~/.ssh/secr3t
  RemoteForward 127.0.0.1:1234 127.0.0.1:1234
  DynamicForward 6666

I think is much easier to have BindAddress configuration in top of config file.

Thanks,
Tautvydas Želvys
--

This patch is made on openssh-5.9p1 version
--

diff -rupN orig/openssh-5.9p1/readconf.c openssh-5.9p1/readconf.c
--- orig/openssh-5.9p1/readconf.c	2011-05-29 14:42:31.000000000 +0300
+++ openssh-5.9p1/readconf.c	2012-02-12 15:43:43.302048950 +0200
@@ -641,8 +641,10 @@ parse_char_array:
 		goto parse_string;

 	case oBindAddress:
-		charptr = &options->bind_address;
-		goto parse_string;
+		cpptr = (char**)&options->bind_addresses;
+		uintptr = &options->num_bind_address;
+		max_entries =SSH_MAX_BIND_ADDRESSES;
+		goto parse_char_array;

 	case oPKCS11Provider:
 		charptr = &options->pkcs11_provider;
@@ -1176,7 +1178,7 @@ initialize_options(Options * options)
 	options->clear_forwardings = -1;
 	options->log_level = SYSLOG_LEVEL_NOT_SET;
 	options->preferred_authentications = NULL;
-	options->bind_address = NULL;
+	options->num_bind_address = 0;
 	options->pkcs11_provider = NULL;
 	options->enable_ssh_keysign = - 1;
 	options->no_host_authentication_for_localhost = - 1;
diff -rupN orig/openssh-5.9p1/readconf.h openssh-5.9p1/readconf.h
--- orig/openssh-5.9p1/readconf.h	2011-05-29 14:42:33.000000000 +0300
+++ openssh-5.9p1/readconf.h	2012-02-12 16:05:42.450092058 +0200
@@ -27,8 +27,12 @@ typedef struct {
 }       Forward;
 /* Data structure for representing option data. */

-#define MAX_SEND_ENV		256
-#define SSH_MAX_HOSTS_FILES	256
+#define MAX_SEND_ENV			256
+#define SSH_MAX_HOSTS_FILES		256
+#define SSH_MAX_BIND_ADDRESSES	8   /* 16 addresses, should be enough */
+
+#define SSH_BIND_ADDRESS_ANY  "any" /* any address mark, used in
configuration file */
+#define SSH_BIND_ADDRESS_ANYlen strlen(SSH_BIND_ADDRESS_ANY)

 typedef struct {
 	int     forward_agent;	/* Forward authentication agent. */
@@ -89,7 +93,10 @@ typedef struct {
 	u_int	num_user_hostfiles;	/* Path for $HOME/.ssh/known_hosts */
 	char   *user_hostfiles[SSH_MAX_HOSTS_FILES];
 	char   *preferred_authentications;
-	char   *bind_address;	/* local socket address for connection to sshd */
+
+	char   *bind_addresses[SSH_MAX_BIND_ADDRESSES];	/* local socket
address list for connection to sshd, main reason for this is ipv4 and
ipv6 only hosts, when using global host match */
+	u_int   num_bind_address; /* count of bind_addresses */
+
 	char   *pkcs11_provider; /* PKCS#11 provider */
 	int	verify_host_key_dns;	/* Verify host key using DNS */

diff -rupN orig/openssh-5.9p1/ssh.c openssh-5.9p1/ssh.c
--- orig/openssh-5.9p1/ssh.c	2011-08-05 23:18:16.000000000 +0300
+++ openssh-5.9p1/ssh.c	2012-02-12 15:41:39.446044903 +0200
@@ -595,7 +595,8 @@ main(int ac, char **av)
 			options.control_path = xstrdup(optarg);
 			break;
 		case 'b':
-			options.bind_address = optarg;
+			options.bind_addresses[0]  = optarg;
+			options.num_bind_address = 1;
 			break;
 		case 'F':
 			config = optarg;
diff -rupN orig/openssh-5.9p1/ssh_config openssh-5.9p1/ssh_config
--- orig/openssh-5.9p1/ssh_config	2010-01-12 10:40:27.000000000 +0200
+++ openssh-5.9p1/ssh_config	2012-02-12 16:57:02.150192696 +0200
@@ -45,3 +45,8 @@
 #   PermitLocalCommand no
 #   VisualHostKey no
 #   ProxyCommand ssh -q -W %h:%p gateway.example.com
+
+#   --Examble of BindAddress
+#   BindAddress 192.168.0.1 3004:aaaa::beef any
+#   This means, that ssh tries 192.168.0.1 if fail to bind, next
address willbe 3004:aaaa::beef and if it fails,
+#    uses default bind strategy, bind on any address
diff -rupN orig/openssh-5.9p1/ssh_config.5 openssh-5.9p1/ssh_config.5
--- orig/openssh-5.9p1/ssh_config.5	2011-08-05 23:17:32.000000000 +0300
+++ openssh-5.9p1/ssh_config.5	2012-02-12 16:40:31.502160322 +0200
@@ -145,8 +145,10 @@ or
 The default is
 .Dq no .
 .It Cm BindAddress
-Use the specified address on the local machine as the source address of
-the connection.
+Use the specified address (or addresses seperated by space) on the
local machine as the source address of
+the connection. This list can be interrupted with
+.Dq any
+address.
 Only useful on systems with more than one address.
 Note that this option does not work if
 .Cm UsePrivilegedPort
diff -rupN orig/openssh-5.9p1/sshconnect.c openssh-5.9p1/sshconnect.c
--- orig/openssh-5.9p1/sshconnect.c	2011-05-29 14:42:34.000000000 +0300
+++ openssh-5.9p1/sshconnect.c	2012-02-12 16:26:33.986132953 +0200
@@ -189,7 +189,7 @@ ssh_create_socket(int privileged, struct
 {
 	int sock, gaierr;
 	struct addrinfo hints, *res;
-
+	uint i=0;
 	/*
 	 * If we are running as root and want to connect to a privileged
 	 * port, bind our own socket to a privileged port.
@@ -214,28 +214,61 @@ ssh_create_socket(int privileged, struct
 	fcntl(sock, F_SETFD, FD_CLOEXEC);

 	/* Bind the socket to an alternative local IP address */
-	if (options.bind_address == NULL)
+	if (options.num_bind_address == 0){
 		return sock;
+	}

-	memset(&hints, 0, sizeof(hints));
-	hints.ai_family = ai->ai_family;
-	hints.ai_socktype = ai->ai_socktype;
-	hints.ai_protocol = ai->ai_protocol;
-	hints.ai_flags = AI_PASSIVE;
-	gaierr = getaddrinfo(options.bind_address, NULL, &hints, &res);
-	if (gaierr) {
-		error("getaddrinfo: %s: %s", options.bind_address,
-		    ssh_gai_strerror(gaierr));
-		close(sock);
-		return -1;
+	uint addrfound = 0;
+	uint last_idx = options.num_bind_address - 1; // last address index
+
+	verbose("Trying %d addresses to connect",options.num_bind_address);
+	for(i=0;i<options.num_bind_address;++i){
+
+		if (strncmp(options.bind_addresses[i],SSH_BIND_ADDRESS_ANY,SSH_BIND_ADDRESS_ANYlen)
== 0){ // any flag, do not bind, can by anywhere
+			logit("warning requested 'any' address in BindAddress");
+			return sock;
+		}
+
+		memset(&hints, 0, sizeof(hints));
+		hints.ai_family = ai->ai_family;
+		hints.ai_socktype = ai->ai_socktype;
+		hints.ai_protocol = ai->ai_protocol;
+		hints.ai_flags = AI_PASSIVE;
+		gaierr = getaddrinfo(options.bind_addresses[i], NULL, &hints, &res);
+		if (gaierr) {
+			error("getaddrinfo: %s: %s", options.bind_addresses[i],
+				ssh_gai_strerror(gaierr));
+			if ( i == last_idx){
+				close(sock);
+				return -1;
+			} else {
+				error("We have more addresses, trying next address:
%s",options.bind_addresses[i+1]);
+				continue;
+			}
+		}
+
+		if (bind(sock, res->ai_addr, res->ai_addrlen) < 0) {
+			error("bind: %s: %s", options.bind_addresses[i], strerror(errno));
+			if (  i == last_idx){
+				close(sock);
+				freeaddrinfo(res);
+				return -1;
+			} else {
+				error("We have more addresses, trying next address:
%s",options.bind_addresses[i+1]);
+				continue;
+			}
+		} else {
+		   addrfound = 1;
+		   break;
+		}
+		freeaddrinfo(res);
 	}
-	if (bind(sock, res->ai_addr, res->ai_addrlen) < 0) {
-		error("bind: %s: %s", options.bind_address, strerror(errno));
+
+	if (!addrfound){
+		error("no bindable addres is found, exiting");
 		close(sock);
-		freeaddrinfo(res);
 		return -1;
 	}
-	freeaddrinfo(res);
 	return sock;
 }


More information about the openssh-unix-dev mailing list