[PATCH] Add an option for RFC5014 IPv6 source address preference
Anton Khirnov
anton at khirnov.net
Sun Apr 11 00:04:17 AEST 2021
For hosts with multiple types of IPv6 addresses, this allows to prefer
one type over others, without explicitly hardcoding the address through
BindAddress.
A typical configuration where this is useful is a host using IPv6
privacy extensions (i.e. short-lived random "temporary" addresses) and a
static "public" address. To provide privacy, it would default to the
temporary addresses for outbound connections. But since temporary
addresses have a limited lifetime, this would break long-running
connections. Therefore one might want to configure the SSH client to
prefer the public address.
Currently only Linux seems to support this.
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 | 30 ++++++++++++++++++
openbsd-compat/port-net.h | 2 ++
readconf.c | 67 ++++++++++++++++++++++++++++++++++++++-
readconf.h | 2 ++
ssh.1 | 1 +
ssh_config.5 | 20 ++++++++++++
sshconnect.c | 4 +++
9 files changed, 142 insertions(+), 1 deletion(-)
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..cbb937a7 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,29 @@ 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, err;
+
+ 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 (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/readconf.c b/readconf.c
index 0f27652b..888ab811 100644
--- a/readconf.c
+++ b/readconf.c
@@ -173,7 +173,7 @@ typedef enum {
oStreamLocalBindMask, oStreamLocalBindUnlink, oRevokedHostKeys,
oFingerprintHash, oUpdateHostkeys, oHostbasedAcceptedAlgorithms,
oPubkeyAcceptedAlgorithms, oCASignatureAlgorithms, oProxyJump,
- oSecurityKeyProvider, oKnownHostsCommand,
+ oSecurityKeyProvider, oKnownHostsCommand, oIPv6AddressPreference,
oIgnore, oIgnoredUnknownOption, oDeprecated, oUnsupported
} OpCodes;
@@ -316,6 +316,7 @@ static struct {
{ "proxyjump", oProxyJump },
{ "securitykeyprovider", oSecurityKeyProvider },
{ "knownhostscommand", oKnownHostsCommand },
+ { "ipv6addresspreference", oIPv6AddressPreference },
{ NULL, oBadOption }
};
@@ -2050,6 +2051,46 @@ parse_pubkey_algos:
*charptr = xstrdup(arg);
break;
+ case oIPv6AddressPreference:
+ arg = strdelim(&s);
+ if (!arg || *arg == '\0') {
+ error("%.200s line %d: Missing argument.",
+ filename, linenum);
+ return -1;
+ }
+
+ value = options->ipv6_address_preference;
+ if (value == -1)
+ value = 0;
+
+ if (strcmp(arg, "home") == 0) {
+ value |= SSH_IPV6_PREFER_SRC_HOME;
+ value &= ~SSH_IPV6_PREFER_SRC_COA;
+ } else if (strcmp(arg, "coa") == 0) {
+ value |= SSH_IPV6_PREFER_SRC_COA;
+ value &= ~SSH_IPV6_PREFER_SRC_HOME;
+ } else if (strcmp(arg, "tmp") == 0) {
+ value |= SSH_IPV6_PREFER_SRC_TMP;
+ value &= ~SSH_IPV6_PREFER_SRC_PUBLIC;
+ } else if (strcmp(arg, "public") == 0) {
+ value |= SSH_IPV6_PREFER_SRC_PUBLIC;
+ value &= ~SSH_IPV6_PREFER_SRC_TMP;
+ } else if (strcmp(arg, "cga") == 0) {
+ value |= SSH_IPV6_PREFER_SRC_CGA ;
+ value &= ~SSH_IPV6_PREFER_SRC_NONCGA;
+ } else if (strcmp(arg, "noncga") == 0) {
+ value |= SSH_IPV6_PREFER_SRC_NONCGA;
+ value &= ~SSH_IPV6_PREFER_SRC_CGA;
+ } else if (strcmp(arg, "none") == 0) {
+ value = 0;
+ } else {
+ error("%.200s line %d: Bad argument.", filename, linenum);
+ return -1;
+ }
+
+ options->ipv6_address_preference = value;
+ break;
+
case oDeprecated:
debug("%s line %d: Deprecated option \"%s\"",
filename, linenum, keyword);
@@ -2275,6 +2316,7 @@ initialize_options(Options * options)
options->hostbased_accepted_algos = NULL;
options->pubkey_accepted_algos = NULL;
options->known_hosts_command = NULL;
+ options->ipv6_address_preference = -1;
}
/*
@@ -2460,6 +2502,8 @@ fill_default_options(Options * options)
options->canonicalize_hostname = SSH_CANONICALISE_NO;
if (options->fingerprint_hash == -1)
options->fingerprint_hash = SSH_FP_HASH_DEFAULT;
+ if (options->ipv6_address_preference == -1)
+ options->ipv6_address_preference = SSH_IPV6_PREFER_SRC_PUBLIC;
#ifdef ENABLE_SK_INTERNAL
if (options->sk_provider == NULL)
options->sk_provider = xstrdup("internal");
@@ -3280,4 +3324,25 @@ dump_client_config(Options *o, const char *host)
o->jump_port <= 0 ? "" : ":",
o->jump_port <= 0 ? "" : buf);
}
+
+ /* oIPv6AddressPreference */
+ printf("ipv6addresspreference ");
+ if (o->ipv6_address_preference == 0) {
+ printf("none");
+ } else {
+ i = o->ipv6_address_preference;
+ if (i & SSH_IPV6_PREFER_SRC_HOME)
+ printf("home ");
+ if (i & SSH_IPV6_PREFER_SRC_COA)
+ printf("coa ");
+ if (i & SSH_IPV6_PREFER_SRC_TMP)
+ printf("tmp ");
+ if (i & SSH_IPV6_PREFER_SRC_PUBLIC)
+ printf("public ");
+ if (i & SSH_IPV6_PREFER_SRC_CGA)
+ printf("cga ");
+ if (i & SSH_IPV6_PREFER_SRC_NONCGA)
+ printf("noncga ");
+ }
+ printf("\n");
}
diff --git a/readconf.h b/readconf.h
index 2fba866e..da4834c2 100644
--- a/readconf.h
+++ b/readconf.h
@@ -175,6 +175,8 @@ typedef struct {
char *known_hosts_command;
+ int ipv6_address_preference;
+
char *ignored_unknown; /* Pattern list of unknown tokens to ignore */
} Options;
diff --git a/ssh.1 b/ssh.1
index 0a01767e..230f0e1f 100644
--- a/ssh.1
+++ b/ssh.1
@@ -517,6 +517,7 @@ For full details of the options listed below, and their possible values, see
.It IdentitiesOnly
.It IdentityAgent
.It IdentityFile
+.It IPv6AddressPreference
.It IPQoS
.It KbdInteractiveAuthentication
.It KbdInteractiveDevices
diff --git a/ssh_config.5 b/ssh_config.5
index 37a55e23..c0cea3ca 100644
--- a/ssh_config.5
+++ b/ssh_config.5
@@ -1078,6 +1078,26 @@ for interactive sessions and
.Cm cs1
(Lower Effort)
for non-interactive sessions.
+.It Cm IPv6AddressPreference
+Specifies the RFC5014 IPv6 source address preference.
+Valid values of the argument are the string
+.Cm none
+(no preference, use OS defaults) or one the flags:
+.Cm home,
+.Cm coa
+(care-of address),
+.Cm tmp
+(temporary address),
+.Cm public
+(the default),
+.Cm cga
+(cryptographically generated address),
+.Cm noncga.
+.Pp
+This option may be specified multiple times. Flag arguments update the
+previous value; the
+.Cm none
+argument overrides the previous value.
.It Cm KbdInteractiveAuthentication
Specifies whether to use keyboard-interactive authentication.
The argument to this keyword must be
diff --git a/sshconnect.c b/sshconnect.c
index 47f0b1c9..26056ede 100644
--- a/sshconnect.c
+++ b/sshconnect.c
@@ -370,6 +370,10 @@ ssh_create_socket(struct addrinfo *ai)
if (options.ip_qos_interactive != INT_MAX)
set_sock_tos(sock, options.ip_qos_interactive);
+ if (options.ipv6_address_preference != 0 &&
+ ai->ai_family == AF_INET6 && options.bind_address == NULL)
+ sys_set_ipv6_addrpref(sock, options.ipv6_address_preference);
+
/* Bind the socket to an alternative local IP address */
if (options.bind_address == NULL && options.bind_interface == NULL)
return sock;
--
2.20.1
More information about the openssh-unix-dev
mailing list