diff mbox

[ovs-dev,v2] ovsdb: implement read-only remote connection type

Message ID 1476468354-811-1-git-send-email-lrichard@redhat.com
State Changes Requested
Headers show

Commit Message

Lance Richardson Oct. 14, 2016, 6:05 p.m. UTC
This change set adds a new optional access-type specifier to
remote connection descriptors for ovsdb-server.

Examples:
    --remote=ptcp:ro:0:192.168.0.10
    --remote=punix:ro:asocket.sock
    --remote=pssl:ro:0:192.168.0.10
    --remote=tcp:ro:192.168.0.99:4444
    --remote=unix:ro:asocket.sock
    --remote=ssl:ro:192.168.0.10:4444

Operations that would alter the state of the database are not
permitted on connections for which the "ro" access-type is specified.

Signed-off-by: Lance Richardson <lrichard@redhat.com>
---
RFC->v1:
   - Added autotest keywords for ssl tests.
   - Changed active tcp and ssl tests to find an unused TCP
     port instead of using hardcoded values (approach borrowed
     from ovsdb-idl.at).
   - Updated ovsdb-server.1 man page to add "ro:" connection types.
   - Fixed line length issue (identified by checkpatch.py) in
     jsonrpc-server.c.
   - Removed references to "read-write" connection type (still
     wondering "rw:" should be supported where "ro:" is allowed
     for symmetry).

v1->v2:
   - Rebased to current tip of master.
   - Adjusted tests per commit 561205007e17
     ("tests: Get rid of overly specific --pidfile and --unixctl options.")

 lib/stream-ssl.c                          |  11 +-
 lib/stream-tcp.c                          |  21 +--
 lib/stream.c                              |  94 ++++++++++++-
 lib/stream.h                              |   4 +-
 manpages.mk                               |   4 +
 ovn/controller-vtep/ovn-controller-vtep.c |   2 +-
 ovn/controller/ovn-controller.c           |   2 +-
 ovn/northd/ovn-northd.c                   |   2 +-
 ovn/utilities/ovn-sbctl.c                 |   2 +-
 ovn/utilities/ovn-trace.c                 |   2 +-
 ovsdb/automake.mk                         |   2 +
 ovsdb/jsonrpc-server.c                    |  11 +-
 ovsdb/ovsdb-client.c                      |   2 +-
 ovsdb/ovsdb-server.1.in                   |   2 +
 ovsdb/ovsdb-server.c                      |   2 +-
 ovsdb/remote-active-ro.man                |  28 ++++
 ovsdb/remote-passive-ro.man               |  35 +++++
 tests/ovsdb-server.at                     | 224 ++++++++++++++++++++++++++++++
 tests/test-jsonrpc.c                      |   2 +-
 utilities/ovs-vsctl.c                     |   2 +-
 vswitchd/ovs-vswitchd.c                   |   2 +-
 vtep/vtep-ctl.c                           |   2 +-
 22 files changed, 424 insertions(+), 34 deletions(-)
 create mode 100644 ovsdb/remote-active-ro.man
 create mode 100644 ovsdb/remote-passive-ro.man

Comments

Ben Pfaff Oct. 17, 2016, 6:35 p.m. UTC | #1
On Fri, Oct 14, 2016 at 02:05:54PM -0400, Lance Richardson wrote:
> This change set adds a new optional access-type specifier to
> remote connection descriptors for ovsdb-server.
> 
> Examples:
>     --remote=ptcp:ro:0:192.168.0.10
>     --remote=punix:ro:asocket.sock
>     --remote=pssl:ro:0:192.168.0.10
>     --remote=tcp:ro:192.168.0.99:4444
>     --remote=unix:ro:asocket.sock
>     --remote=ssl:ro:192.168.0.10:4444
> 
> Operations that would alter the state of the database are not
> permitted on connections for which the "ro" access-type is specified.
> 
> Signed-off-by: Lance Richardson <lrichard@redhat.com>

I'm nervous about this means of configuration for a couple of reasons.

First, it looks like a layering violation.  --remote specifies the L3
and L4 information to connect to a remote database server, in a form
used by OVS in other places too to specify an L3 and L4 (for example, it
is also used to specify a remote OpenFlow switch or controller).  Adding
extra specifiers in the middle that only apply to a particular higher
level protocol does not seem graceful.  It's better to add these extra
specifiers at a higher level too.

Second, it doesn't generalize well.  If we need additional OVSDB-level
specifiers later, then it looks like using this technique they'd also
have to be added at each of the stream protocol implementations.  It's
better to have a single implementation.

I suggest that, instead, we do something based on ovsdb-server's support
for remotes that come from the database.  Take a look at the "db:" form
for the --remote option described in ovsdb-server(1).  It offers support
for a number of configuration options that aren't available via the
command line, via columns in the database table.  It would be natural to
make it support a "read_only" configuration option column as well.
Once we have that, we can think about how to support configuring these
kinds of options for remotes added through the command line instead of
through the database table.  One could, for example, add a command-line
option corresponding to each configuration column, with some kind of
convention such as the option affects the remote specified in the most
recent --remote option.  There are other possibilities too, of course.

Thanks,

Ben.
Lance Richardson Oct. 18, 2016, 9:16 p.m. UTC | #2
> From: "Ben Pfaff" <blp@ovn.org>
> To: "Lance Richardson" <lrichard@redhat.com>
> Cc: dev@openvswitch.org, azhou@ovn.org, nusiddiq@redhat.com, bschanmu@redhat.com, jpettit@ovn.org, russell@ovn.org
> Sent: Monday, October 17, 2016 2:35:22 PM
> Subject: Re: [PATCH v2] ovsdb: implement read-only remote connection type
> 
> On Fri, Oct 14, 2016 at 02:05:54PM -0400, Lance Richardson wrote:
> > This change set adds a new optional access-type specifier to
> > remote connection descriptors for ovsdb-server.
> > 
> > Examples:
> >     --remote=ptcp:ro:0:192.168.0.10
> >     --remote=punix:ro:asocket.sock
> >     --remote=pssl:ro:0:192.168.0.10
> >     --remote=tcp:ro:192.168.0.99:4444
> >     --remote=unix:ro:asocket.sock
> >     --remote=ssl:ro:192.168.0.10:4444
> > 
> > Operations that would alter the state of the database are not
> > permitted on connections for which the "ro" access-type is specified.
> > 
> > Signed-off-by: Lance Richardson <lrichard@redhat.com>
> 
> I'm nervous about this means of configuration for a couple of reasons.
> 
> First, it looks like a layering violation.  --remote specifies the L3
> and L4 information to connect to a remote database server, in a form
> used by OVS in other places too to specify an L3 and L4 (for example, it
> is also used to specify a remote OpenFlow switch or controller).  Adding
> extra specifiers in the middle that only apply to a particular higher
> level protocol does not seem graceful.  It's better to add these extra
> specifiers at a higher level too.
> 
> Second, it doesn't generalize well.  If we need additional OVSDB-level
> specifiers later, then it looks like using this technique they'd also
> have to be added at each of the stream protocol implementations.  It's
> better to have a single implementation.
> 
> I suggest that, instead, we do something based on ovsdb-server's support
> for remotes that come from the database.  Take a look at the "db:" form
> for the --remote option described in ovsdb-server(1).  It offers support
> for a number of configuration options that aren't available via the
> command line, via columns in the database table.  It would be natural to
> make it support a "read_only" configuration option column as well.
> Once we have that, we can think about how to support configuring these
> kinds of options for remotes added through the command line instead of
> through the database table.  One could, for example, add a command-line
> option corresponding to each configuration column, with some kind of
> convention such as the option affects the remote specified in the most
> recent --remote option.  There are other possibilities too, of course.
> 
> Thanks,
> 
> Ben.
> 

I had a pretty good idea that the configuration method would be controversial,
but figured it was better to start somewhere (anywhere) than not start at all.

For the db method, adding a "read only" column makes sense and would match
up nicely with the max_backoff and inactivity_probe columns that are already
present.

For the command line, I wonder if we could support something like:

    --remote=ptcp:0:192.168.0.10,read_only=true

This could also be applied to other existing and future per-connection
configuration options, e.g.:

    --remote=ptcp:0:192.168.0.10,max_backoff=1000,inactivity_probe=5000

Would that be more palatable?

Thanks,

    Lance
Ben Pfaff Oct. 20, 2016, 5:46 p.m. UTC | #3
On Tue, Oct 18, 2016 at 05:16:39PM -0400, Lance Richardson wrote:
> > From: "Ben Pfaff" <blp@ovn.org>
> > To: "Lance Richardson" <lrichard@redhat.com>
> > Cc: dev@openvswitch.org, azhou@ovn.org, nusiddiq@redhat.com, bschanmu@redhat.com, jpettit@ovn.org, russell@ovn.org
> > Sent: Monday, October 17, 2016 2:35:22 PM
> > Subject: Re: [PATCH v2] ovsdb: implement read-only remote connection type
> > 
> > On Fri, Oct 14, 2016 at 02:05:54PM -0400, Lance Richardson wrote:
> > > This change set adds a new optional access-type specifier to
> > > remote connection descriptors for ovsdb-server.
> > > 
> > > Examples:
> > >     --remote=ptcp:ro:0:192.168.0.10
> > >     --remote=punix:ro:asocket.sock
> > >     --remote=pssl:ro:0:192.168.0.10
> > >     --remote=tcp:ro:192.168.0.99:4444
> > >     --remote=unix:ro:asocket.sock
> > >     --remote=ssl:ro:192.168.0.10:4444
> > > 
> > > Operations that would alter the state of the database are not
> > > permitted on connections for which the "ro" access-type is specified.
> > > 
> > > Signed-off-by: Lance Richardson <lrichard@redhat.com>
> > 
> > I'm nervous about this means of configuration for a couple of reasons.
> > 
> > First, it looks like a layering violation.  --remote specifies the L3
> > and L4 information to connect to a remote database server, in a form
> > used by OVS in other places too to specify an L3 and L4 (for example, it
> > is also used to specify a remote OpenFlow switch or controller).  Adding
> > extra specifiers in the middle that only apply to a particular higher
> > level protocol does not seem graceful.  It's better to add these extra
> > specifiers at a higher level too.
> > 
> > Second, it doesn't generalize well.  If we need additional OVSDB-level
> > specifiers later, then it looks like using this technique they'd also
> > have to be added at each of the stream protocol implementations.  It's
> > better to have a single implementation.
> > 
> > I suggest that, instead, we do something based on ovsdb-server's support
> > for remotes that come from the database.  Take a look at the "db:" form
> > for the --remote option described in ovsdb-server(1).  It offers support
> > for a number of configuration options that aren't available via the
> > command line, via columns in the database table.  It would be natural to
> > make it support a "read_only" configuration option column as well.
> > Once we have that, we can think about how to support configuring these
> > kinds of options for remotes added through the command line instead of
> > through the database table.  One could, for example, add a command-line
> > option corresponding to each configuration column, with some kind of
> > convention such as the option affects the remote specified in the most
> > recent --remote option.  There are other possibilities too, of course.
> > 
> > Thanks,
> > 
> > Ben.
> > 
> 
> I had a pretty good idea that the configuration method would be controversial,
> but figured it was better to start somewhere (anywhere) than not start at all.
> 
> For the db method, adding a "read only" column makes sense and would match
> up nicely with the max_backoff and inactivity_probe columns that are already
> present.

Yes.

> For the command line, I wonder if we could support something like:
> 
>     --remote=ptcp:0:192.168.0.10,read_only=true
> 
> This could also be applied to other existing and future per-connection
> configuration options, e.g.:
> 
>     --remote=ptcp:0:192.168.0.10,max_backoff=1000,inactivity_probe=5000
> 
> Would that be more palatable?

Hmm.  I'm not super happy with adding a syntax with commas that can't be
escaped or quoted.

How about adding a --ro-remote option for a read-only remote?  It
doesn't generalize well either, but it's easy to deal with for now,
doesn't have layering violations, doesn't impose any new requirements on
the syntax of remotes (e.g. no commas), and it's easy to support later
in a deprecated form if we add something more general.

Another possibility to make this only configurable through the database.
I guess that's OK too.
diff mbox

Patch

diff --git a/lib/stream-ssl.c b/lib/stream-ssl.c
index a5c32a1..2443005 100644
--- a/lib/stream-ssl.c
+++ b/lib/stream-ssl.c
@@ -778,13 +778,14 @@  pssl_pstream_cast(struct pstream *pstream)
 }
 
 static int
-pssl_open(const char *name OVS_UNUSED, char *suffix, struct pstream **pstreamp,
+pssl_open(const char *name, char *suffix, struct pstream **pstreamp,
           uint8_t dscp)
 {
     char bound_name[SS_NTOP_BUFSIZE + 16];
     char addrbuf[SS_NTOP_BUFSIZE];
     struct sockaddr_storage ss;
     struct pssl_pstream *pssl;
+    const char *access = "";
     uint16_t port;
     int retval;
     int fd;
@@ -799,9 +800,13 @@  pssl_open(const char *name OVS_UNUSED, char *suffix, struct pstream **pstreamp,
         return -fd;
     }
 
+    if (!strncmp(name, "pssl:ro:", 8)) {
+        access = "ro:";
+    }
+
     port = ss_get_port(&ss);
-    snprintf(bound_name, sizeof bound_name, "pssl:%"PRIu16":%s",
-             port, ss_format_address(&ss, addrbuf, sizeof addrbuf));
+    snprintf(bound_name, sizeof bound_name, "pssl:%s%"PRIu16":%s",
+             access, port, ss_format_address(&ss, addrbuf, sizeof addrbuf));
 
     pssl = xmalloc(sizeof *pssl);
     pstream_init(&pssl->pstream, &pssl_pstream_class, bound_name);
diff --git a/lib/stream-tcp.c b/lib/stream-tcp.c
index 1749fad..e0aaa68 100644
--- a/lib/stream-tcp.c
+++ b/lib/stream-tcp.c
@@ -84,13 +84,13 @@  static int
 new_pstream(char *suffix, const char *name, struct pstream **pstreamp,
             int dscp, char *unlink_path, bool kernel_print_port)
 {
-    char bound_name[SS_NTOP_BUFSIZE + 16];
+    char bound_name[SS_NTOP_BUFSIZE + 20];
     char addrbuf[SS_NTOP_BUFSIZE];
     struct sockaddr_storage ss;
+    const char *access = "";
     int error;
     uint16_t port;
     int fd;
-    char *conn_name = CONST_CAST(char *, name);
 
     fd = inet_open_passive(SOCK_STREAM, suffix, -1, &ss, dscp,
                            kernel_print_port);
@@ -98,14 +98,15 @@  new_pstream(char *suffix, const char *name, struct pstream **pstreamp,
         return -fd;
     }
 
-    port = ss_get_port(&ss);
-    if (!conn_name) {
-        snprintf(bound_name, sizeof bound_name, "ptcp:%"PRIu16":%s",
-                 port, ss_format_address(&ss, addrbuf, sizeof addrbuf));
-        conn_name = bound_name;
+    if (!strncmp(name, "ptcp:ro:", 8)) {
+        access = "ro:";
     }
 
-    error = new_fd_pstream(conn_name, fd, ptcp_accept, unlink_path, pstreamp);
+    port = ss_get_port(&ss);
+    snprintf(bound_name, sizeof bound_name, "ptcp:%s%"PRIu16":%s",
+             access, port, ss_format_address(&ss, addrbuf, sizeof addrbuf));
+
+    error = new_fd_pstream(bound_name, fd, ptcp_accept, unlink_path, pstreamp);
     if (!error) {
         pstream_set_bound_port(*pstreamp, htons(port));
     }
@@ -113,10 +114,10 @@  new_pstream(char *suffix, const char *name, struct pstream **pstreamp,
 }
 
 static int
-ptcp_open(const char *name OVS_UNUSED, char *suffix, struct pstream **pstreamp,
+ptcp_open(const char *name, char *suffix, struct pstream **pstreamp,
           uint8_t dscp)
 {
-    return new_pstream(suffix, NULL, pstreamp, dscp, NULL, true);
+    return new_pstream(suffix, name, pstreamp, dscp, NULL, true);
 }
 
 static int
diff --git a/lib/stream.c b/lib/stream.c
index f6ea849..47cd0fb 100644
--- a/lib/stream.c
+++ b/lib/stream.c
@@ -116,7 +116,7 @@  check_stream_classes(void)
  * connection methods supported by the stream. */
 void
 stream_usage(const char *name, bool active, bool passive,
-             bool bootstrap OVS_UNUSED)
+             bool bootstrap OVS_UNUSED, bool access)
 {
     /* Really this should be implemented via callbacks into the stream
      * providers, but that seems too heavy-weight to bother with at the
@@ -135,6 +135,17 @@  stream_usage(const char *name, bool active, bool passive,
                "Unix domain socket named FILE\n");
     }
 
+    if (active && access) {
+        printf("  tcp:ro:IP:PORT          "
+               "PORT at remote IP (read-only access)\n");
+#ifdef HAVE_OPENSSL
+        printf("  ssl:ro:IP:PORT          "
+               "SSL PORT at remote IP (read-only access)\n");
+#endif
+        printf("  unix:FILE               "
+               "Unix domain socket named FILE (read-only access)\n");
+    }
+
     if (passive) {
         printf("Passive %s connection methods:\n", name);
         printf("  ptcp:PORT[:IP]          "
@@ -147,6 +158,17 @@  stream_usage(const char *name, bool active, bool passive,
                "listen on Unix domain socket FILE\n");
     }
 
+    if (passive && access) {
+        printf("  ptcp:ro:PORT[:IP]       "
+               "listen to TCP PORT on IP (read-only access)\n");
+#ifdef HAVE_OPENSSL
+        printf("  pssl:ro:PORT[:IP]       "
+               "listen for SSL on PORT on IP (read-only access)\n");
+#endif
+        printf("  punix:ro:FILE           "
+               "listen on Unix domain socket FILE (read-only access)\n");
+    }
+
 #ifdef HAVE_OPENSSL
     printf("PKI configuration (required to use SSL):\n"
            "  -p, --private-key=FILE  file with private key\n"
@@ -209,6 +231,7 @@  stream_open(const char *name, struct stream **streamp, uint8_t dscp)
     const struct stream_class *class;
     struct stream *stream;
     char *suffix_copy;
+    const char *next;
     int error;
 
     COVERAGE_INC(stream_open);
@@ -219,8 +242,14 @@  stream_open(const char *name, struct stream **streamp, uint8_t dscp)
         goto error;
     }
 
+    /* Check for read-only access specifier */
+    next = strchr(name, ':') + 1;
+    if (!strncmp(next, "ro:", 3)) {
+        next += 3;
+    }
+
     /* Call class's "open" function. */
-    suffix_copy = xstrdup(strchr(name, ':') + 1);
+    suffix_copy = xstrdup(next);
     error = class->open(name, suffix_copy, &stream, dscp);
     free(suffix_copy);
     if (error) {
@@ -486,6 +515,30 @@  stream_or_pstream_needs_probes(const char *name)
     }
 }
 
+/* Returns 1 if the stream or pstream specified by 'name' includes a read-
+ * only access specification. */
+int
+stream_or_pstream_is_read_only(const char *name)
+{
+    const struct pstream_class *pclass;
+    const struct stream_class *class;
+    const char *next;
+
+    if (!stream_lookup_class(name, &class)) {
+        next = strchr(name, ':') + 1;
+        if (!strncmp(next, "ro:", 3)) {
+            return true;
+        }
+    } else if (!pstream_lookup_class(name, &pclass)) {
+        next = strchr(name, ':') + 1;
+        if (!strncmp(next, "ro:", 3)) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
 /* Attempts to start listening for remote stream connections.  'name' is a
  * connection name in the form "TYPE:ARGS", where TYPE is an passive stream
  * class's name and ARGS are stream class-specific.
@@ -499,6 +552,7 @@  pstream_open(const char *name, struct pstream **pstreamp, uint8_t dscp)
     const struct pstream_class *class;
     struct pstream *pstream;
     char *suffix_copy;
+    const char *next;
     int error;
 
     COVERAGE_INC(pstream_open);
@@ -509,8 +563,14 @@  pstream_open(const char *name, struct pstream **pstreamp, uint8_t dscp)
         goto error;
     }
 
+    /* Check for read-only access specifier */
+    next = strchr(name, ':') + 1;
+    if (!strncmp(next, "ro:", 3)) {
+        next += 3;
+    }
+
     /* Call class's "open" function. */
-    suffix_copy = xstrdup(strchr(name, ':') + 1);
+    suffix_copy = xstrdup(next);
     error = class->listen(name, suffix_copy, &pstream, dscp);
     free(suffix_copy);
     if (error) {
@@ -674,7 +734,19 @@  stream_open_with_default_port(const char *name_,
     char *name;
     int error;
 
-    if ((!strncmp(name_, "tcp:", 4) || !strncmp(name_, "ssl:", 4))
+    if ((!strncmp(name_, "tcp:ro:", 7) || !strncmp(name_, "ssl:ro:", 7))
+        && count_fields(name_) < 4) {
+        if (default_port == OFP_PORT) {
+            VLOG_WARN_ONCE("The default OpenFlow port number has changed "
+                           "from %d to %d",
+                           OFP_OLD_PORT, OFP_PORT);
+        } else if (default_port == OVSDB_PORT) {
+            VLOG_WARN_ONCE("The default OVSDB port number has changed "
+                           "from %d to %d",
+                           OVSDB_OLD_PORT, OVSDB_PORT);
+        }
+        name = xasprintf("%s:%d", name_, default_port);
+    } else if ((!strncmp(name_, "tcp:", 4) || !strncmp(name_, "ssl:", 4))
         && count_fields(name_) < 3) {
         if (default_port == OFP_PORT) {
             VLOG_WARN_ONCE("The default OpenFlow port number has changed "
@@ -706,7 +778,10 @@  pstream_open_with_default_port(const char *name_,
     char *name;
     int error;
 
-    if ((!strncmp(name_, "ptcp:", 5) || !strncmp(name_, "pssl:", 5))
+    if ((!strncmp(name_, "ptcp:ro:", 8) || !strncmp(name_, "pssl:ro:", 8))
+        && count_fields(name_) < 3) {
+        name = xasprintf("%s%d", name_, default_port);
+    } else if ((!strncmp(name_, "ptcp:", 5) || !strncmp(name_, "pssl:", 5))
         && count_fields(name_) < 2) {
         name = xasprintf("%s%d", name_, default_port);
     } else {
@@ -731,8 +806,13 @@  stream_parse_target_with_default_port(const char *target,
                                       uint16_t default_port,
                                       struct sockaddr_storage *ss)
 {
-    return ((!strncmp(target, "tcp:", 4) || !strncmp(target, "ssl:", 4))
-            && inet_parse_active(target + 4, default_port, ss));
+    if (!strncmp(target, "tcp:ro:", 7) || !strncmp(target, "ssl:ro:", 7)) {
+            return inet_parse_active(target + 7, default_port, ss);
+    } else if (!strncmp(target, "tcp:", 4) || !strncmp(target, "ssl:", 4)) {
+            return inet_parse_active(target + 4, default_port, ss);
+    } else {
+        return false;
+    }
 }
 
 /* Attempts to guess the content type of a stream whose first few bytes were
diff --git a/lib/stream.h b/lib/stream.h
index f8e1891..bded34d 100644
--- a/lib/stream.h
+++ b/lib/stream.h
@@ -29,7 +29,8 @@  struct pstream;
 struct stream;
 struct vlog_module;
 
-void stream_usage(const char *name, bool active, bool passive, bool bootstrap);
+void stream_usage(const char *name, bool active, bool passive,
+                  bool bootstrap, bool access);
 
 /* Bidirectional byte streams. */
 int stream_verify_name(const char *name);
@@ -79,6 +80,7 @@  bool stream_parse_target_with_default_port(const char *target,
                                            uint16_t default_port,
                                            struct sockaddr_storage *ss);
 int stream_or_pstream_needs_probes(const char *name);
+int stream_or_pstream_is_read_only(const char *name);
 
 /* Error reporting. */
 
diff --git a/manpages.mk b/manpages.mk
index fa9e59b..4fa58df 100644
--- a/manpages.mk
+++ b/manpages.mk
@@ -63,7 +63,9 @@  ovsdb/ovsdb-server.1: \
 	lib/vlog-syn.man \
 	lib/vlog-unixctl.man \
 	lib/vlog.man \
+	ovsdb/remote-active-ro.man \
 	ovsdb/remote-active.man \
+	ovsdb/remote-passive-ro.man \
 	ovsdb/remote-passive.man \
 	ovsdb/replication-syn.man \
 	ovsdb/replication.man
@@ -87,7 +89,9 @@  lib/unixctl.man:
 lib/vlog-syn.man:
 lib/vlog-unixctl.man:
 lib/vlog.man:
+ovsdb/remote-active-ro.man:
 ovsdb/remote-active.man:
+ovsdb/remote-passive-ro.man:
 ovsdb/remote-passive.man:
 ovsdb/replication-syn.man:
 ovsdb/replication.man:
diff --git a/ovn/controller-vtep/ovn-controller-vtep.c b/ovn/controller-vtep/ovn-controller-vtep.c
index baee789..9c2ed75 100644
--- a/ovn/controller-vtep/ovn-controller-vtep.c
+++ b/ovn/controller-vtep/ovn-controller-vtep.c
@@ -256,7 +256,7 @@  Options:\n\
   -o, --options             list available options\n\
   -V, --version             display version information\n\
 ", program_name, program_name, default_db(), default_db());
-    stream_usage("database", true, false, false);
+    stream_usage("database", true, false, false, false);
     daemon_usage();
     vlog_usage();
     exit(EXIT_SUCCESS);
diff --git a/ovn/controller/ovn-controller.c b/ovn/controller/ovn-controller.c
index 4ac1425..2c7d743 100644
--- a/ovn/controller/ovn-controller.c
+++ b/ovn/controller/ovn-controller.c
@@ -749,7 +749,7 @@  usage(void)
            "usage %s [OPTIONS] [OVS-DATABASE]\n"
            "where OVS-DATABASE is a socket on which the OVS OVSDB server is listening.\n",
                program_name, program_name);
-    stream_usage("OVS-DATABASE", true, false, false);
+    stream_usage("OVS-DATABASE", true, false, false, false);
     daemon_usage();
     vlog_usage();
     printf("\nOther options:\n"
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index 9d74ec6..a77e5c4 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -223,7 +223,7 @@  Options:\n\
 ", program_name, program_name, default_nb_db(), default_sb_db());
     daemon_usage();
     vlog_usage();
-    stream_usage("database", true, true, false);
+    stream_usage("database", true, true, false, false);
 }
 
 struct tnlid_node {
diff --git a/ovn/utilities/ovn-sbctl.c b/ovn/utilities/ovn-sbctl.c
index b894b8b..e064330 100644
--- a/ovn/utilities/ovn-sbctl.c
+++ b/ovn/utilities/ovn-sbctl.c
@@ -326,7 +326,7 @@  Options:\n\
 Other options:\n\
   -h, --help                  display this help message\n\
   -V, --version               display version information\n");
-    stream_usage("database", true, true, false);
+    stream_usage("database", true, true, false, false);
     exit(EXIT_SUCCESS);
 }
 
diff --git a/ovn/utilities/ovn-trace.c b/ovn/utilities/ovn-trace.c
index 7863f70..7b0eb2a 100644
--- a/ovn/utilities/ovn-trace.c
+++ b/ovn/utilities/ovn-trace.c
@@ -255,7 +255,7 @@  Other options:\n\
   --unixctl=SOCKET            set control socket name\n\
   -h, --help                  display this help message\n\
   -V, --version               display version information\n");
-    stream_usage("database", true, true, false);
+    stream_usage("database", true, true, false, false);
     exit(EXIT_SUCCESS);
 }
 
diff --git a/ovsdb/automake.mk b/ovsdb/automake.mk
index 099ed3c..112cc9b 100644
--- a/ovsdb/automake.mk
+++ b/ovsdb/automake.mk
@@ -44,7 +44,9 @@  pkgconfig_DATA += \
 
 MAN_FRAGMENTS += \
 	ovsdb/remote-active.man \
+	ovsdb/remote-active-ro.man \
 	ovsdb/remote-passive.man \
+	ovsdb/remote-passive-ro.man \
 	ovsdb/replication.man \
 	ovsdb/replication-syn.man
 
diff --git a/ovsdb/jsonrpc-server.c b/ovsdb/jsonrpc-server.c
index 87fc240..9252a20 100644
--- a/ovsdb/jsonrpc-server.c
+++ b/ovsdb/jsonrpc-server.c
@@ -272,7 +272,8 @@  ovsdb_jsonrpc_server_add_remote(struct ovsdb_jsonrpc_server *svr,
 
     if (!listener) {
         ovsdb_jsonrpc_session_create(remote, jsonrpc_session_open(name, true),
-                                      svr->read_only);
+                                      svr->read_only ||
+                                      stream_or_pstream_is_read_only(name));
     }
     return remote;
 }
@@ -364,9 +365,15 @@  ovsdb_jsonrpc_server_run(struct ovsdb_jsonrpc_server *svr)
             error = pstream_accept(remote->listener, &stream);
             if (!error) {
                 struct jsonrpc_session *js;
+                const char *stream_name;
+                bool read_only;
+
                 js = jsonrpc_session_open_unreliably(jsonrpc_open(stream),
                                                      remote->dscp);
-                ovsdb_jsonrpc_session_create(remote, js, svr->read_only);
+                stream_name = pstream_get_name(remote->listener);
+                read_only = svr->read_only ||
+                            stream_or_pstream_is_read_only(stream_name);
+                ovsdb_jsonrpc_session_create(remote, js, read_only);
             } else if (error != EAGAIN) {
                 VLOG_WARN_RL(&rl, "%s: accept failed: %s",
                              pstream_get_name(remote->listener),
diff --git a/ovsdb/ovsdb-client.c b/ovsdb/ovsdb-client.c
index 5f569e8..3bc4898 100644
--- a/ovsdb/ovsdb-client.c
+++ b/ovsdb/ovsdb-client.c
@@ -278,7 +278,7 @@  usage(void)
            "\nThe default SERVER is unix:%s/db.sock.\n"
            "The default DATABASE is Open_vSwitch.\n",
            program_name, program_name, ovs_rundir());
-    stream_usage("SERVER", true, true, true);
+    stream_usage("SERVER", true, true, true, false);
     printf("\nOutput formatting options:\n"
            "  -f, --format=FORMAT         set output formatting to FORMAT\n"
            "                              (\"table\", \"html\", \"csv\", "
diff --git a/ovsdb/ovsdb-server.1.in b/ovsdb/ovsdb-server.1.in
index e2e96ae..7ed9ea3 100644
--- a/ovsdb/ovsdb-server.1.in
+++ b/ovsdb/ovsdb-server.1.in
@@ -61,7 +61,9 @@  Adds \fIremote\fR as a connection method used by \fBovsdb\-server\fR.
 .
 .RS
 .so ovsdb/remote-passive.man
+.so ovsdb/remote-passive-ro.man
 .so ovsdb/remote-active.man
+.so ovsdb/remote-active-ro.man
 .
 .IP "\fBdb:\fIdb\fB,\fItable\fB,\fIcolumn\fR"
 Reads additional connection methods from \fIcolumn\fR in all of the
diff --git a/ovsdb/ovsdb-server.c b/ovsdb/ovsdb-server.c
index 0e3f9ac..ab284b8 100644
--- a/ovsdb/ovsdb-server.c
+++ b/ovsdb/ovsdb-server.c
@@ -1623,7 +1623,7 @@  usage(void)
            program_name, program_name, ovs_dbdir());
     printf("\nJSON-RPC options (may be specified any number of times):\n"
            "  --remote=REMOTE         connect or listen to REMOTE\n");
-    stream_usage("JSON-RPC", true, true, true);
+    stream_usage("JSON-RPC", true, true, true, true);
     daemon_usage();
     vlog_usage();
     replication_usage();
diff --git a/ovsdb/remote-active-ro.man b/ovsdb/remote-active-ro.man
new file mode 100644
index 0000000..ff6b089
--- /dev/null
+++ b/ovsdb/remote-active-ro.man
@@ -0,0 +1,28 @@ 
+.IP "\fBssl:ro:\fIip\fB:\fIport\fR"
+Connect to the specified SSL \fIport\fR on the host at the given \fIip\fR, which
+must be expressed as an IP address (not a DNS name) in IPv4 or IPv6 address
+format.  If \fIip\fR is an IPv6 address, then wrap \fIip\fR with square
+brackets, e.g.: \fBssl:[::1]:6640\fR.
+The \fB\-\-private\-key\fR, \fB\-\-certificate\fR, and \fB\-\-ca\-cert\fR
+options are mandatory when this form is used.
+.IP
+The \fBro:\fR element indicates that database modifications are not allowed
+on this connection.
+.
+.IP "\fBtcp:ro:\fIip\fB:\fIport\fR"
+Connect to the given TCP \fIport\fR on \fIip\fR, where \fIip\fR can be IPv4
+or IPv6 address. If \fIip\fR is an IPv6 address, then wrap \fIip\fR with
+square brackets, e.g.: \fBtcp:[::1]:6640\fR.
+.IP
+The \fBro:\fR element indicates that database modifications are not allowed
+on this connection.
+.
+.IP "\fBunix:ro:\fIfile\fR"
+On POSIX, connect to the Unix domain server socket named \fIfile\fR.
+.IP
+On Windows, connect to a local named pipe that is represented by a file
+created in the path \fIfile\fR to mimic the behavior of a Unix domain
+socket.
+.IP
+The \fBro:\fR element indicates that database modifications are not allowed
+on this connection.
diff --git a/ovsdb/remote-passive-ro.man b/ovsdb/remote-passive-ro.man
new file mode 100644
index 0000000..4e47a97
--- /dev/null
+++ b/ovsdb/remote-passive-ro.man
@@ -0,0 +1,35 @@ 
+.IP "\fBpssl:ro:\fIport\fR[\fB:\fIip\fR]"
+Listen on the given SSL \fIport\fR for a connection.  By default,
+connections are not bound to a particular local IP address and
+it listens only on IPv4 (but not IPv6) addresses, but
+specifying \fIip\fR limits connections to those from the given
+\fIip\fR, either IPv4 or IPv6 address.  If \fIip\fR is
+an IPv6 address, then wrap \fIip\fR with square brackets, e.g.:
+\fBpssl:6640:[::1]\fR.  The \fB\-\-private\-key\fR,
+\fB\-\-certificate\fR, and \fB\-\-ca\-cert\fR options are mandatory
+when this form is used.
+.IP
+The \fBro:\fR element indicates that database modifications are not allowed
+on this connection.
+.
+.IP "\fBptcp:ro:\fIport\fR[\fB:\fIip\fR]"
+Listen on the given TCP \fIport\fR for a connection.  By default,
+connections are not bound to a particular local IP address and
+it listens only on IPv4 (but not IPv6) addresses, but
+\fIip\fR may be specified to listen only for connections to the given
+\fIip\fR, either IPv4 or IPv6 address.  If \fIip\fR is
+an IPv6 address, then wrap \fIip\fR with square brackets, e.g.:
+\fBptcp:6640:[::1]\fR.
+.IP
+The \fBro:\fR element indicates that database modifications are not allowed
+on this connection.
+.
+.IP "\fBpunix:ro:\fIfile\fR"
+On POSIX, listen on the Unix domain server socket named \fIfile\fR for a
+connection.
+.IP
+On Windows, listen on a local named pipe.  A file is created in the
+path \fIfile\fR to mimic the behavior of a Unix domain socket.
+.IP
+The \fBro:\fR element indicates that database modifications are not allowed
+on this connection.
diff --git a/tests/ovsdb-server.at b/tests/ovsdb-server.at
index d04941b..6cd600c 100644
--- a/tests/ovsdb-server.at
+++ b/tests/ovsdb-server.at
@@ -1367,3 +1367,227 @@  AT_CHECK([diff dump1 dump2])
 dnl OVSDB_SERVER_SHUTDOWN
 dnl OVSDB_SERVER_SHUTDOWN2
 AT_CLEANUP
+
+AT_SETUP([ovsdb-server/read-only ptcp connection])
+AT_KEYWORDS([ovsdb server read-only])
+ordinal_schema > schema
+AT_CHECK([ovsdb-tool create db schema], [0], [ignore], [ignore])
+AT_CHECK([ovsdb-server --log-file --detach --no-chdir --pidfile --remote=ptcp:ro:0:127.0.0.1 db], [0], [ignore], [ignore])
+PARSE_LISTENING_PORT([ovsdb-server.log], [TCP_PORT])
+AT_CHECK([ovsdb-client get-schema-version tcp:127.0.0.1:$TCP_PORT ordinals], [0], [5.1.3
+])
+
+AT_CHECK([ovsdb-client transact tcp:127.0.0.1:$TCP_PORT \
+        ['["ordinals",
+         {"op": "insert",
+          "table": "ordinals",
+          "row": {"name": "two", "number": '2'}}
+         ]']], [0], [stdout], [ignore])
+cat stdout >> output
+AT_CHECK([${PERL} $srcdir/uuidfilt.pl output], [0], [[[{"details":"insert operation not allowed when database server is in read only mode","error":"not allowed"}]]
+], [ignore])
+OVSDB_SERVER_SHUTDOWN
+AT_CLEANUP
+
+AT_SETUP([ovsdb-server/read-only punix connection])
+AT_KEYWORDS([ovsdb server read-only])
+ordinal_schema > schema
+AT_CHECK([ovsdb-tool create db schema], [0], [ignore], [ignore])
+AT_CHECK([ovsdb-server --log-file --detach --no-chdir --pidfile --remote=punix:ro:test-socket db], [0], [ignore], [ignore])
+AT_CHECK([ovsdb-client get-schema-version unix:test-socket ordinals], [0], [5.1.3
+])
+
+AT_CHECK([ovsdb-client transact unix:test-socket \
+        ['["ordinals",
+         {"op": "insert",
+          "table": "ordinals",
+          "row": {"name": "two", "number": '2'}}
+         ]']], [0], [stdout], [ignore])
+cat stdout >> output
+AT_CHECK([${PERL} $srcdir/uuidfilt.pl output], [0], [[[{"details":"insert operation not allowed when database server is in read only mode","error":"not allowed"}]]
+], [ignore])
+OVSDB_SERVER_SHUTDOWN
+AT_CLEANUP
+
+AT_SETUP([ovsdb-server/read-only pssl connection])
+AT_KEYWORDS([ovsdb server read-only])
+AT_SKIP_IF([test "$HAVE_OPENSSL" = no])
+PKIDIR="$(cd $abs_top_builddir/tests && pwd)"
+AT_SKIP_IF([expr "$PKIDIR" : ".*[ 	'\"
+\\]"])
+AT_DATA([schema],
+  [[{"name": "mydb",
+     "tables": {
+       "SSL": {
+         "columns": {
+           "private_key": {"type": "string"},
+           "certificate": {"type": "string"},
+           "ca_cert": {"type": "string"}}},
+       "ordinals": {
+         "columns": {
+           "number": {"type": "integer"},
+           "name": {"type": "string"}},
+         "indexes": [["number"]]}
+    },
+     "version": "5.1.3",
+     "cksum": "12345678 9"}
+]])
+AT_CHECK([ovsdb-tool create db schema], [0], [stdout], [ignore])
+AT_CHECK(
+  [[ovsdb-tool transact db \
+     '["mydb",
+       {"op": "insert",
+        "table": "SSL",
+        "row": {"private_key": "'"$PKIDIR/testpki-privkey2.pem"'",
+                "certificate": "'"$PKIDIR/testpki-cert2.pem"'",
+                "ca_cert": "'"$PKIDIR/testpki-cacert.pem"'"}}]']],
+  [0], [ignore], [ignore])
+AT_CHECK(
+  [ovsdb-server --log-file --detach --no-chdir --pidfile \
+        --private-key=db:mydb,SSL,private_key \
+        --certificate=db:mydb,SSL,certificate \
+        --ca-cert=db:mydb,SSL,ca_cert \
+        --remote=pssl:ro:0:127.0.0.1 db],
+  [0], [ignore], [ignore])
+PARSE_LISTENING_PORT([ovsdb-server.log], [SSL_PORT])
+AT_CHECK([ovsdb-client \
+        --private-key=$PKIDIR/testpki-privkey.pem \
+        --certificate=$PKIDIR/testpki-cert.pem \
+        --ca-cert=$PKIDIR/testpki-cacert.pem \
+        get-schema-version ssl:127.0.0.1:$SSL_PORT mydb], \
+        [0], [5.1.3
+])
+AT_CHECK([ovsdb-client \
+        --private-key=$PKIDIR/testpki-privkey.pem \
+        --certificate=$PKIDIR/testpki-cert.pem \
+        --ca-cert=$PKIDIR/testpki-cacert.pem \
+        transact ssl:127.0.0.1:$SSL_PORT \
+        ['["mydb",
+         {"op": "insert",
+          "table": "ordinals",
+          "row": {"name": "two", "number": '2'}}
+         ]']], [0], [stdout], [ignore])
+cat stdout >> output
+AT_CHECK([${PERL} $srcdir/uuidfilt.pl output], [0], [[[{"details":"insert operation not allowed when database server is in read only mode","error":"not allowed"}]]
+], [ignore])
+OVSDB_SERVER_SHUTDOWN
+AT_CLEANUP
+
+AT_SETUP([ovsdb-server/read-only tcp connection])
+AT_KEYWORDS([ovsdb server read-only])
+ordinal_schema > schema
+AT_CHECK([ovsdb-tool create db schema], [0], [ignore], [ignore])
+
+# find a free TCP port
+AT_CHECK([ovsdb-server --log-file '-vPATTERN:console:ovsdb-server|%c|%m' --detach --no-chdir --pidfile --remote=ptcp:0:127.0.0.1 db], [0], [ignore], [ignore])
+on_exit 'kill `cat ovsdb-server.pid`'
+PARSE_LISTENING_PORT([ovsdb-server.log], [TCP_PORT])
+OVSDB_SERVER_SHUTDOWN
+
+AT_CHECK([ovsdb-server --log-file --detach --no-chdir --pidfile --remote=tcp:ro:127.0.0.1:$TCP_PORT db], [0], [ignore], [ignore])
+AT_CHECK([ovsdb-client -vwarn get-schema-version ptcp:$TCP_PORT:127.0.0.1 ordinals], [0], [5.1.3
+])
+
+AT_CHECK([ovsdb-client transact ptcp:$TCP_PORT:127.0.0.1 \
+        ['["ordinals",
+         {"op": "insert",
+          "table": "ordinals",
+          "row": {"name": "two", "number": '2'}}
+         ]']], [0], [stdout], [ignore])
+cat stdout >> output
+AT_CHECK([${PERL} $srcdir/uuidfilt.pl output], [0], [[[{"details":"insert operation not allowed when database server is in read only mode","error":"not allowed"}]]
+], [ignore])
+OVSDB_SERVER_SHUTDOWN
+AT_CLEANUP
+
+AT_SETUP([ovsdb-server/read-only unix connection])
+AT_KEYWORDS([ovsdb server read-only])
+ordinal_schema > schema
+AT_CHECK([ovsdb-tool create db schema], [0], [ignore], [ignore])
+AT_CHECK([ovsdb-server --log-file --detach --no-chdir --pidfile --remote=unix:ro:test-socket db], [0], [ignore], [ignore])
+ovsdb-client -v get-schema-version punix:test-socket ordinals
+AT_CHECK([ovsdb-client -vwarn get-schema-version punix:test-socket ordinals], [0], [5.1.3
+])
+
+AT_CHECK([ovsdb-client transact punix:test-socket \
+        ['["ordinals",
+         {"op": "insert",
+          "table": "ordinals",
+          "row": {"name": "two", "number": '2'}}
+         ]']], [0], [stdout], [ignore])
+cat stdout >> output
+AT_CHECK([${PERL} $srcdir/uuidfilt.pl output], [0], [[[{"details":"insert operation not allowed when database server is in read only mode","error":"not allowed"}]]
+], [ignore])
+OVSDB_SERVER_SHUTDOWN
+AT_CLEANUP
+
+AT_SETUP([ovsdb-server/read-only ssl connection])
+AT_KEYWORDS([ovsdb server read-only])
+AT_SKIP_IF([test "$HAVE_OPENSSL" = no])
+PKIDIR="$(cd $abs_top_builddir/tests && pwd)"
+AT_SKIP_IF([expr "$PKIDIR" : ".*[ 	'\"
+\\]"])
+AT_DATA([schema],
+  [[{"name": "mydb",
+     "tables": {
+       "SSL": {
+         "columns": {
+           "private_key": {"type": "string"},
+           "certificate": {"type": "string"},
+           "ca_cert": {"type": "string"}}},
+       "ordinals": {
+         "columns": {
+           "number": {"type": "integer"},
+           "name": {"type": "string"}},
+         "indexes": [["number"]]}
+    },
+     "version": "5.1.3",
+     "cksum": "12345678 9"}
+]])
+AT_CHECK([ovsdb-tool create db schema], [0], [stdout], [ignore])
+AT_CHECK(
+  [[ovsdb-tool transact db \
+     '["mydb",
+       {"op": "insert",
+        "table": "SSL",
+        "row": {"private_key": "'"$PKIDIR/testpki-privkey2.pem"'",
+                "certificate": "'"$PKIDIR/testpki-cert2.pem"'",
+                "ca_cert": "'"$PKIDIR/testpki-cacert.pem"'"}}]']],
+  [0], [ignore], [ignore])
+
+# find a free TCP port
+AT_CHECK([ovsdb-server --log-file '-vPATTERN:console:ovsdb-server|%c|%m' --detach --no-chdir --pidfile --remote=ptcp:0:127.0.0.1 db], [0], [ignore], [ignore])
+on_exit 'kill `cat ovsdb-server.pid`'
+PARSE_LISTENING_PORT([ovsdb-server.log], [TCP_PORT])
+OVSDB_SERVER_SHUTDOWN
+
+AT_CHECK(
+  [ovsdb-server --log-file --detach --no-chdir --pidfile \
+        --private-key=db:mydb,SSL,private_key \
+        --certificate=db:mydb,SSL,certificate \
+        --ca-cert=db:mydb,SSL,ca_cert \
+        --remote=ssl:ro:127.0.0.1:$TCP_PORT db],
+  [0], [ignore], [ignore])
+AT_CHECK([ovsdb-client -vwarn \
+        --private-key=$PKIDIR/testpki-privkey.pem \
+        --certificate=$PKIDIR/testpki-cert.pem \
+        --ca-cert=$PKIDIR/testpki-cacert.pem \
+        get-schema-version pssl:$TCP_PORT:127.0.0.1 mydb], \
+        [0], [5.1.3
+])
+AT_CHECK([ovsdb-client \
+        --private-key=$PKIDIR/testpki-privkey.pem \
+        --certificate=$PKIDIR/testpki-cert.pem \
+        --ca-cert=$PKIDIR/testpki-cacert.pem \
+        transact pssl:$TCP_PORT:127.0.0.1 \
+        ['["mydb",
+         {"op": "insert",
+          "table": "ordinals",
+          "row": {"name": "two", "number": '2'}}
+         ]']], [0], [stdout], [ignore])
+cat stdout >> output
+AT_CHECK([${PERL} $srcdir/uuidfilt.pl output], [0], [[[{"details":"insert operation not allowed when database server is in read only mode","error":"not allowed"}]]
+], [ignore])
+OVSDB_SERVER_SHUTDOWN
+AT_CLEANUP
+
diff --git a/tests/test-jsonrpc.c b/tests/test-jsonrpc.c
index 684601a..7ee0681 100644
--- a/tests/test-jsonrpc.c
+++ b/tests/test-jsonrpc.c
@@ -108,7 +108,7 @@  usage(void)
            "  request REMOTE METHOD PARAMS   send request, print reply\n"
            "  notify REMOTE METHOD PARAMS  send notification and exit\n",
            program_name, program_name);
-    stream_usage("JSON-RPC", true, true, true);
+    stream_usage("JSON-RPC", true, true, true, false);
     daemon_usage();
     vlog_usage();
     printf("\nOther options:\n"
diff --git a/utilities/ovs-vsctl.c b/utilities/ovs-vsctl.c
index e710095..c61f7df 100644
--- a/utilities/ovs-vsctl.c
+++ b/utilities/ovs-vsctl.c
@@ -424,7 +424,7 @@  Options:\n\
     vlog_usage();
     printf("\
   --no-syslog             equivalent to --verbose=vsctl:syslog:warn\n");
-    stream_usage("database", true, true, false);
+    stream_usage("database", true, true, false, false);
     printf("\n\
 Other options:\n\
   -h, --help                  display this help message\n\
diff --git a/vswitchd/ovs-vswitchd.c b/vswitchd/ovs-vswitchd.c
index 72448bb..ebc8d12 100644
--- a/vswitchd/ovs-vswitchd.c
+++ b/vswitchd/ovs-vswitchd.c
@@ -250,7 +250,7 @@  usage(void)
            "where DATABASE is a socket on which ovsdb-server is listening\n"
            "      (default: \"unix:%s/db.sock\").\n",
            program_name, program_name, ovs_rundir());
-    stream_usage("DATABASE", true, false, true);
+    stream_usage("DATABASE", true, false, true, false);
     daemon_usage();
     vlog_usage();
     printf("\nDPDK options:\n"
diff --git a/vtep/vtep-ctl.c b/vtep/vtep-ctl.c
index 245ba0d..01586ac 100644
--- a/vtep/vtep-ctl.c
+++ b/vtep/vtep-ctl.c
@@ -370,7 +370,7 @@  Options:\n\
     vlog_usage();
     printf("\
   --no-syslog                 equivalent to --verbose=vtep_ctl:syslog:warn\n");
-    stream_usage("database", true, true, false);
+    stream_usage("database", true, true, false, false);
     printf("\n\
 Other options:\n\
   -h, --help                  display this help message\n\