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 _______________________________________________ openssh-unix-dev mailing list openssh-unix-dev@xxxxxxxxxxx https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev