From e93d805c6b39fe733b6ff223ce655a5b71ccdbf4 Mon Sep 17 00:00:00 2001 Message-Id: From: Matthias Schiffer Date: Thu, 22 Oct 2015 02:59:14 +0200 Subject: [PATCH] Allow specifying multiple bind addresses --- readconf.c | 10 ++++--- readconf.h | 12 +++++++- ssh.c | 3 +- ssh_config | 5 ++++ ssh_config.5 | 7 +++-- sshconnect.c | 89 +++++++++++++++++++++++++++++++++++------------------------- 6 files changed, 81 insertions(+), 45 deletions(-) diff --git a/readconf.c b/readconf.c index 1d03bdf..a78ff2f 100644 --- a/readconf.c +++ b/readconf.c @@ -1031,8 +1031,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_addresses; + max_entries = SSH_MAX_BIND_ADDRESSES; + goto parse_char_array; case oPKCS11Provider: charptr = &options->pkcs11_provider; @@ -1639,7 +1641,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_addresses = 0; options->pkcs11_provider = NULL; options->enable_ssh_keysign = - 1; options->no_host_authentication_for_localhost = - 1; @@ -2300,7 +2302,6 @@ dump_client_config(Options *o, const char *host) dump_cfg_int(oServerAliveInterval, o->server_alive_interval); /* String options */ - dump_cfg_string(oBindAddress, o->bind_address); dump_cfg_string(oCiphers, o->ciphers ? o->ciphers : KEX_CLIENT_ENCRYPT); dump_cfg_string(oControlPath, o->control_path); dump_cfg_string(oHostKeyAlgorithms, o->hostkeyalgorithms ? o->hostkeyalgorithms : KEX_DEFAULT_PK_ALG); @@ -2324,6 +2325,7 @@ dump_client_config(Options *o, const char *host) /* String array options */ dump_cfg_strarray(oIdentityFile, o->num_identity_files, o->identity_files); + dump_cfg_strarray_oneline(oBindAddress, o->num_bind_addresses, o->bind_addresses); dump_cfg_strarray_oneline(oCanonicalDomains, o->num_canonical_domains, o->canonical_domains); dump_cfg_strarray_oneline(oGlobalKnownHostsFile, o->num_system_hostfiles, o->system_hostfiles); dump_cfg_strarray_oneline(oUserKnownHostsFile, o->num_user_hostfiles, o->user_hostfiles); diff --git a/readconf.h b/readconf.h index bb2d552..9750fe5 100644 --- a/readconf.h +++ b/readconf.h @@ -27,6 +27,11 @@ struct allowed_cname { char *source_list; char *target_list; }; +#define SSH_MAX_BIND_ADDRESSES 8 /* 8 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. */ @@ -86,7 +91,12 @@ 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_addresses; /* count of bind_addresses */ + char *pkcs11_provider; /* PKCS#11 provider */ int verify_host_key_dns; /* Verify host key using DNS */ diff --git a/ssh.c b/ssh.c index 59c1f93..47e6fdb 100644 --- a/ssh.c +++ b/ssh.c @@ -902,7 +902,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_addresses = 1; break; case 'F': config = optarg; diff --git a/ssh_config b/ssh_config index 03a228f..c1b653b 100644 --- a/ssh_config +++ b/ssh_config @@ -46,3 +46,8 @@ # VisualHostKey no # ProxyCommand ssh -q -W %h:%p gateway.example.com # RekeyLimit 1G 1h + +# --Example 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 --git a/ssh_config.5 b/ssh_config.5 index a47f3ca..b9aaf2f 100644 --- a/ssh_config.5 +++ b/ssh_config.5 @@ -242,8 +242,11 @@ 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 --git a/sshconnect.c b/sshconnect.c index 17fbe39..777b715 100644 --- a/sshconnect.c +++ b/sshconnect.c @@ -283,49 +283,64 @@ ssh_create_socket(int privileged, struct addrinfo *ai) fcntl(sock, F_SETFD, FD_CLOEXEC); /* Bind the socket to an alternative local IP address */ - if (options.bind_address == NULL && !privileged) + if (options.num_bind_addresses == 0 && !privileged) return sock; - if (options.bind_address) { - 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; + verbose("Trying %d addresses to connect", options.num_bind_addresses); + uint i; + for (i = 0; i < options.num_bind_addresses || i == 0; i++) { + if (options.num_bind_addresses > 0) + verbose("Trying bind address: %s", options.bind_addresses[i]); + + if (options.num_bind_addresses > 0 && strncmp(options.bind_addresses[i], SSH_BIND_ADDRESS_ANY, SSH_BIND_ADDRESS_ANYlen) != 0) { + 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)); + continue; + } } - } - /* - * If we are running as root and want to connect to a privileged - * port, bind our own socket to a privileged port. - */ - if (privileged) { - PRIV_START; - r = bindresvport_sa(sock, res ? res->ai_addr : NULL); - PRIV_END; - if (r < 0) { - error("bindresvport_sa: af=%d %s", ai->ai_family, - strerror(errno)); - goto fail; + else if (!privileged) { + return sock; } - } else { - if (bind(sock, res->ai_addr, res->ai_addrlen) < 0) { - error("bind: %s: %s", options.bind_address, - strerror(errno)); - fail: - close(sock); - freeaddrinfo(res); - return -1; + + /* + * If we are running as root and want to connect to a privileged + * port, bind our own socket to a privileged port. + */ + if (privileged) { + PRIV_START; + r = bindresvport_sa(sock, res ? res->ai_addr : NULL); + PRIV_END; + if (r < 0) { + error("bindresvport_sa: af=%d %s", ai->ai_family, + strerror(errno)); + goto fail; + } + } else { + if (bind(sock, res->ai_addr, res->ai_addrlen) < 0) { + error("bind: %s: %s", options.bind_addresses[i], + strerror(errno)); + fail: + freeaddrinfo(res); + res = NULL; + continue; + } } + + if (res != NULL) + freeaddrinfo(res); + + return sock; } - if (res != NULL) - freeaddrinfo(res); - return sock; + + close(sock); + return -1; } static int -- 2.6.2