upstream: Add BindInterface ssh_config directive and -B

command-line argument to ssh(1) that directs it to bind its outgoing
connection to the address of the specified network interface.

BindInterface prefers to use addresses that aren't loopback or link-
local, but will fall back to those if no other addresses of the
required family are available on that interface.

Based on patch by Mike Manning in bz#2820, ok dtucker@

OpenBSD-Commit-ID: c5064d285c2851f773dd736a2c342aa384fbf713
This commit is contained in:
djm@openbsd.org 2018-02-23 02:34:33 +00:00 committed by Damien Miller
parent fcdb9d7778
commit ac2e3026bb
6 changed files with 162 additions and 37 deletions

View File

@ -1,4 +1,4 @@
/* $OpenBSD: readconf.c,v 1.281 2017/12/05 23:59:47 dtucker Exp $ */
/* $OpenBSD: readconf.c,v 1.282 2018/02/23 02:34:33 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@ -156,7 +156,7 @@ typedef enum {
oPubkeyAuthentication,
oKbdInteractiveAuthentication, oKbdInteractiveDevices, oHostKeyAlias,
oDynamicForward, oPreferredAuthentications, oHostbasedAuthentication,
oHostKeyAlgorithms, oBindAddress, oPKCS11Provider,
oHostKeyAlgorithms, oBindAddress, oBindInterface, oPKCS11Provider,
oClearAllForwardings, oNoHostAuthenticationForLocalhost,
oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout,
oAddressFamily, oGssAuthentication, oGssDelegateCreds,
@ -266,6 +266,7 @@ static struct {
{ "preferredauthentications", oPreferredAuthentications },
{ "hostkeyalgorithms", oHostKeyAlgorithms },
{ "bindaddress", oBindAddress },
{ "bindinterface", oBindInterface },
{ "clearallforwardings", oClearAllForwardings },
{ "enablesshkeysign", oEnableSSHKeysign },
{ "verifyhostkeydns", oVerifyHostKeyDNS },
@ -1099,6 +1100,10 @@ parse_char_array:
charptr = &options->bind_address;
goto parse_string;
case oBindInterface:
charptr = &options->bind_interface;
goto parse_string;
case oPKCS11Provider:
charptr = &options->pkcs11_provider;
goto parse_string;
@ -1800,6 +1805,7 @@ initialize_options(Options * options)
options->log_level = SYSLOG_LEVEL_NOT_SET;
options->preferred_authentications = NULL;
options->bind_address = NULL;
options->bind_interface = NULL;
options->pkcs11_provider = NULL;
options->enable_ssh_keysign = - 1;
options->no_host_authentication_for_localhost = - 1;
@ -2509,6 +2515,7 @@ dump_client_config(Options *o, const char *host)
/* String options */
dump_cfg_string(oBindAddress, o->bind_address);
dump_cfg_string(oBindInterface, o->bind_interface);
dump_cfg_string(oCiphers, o->ciphers ? o->ciphers : KEX_CLIENT_ENCRYPT);
dump_cfg_string(oControlPath, o->control_path);
dump_cfg_string(oHostKeyAlgorithms, o->hostkeyalgorithms);

View File

@ -1,4 +1,4 @@
/* $OpenBSD: readconf.h,v 1.124 2017/10/21 23:06:24 millert Exp $ */
/* $OpenBSD: readconf.h,v 1.125 2018/02/23 02:34:33 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
@ -81,6 +81,7 @@ typedef struct {
char *user_hostfiles[SSH_MAX_HOSTS_FILES];
char *preferred_authentications;
char *bind_address; /* local socket address for connection to sshd */
char *bind_interface; /* local interface for bind address */
char *pkcs11_provider; /* PKCS#11 provider */
int verify_host_key_dns; /* Verify host key using DNS */

11
ssh.1
View File

@ -33,8 +33,8 @@
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\"
.\" $OpenBSD: ssh.1,v 1.389 2017/11/03 02:29:17 djm Exp $
.Dd $Mdocdate: November 3 2017 $
.\" $OpenBSD: ssh.1,v 1.390 2018/02/23 02:34:33 djm Exp $
.Dd $Mdocdate: February 23 2018 $
.Dt SSH 1
.Os
.Sh NAME
@ -43,6 +43,7 @@
.Sh SYNOPSIS
.Nm ssh
.Op Fl 46AaCfGgKkMNnqsTtVvXxYy
.Op Fl B Ar bind_interface
.Op Fl b Ar bind_address
.Op Fl c Ar cipher_spec
.Op Fl D Oo Ar bind_address : Oc Ns Ar port
@ -124,6 +125,12 @@ authenticate using the identities loaded into the agent.
.It Fl a
Disables forwarding of the authentication agent connection.
.Pp
.It Fl B Ar interface
Bind to the address of
.Ar interface
before attempting to connect to the destination host.
This is only useful on systems with more than one address.
.Pp
.It Fl b Ar bind_address
Use
.Ar bind_address

21
ssh.c
View File

@ -1,4 +1,4 @@
/* $OpenBSD: ssh.c,v 1.473 2018/02/13 03:36:56 djm Exp $ */
/* $OpenBSD: ssh.c,v 1.474 2018/02/23 02:34:33 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@ -201,13 +201,13 @@ static void
usage(void)
{
fprintf(stderr,
"usage: ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-b bind_address] [-c cipher_spec]\n"
" [-D [bind_address:]port] [-E log_file] [-e escape_char]\n"
" [-F configfile] [-I pkcs11] [-i identity_file]\n"
" [-J [user@]host[:port]] [-L address] [-l login_name] [-m mac_spec]\n"
" [-O ctl_cmd] [-o option] [-p port] [-Q query_option] [-R address]\n"
" [-S ctl_path] [-W host:port] [-w local_tun[:remote_tun]]\n"
" destination [command]\n"
"usage: ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-B bind_interface]\n"
" [-b bind_address] [-c cipher_spec] [-D [bind_address:]port]\n"
" [-E log_file] [-e escape_char] [-F configfile] [-I pkcs11]\n"
" [-i identity_file] [-J [user@]host[:port]] [-L address]\n"
" [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port]\n"
" [-Q query_option] [-R address] [-S ctl_path] [-W host:port]\n"
" [-w local_tun[:remote_tun]] destination [command]\n"
);
exit(255);
}
@ -663,7 +663,7 @@ main(int ac, char **av)
again:
while ((opt = getopt(ac, av, "1246ab:c:e:fgi:kl:m:no:p:qstvx"
"ACD:E:F:GI:J:KL:MNO:PQ:R:S:TVw:W:XYy")) != -1) {
"AB:CD:E:F:GI:J:KL:MNO:PQ:R:S:TVw:W:XYy")) != -1) {
switch (opt) {
case '1':
fatal("SSH protocol v.1 is no longer supported");
@ -973,6 +973,9 @@ main(int ac, char **av)
case 'b':
options.bind_address = optarg;
break;
case 'B':
options.bind_interface = optarg;
break;
case 'F':
config = optarg;
break;

View File

@ -33,8 +33,8 @@
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\"
.\" $OpenBSD: ssh_config.5,v 1.266 2018/02/16 02:40:45 djm Exp $
.Dd $Mdocdate: February 16 2018 $
.\" $OpenBSD: ssh_config.5,v 1.267 2018/02/23 02:34:33 djm Exp $
.Dd $Mdocdate: February 23 2018 $
.Dt SSH_CONFIG 5
.Os
.Sh NAME
@ -254,6 +254,13 @@ The argument must be
or
.Cm no
(the default).
.It Cm BindInterface
Use the address of the specified interface on the local machine as the
source address of the connection.
Note that this option does not work if
.Cm UsePrivilegedPort
is set to
.Cm yes .
.It Cm BindAddress
Use the specified address on the local machine as the source address of
the connection.

View File

@ -1,4 +1,4 @@
/* $OpenBSD: sshconnect.c,v 1.294 2018/02/10 09:25:35 djm Exp $ */
/* $OpenBSD: sshconnect.c,v 1.295 2018/02/23 02:34:33 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@ -23,6 +23,7 @@
# include <sys/time.h>
#endif
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
@ -43,6 +44,7 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ifaddrs.h>
#include "xmalloc.h"
#include "key.h"
@ -270,14 +272,79 @@ ssh_kill_proxy_command(void)
kill(proxy_command_pid, SIGHUP);
}
/*
* Search a interface address list (returned from getifaddrs(3)) for an
* address that matches the desired address family on the specifed interface.
* Returns 0 and fills in *resultp and *rlenp on success. Returns -1 on failure.
*/
static int
check_ifaddrs(const char *ifname, int af, const struct ifaddrs *ifaddrs,
struct sockaddr_storage *resultp, socklen_t *rlenp)
{
struct sockaddr_in6 *sa6;
struct sockaddr_in *sa;
struct in6_addr *v6addr;
const struct ifaddrs *ifa;
int allow_local;
/*
* Prefer addresses that are not loopback or linklocal, but use them
* if nothing else matches.
*/
for (allow_local = 0; allow_local < 2; allow_local++) {
for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL || ifa->ifa_name == NULL ||
(ifa->ifa_flags & IFF_UP) == 0 ||
ifa->ifa_addr->sa_family != af ||
strcmp(ifa->ifa_name, options.bind_interface) != 0)
continue;
switch (ifa->ifa_addr->sa_family) {
case AF_INET:
sa = (struct sockaddr_in *)ifa->ifa_addr;
if (!allow_local && sa->sin_addr.s_addr ==
htonl(INADDR_LOOPBACK))
continue;
if (*rlenp < sizeof(struct sockaddr_in)) {
error("%s: v4 addr doesn't fit",
__func__);
return -1;
}
*rlenp = sizeof(struct sockaddr_in);
memcpy(resultp, sa, *rlenp);
return 0;
case AF_INET6:
sa6 = (struct sockaddr_in6 *)ifa->ifa_addr;
v6addr = &sa6->sin6_addr;
if (!allow_local &&
(IN6_IS_ADDR_LINKLOCAL(v6addr) ||
IN6_IS_ADDR_LOOPBACK(v6addr)))
continue;
if (*rlenp < sizeof(struct sockaddr_in6)) {
error("%s: v6 addr doesn't fit",
__func__);
return -1;
}
*rlenp = sizeof(struct sockaddr_in6);
memcpy(resultp, sa6, *rlenp);
return 0;
}
}
}
return -1;
}
/*
* Creates a (possibly privileged) socket for use as the ssh connection.
*/
static int
ssh_create_socket(int privileged, struct addrinfo *ai)
{
int sock, r, gaierr;
int sock, r, oerrno;
struct sockaddr_storage bindaddr;
socklen_t bindaddrlen = 0;
struct addrinfo hints, *res = NULL;
struct ifaddrs *ifaddrs = NULL;
char ntop[NI_MAXHOST];
sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (sock < 0) {
@ -287,22 +354,50 @@ 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.bind_address == NULL && options.bind_interface == NULL &&
!privileged)
return sock;
if (options.bind_address) {
if (options.bind_address != NULL) {
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) {
if ((r = getaddrinfo(options.bind_address, NULL,
&hints, &res)) != 0) {
error("getaddrinfo: %s: %s", options.bind_address,
ssh_gai_strerror(gaierr));
close(sock);
return -1;
ssh_gai_strerror(r));
goto fail;
}
if (res == NULL)
error("getaddrinfo: no addrs");
goto fail;
if (res->ai_addrlen > sizeof(bindaddr)) {
error("%s: addr doesn't fit", __func__);
goto fail;
}
memcpy(&bindaddr, res->ai_addr, res->ai_addrlen);
bindaddrlen = res->ai_addrlen;
} else if (options.bind_interface != NULL) {
if ((r = getifaddrs(&ifaddrs)) != 0) {
error("getifaddrs: %s: %s", options.bind_interface,
strerror(errno));
goto fail;
}
bindaddrlen = sizeof(bindaddr);
if (check_ifaddrs(options.bind_interface, ai->ai_family,
ifaddrs, &bindaddr, &bindaddrlen) != 0) {
logit("getifaddrs: %s: no suitable addresses",
options.bind_interface);
goto fail;
}
}
if ((r = getnameinfo((struct sockaddr *)&bindaddr, bindaddrlen,
ntop, sizeof(ntop), NULL, 0, NI_NUMERICHOST)) != 0) {
error("%s: getnameinfo failed: %s", __func__,
ssh_gai_strerror(r));
goto fail;
}
/*
* If we are running as root and want to connect to a privileged
@ -310,25 +405,30 @@ ssh_create_socket(int privileged, struct addrinfo *ai)
*/
if (privileged) {
PRIV_START;
r = bindresvport_sa(sock, res ? res->ai_addr : NULL);
r = bindresvport_sa(sock,
bindaddrlen == 0 ? NULL : (struct sockaddr *)&bindaddr);
oerrno = errno;
PRIV_END;
if (r < 0) {
error("bindresvport_sa: af=%d %s", ai->ai_family,
strerror(errno));
error("bindresvport_sa %s: %s", ntop,
strerror(oerrno));
goto fail;
}
} 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;
}
} else if (bind(sock, (struct sockaddr *)&bindaddr, bindaddrlen) != 0) {
error("bind %s: %s", ntop, strerror(errno));
goto fail;
}
debug("%s: bound to %s", __func__, ntop);
/* success */
goto out;
fail:
close(sock);
sock = -1;
out:
if (res != NULL)
freeaddrinfo(res);
if (ifaddrs != NULL)
freeifaddrs(ifaddrs);
return sock;
}