Mailing List Archive

[PATCH] Add an option for RFC5014 IPv6 source address preference
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@mindrot.org
https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev
Re: [PATCH] Add an option for RFC5014 IPv6 source address preference [ In reply to ]
ping

Any opinions?

--
Anton Khirnov
_______________________________________________
openssh-unix-dev mailing list
openssh-unix-dev@mindrot.org
https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev
Re: [PATCH] Add an option for RFC5014 IPv6 source address preference [ In reply to ]
Another month, another ping.

I would love to see this upstream, so my long-term connections stop
breaking after a week.

--
Anton Khirnov
_______________________________________________
openssh-unix-dev mailing list
openssh-unix-dev@mindrot.org
https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev
Re: [PATCH] Add an option for RFC5014 IPv6 source address preference [ In reply to ]
Hey,

this topic is indeed interesting.

I've read up on the bugreport: The patch does not seem to include the
preference flags to getaddinfo(3) as discussed here
https://bugzilla.redhat.com/show_bug.cgi?id=512032#c8. Is this
intentional?

https://biplane.com.au/blog/?p=30 lists the first three options to
globally control the ipv6 source address preference on linux. Technique
four and five can be used per socket (and by extension per process):

- Technique One: Deprecate the addresses you don't want selected
via ip addr change $ip dev $dev preferred_lft 0
- Technique Two: Modify the label table
via ip addrlabel
- Technique Three: Prefer privacy addresses
via sysctl /proc/sys/net/ipv6/conf/$dev/use_tempaddr
- Technique Four: setsockopt(2) (may be set per socket)
- Technique Five: bind(2) (may be set per socket)

Is this assessment correct or did I miss some options?

BR

Maximilian Eschenbacher
_______________________________________________
openssh-unix-dev mailing list
openssh-unix-dev@mindrot.org
https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev
Re: [PATCH] Add an option for RFC5014 IPv6 source address preference [ In reply to ]
Quoting Maximilian Eschenbacher (2021-06-02 21:21:52)
> Hey,
>
> this topic is indeed interesting.
>
> I've read up on the bugreport: The patch does not seem to include the
> preference flags to getaddinfo(3) as discussed here
> https://bugzilla.redhat.com/show_bug.cgi?id=512032#c8. Is this
> intentional?

Sort of. As far as I can tell, AI_EXTFLAGS is not actually implemented
on Linux (or any other commonly used OS).

>
> https://biplane.com.au/blog/?p=30 lists the first three options to
> globally control the ipv6 source address preference on linux. Technique
> four and five can be used per socket (and by extension per process):
>
> - Technique One: Deprecate the addresses you don't want selected
> via ip addr change $ip dev $dev preferred_lft 0
> - Technique Two: Modify the label table
> via ip addrlabel
> - Technique Three: Prefer privacy addresses
> via sysctl /proc/sys/net/ipv6/conf/$dev/use_tempaddr
> - Technique Four: setsockopt(2) (may be set per socket)
> - Technique Five: bind(2) (may be set per socket)
>
> Is this assessment correct or did I miss some options?

I don't know of any others.
bind()ing to a specific address is of course already implemented in
openssh, but that requires me to hardcode the public address in ssh
config on every machine I ssh from, which I would really like to avoid.

--
Anton Khirnov
_______________________________________________
openssh-unix-dev mailing list
openssh-unix-dev@mindrot.org
https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev
Re: [PATCH] Add an option for RFC5014 IPv6 source address preference [ In reply to ]
Anton Khirnov:

> Another month, another ping.
>
> I would love to see this upstream, so my long-term connections stop
> breaking after a week.

Well, OpenSSH is developed on OpenBSD and then portability goo is
added for the -portable version.

OpenBSD simply does not have such an address selection API, and I'm
skeptical that adding a whole feature falls into the purview of
-portable.

--
Christian "naddy" Weisgerber naddy@mips.inka.de
_______________________________________________
openssh-unix-dev mailing list
openssh-unix-dev@mindrot.org
https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev
Re: [PATCH] Add an option for RFC5014 IPv6 source address preference [ In reply to ]
Quoting Christian Weisgerber (2021-06-04 23:25:18)
> Anton Khirnov:
>
> > Another month, another ping.
> >
> > I would love to see this upstream, so my long-term connections stop
> > breaking after a week.
>
> Well, OpenSSH is developed on OpenBSD and then portability goo is
> added for the -portable version.
>
> OpenBSD simply does not have such an address selection API, and I'm
> skeptical that adding a whole feature falls into the purview of
> -portable.

There are already Linux-specific features in the tree - e.g. SELinux
support.

And unlike SELinux, this code is only Linux-specific because Linux
happens to be the only platform that implements the relevant RFC. The
patch is written so that platform-specific bits are isolated in one
place and it would be simple to add support for other systems if and
when they implement this socket option.

--
Anton Khirnov
_______________________________________________
openssh-unix-dev mailing list
openssh-unix-dev@mindrot.org
https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev