]> git.ipfire.org Git - thirdparty/openssh-portable.git/commitdiff
upstream commit
authordjm@openbsd.org <djm@openbsd.org>
Wed, 25 Oct 2017 00:15:35 +0000 (00:15 +0000)
committerDamien Miller <djm@mindrot.org>
Wed, 25 Oct 2017 01:26:06 +0000 (12:26 +1100)
Add optional rdomain qualifier to sshd_config's
ListenAddress option to allow listening on a different rdomain(4), e.g.

ListenAddress 0.0.0.0 rdomain 4

Upstream-ID: 24b6622c376feeed9e9be8b9605e593695ac9091

channels.c
misc.c
misc.h
servconf.c
servconf.h
sshd.c
sshd_config.5

index 83442be06432cb3260a882d3abc370615a13b06d..07dc9577ab7e71689a4e6bd52d70ada35ddd9fec 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: channels.c,v 1.375 2017/09/24 13:45:34 djm Exp $ */
+/* $OpenBSD: channels.c,v 1.376 2017/10/25 00:15:35 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -1668,19 +1668,6 @@ port_open_helper(struct ssh *ssh, Channel *c, char *rtype)
        free(local_ipaddr);
 }
 
-static void
-channel_set_reuseaddr(int fd)
-{
-       int on = 1;
-
-       /*
-        * Set socket options.
-        * Allow local port reuse in TIME_WAIT.
-        */
-       if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
-               error("setsockopt SO_REUSEADDR fd %d: %s", fd, strerror(errno));
-}
-
 void
 channel_set_x11_refuse_time(struct ssh *ssh, u_int refuse_time)
 {
@@ -3368,7 +3355,7 @@ channel_setup_fwd_listener_tcpip(struct ssh *ssh, int type,
                        continue;
                }
 
-               channel_set_reuseaddr(sock);
+               set_reuseaddr(sock);
                if (ai->ai_family == AF_INET6)
                        sock_set_v6only(sock);
 
@@ -4439,7 +4426,7 @@ x11_create_display_inet(struct ssh *ssh, int x11_display_offset,
                        if (ai->ai_family == AF_INET6)
                                sock_set_v6only(sock);
                        if (x11_use_localhost)
-                               channel_set_reuseaddr(sock);
+                               set_reuseaddr(sock);
                        if (bind(sock, ai->ai_addr, ai->ai_addrlen) < 0) {
                                debug2("%s: bind port %d: %.100s", __func__,
                                    port, strerror(errno));
diff --git a/misc.c b/misc.c
index cb8bf5c14705edbdb7f5511852171f470028cb03..cc22fbef419113f2813c93959a063f8c748897cc 100644 (file)
--- a/misc.c
+++ b/misc.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: misc.c,v 1.116 2017/10/24 19:41:45 millert Exp $ */
+/* $OpenBSD: misc.c,v 1.117 2017/10/25 00:15:35 djm Exp $ */
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
  * Copyright (c) 2005,2006 Damien Miller.  All rights reserved.
@@ -167,6 +167,44 @@ set_nodelay(int fd)
                error("setsockopt TCP_NODELAY: %.100s", strerror(errno));
 }
 
+/* Allow local port reuse in TIME_WAIT */
+int
+set_reuseaddr(int fd)
+{
+       int on = 1;
+
+       if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
+               error("setsockopt SO_REUSEADDR fd %d: %s", fd, strerror(errno));
+               return -1;
+       }
+       return 0;
+}
+
+/* Set routing table */
+int
+set_rdomain(int fd, const char *name)
+{
+       int rtable;
+       const char *errstr;
+
+       if (name == NULL)
+               return 0; /* default table */
+
+       rtable = (int)strtonum(name, 0, 255, &errstr);
+       if (errstr != NULL) {
+               /* Shouldn't happen */
+               error("Invalid routing domain \"%s\": %s", name, errstr);
+               return -1;
+       }
+       if (setsockopt(fd, SOL_SOCKET, SO_RTABLE,
+           &rtable, sizeof(rtable)) == -1) {
+               error("Failed to set routing domain %d on fd %d: %s",
+                   rtable, fd, strerror(errno));
+               return -1;
+       }
+       return 0;
+}
+
 /* Characters considered whitespace in strsep calls. */
 #define WHITESPACE " \t\r\n"
 #define QUOTE  "\""
diff --git a/misc.h b/misc.h
index 19fdf5c84f263d5c44839689ec3db6945088d761..f36081f5d59e0b0302664628a0afbf0d27bf96b9 100644 (file)
--- a/misc.h
+++ b/misc.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: misc.h,v 1.65 2017/10/23 05:08:00 djm Exp $ */
+/* $OpenBSD: misc.h,v 1.66 2017/10/25 00:15:35 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -48,6 +48,8 @@ char  *strdelim(char **);
 int     set_nonblock(int);
 int     unset_nonblock(int);
 void    set_nodelay(int);
+int     set_reuseaddr(int);
+int     set_rdomain(int, const char *);
 int     a2port(const char *);
 int     a2tun(const char *, int *);
 char   *put_host_port(const char *, u_short);
index a96df4f67e6fab5b33b83b66edd2f2ef7d197e04..68db047f2b7a5ef45e02db4eda2a40a08d82dddd 100644 (file)
@@ -1,5 +1,5 @@
 
-/* $OpenBSD: servconf.c,v 1.314 2017/10/05 15:52:03 djm Exp $ */
+/* $OpenBSD: servconf.c,v 1.315 2017/10/25 00:15:35 djm Exp $ */
 /*
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
  *                    All rights reserved
 
 #include <sys/types.h>
 #include <sys/socket.h>
+#ifdef HAVE_SYS_SYSCTL_H
+#include <sys/sysctl.h>
+#endif
 
 #include <netinet/in.h>
 #include <netinet/in_systm.h>
 #include <netinet/ip.h>
+#ifdef HAVE_NET_ROUTE_H
+#include <net/route.h>
+#endif
 
 #include <ctype.h>
 #include <netdb.h>
 #include "myproposal.h"
 #include "digest.h"
 
-static void add_listen_addr(ServerOptions *, char *, int);
-static void add_one_listen_addr(ServerOptions *, char *, int);
+static void add_listen_addr(ServerOptions *, const char *,
+    const char *, int);
+static void add_one_listen_addr(ServerOptions *, const char *,
+    const char *, int);
 
 /* Use of privilege separation or not */
 extern int use_privsep;
@@ -81,6 +89,7 @@ initialize_server_options(ServerOptions *options)
        options->queued_listen_addrs = NULL;
        options->num_queued_listens = 0;
        options->listen_addrs = NULL;
+       options->num_listen_addrs = 0;
        options->address_family = -1;
        options->num_host_key_files = 0;
        options->num_host_cert_files = 0;
@@ -252,7 +261,7 @@ fill_default_server_options(ServerOptions *options)
        if (options->address_family == -1)
                options->address_family = AF_UNSPEC;
        if (options->listen_addrs == NULL)
-               add_listen_addr(options, NULL, 0);
+               add_listen_addr(options, NULL, NULL, 0);
        if (options->pid_file == NULL)
                options->pid_file = xstrdup(_PATH_SSH_DAEMON_PID_FILE);
        if (options->login_grace_time == -1)
@@ -658,23 +667,51 @@ derelativise_path(const char *path)
 }
 
 static void
-add_listen_addr(ServerOptions *options, char *addr, int port)
+add_listen_addr(ServerOptions *options, const char *addr,
+    const char *rdomain, int port)
 {
        u_int i;
 
-       if (port == 0)
-               for (i = 0; i < options->num_ports; i++)
-                       add_one_listen_addr(options, addr, options->ports[i]);
-       else
-               add_one_listen_addr(options, addr, port);
+       if (port > 0)
+               add_one_listen_addr(options, addr, rdomain, port);
+       else {
+               for (i = 0; i < options->num_ports; i++) {
+                       add_one_listen_addr(options, addr, rdomain,
+                           options->ports[i]);
+               }
+       }
 }
 
 static void
-add_one_listen_addr(ServerOptions *options, char *addr, int port)
+add_one_listen_addr(ServerOptions *options, const char *addr,
+    const char *rdomain, int port)
 {
        struct addrinfo hints, *ai, *aitop;
        char strport[NI_MAXSERV];
        int gaierr;
+       u_int i;
+
+       /* Find listen_addrs entry for this rdomain */
+       for (i = 0; i < options->num_listen_addrs; i++) {
+               if (rdomain == NULL && options->listen_addrs[i].rdomain == NULL)
+                       break;
+               if (rdomain == NULL || options->listen_addrs[i].rdomain == NULL)
+                       continue;
+               if (strcmp(rdomain, options->listen_addrs[i].rdomain) == 0)
+                       break;
+       }
+       if (i >= options->num_listen_addrs) {
+               /* No entry for this rdomain; allocate one */
+               if (i >= INT_MAX)
+                       fatal("%s: too many listen addresses", __func__);
+               options->listen_addrs = xrecallocarray(options->listen_addrs,
+                   options->num_listen_addrs, options->num_listen_addrs + 1,
+                   sizeof(*options->listen_addrs));
+               i = options->num_listen_addrs++;
+               if (rdomain != NULL)
+                       options->listen_addrs[i].rdomain = xstrdup(rdomain);
+       }
+       /* options->listen_addrs[i] points to the addresses for this rdomain */
 
        memset(&hints, 0, sizeof(hints));
        hints.ai_family = options->address_family;
@@ -687,8 +724,37 @@ add_one_listen_addr(ServerOptions *options, char *addr, int port)
                    ssh_gai_strerror(gaierr));
        for (ai = aitop; ai->ai_next; ai = ai->ai_next)
                ;
-       ai->ai_next = options->listen_addrs;
-       options->listen_addrs = aitop;
+       ai->ai_next = options->listen_addrs[i].addrs;
+       options->listen_addrs[i].addrs = aitop;
+}
+
+/* Returns nonzero if the routing domain name is valid */
+static int
+valid_rdomain(const char *name)
+{
+       const char *errstr;
+       long long num;
+       struct rt_tableinfo info;
+       int mib[6];
+       size_t miblen = sizeof(mib);
+
+       if (name == NULL)
+               return 1;
+
+       num = strtonum(name, 0, 255, &errstr);
+       if (errstr != NULL)
+               return 0;
+
+       /* Check whether the table actually exists */
+       memset(mib, 0, sizeof(mib));
+       mib[0] = CTL_NET;
+       mib[1] = PF_ROUTE;
+       mib[4] = NET_RT_TABLE;
+       mib[5] = (int)num;
+       if (sysctl(mib, 6, &info, &miblen, NULL, 0) == -1)
+               return 0;
+
+       return 1;
 }
 
 /*
@@ -696,18 +762,19 @@ add_one_listen_addr(ServerOptions *options, char *addr, int port)
  * and AddressFamily options.
  */
 static void
-queue_listen_addr(ServerOptions *options, char *addr, int port)
+queue_listen_addr(ServerOptions *options, const char *addr,
+    const char *rdomain, int port)
 {
-       options->queued_listen_addrs = xreallocarray(
-           options->queued_listen_addrs, options->num_queued_listens + 1,
-           sizeof(addr));
-       options->queued_listen_ports = xreallocarray(
-           options->queued_listen_ports, options->num_queued_listens + 1,
-           sizeof(port));
-       options->queued_listen_addrs[options->num_queued_listens] =
-           xstrdup(addr);
-       options->queued_listen_ports[options->num_queued_listens] = port;
-       options->num_queued_listens++;
+       struct queued_listenaddr *qla;
+
+       options->queued_listen_addrs = xrecallocarray(
+           options->queued_listen_addrs,
+           options->num_queued_listens, options->num_queued_listens + 1,
+           sizeof(*options->queued_listen_addrs));
+       qla = &options->queued_listen_addrs[options->num_queued_listens++];
+       qla->addr = xstrdup(addr);
+       qla->port = port;
+       qla->rdomain = rdomain == NULL ? NULL : xstrdup(rdomain);
 }
 
 /*
@@ -717,6 +784,7 @@ static void
 process_queued_listen_addrs(ServerOptions *options)
 {
        u_int i;
+       struct queued_listenaddr *qla;
 
        if (options->num_ports == 0)
                options->ports[options->num_ports++] = SSH_DEFAULT_PORT;
@@ -724,15 +792,13 @@ process_queued_listen_addrs(ServerOptions *options)
                options->address_family = AF_UNSPEC;
 
        for (i = 0; i < options->num_queued_listens; i++) {
-               add_listen_addr(options, options->queued_listen_addrs[i],
-                   options->queued_listen_ports[i]);
-               free(options->queued_listen_addrs[i]);
-               options->queued_listen_addrs[i] = NULL;
+               qla = &options->queued_listen_addrs[i];
+               add_listen_addr(options, qla->addr, qla->rdomain, qla->port);
+               free(qla->addr);
+               free(qla->rdomain);
        }
        free(options->queued_listen_addrs);
        options->queued_listen_addrs = NULL;
-       free(options->queued_listen_ports);
-       options->queued_listen_ports = NULL;
        options->num_queued_listens = 0;
 }
 
@@ -1127,20 +1193,33 @@ process_server_config_line(ServerOptions *options, char *line,
                /* check for bare IPv6 address: no "[]" and 2 or more ":" */
                if (strchr(arg, '[') == NULL && (p = strchr(arg, ':')) != NULL
                    && strchr(p+1, ':') != NULL) {
-                       queue_listen_addr(options, arg, 0);
-                       break;
-               }
-               p = hpdelim(&arg);
-               if (p == NULL)
-                       fatal("%s line %d: bad address:port usage",
-                           filename, linenum);
-               p = cleanhostname(p);
-               if (arg == NULL)
                        port = 0;
-               else if ((port = a2port(arg)) <= 0)
-                       fatal("%s line %d: bad port number", filename, linenum);
+                       p = arg;
+               } else {
+                       p = hpdelim(&arg);
+                       if (p == NULL)
+                               fatal("%s line %d: bad address:port usage",
+                                   filename, linenum);
+                       p = cleanhostname(p);
+                       if (arg == NULL)
+                               port = 0;
+                       else if ((port = a2port(arg)) <= 0)
+                               fatal("%s line %d: bad port number",
+                                   filename, linenum);
+               }
+               /* Optional routing table */
+               arg2 = NULL;
+               if ((arg = strdelim(&cp)) != NULL) {
+                       if (strcmp(arg, "rdomain") != 0 ||
+                           (arg2 = strdelim(&cp)) == NULL)
+                               fatal("%s line %d: bad ListenAddress syntax",
+                                   filename, linenum);
+                       if (!valid_rdomain(arg2))
+                               fatal("%s line %d: bad routing domain",
+                                   filename, linenum);
+               }
 
-               queue_listen_addr(options, p, port);
+               queue_listen_addr(options, p, arg2, port);
 
                break;
 
@@ -2251,45 +2330,61 @@ dump_cfg_strarray_oneline(ServerOpCodes code, u_int count, char **vals)
        printf("\n");
 }
 
-void
-dump_config(ServerOptions *o)
+static char *
+format_listen_addrs(struct listenaddr *la)
 {
-       u_int i;
-       int ret;
+       int r;
        struct addrinfo *ai;
-       char addr[NI_MAXHOST], port[NI_MAXSERV], *s = NULL;
+       char addr[NI_MAXHOST], port[NI_MAXSERV];
        char *laddr1 = xstrdup(""), *laddr2 = NULL;
 
-       /* these are usually at the top of the config */
-       for (i = 0; i < o->num_ports; i++)
-               printf("port %d\n", o->ports[i]);
-       dump_cfg_fmtint(sAddressFamily, o->address_family);
-
        /*
         * ListenAddress must be after Port.  add_one_listen_addr pushes
         * addresses onto a stack, so to maintain ordering we need to
         * print these in reverse order.
         */
-       for (ai = o->listen_addrs; ai; ai = ai->ai_next) {
-               if ((ret = getnameinfo(ai->ai_addr, ai->ai_addrlen, addr,
+       for (ai = la->addrs; ai; ai = ai->ai_next) {
+               if ((r = getnameinfo(ai->ai_addr, ai->ai_addrlen, addr,
                    sizeof(addr), port, sizeof(port),
                    NI_NUMERICHOST|NI_NUMERICSERV)) != 0) {
-                       error("getnameinfo failed: %.100s",
-                           (ret != EAI_SYSTEM) ? gai_strerror(ret) :
-                           strerror(errno));
+                       error("getnameinfo: %.100s", ssh_gai_strerror(r));
+                       continue;
+               }
+               laddr2 = laddr1;
+               if (ai->ai_family == AF_INET6) {
+                       xasprintf(&laddr1, "listenaddress [%s]:%s%s%s\n%s",
+                           addr, port,
+                           la->rdomain == NULL ? "" : " rdomain ",
+                           la->rdomain == NULL ? "" : la->rdomain,
+                           laddr2);
                } else {
-                       laddr2 = laddr1;
-                       if (ai->ai_family == AF_INET6)
-                               xasprintf(&laddr1, "listenaddress [%s]:%s\n%s",
-                                   addr, port, laddr2);
-                       else
-                               xasprintf(&laddr1, "listenaddress %s:%s\n%s",
-                                   addr, port, laddr2);
-                       free(laddr2);
+                       xasprintf(&laddr1, "listenaddress %s:%s%s%s\n%s",
+                           addr, port,
+                           la->rdomain == NULL ? "" : " rdomain ",
+                           la->rdomain == NULL ? "" : la->rdomain,
+                           laddr2);
                }
+               free(laddr2);
+       }
+       return laddr1;
+}
+
+void
+dump_config(ServerOptions *o)
+{
+       char *s;
+       u_int i;
+
+       /* these are usually at the top of the config */
+       for (i = 0; i < o->num_ports; i++)
+               printf("port %d\n", o->ports[i]);
+       dump_cfg_fmtint(sAddressFamily, o->address_family);
+
+       for (i = 0; i < o->num_listen_addrs; i++) {
+               s = format_listen_addrs(&o->listen_addrs[i]);
+               printf("%s", s);
+               free(s);
        }
-       printf("%s", laddr1);
-       free(laddr1);
 
        /* integer arguments */
 #ifdef USE_PAM
index 1ff3bc5a1792803b5c707379a080598bef5cd26a..3d0a0653f1c3b7d1468513e975f8f466a7c3e9b7 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: servconf.h,v 1.127 2017/10/05 15:52:03 djm Exp $ */
+/* $OpenBSD: servconf.h,v 1.128 2017/10/25 00:15:35 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
 struct ssh;
 struct fwd_perm_list;
 
+/*
+ * Used to store addresses from ListenAddr directives. These may be
+ * incomplete, as they may specify addresses that need to be merged
+ * with any ports requested by ListenPort.
+ */
+struct queued_listenaddr {
+       char *addr;
+       int port; /* <=0 if unspecified */
+       char *rdomain;
+};
+
+/* Resolved listen addresses, grouped by optional routing domain */
+struct listenaddr {
+       char *rdomain;
+       struct addrinfo *addrs;
+};
+
 typedef struct {
        u_int   num_ports;
        u_int   ports_from_cmdline;
        int     ports[MAX_PORTS];       /* Port number to listen on. */
+       struct queued_listenaddr *queued_listen_addrs;
        u_int   num_queued_listens;
-       char   **queued_listen_addrs;
-       int    *queued_listen_ports;
-       struct addrinfo *listen_addrs;  /* Addresses for server to listen. */
+       struct listenaddr *listen_addrs;
+       u_int   num_listen_addrs;
        int     address_family;         /* Address family used by the server. */
 
        char   **host_key_files;        /* Files containing host keys. */
diff --git a/sshd.c b/sshd.c
index 0e3ac26dbe25b4020e0ab211e1f593860c37c495..93b02b6c8b9525a77c63099c2ee4c5ee984519bc 100644 (file)
--- a/sshd.c
+++ b/sshd.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshd.c,v 1.493 2017/10/05 15:52:03 djm Exp $ */
+/* $OpenBSD: sshd.c,v 1.494 2017/10/25 00:15:35 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -1015,13 +1015,13 @@ server_accept_inetd(int *sock_in, int *sock_out)
  * Listen for TCP connections
  */
 static void
-server_listen(void)
+listen_on_addrs(struct listenaddr *la)
 {
-       int ret, listen_sock, on = 1;
+       int ret, listen_sock;
        struct addrinfo *ai;
        char ntop[NI_MAXHOST], strport[NI_MAXSERV];
 
-       for (ai = options.listen_addrs; ai; ai = ai->ai_next) {
+       for (ai = la->addrs; ai; ai = ai->ai_next) {
                if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
                        continue;
                if (num_listen_socks >= MAX_LISTEN_SOCKS)
@@ -1051,13 +1051,13 @@ server_listen(void)
                        close(listen_sock);
                        continue;
                }
-               /*
-                * Set socket options.
-                * Allow local port reuse in TIME_WAIT.
-                */
-               if (setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR,
-                   &on, sizeof(on)) == -1)
-                       error("setsockopt SO_REUSEADDR: %s", strerror(errno));
+               /* Socket options */
+               set_reuseaddr(listen_sock);
+               if (la->rdomain != NULL &&
+                   set_rdomain(listen_sock, la->rdomain) == -1) {
+                       close(listen_sock);
+                       continue;
+               }
 
                /* Only communicate in IPv6 over AF_INET6 sockets. */
                if (ai->ai_family == AF_INET6)
@@ -1079,9 +1079,28 @@ server_listen(void)
                if (listen(listen_sock, SSH_LISTEN_BACKLOG) < 0)
                        fatal("listen on [%s]:%s: %.100s",
                            ntop, strport, strerror(errno));
-               logit("Server listening on %s port %s.", ntop, strport);
+               logit("Server listening on %s port %s%s%s.",
+                   ntop, strport,
+                   la->rdomain == NULL ? "" : " rdomain ",
+                   la->rdomain == NULL ? "" : la->rdomain);
+       }
+}
+
+static void
+server_listen(void)
+{
+       u_int i;
+
+       for (i = 0; i < options.num_listen_addrs; i++) {
+               listen_on_addrs(&options.listen_addrs[i]);
+               freeaddrinfo(options.listen_addrs[i].addrs);
+               free(options.listen_addrs[i].rdomain);
+               memset(&options.listen_addrs[i], 0,
+                   sizeof(options.listen_addrs[i]));
        }
-       freeaddrinfo(options.listen_addrs);
+       free(options.listen_addrs);
+       options.listen_addrs = NULL;
+       options.num_listen_addrs = 0;
 
        if (!num_listen_socks)
                fatal("Cannot bind any address.");
index 77a8354abab97736dcd9898fe79efdfd32ff83ce..b63a022b7a020480ffa59c2a4896c87ceb14f973 100644 (file)
@@ -33,8 +33,8 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.\" $OpenBSD: sshd_config.5,v 1.255 2017/10/13 16:50:45 jmc Exp $
-.Dd $Mdocdate: October 13 2017 $
+.\" $OpenBSD: sshd_config.5,v 1.256 2017/10/25 00:15:35 djm Exp $
+.Dd $Mdocdate: October 25 2017 $
 .Dt SSHD_CONFIG 5
 .Os
 .Sh NAME
@@ -909,31 +909,48 @@ The following forms may be used:
 .It
 .Cm ListenAddress
 .Sm off
-.Ar host | Ar IPv4_addr | Ar IPv6_addr
+.Ar hostname | Ar address
 .Sm on
+.Oo rdomain Ar domain Oc
 .It
 .Cm ListenAddress
 .Sm off
-.Ar host | Ar IPv4_addr : Ar port
+.Ar hostname : Ar port
 .Sm on
+.Oo rdomain Ar domain Oc
+.It
+.Cm ListenAddress
+.Sm off
+.Ar IPv4_address : Ar port
+.Sm on
+.Oo rdomain Ar domain Oc
 .It
 .Cm ListenAddress
 .Sm off
 .Oo
-.Ar host | Ar IPv6_addr Oc : Ar port
+.Ar hostname | address Oc : Ar port
 .Sm on
+.Oo rdomain Ar domain Oc
 .El
 .Pp
+The optional
+.Cm rdomain
+qualifier requests
+.Xr sshd 8
+listen in an explicit routing domain.
 If
 .Ar port
 is not specified,
 sshd will listen on the address and all
 .Cm Port
 options specified.
-The default is to listen on all local addresses.
+The default is to listen on all local addresses on the current default
+routing domain.
 Multiple
 .Cm ListenAddress
 options are permitted.
+For more information on routing domains, see
+.Xr rdomain 4.
 .It Cm LoginGraceTime
 The server disconnects after this time if the user has not
 successfully logged in.