As my previous patch (msgid 20210410140417.19505-1-anton at khirnov.net)
was rejected because it added an option that only took effect on Linux,
I am submitting a stripped-down version that does not add any
new options. Hopefully this is more acceptable for
inclusion.>8--------------------------------------------------------------------
When both public and temporary IPv6 addresses can be used to reach a
given server, it makes sense for the SSH client to prefer the public
ones, since
- temporary addresses are typically short-lived, which breaks
long-running connections
- certain OpenSSH features (like from=... in authorized_keys or
Match Address/Host in sshd_config) depend on the client connecting
from a predictable address
- since SSH is an authenticated protocol, it rarely makes sense for the
client to hide its identity from the server
Uses the socket options defined in RFC5014, which are currently only
implemented on Linux.
Inspired by a patch posted by Maciej ?enczykowski at
https://bugzilla.redhat.com/show_bug.cgi?id=512032
---
configure.ac | 4 ++++
defines.h | 13 +++++++++++++
openbsd-compat/port-net.c | 31 +++++++++++++++++++++++++++++++
openbsd-compat/port-net.h | 2 ++
sshconnect.c | 5 +++++
5 files changed, 55 insertions(+)
diff --git a/configure.ac b/configure.ac
index 1c2757ca..7de46ba8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -860,6 +860,10 @@ main() { if (NSVersionOfRunTimeLibrary("System")
>= (60 << 16))
])
AC_CHECK_HEADERS([linux/seccomp.h linux/filter.h linux/audit.h], [],
[], [#include <linux/types.h>])
+ AC_CHECK_DECLS([IPV6_ADDR_PREFERENCES],
+ AC_DEFINE([SYS_IPV6_ADDR_PREF_LINUX], [1],
+ [IPv6 address preferences on Linux]), [],
+ [#include <linux/ipv6.h>])
# Obtain MIPS ABI
case "$host" in
mips*)
diff --git a/defines.h b/defines.h
index d6a1d014..1bda872d 100644
--- a/defines.h
+++ b/defines.h
@@ -901,4 +901,17 @@ struct winsize {
#ifdef VARIABLE_LENGTH_ARRAYS
# define USE_SNTRUP761X25519 1
#endif
+
+/* RFC5014 IPv6 source address selection flags.
+ * They do not have standard values or a header
+ * so we define our own values and convert to the
+ * platform-specific ones on use.
+ */
+#define SSH_IPV6_PREFER_SRC_HOME (1 << 0)
+#define SSH_IPV6_PREFER_SRC_COA (1 << 1)
+#define SSH_IPV6_PREFER_SRC_TMP (1 << 2)
+#define SSH_IPV6_PREFER_SRC_PUBLIC (1 << 3)
+#define SSH_IPV6_PREFER_SRC_CGA (1 << 4)
+#define SSH_IPV6_PREFER_SRC_NONCGA (1 << 5)
+
#endif /* _DEFINES_H */
diff --git a/openbsd-compat/port-net.c b/openbsd-compat/port-net.c
index 198e73f0..f541cc11 100644
--- a/openbsd-compat/port-net.c
+++ b/openbsd-compat/port-net.c
@@ -46,6 +46,10 @@
#include <linux/if.h>
#endif
+#ifdef SYS_IPV6_ADDR_PREF_LINUX
+#include <linux/ipv6.h>
+#endif
+
#if defined(SYS_RDOMAIN_LINUX)
char *
sys_get_rdomain(int fd)
@@ -376,3 +380,30 @@ sys_tun_outfilter(struct ssh *ssh, struct Channel *c,
return (buf);
}
#endif /* SSH_TUN_FILTER */
+
+void sys_set_ipv6_addrpref(int sock, int addr_pref)
+{
+#ifdef SYS_IPV6_ADDR_PREF_LINUX
+ int val = 0;
+
+ if (addr_pref & SSH_IPV6_PREFER_SRC_HOME)
+ val |= IPV6_PREFER_SRC_HOME;
+ if (addr_pref & SSH_IPV6_PREFER_SRC_COA)
+ val |= IPV6_PREFER_SRC_COA;
+ if (addr_pref & SSH_IPV6_PREFER_SRC_TMP)
+ val |= IPV6_PREFER_SRC_TMP;
+ if (addr_pref & SSH_IPV6_PREFER_SRC_PUBLIC)
+ val |= IPV6_PREFER_SRC_PUBLIC;
+ if (addr_pref & SSH_IPV6_PREFER_SRC_CGA)
+ val |= IPV6_PREFER_SRC_CGA;
+ if (addr_pref & SSH_IPV6_PREFER_SRC_NONCGA)
+ val |= IPV6_PREFER_SRC_NONCGA;
+
+ if (val != 0 &&
+ setsockopt(sock, IPPROTO_IPV6, IPV6_ADDR_PREFERENCES,
+ &val, sizeof(val)) == -1) {
+ error("setsockopt %d, IPV6_ADDR_PREFERENCES %d: %.100s",
+ sock, val, strerror(errno));
+ }
+#endif
+}
diff --git a/openbsd-compat/port-net.h b/openbsd-compat/port-net.h
index 3a0d1104..040e2120 100644
--- a/openbsd-compat/port-net.h
+++ b/openbsd-compat/port-net.h
@@ -45,4 +45,6 @@ int sys_valid_rdomain(const char *name);
void sys_set_process_rdomain(const char *name);
#endif
+void sys_set_ipv6_addrpref(int sock, int addr_pref);
+
#endif
diff --git a/sshconnect.c b/sshconnect.c
index 47f0b1c9..e604212b 100644
--- a/sshconnect.c
+++ b/sshconnect.c
@@ -370,6 +370,11 @@ ssh_create_socket(struct addrinfo *ai)
if (options.ip_qos_interactive != INT_MAX)
set_sock_tos(sock, options.ip_qos_interactive);
+ /* Prefer public IPv6 source addresses when both public and
+ * temporary are available */
+ if (ai->ai_family == AF_INET6 && options.bind_address == NULL)
+ sys_set_ipv6_addrpref(sock, SSH_IPV6_PREFER_SRC_PUBLIC);
+
/* Bind the socket to an alternative local IP address */
if (options.bind_address == NULL && options.bind_interface == NULL)
return sock;
--
2.20.1