[PATCH] Basic SCTP support for OpenSSH client and server

Hugo Landau hlandau at devever.net
Mon Feb 10 22:34:03 EST 2014


This patch allows the OpenSSH client to make connections over SCTP,
and allows the OpenSSH server to listen for connections over SCTP.

SCTP is a robust transport-layer protocol which supports, amongst other things,
the changing of endpoint IPs without breaking the connection.

To connect via SCTP, pass -H or set "ConnectViaSCTP yes".
To listen via SCTP as well as TCP, set "ListenViaSCTP yes".

You will need to run autoreconf after applying this patch since there are
changes to configure.ac.

SCTP will be automatically enabled if detected by configure. If you pass
--with-sctp, configure will fail if SCTP is not detected.

The include file <netinet/sctp.h> is required. Linux users may need to install
lksctp <http://lksctp.sf.net/> before this file is present.

You may need to adjust system settings to enable some features of SCTP.
For Linux, see the sysctls under /proc/sys/net/sctp such as addip_enable and
default_auto_asconf, both of which should be enabled; see
  <https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt>.
For BSD, see the net.inet.sctp.* sysctls.

Signed-off-by: Hugo Landau <hlandau at devever.net>
---
 configure.ac  | 28 ++++++++++++++++++++++++++++
 misc.c        | 40 ++++++++++++++++++++++++++++++----------
 readconf.c    | 14 +++++++++++++-
 readconf.h    |  1 +
 servconf.c    | 34 +++++++++++++++++++++++++++++++++-
 servconf.h    |  2 ++
 ssh.c         | 20 +++++++++++++++++---
 ssh_config    |  1 +
 ssh_config.5  |  8 ++++++++
 sshd_config   |  3 +++
 sshd_config.5 |  8 ++++++++
 11 files changed, 144 insertions(+), 15 deletions(-)

diff --git a/configure.ac b/configure.ac
index a350a2a..42438b2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2867,6 +2867,33 @@ if test "x$PAM_MSG" = "xyes" ; then
 	])
 fi
 
+# Check for SCTP support
+AC_CHECK_DECL([IPPROTO_SCTP], [have_sctp=yes], , [
+	#include <netinet/in.h>
+	#include <netinet/sctp.h>
+])
+
+SCTP_MSG="$have_sctp"
+AC_ARG_WITH([sctp],
+	[  --with-sctp             Enable SCTP support ],
+	[
+		if test "x$withval" != "xno" ; then
+			SCTP_MSG="yes"
+		else
+			SCTP_MSG="no"
+		fi
+	]
+)
+
+if test "x$SCTP_MSG" = "xyes"; then
+	if test "x$have_sctp" != "xyes" ; then
+		AC_MSG_ERROR([SCTP support not found])
+	fi
+
+	AC_DEFINE([USE_SCTP], [1],
+		[Define if you want to enable SCTP support])
+fi
+
 SSH_PRIVSEP_USER=sshd
 AC_ARG_WITH([privsep-user],
 	[  --with-privsep-user=user Specify non-privileged user for privilege separation],
@@ -4838,6 +4865,7 @@ echo "          sshd superuser user PATH: $J"
 fi
 echo "                    Manpage format: $MANTYPE"
 echo "                       PAM support: $PAM_MSG"
+echo "                      SCTP support: $SCTP_MSG"
 echo "                   OSF SIA support: $SIA_MSG"
 echo "                 KerberosV support: $KRB5_MSG"
 echo "                   SELinux support: $SELINUX_MSG"
diff --git a/misc.c b/misc.c
index e4c8c32..843ccd4 100644
--- a/misc.c
+++ b/misc.c
@@ -42,6 +42,9 @@
 #include <netinet/in_systm.h>
 #include <netinet/ip.h>
 #include <netinet/tcp.h>
+#ifdef USE_SCTP
+# include <netinet/sctp.h>
+#endif
 
 #include <ctype.h>
 #include <errno.h>
@@ -134,25 +137,42 @@ ssh_gai_strerror(int gaierr)
 }
 
 /* disable nagle on socket */
-void
-set_nodelay(int fd)
+static int
+set_nodelay_proto(int fd, int proto, int optno, const char *pname)
 {
 	int opt;
 	socklen_t optlen;
 
 	optlen = sizeof opt;
-	if (getsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, &optlen) == -1) {
-		debug("getsockopt TCP_NODELAY: %.100s", strerror(errno));
-		return;
+	if (getsockopt(fd, proto, optno, &opt, &optlen) == -1) {
+		debug("getsockopt %s_NODELAY: %.100s", pname, strerror(errno));
+		return -1;
 	}
 	if (opt == 1) {
-		debug2("fd %d is TCP_NODELAY", fd);
-		return;
+		debug2("fd %d is %s_NODELAY", fd, pname);
+		return 0;
 	}
 	opt = 1;
-	debug2("fd %d setting TCP_NODELAY", fd);
-	if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof opt) == -1)
-		error("setsockopt TCP_NODELAY: %.100s", strerror(errno));
+	debug2("fd %d setting %s_NODELAY", fd, pname);
+	if (setsockopt(fd, proto, optno, &opt, sizeof opt) == -1) {
+		error("setsockopt %s_NODELAY: %.100s", pname, strerror(errno));
+    return -1;
+  }
+  return 0;
+}
+
+void
+set_nodelay(int fd)
+{
+  /* We could use SO_PROTOCOL here to try and determine whether to set
+   * TCP_NODELAY or SCTP_NODELAY. But SO_PROTOCOL is supported only in Linux
+   * 2.6.32+ and is not portable. Let's just try both.
+   */
+  if (set_nodelay_proto(fd, IPPROTO_TCP, TCP_NODELAY, "TCP") < 0) {
+#ifdef USE_SCTP
+    set_nodelay_proto(fd, IPPROTO_SCTP, SCTP_NODELAY, "SCTP");
+#endif
+  }
 }
 
 /* Characters considered whitespace in strsep calls. */
diff --git a/readconf.c b/readconf.c
index f80d1cc..1dc33fd 100644
--- a/readconf.c
+++ b/readconf.c
@@ -148,7 +148,7 @@ typedef enum {
 	oVisualHostKey, oUseRoaming,
 	oKexAlgorithms, oIPQoS, oRequestTTY, oIgnoreUnknown, oProxyUseFdpass,
 	oCanonicalDomains, oCanonicalizeHostname, oCanonicalizeMaxDots,
-	oCanonicalizeFallbackLocal, oCanonicalizePermittedCNAMEs,
+	oCanonicalizeFallbackLocal, oCanonicalizePermittedCNAMEs, oConnectViaSCTP,
 	oIgnoredUnknownOption, oDeprecated, oUnsupported
 } OpCodes;
 
@@ -261,6 +261,11 @@ static struct {
 	{ "canonicalizehostname", oCanonicalizeHostname },
 	{ "canonicalizemaxdots", oCanonicalizeMaxDots },
 	{ "canonicalizepermittedcnames", oCanonicalizePermittedCNAMEs },
+#ifdef USE_SCTP
+	{ "connectviasctp", oConnectViaSCTP },
+#else
+	{ "connectviasctp", oUnsupported },
+#endif
 	{ "ignoreunknown", oIgnoreUnknown },
 
 	{ NULL, oBadOption }
@@ -1370,6 +1375,10 @@ parse_int:
 		}
 		break;
 
+	case oConnectViaSCTP:
+		intptr = &options->connect_via_sctp;
+		goto parse_flag;
+
 	case oCanonicalizeHostname:
 		intptr = &options->canonicalize_hostname;
 		multistate_ptr = multistate_canonicalizehostname;
@@ -1550,6 +1559,7 @@ initialize_options(Options * options)
 	options->canonicalize_max_dots = -1;
 	options->canonicalize_fallback_local = -1;
 	options->canonicalize_hostname = -1;
+	options->connect_via_sctp = -1;
 }
 
 /*
@@ -1709,6 +1719,8 @@ fill_default_options(Options * options)
 		options->canonicalize_fallback_local = 1;
 	if (options->canonicalize_hostname == -1)
 		options->canonicalize_hostname = SSH_CANONICALISE_NO;
+	if (options->connect_via_sctp == -1)
+		options->connect_via_sctp = 0;
 #define CLEAR_ON_NONE(v) \
 	do { \
 		if (v != NULL && strcasecmp(v, "none") == 0) { \
diff --git a/readconf.h b/readconf.h
index 9723da0..0ed4838 100644
--- a/readconf.h
+++ b/readconf.h
@@ -152,6 +152,7 @@ typedef struct {
 	int	canonicalize_fallback_local;
 	int	num_permitted_cnames;
 	struct allowed_cname permitted_cnames[MAX_CANON_DOMAINS];
+	int	connect_via_sctp;
 
 	char	*ignored_unknown; /* Pattern list of unknown tokens to ignore */
 }       Options;
diff --git a/servconf.c b/servconf.c
index 7ba65d5..b106458 100644
--- a/servconf.c
+++ b/servconf.c
@@ -57,6 +57,8 @@
 
 static void add_listen_addr(ServerOptions *, char *, int);
 static void add_one_listen_addr(ServerOptions *, char *, int);
+static void add_one_listen_addr_proto(ServerOptions *, char *,
+	int, int);
 
 /* Use of privilege separation or not */
 extern int use_privsep;
@@ -153,6 +155,7 @@ initialize_server_options(ServerOptions *options)
 	options->ip_qos_interactive = -1;
 	options->ip_qos_bulk = -1;
 	options->version_addendum = NULL;
+	options->listen_via_sctp = -1;
 }
 
 void
@@ -300,6 +303,9 @@ fill_default_server_options(ServerOptions *options)
 		options->ip_qos_bulk = IPTOS_THROUGHPUT;
 	if (options->version_addendum == NULL)
 		options->version_addendum = xstrdup("");
+	if (options->listen_via_sctp == -1)
+		options->listen_via_sctp = 0;
+
 	/* Turn privilege separation on by default */
 	if (use_privsep == -1)
 		use_privsep = PRIVSEP_NOSANDBOX;
@@ -347,7 +353,7 @@ typedef enum {
 	sRevokedKeys, sTrustedUserCAKeys, sAuthorizedPrincipalsFile,
 	sKexAlgorithms, sIPQoS, sVersionAddendum,
 	sAuthorizedKeysCommand, sAuthorizedKeysCommandUser,
-	sAuthenticationMethods, sHostKeyAgent,
+	sAuthenticationMethods, sHostKeyAgent, sListenViaSCTP,
 	sDeprecated, sUnsupported
 } ServerOpCodes;
 
@@ -474,6 +480,11 @@ static struct {
 	{ "authorizedkeyscommanduser", sAuthorizedKeysCommandUser, SSHCFG_ALL },
 	{ "versionaddendum", sVersionAddendum, SSHCFG_GLOBAL },
 	{ "authenticationmethods", sAuthenticationMethods, SSHCFG_ALL },
+#ifdef USE_SCTP
+	{ "listenviasctp", sListenViaSCTP, SSHCFG_GLOBAL },
+#else
+	{ "listenviasctp", sUnsupported, SSHCFG_GLOBAL },
+#endif
 	{ NULL, sBadOption, 0 }
 };
 
@@ -543,6 +554,17 @@ add_listen_addr(ServerOptions *options, char *addr, int port)
 static void
 add_one_listen_addr(ServerOptions *options, char *addr, int port)
 {
+	add_one_listen_addr_proto(options, addr, port, 0);
+
+#ifdef USE_SCTP
+	if (options->listen_via_sctp)
+		add_one_listen_addr_proto(options, addr, port, IPPROTO_SCTP);
+#endif
+}
+
+static void
+add_one_listen_addr_proto(ServerOptions *options, char *addr, int port, int proto)
+{
 	struct addrinfo hints, *ai, *aitop;
 	char strport[NI_MAXSERV];
 	int gaierr;
@@ -551,6 +573,7 @@ add_one_listen_addr(ServerOptions *options, char *addr, int port)
 	hints.ai_family = options->address_family;
 	hints.ai_socktype = SOCK_STREAM;
 	hints.ai_flags = (addr == NULL) ? AI_PASSIVE : 0;
+	hints.ai_protocol = proto;
 	snprintf(strport, sizeof strport, "%d", port);
 	if ((gaierr = getaddrinfo(addr, strport, &hints, &aitop)) != 0)
 		fatal("bad addr or host: %s (%s)",
@@ -949,6 +972,12 @@ process_server_config_line(ServerOptions *options, char *line,
 
 		break;
 
+#ifdef USE_SCTP
+	case sListenViaSCTP:
+		intptr = &options->listen_via_sctp;
+		goto parse_flag;
+#endif
+
 	case sAddressFamily:
 		intptr = &options->address_family;
 		multistate_ptr = multistate_addressfamily;
@@ -1974,6 +2003,9 @@ dump_config(ServerOptions *o)
 	dump_cfg_int(sMaxSessions, o->max_sessions);
 	dump_cfg_int(sClientAliveInterval, o->client_alive_interval);
 	dump_cfg_int(sClientAliveCountMax, o->client_alive_count_max);
+#ifdef USE_SCTP
+	dump_cfg_int(sListenViaSCTP, o->listen_via_sctp);
+#endif
 
 	/* formatted integer arguments */
 	dump_cfg_fmtint(sPermitRootLogin, o->permit_root_login);
diff --git a/servconf.h b/servconf.h
index 752d1c5..77bb49e 100644
--- a/servconf.h
+++ b/servconf.h
@@ -183,6 +183,8 @@ typedef struct {
 
 	u_int	num_auth_methods;
 	char   *auth_methods[MAX_AUTH_METHODS];
+
+	int    listen_via_sctp;
 }       ServerOptions;
 
 /* Information about the incoming connection as used by Match */
diff --git a/ssh.c b/ssh.c
index add760c..c2f663c 100644
--- a/ssh.c
+++ b/ssh.c
@@ -196,7 +196,7 @@ static void
 usage(void)
 {
 	fprintf(stderr,
-"usage: ssh [-1246AaCfgKkMNnqsTtVvXxYy] [-b bind_address] [-c cipher_spec]\n"
+"usage: ssh [-1246AaCfgHhKkMNnqsTtVvXxYy] [-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"
 "           [-L [bind_address:]port:host:hostport] [-l login_name] [-m mac_spec]\n"
@@ -242,6 +242,10 @@ resolve_host(const char *name, u_int port, int logerr, char *cname, size_t clen)
 	memset(&hints, 0, sizeof(hints));
 	hints.ai_family = options.address_family;
 	hints.ai_socktype = SOCK_STREAM;
+#ifdef USE_SCTP
+	if (options.connect_via_sctp)
+		hints.ai_protocol = IPPROTO_SCTP;
+#endif
 	if (cname != NULL)
 		hints.ai_flags = AI_CANONNAME;
 	if ((gaierr = getaddrinfo(name, strport, &hints, &res)) != 0) {
@@ -454,8 +458,8 @@ main(int ac, char **av)
 	argv0 = av[0];
 
  again:
-	while ((opt = getopt(ac, av, "1246ab:c:e:fgi:kl:m:no:p:qstvx"
-	    "ACD:E:F:I:KL:MNO:PQ:R:S:TVw:W:XYy")) != -1) {
+	while ((opt = getopt(ac, av, "1246ab:c:e:fghi:kl:m:no:p:qstvx"
+	    "ACD:E:F:HI:KL:MNO:PQ:R:S:TVw:W:XYy")) != -1) {
 		switch (opt) {
 		case '1':
 			options.protocol = SSH_PROTO_1;
@@ -488,6 +492,16 @@ main(int ac, char **av)
 		case 'E':
 			logfile = xstrdup(optarg);
 			break;
+		case 'h':
+			options.connect_via_sctp = 0;
+			break;
+		case 'H':
+#ifdef USE_SCTP
+			options.connect_via_sctp = 1;
+#else
+			fprintf(stderr, "no support for SCTP.\n");
+#endif
+			break;
 		case 'Y':
 			options.forward_x11 = 1;
 			options.forward_x11_trusted = 1;
diff --git a/ssh_config b/ssh_config
index 03a228f..2e8b9de 100644
--- a/ssh_config
+++ b/ssh_config
@@ -46,3 +46,4 @@
 #   VisualHostKey no
 #   ProxyCommand ssh -q -W %h:%p gateway.example.com
 #   RekeyLimit 1G 1h
+#   ConnectViaSCTP no
diff --git a/ssh_config.5 b/ssh_config.5
index 3cadcd7..cab0cde 100644
--- a/ssh_config.5
+++ b/ssh_config.5
@@ -1372,6 +1372,14 @@ Note that this option must be set to
 for
 .Cm RhostsRSAAuthentication
 with older servers.
+.It Cm ConnectViaSCTP
+Specifies whether to make SSH connections using SCTP instead of TCP.
+The argument must be
+.Dq yes
+or
+.Dq no .
+The default is
+.Dq no .
 .It Cm User
 Specifies the user to log in as.
 This can be useful when a different user name is used on different machines.
diff --git a/sshd_config b/sshd_config
index e9045bc..a1a8f03 100644
--- a/sshd_config
+++ b/sshd_config
@@ -96,6 +96,9 @@ AuthorizedKeysFile	.ssh/authorized_keys
 # and ChallengeResponseAuthentication to 'no'.
 #UsePAM no
 
+# Set to yes to enable connections via SCTP as well as TCP.
+#ListenViaSCTP no
+
 #AllowAgentForwarding yes
 #AllowTcpForwarding yes
 #GatewayPorts no
diff --git a/sshd_config.5 b/sshd_config.5
index de330a0..4ef068b 100644
--- a/sshd_config.5
+++ b/sshd_config.5
@@ -728,6 +728,14 @@ options are permitted.
 Additionally, any
 .Cm Port
 options must precede this option for non-port qualified addresses.
+.It Cm ListenViaSCTP
+Specifies whether the daemon should listen via SCTP as well as TCP.
+The argument must be
+.Dq yes
+or
+.Dq no .
+The default is
+.Dq no .
 .It Cm LoginGraceTime
 The server disconnects after this time if the user has not
 successfully logged in.
-- 
1.8.5.4



More information about the openssh-unix-dev mailing list